├── .gitignore
├── LICENSE
├── README.md
├── movies
├── .gitignore
├── .metadata
├── README.md
├── android
│ ├── .gitignore
│ ├── app
│ │ ├── build.gradle
│ │ └── src
│ │ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── kotlin
│ │ │ │ └── com
│ │ │ │ │ └── example
│ │ │ │ │ └── douban
│ │ │ │ │ └── MainActivity.kt
│ │ │ └── res
│ │ │ │ ├── drawable
│ │ │ │ └── launch_background.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ └── values
│ │ │ │ └── styles.xml
│ │ │ └── profile
│ │ │ └── AndroidManifest.xml
│ ├── build.gradle
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ └── settings.gradle
├── ios
│ ├── .gitignore
│ ├── Flutter
│ │ ├── .last_build_id
│ │ ├── AppFrameworkInfo.plist
│ │ ├── Debug.xcconfig
│ │ └── Release.xcconfig
│ ├── MoviesWidget
│ │ ├── Assets.xcassets
│ │ │ ├── AccentColor.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ │ ├── Contents.json
│ │ │ └── WidgetBackground.colorset
│ │ │ │ └── Contents.json
│ │ ├── Info.plist
│ │ └── MoviesWidget.swift
│ ├── MoviesWidgetExtension.entitlements
│ ├── Podfile
│ ├── Runner.xcodeproj
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace
│ │ │ └── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── Runner.xcscheme
│ ├── Runner.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── Runner
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── Icon-60@2x.png
│ │ │ ├── Icon-60@3x.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-Notification@2x.png
│ │ │ ├── Icon-Notification@3x.png
│ │ │ ├── Icon-Small-40@2x.png
│ │ │ ├── Icon-Small-40@3x.png
│ │ │ ├── Icon-Small@2x.png
│ │ │ ├── Icon-Small@3x.png
│ │ │ └── icon.png
│ │ ├── Contents.json
│ │ ├── background.colorset
│ │ │ └── Contents.json
│ │ └── icon.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ ├── Runner-Bridging-Header.h
│ │ └── Runner.entitlements
├── lib
│ ├── generated
│ │ ├── intl
│ │ │ ├── messages_all.dart
│ │ │ ├── messages_en.dart
│ │ │ └── messages_zh.dart
│ │ └── l10n.dart
│ ├── l10n
│ │ ├── intl_en.arb
│ │ └── intl_zh.arb
│ ├── main.dart
│ ├── model
│ │ ├── base_model.dart
│ │ ├── comment_model.dart
│ │ ├── movie_model.dart
│ │ ├── movies_model.dart
│ │ ├── photo_model.dart
│ │ ├── rank_model.dart
│ │ ├── search_model.dart
│ │ └── user_model.dart
│ ├── moudule
│ │ ├── bottom_tabBar_view.dart
│ │ ├── movie
│ │ │ ├── movie_comment_view.dart
│ │ │ ├── movie_photo_view.dart
│ │ │ ├── movie_recommend_view.dart
│ │ │ ├── movie_review_view.dart
│ │ │ └── movie_view.dart
│ │ ├── movies
│ │ │ ├── movies_list_view.dart
│ │ │ ├── movies_ranks_view.dart
│ │ │ ├── movies_today_view.dart
│ │ │ └── movies_view.dart
│ │ ├── search
│ │ │ ├── search_results_view.dart
│ │ │ ├── search_suggestions_view.dart
│ │ │ └── search_view.dart
│ │ ├── settings
│ │ │ ├── settings_detail_view.dart
│ │ │ └── settings_view.dart
│ │ └── tvs
│ │ │ ├── tvs_tab_view.dart
│ │ │ └── tvs_view.dart
│ ├── util
│ │ ├── constant.dart
│ │ ├── network_manager.dart
│ │ ├── router_manager.dart
│ │ ├── storage_manager.dart
│ │ └── util.dart
│ ├── view
│ │ ├── base_view.dart
│ │ ├── error_view.dart
│ │ ├── gallery_view.dart
│ │ ├── item
│ │ │ ├── comment_item_view.dart
│ │ │ ├── grid_item_view.dart
│ │ │ ├── list_item_view.dart
│ │ │ ├── movies_item_view.dart
│ │ │ └── rank_item_view.dart
│ │ ├── movie
│ │ │ ├── movie_cover_view.dart
│ │ │ ├── movie_other_view.dart
│ │ │ ├── movie_rating_view.dart
│ │ │ ├── movie_staff_view.dart
│ │ │ ├── movie_summary_view.dart
│ │ │ └── movie_trailer_view.dart
│ │ ├── player_view.dart
│ │ ├── provider_view.dart
│ │ ├── rating_view.dart
│ │ ├── refresh_view.dart
│ │ ├── save_view.dart
│ │ └── webpage_view.dart
│ └── view_model
│ │ ├── base_view_model.dart
│ │ ├── locale_view_model.dart
│ │ ├── movie_view_model.dart
│ │ ├── movies_view_model.dart
│ │ ├── search_view_model.dart
│ │ ├── theme_view_model.dart
│ │ └── tvs_view_model.dart
├── pubspec.lock
├── pubspec.yaml
└── test
│ └── widget_test.dart
└── previews
├── 1.gif
├── 2.gif
├── 3.gif
├── 4.gif
├── 5.gif
├── 6.gif
├── 7.gif
└── 8.gif
/.gitignore:
--------------------------------------------------------------------------------
1 | movies/ios/Gemfile
2 | movies/ios/fastlane/.env
3 | movies/ios/fastlane/Appfile
4 | movies/ios/fastlane/Fastfile
5 | movies/ios/fastlane/README.md
6 | movies/ios/fastlane/report.xml
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 ZzzM
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | Flutter Movies
3 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | A mobile app for movie information based on Flutter( ❌ *API is unavailable* )
13 |
14 | ## Features
15 | - [x] Switching theme dynamically
16 | - [x] Localization (简体中文、English)
17 | - [x] Video, photo download
18 | - [x] Video play
19 |
20 |
21 | ## Compatibility
22 | - Requires **iOS 12.0** or later
23 |
24 | ## Version History
25 |
26 | | | |
27 | | ---- | ---- |
28 | | 2.6.0 | Enhance search |
29 | | 2.1.0 | Support search
Support video and photo download
Support full-screen video |
30 | | 2.0.0 | Update API |
31 | | 1.1.0 | Support Sharing |
32 | | 1.0.0 | - |
33 |
34 |
35 | ## Snapshots
36 |
37 | - Home page
38 |
39 |
40 |
41 |
42 |
43 | - Detail page
44 |
45 |
46 |
47 |
48 |
49 | ## Dependencies
50 | - cached_network_image
51 | - dio
52 | - provider
53 | - package_info
54 | - webview_flutter
55 | - fluro
56 | - pull_to_refresh
57 | - shared_preferences
58 | - photo_view
59 | - video_player
60 | - chewie
61 | - share
62 | - flutter_staggered_grid_view
63 | - permission_handler
64 | - image_gallery_saver
65 | - flutter_staggered_animations
66 |
--------------------------------------------------------------------------------
/movies/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | .dart_tool/
26 | .flutter-plugins
27 | .flutter-plugins-dependencies
28 | .packages
29 | .pub-cache/
30 | .pub/
31 | /build/
32 |
33 | # Web related
34 | lib/generated_plugin_registrant.dart
35 |
36 | # Exceptions to above rules.
37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
38 |
--------------------------------------------------------------------------------
/movies/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 27321ebbad34b0a3fafe99fac037102196d655ff
8 | channel: stable
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/movies/README.md:
--------------------------------------------------------------------------------
1 | # movies
2 |
3 | A new Flutter project.
4 |
5 | ## Getting Started
6 |
7 | This project is a starting point for a Flutter application.
8 |
9 | A few resources to get you started if this is your first Flutter project:
10 |
11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
13 |
14 | For help getting started with Flutter, view our
15 | [online documentation](https://flutter.dev/docs), which offers tutorials,
16 | samples, guidance on mobile development, and a full API reference.
17 |
--------------------------------------------------------------------------------
/movies/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
--------------------------------------------------------------------------------
/movies/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply plugin: 'kotlin-android'
26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
27 |
28 | android {
29 | compileSdkVersion 28
30 |
31 | sourceSets {
32 | main.java.srcDirs += 'src/main/kotlin'
33 | }
34 |
35 | lintOptions {
36 | disable 'InvalidPackage'
37 | }
38 |
39 | defaultConfig {
40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
41 | applicationId "com.example.movies"
42 | minSdkVersion 16
43 | targetSdkVersion 28
44 | versionCode flutterVersionCode.toInteger()
45 | versionName flutterVersionName
46 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
47 | }
48 |
49 | buildTypes {
50 | release {
51 | // TODO: Add your own signing config for the release build.
52 | // Signing with the debug keys for now, so `flutter run --release` works.
53 | signingConfig signingConfigs.debug
54 | }
55 | }
56 | }
57 |
58 | flutter {
59 | source '../..'
60 | }
61 |
62 | dependencies {
63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
64 | testImplementation 'junit:junit:4.12'
65 | androidTestImplementation 'androidx.test:runner:1.1.1'
66 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
67 | }
68 |
--------------------------------------------------------------------------------
/movies/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/movies/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
8 |
12 |
19 |
20 |
21 |
22 |
23 |
24 |
26 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/movies/android/app/src/main/kotlin/com/example/douban/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.movies
2 |
3 | import androidx.annotation.NonNull;
4 | import io.flutter.embedding.android.FlutterActivity
5 | import io.flutter.embedding.engine.FlutterEngine
6 | import io.flutter.plugins.GeneratedPluginRegistrant
7 |
8 | class MainActivity: FlutterActivity() {
9 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
10 | GeneratedPluginRegistrant.registerWith(flutterEngine);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/movies/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/movies/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZzzM/Flutter-Movies/bd027acf04e989dd86eeb0795afa23d6b84d8df5/movies/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/movies/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZzzM/Flutter-Movies/bd027acf04e989dd86eeb0795afa23d6b84d8df5/movies/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/movies/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZzzM/Flutter-Movies/bd027acf04e989dd86eeb0795afa23d6b84d8df5/movies/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/movies/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZzzM/Flutter-Movies/bd027acf04e989dd86eeb0795afa23d6b84d8df5/movies/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/movies/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZzzM/Flutter-Movies/bd027acf04e989dd86eeb0795afa23d6b84d8df5/movies/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/movies/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/movies/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/movies/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.3.50'
3 | repositories {
4 | google()
5 | jcenter()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.5.0'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | jcenter()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | }
25 | subprojects {
26 | project.evaluationDependsOn(':app')
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/movies/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.enableR8=true
3 | android.useAndroidX=true
4 | android.enableJetifier=true
5 |
--------------------------------------------------------------------------------
/movies/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
7 |
--------------------------------------------------------------------------------
/movies/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
4 |
5 | def plugins = new Properties()
6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
7 | if (pluginsFile.exists()) {
8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
9 | }
10 |
11 | plugins.each { name, path ->
12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
13 | include ":$name"
14 | project(":$name").projectDir = pluginDirectory
15 | }
16 |
--------------------------------------------------------------------------------
/movies/ios/.gitignore:
--------------------------------------------------------------------------------
1 | *.mode1v3
2 | *.mode2v3
3 | *.moved-aside
4 | *.pbxuser
5 | *.perspectivev3
6 | **/*sync/
7 | .sconsign.dblite
8 | .tags*
9 | **/.vagrant/
10 | **/DerivedData/
11 | Icon?
12 | **/Pods/
13 | **/.symlinks/
14 | profile
15 | xcuserdata
16 | **/.generated/
17 | Flutter/App.framework
18 | Flutter/Flutter.framework
19 | Flutter/Flutter.podspec
20 | Flutter/Generated.xcconfig
21 | Flutter/app.flx
22 | Flutter/app.zip
23 | Flutter/flutter_assets/
24 | Flutter/flutter_export_environment.sh
25 | ServiceDefinitions.json
26 | Runner/GeneratedPluginRegistrant.*
27 |
28 | # Exceptions to above rules.
29 | !default.mode1v3
30 | !default.mode2v3
31 | !default.pbxuser
32 | !default.perspectivev3
33 |
--------------------------------------------------------------------------------
/movies/ios/Flutter/.last_build_id:
--------------------------------------------------------------------------------
1 | 9f01e77898791a9289af3ccf805da68b
--------------------------------------------------------------------------------
/movies/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 8.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/movies/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/movies/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/movies/ios/MoviesWidget/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/movies/ios/MoviesWidget/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/movies/ios/MoviesWidget/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/movies/ios/MoviesWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/movies/ios/MoviesWidget/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | MoviesWidget
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | NSExtension
24 |
25 | NSExtensionPointIdentifier
26 | com.apple.widgetkit-extension
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/movies/ios/MoviesWidget/MoviesWidget.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MoviesWidget.swift
3 | // MoviesWidget
4 | //
5 | // Created by zm on 2020/11/18.
6 | // Copyright © 2020 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | import WidgetKit
10 | import SwiftUI
11 |
12 | struct Provider: TimelineProvider {
13 | func placeholder(in context: Context) -> SimpleEntry {
14 | SimpleEntry(date: Date())
15 | }
16 |
17 | func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
18 | let entry = SimpleEntry(date: Date())
19 | completion(entry)
20 | }
21 |
22 | func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) {
23 | var entries: [SimpleEntry] = []
24 |
25 | // Generate a timeline consisting of five entries an hour apart, starting from the current date.
26 | let currentDate = Date()
27 | for hourOffset in 0 ..< 5 {
28 | let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
29 | let entry = SimpleEntry(date: entryDate)
30 | entries.append(entry)
31 | }
32 |
33 | let timeline = Timeline(entries: entries, policy: .atEnd)
34 | completion(timeline)
35 | }
36 | }
37 |
38 | struct SimpleEntry: TimelineEntry {
39 | let date: Date
40 | }
41 |
42 |
43 | struct MoviesWidgetEntryView : View {
44 | var entry: Provider.Entry
45 |
46 | var body: some View {
47 |
48 |
49 |
50 | Text(entry.date, style: .time)
51 | }
52 | }
53 |
54 | @main
55 | struct MoviesWidget: Widget {
56 | let kind: String = "MoviesWidget"
57 |
58 | var body: some WidgetConfiguration {
59 | StaticConfiguration(kind: kind, provider: Provider()) { entry in
60 | MoviesWidgetEntryView(entry: entry)
61 | }
62 | .configurationDisplayName("My Widget")
63 | .description("This is an example widget.")
64 | }
65 | }
66 |
67 | struct MoviesWidget_Previews: PreviewProvider {
68 | static var previews: some View {
69 | MoviesWidgetEntryView(entry: SimpleEntry(date: Date()))
70 | .previewContext(WidgetPreviewContext(family: .systemMedium))
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/movies/ios/MoviesWidgetExtension.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.network.client
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/movies/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | end
36 |
37 | post_install do |installer|
38 | installer.pods_project.targets.each do |target|
39 | flutter_additional_ios_build_settings(target)
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/movies/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/movies/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/movies/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/movies/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/movies/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/movies/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-Notification@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-Notification@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-Small@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-Small@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-Small-40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-Small-40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "1024x1024",
59 | "idiom" : "ios-marketing",
60 | "filename" : "icon.png",
61 | "scale" : "1x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/movies/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZzzM/Flutter-Movies/bd027acf04e989dd86eeb0795afa23d6b84d8df5/movies/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png
--------------------------------------------------------------------------------
/movies/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZzzM/Flutter-Movies/bd027acf04e989dd86eeb0795afa23d6b84d8df5/movies/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png
--------------------------------------------------------------------------------
/movies/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZzzM/Flutter-Movies/bd027acf04e989dd86eeb0795afa23d6b84d8df5/movies/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/movies/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Notification@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZzzM/Flutter-Movies/bd027acf04e989dd86eeb0795afa23d6b84d8df5/movies/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Notification@2x.png
--------------------------------------------------------------------------------
/movies/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Notification@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZzzM/Flutter-Movies/bd027acf04e989dd86eeb0795afa23d6b84d8df5/movies/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Notification@3x.png
--------------------------------------------------------------------------------
/movies/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZzzM/Flutter-Movies/bd027acf04e989dd86eeb0795afa23d6b84d8df5/movies/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png
--------------------------------------------------------------------------------
/movies/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZzzM/Flutter-Movies/bd027acf04e989dd86eeb0795afa23d6b84d8df5/movies/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png
--------------------------------------------------------------------------------
/movies/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZzzM/Flutter-Movies/bd027acf04e989dd86eeb0795afa23d6b84d8df5/movies/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png
--------------------------------------------------------------------------------
/movies/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZzzM/Flutter-Movies/bd027acf04e989dd86eeb0795afa23d6b84d8df5/movies/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png
--------------------------------------------------------------------------------
/movies/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZzzM/Flutter-Movies/bd027acf04e989dd86eeb0795afa23d6b84d8df5/movies/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon.png
--------------------------------------------------------------------------------
/movies/ios/Runner/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/movies/ios/Runner/Assets.xcassets/background.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "platform" : "ios",
6 | "reference" : "systemBackgroundColor"
7 | },
8 | "idiom" : "universal"
9 | },
10 | {
11 | "appearances" : [
12 | {
13 | "appearance" : "luminosity",
14 | "value" : "dark"
15 | }
16 | ],
17 | "color" : {
18 | "color-space" : "srgb",
19 | "components" : {
20 | "alpha" : "1.000",
21 | "blue" : "0x24",
22 | "green" : "0x21",
23 | "red" : "0x1D"
24 | }
25 | },
26 | "idiom" : "universal"
27 | }
28 | ],
29 | "info" : {
30 | "author" : "xcode",
31 | "version" : 1
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/movies/ios/Runner/Assets.xcassets/icon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icon.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/movies/ios/Runner/Assets.xcassets/icon.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZzzM/Flutter-Movies/bd027acf04e989dd86eeb0795afa23d6b84d8df5/movies/ios/Runner/Assets.xcassets/icon.imageset/icon.png
--------------------------------------------------------------------------------
/movies/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/movies/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/movies/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | Movies
9 | CFBundleDocumentTypes
10 |
11 |
12 |
13 |
14 | LSHandlerRank
15 | Default
16 |
17 |
18 |
19 | CFBundleExecutable
20 | $(EXECUTABLE_NAME)
21 | CFBundleIdentifier
22 | $(PRODUCT_BUNDLE_IDENTIFIER)
23 | CFBundleInfoDictionaryVersion
24 | 6.0
25 | CFBundleName
26 | Movies
27 | CFBundlePackageType
28 | APPL
29 | CFBundleShortVersionString
30 | $(MARKETING_VERSION)
31 | CFBundleSignature
32 | ????
33 | CFBundleVersion
34 | $(FLUTTER_BUILD_NUMBER)
35 | LSRequiresIPhoneOS
36 |
37 | NSAppTransportSecurity
38 |
39 | NSAllowsArbitraryLoads
40 |
41 |
42 | NSPhotoLibraryAddUsageDescription
43 | App需要您的同意,才能保存图片
44 | NSPhotoLibraryUsageDescription
45 | App需要访问相册
46 | UILaunchStoryboardName
47 | LaunchScreen
48 | UIMainStoryboardFile
49 | Main
50 | UISupportedInterfaceOrientations
51 |
52 | UIInterfaceOrientationPortrait
53 | UIInterfaceOrientationLandscapeLeft
54 | UIInterfaceOrientationLandscapeRight
55 |
56 | UISupportedInterfaceOrientations~ipad
57 |
58 | UIInterfaceOrientationPortrait
59 | UIInterfaceOrientationPortraitUpsideDown
60 | UIInterfaceOrientationLandscapeLeft
61 | UIInterfaceOrientationLandscapeRight
62 |
63 | UIViewControllerBasedStatusBarAppearance
64 |
65 | io.flutter.embedded_views_preview
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/movies/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
--------------------------------------------------------------------------------
/movies/ios/Runner/Runner.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.network.client
8 |
9 | com.apple.security.personal-information.photos-library
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/movies/lib/generated/intl/messages_all.dart:
--------------------------------------------------------------------------------
1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
2 | // This is a library that looks up messages for specific locales by
3 | // delegating to the appropriate library.
4 |
5 | // Ignore issues from commonly used lints in this file.
6 | // ignore_for_file:implementation_imports, file_names, unnecessary_new
7 | // ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering
8 | // ignore_for_file:argument_type_not_assignable, invalid_assignment
9 | // ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases
10 | // ignore_for_file:comment_references
11 |
12 | import 'dart:async';
13 |
14 | import 'package:intl/intl.dart';
15 | import 'package:intl/message_lookup_by_library.dart';
16 | import 'package:intl/src/intl_helpers.dart';
17 |
18 | import 'messages_en.dart' as messages_en;
19 | import 'messages_zh.dart' as messages_zh;
20 |
21 | typedef Future LibraryLoader();
22 | Map _deferredLibraries = {
23 | 'en': () => new Future.value(null),
24 | 'zh': () => new Future.value(null),
25 | };
26 |
27 | MessageLookupByLibrary _findExact(String localeName) {
28 | switch (localeName) {
29 | case 'en':
30 | return messages_en.messages;
31 | case 'zh':
32 | return messages_zh.messages;
33 | default:
34 | return null;
35 | }
36 | }
37 |
38 | /// User programs should call this before using [localeName] for messages.
39 | Future initializeMessages(String localeName) async {
40 | var availableLocale = Intl.verifiedLocale(
41 | localeName,
42 | (locale) => _deferredLibraries[locale] != null,
43 | onFailure: (_) => null);
44 | if (availableLocale == null) {
45 | return new Future.value(false);
46 | }
47 | var lib = _deferredLibraries[availableLocale];
48 | await (lib == null ? new Future.value(false) : lib());
49 | initializeInternalMessageLookup(() => new CompositeMessageLookup());
50 | messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor);
51 | return new Future.value(true);
52 | }
53 |
54 | bool _messagesExistFor(String locale) {
55 | try {
56 | return _findExact(locale) != null;
57 | } catch (e) {
58 | return false;
59 | }
60 | }
61 |
62 | MessageLookupByLibrary _findGeneratedMessagesFor(String locale) {
63 | var actualLocale = Intl.verifiedLocale(locale, _messagesExistFor,
64 | onFailure: (_) => null);
65 | if (actualLocale == null) return null;
66 | return _findExact(actualLocale);
67 | }
68 |
--------------------------------------------------------------------------------
/movies/lib/generated/intl/messages_en.dart:
--------------------------------------------------------------------------------
1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
2 | // This is a library that provides messages for a en locale. All the
3 | // messages from the main program should be duplicated here with the same
4 | // function name.
5 |
6 | // Ignore issues from commonly used lints in this file.
7 | // ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
8 | // ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
9 | // ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
10 | // ignore_for_file:unused_import, file_names
11 |
12 | import 'package:intl/intl.dart';
13 | import 'package:intl/message_lookup_by_library.dart';
14 |
15 | final messages = new MessageLookup();
16 |
17 | typedef String MessageIfAbsent(String messageStr, List args);
18 |
19 | class MessageLookup extends MessageLookupByLibrary {
20 | String get localeName => 'en';
21 |
22 | final messages = _notInlinedMessages(_notInlinedMessages);
23 | static _notInlinedMessages(_) => {
24 | "file_cancel" : MessageLookupByLibrary.simpleMessage("Cancel"),
25 | "file_completed" : MessageLookupByLibrary.simpleMessage("Completed:"),
26 | "file_download" : MessageLookupByLibrary.simpleMessage("Download to Album"),
27 | "file_downloading" : MessageLookupByLibrary.simpleMessage("Downloading..."),
28 | "file_permission" : MessageLookupByLibrary.simpleMessage("No Permission to Access Album"),
29 | "file_save" : MessageLookupByLibrary.simpleMessage("Save to Album"),
30 | "file_settings" : MessageLookupByLibrary.simpleMessage("Open App Settings"),
31 | "file_total" : MessageLookupByLibrary.simpleMessage("Total:"),
32 | "movie_casts" : MessageLookupByLibrary.simpleMessage("Directors / Actors"),
33 | "movie_comments" : MessageLookupByLibrary.simpleMessage("Comments"),
34 | "movie_comments_all" : MessageLookupByLibrary.simpleMessage("All"),
35 | "movie_director" : MessageLookupByLibrary.simpleMessage("Director"),
36 | "movie_duration" : MessageLookupByLibrary.simpleMessage("Duration"),
37 | "movie_genre" : MessageLookupByLibrary.simpleMessage("Genre"),
38 | "movie_language" : MessageLookupByLibrary.simpleMessage("Language"),
39 | "movie_none_rating" : MessageLookupByLibrary.simpleMessage("None"),
40 | "movie_photos" : MessageLookupByLibrary.simpleMessage("Photos"),
41 | "movie_recommended" : MessageLookupByLibrary.simpleMessage("Recommended"),
42 | "movie_region" : MessageLookupByLibrary.simpleMessage("Region"),
43 | "movie_release" : MessageLookupByLibrary.simpleMessage("Release"),
44 | "movie_review" : MessageLookupByLibrary.simpleMessage("Review"),
45 | "movie_scored" : MessageLookupByLibrary.simpleMessage(" Scored"),
46 | "movie_seen" : MessageLookupByLibrary.simpleMessage(" Seen"),
47 | "movie_share" : MessageLookupByLibrary.simpleMessage("Share"),
48 | "movie_summary" : MessageLookupByLibrary.simpleMessage("Summary"),
49 | "movie_trailers" : MessageLookupByLibrary.simpleMessage("Trailers"),
50 | "movie_unrelease" : MessageLookupByLibrary.simpleMessage("Unrelease"),
51 | "movies_ranks" : MessageLookupByLibrary.simpleMessage("Ranks"),
52 | "refresh_empty" : MessageLookupByLibrary.simpleMessage("Reload"),
53 | "refresh_reload" : MessageLookupByLibrary.simpleMessage("No Data"),
54 | "search_find" : MessageLookupByLibrary.simpleMessage("Find Movies、TVs"),
55 | "search_hint" : MessageLookupByLibrary.simpleMessage("Enter Keywords"),
56 | "search_recommended" : MessageLookupByLibrary.simpleMessage("Recommended"),
57 | "search_results" : MessageLookupByLibrary.simpleMessage("Results"),
58 | "search_title" : MessageLookupByLibrary.simpleMessage("Search"),
59 | "settings_about" : MessageLookupByLibrary.simpleMessage("About"),
60 | "settings_language" : MessageLookupByLibrary.simpleMessage("Language"),
61 | "settings_theme" : MessageLookupByLibrary.simpleMessage("Theme"),
62 | "settings_theme_dark" : MessageLookupByLibrary.simpleMessage("Dark"),
63 | "settings_theme_light" : MessageLookupByLibrary.simpleMessage("Light"),
64 | "settings_theme_system" : MessageLookupByLibrary.simpleMessage("Follow System"),
65 | "settings_title" : MessageLookupByLibrary.simpleMessage("Settings"),
66 | "show_domestic" : MessageLookupByLibrary.simpleMessage("Domestic"),
67 | "show_foreign" : MessageLookupByLibrary.simpleMessage("Foreign"),
68 | "show_hot" : MessageLookupByLibrary.simpleMessage("Hot"),
69 | "tab_movies" : MessageLookupByLibrary.simpleMessage("Movies"),
70 | "tab_settings" : MessageLookupByLibrary.simpleMessage("Settings"),
71 | "tab_shows" : MessageLookupByLibrary.simpleMessage("Shows"),
72 | "tab_tvs" : MessageLookupByLibrary.simpleMessage("TVs"),
73 | "tv_american" : MessageLookupByLibrary.simpleMessage("American"),
74 | "tv_animation" : MessageLookupByLibrary.simpleMessage("Animation"),
75 | "tv_domestic" : MessageLookupByLibrary.simpleMessage("Domestic"),
76 | "tv_hot" : MessageLookupByLibrary.simpleMessage("Hot"),
77 | "tv_japanese" : MessageLookupByLibrary.simpleMessage("Japanese"),
78 | "tv_korean" : MessageLookupByLibrary.simpleMessage("Korean")
79 | };
80 | }
81 |
--------------------------------------------------------------------------------
/movies/lib/generated/intl/messages_zh.dart:
--------------------------------------------------------------------------------
1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
2 | // This is a library that provides messages for a zh locale. All the
3 | // messages from the main program should be duplicated here with the same
4 | // function name.
5 |
6 | // Ignore issues from commonly used lints in this file.
7 | // ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
8 | // ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
9 | // ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
10 | // ignore_for_file:unused_import, file_names
11 |
12 | import 'package:intl/intl.dart';
13 | import 'package:intl/message_lookup_by_library.dart';
14 |
15 | final messages = new MessageLookup();
16 |
17 | typedef String MessageIfAbsent(String messageStr, List args);
18 |
19 | class MessageLookup extends MessageLookupByLibrary {
20 | String get localeName => 'zh';
21 |
22 | final messages = _notInlinedMessages(_notInlinedMessages);
23 | static _notInlinedMessages(_) => {
24 | "file_cancel" : MessageLookupByLibrary.simpleMessage("取消"),
25 | "file_completed" : MessageLookupByLibrary.simpleMessage("已下载:"),
26 | "file_download" : MessageLookupByLibrary.simpleMessage("下载到相册"),
27 | "file_downloading" : MessageLookupByLibrary.simpleMessage("正在下载..."),
28 | "file_permission" : MessageLookupByLibrary.simpleMessage("无相册访问权限"),
29 | "file_save" : MessageLookupByLibrary.simpleMessage("保存到相册"),
30 | "file_settings" : MessageLookupByLibrary.simpleMessage("去设置"),
31 | "file_total" : MessageLookupByLibrary.simpleMessage("共计:"),
32 | "movie_casts" : MessageLookupByLibrary.simpleMessage("导演 / 演员"),
33 | "movie_comments" : MessageLookupByLibrary.simpleMessage("短评"),
34 | "movie_comments_all" : MessageLookupByLibrary.simpleMessage("全部"),
35 | "movie_director" : MessageLookupByLibrary.simpleMessage("导演"),
36 | "movie_duration" : MessageLookupByLibrary.simpleMessage("时长"),
37 | "movie_genre" : MessageLookupByLibrary.simpleMessage("类型"),
38 | "movie_language" : MessageLookupByLibrary.simpleMessage("语言"),
39 | "movie_none_rating" : MessageLookupByLibrary.simpleMessage("暂无评分"),
40 | "movie_photos" : MessageLookupByLibrary.simpleMessage("剧照"),
41 | "movie_recommended" : MessageLookupByLibrary.simpleMessage("同类推荐"),
42 | "movie_region" : MessageLookupByLibrary.simpleMessage("地区"),
43 | "movie_release" : MessageLookupByLibrary.simpleMessage("上映"),
44 | "movie_review" : MessageLookupByLibrary.simpleMessage("影评"),
45 | "movie_scored" : MessageLookupByLibrary.simpleMessage("人评分"),
46 | "movie_seen" : MessageLookupByLibrary.simpleMessage("人看过"),
47 | "movie_share" : MessageLookupByLibrary.simpleMessage("分享"),
48 | "movie_summary" : MessageLookupByLibrary.simpleMessage("简介"),
49 | "movie_trailers" : MessageLookupByLibrary.simpleMessage("预告"),
50 | "movie_unrelease" : MessageLookupByLibrary.simpleMessage("暂未上映"),
51 | "movies_ranks" : MessageLookupByLibrary.simpleMessage("热门榜单"),
52 | "refresh_empty" : MessageLookupByLibrary.simpleMessage("暂无数据"),
53 | "refresh_reload" : MessageLookupByLibrary.simpleMessage("重新加载"),
54 | "search_find" : MessageLookupByLibrary.simpleMessage("找电影、电视剧"),
55 | "search_hint" : MessageLookupByLibrary.simpleMessage("请输入搜索关键字"),
56 | "search_recommended" : MessageLookupByLibrary.simpleMessage("推荐"),
57 | "search_results" : MessageLookupByLibrary.simpleMessage("搜索结果"),
58 | "search_title" : MessageLookupByLibrary.simpleMessage("搜索"),
59 | "settings_about" : MessageLookupByLibrary.simpleMessage("关于"),
60 | "settings_language" : MessageLookupByLibrary.simpleMessage("语言"),
61 | "settings_theme" : MessageLookupByLibrary.simpleMessage("主题"),
62 | "settings_theme_dark" : MessageLookupByLibrary.simpleMessage("深色"),
63 | "settings_theme_light" : MessageLookupByLibrary.simpleMessage("浅色"),
64 | "settings_theme_system" : MessageLookupByLibrary.simpleMessage("跟随系统"),
65 | "settings_title" : MessageLookupByLibrary.simpleMessage("设置"),
66 | "show_domestic" : MessageLookupByLibrary.simpleMessage("国内"),
67 | "show_foreign" : MessageLookupByLibrary.simpleMessage("国外"),
68 | "show_hot" : MessageLookupByLibrary.simpleMessage("综合"),
69 | "tab_movies" : MessageLookupByLibrary.simpleMessage("电影"),
70 | "tab_settings" : MessageLookupByLibrary.simpleMessage("设置"),
71 | "tab_shows" : MessageLookupByLibrary.simpleMessage("综艺"),
72 | "tab_tvs" : MessageLookupByLibrary.simpleMessage("电视剧"),
73 | "tv_american" : MessageLookupByLibrary.simpleMessage("欧美剧"),
74 | "tv_animation" : MessageLookupByLibrary.simpleMessage("动画"),
75 | "tv_domestic" : MessageLookupByLibrary.simpleMessage("国产剧"),
76 | "tv_hot" : MessageLookupByLibrary.simpleMessage("综合"),
77 | "tv_japanese" : MessageLookupByLibrary.simpleMessage("日剧"),
78 | "tv_korean" : MessageLookupByLibrary.simpleMessage("韩剧")
79 | };
80 | }
81 |
--------------------------------------------------------------------------------
/movies/lib/generated/l10n.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 | import 'package:flutter/material.dart';
3 | import 'package:intl/intl.dart';
4 | import 'intl/messages_all.dart';
5 |
6 | // **************************************************************************
7 | // Generator: Flutter Intl IDE plugin
8 | // Made by Localizely
9 | // **************************************************************************
10 |
11 | // ignore_for_file: non_constant_identifier_names, lines_longer_than_80_chars
12 | // ignore_for_file: join_return_with_assignment, prefer_final_in_for_each
13 | // ignore_for_file: avoid_redundant_argument_values
14 |
15 | class S {
16 | S();
17 |
18 | static S current;
19 |
20 | static const AppLocalizationDelegate delegate =
21 | AppLocalizationDelegate();
22 |
23 | static Future load(Locale locale) {
24 | final name = (locale.countryCode?.isEmpty ?? false) ? locale.languageCode : locale.toString();
25 | final localeName = Intl.canonicalizedLocale(name);
26 | return initializeMessages(localeName).then((_) {
27 | Intl.defaultLocale = localeName;
28 | S.current = S();
29 |
30 | return S.current;
31 | });
32 | }
33 |
34 | static S of(BuildContext context) {
35 | return Localizations.of(context, S);
36 | }
37 |
38 | /// `Movies`
39 | String get tab_movies {
40 | return Intl.message(
41 | 'Movies',
42 | name: 'tab_movies',
43 | desc: '',
44 | args: [],
45 | );
46 | }
47 |
48 | /// `TVs`
49 | String get tab_tvs {
50 | return Intl.message(
51 | 'TVs',
52 | name: 'tab_tvs',
53 | desc: '',
54 | args: [],
55 | );
56 | }
57 |
58 | /// `Shows`
59 | String get tab_shows {
60 | return Intl.message(
61 | 'Shows',
62 | name: 'tab_shows',
63 | desc: '',
64 | args: [],
65 | );
66 | }
67 |
68 | /// `Settings`
69 | String get tab_settings {
70 | return Intl.message(
71 | 'Settings',
72 | name: 'tab_settings',
73 | desc: '',
74 | args: [],
75 | );
76 | }
77 |
78 | /// `Ranks`
79 | String get movies_ranks {
80 | return Intl.message(
81 | 'Ranks',
82 | name: 'movies_ranks',
83 | desc: '',
84 | args: [],
85 | );
86 | }
87 |
88 | /// `Recommended`
89 | String get search_recommended {
90 | return Intl.message(
91 | 'Recommended',
92 | name: 'search_recommended',
93 | desc: '',
94 | args: [],
95 | );
96 | }
97 |
98 | /// `Search`
99 | String get search_title {
100 | return Intl.message(
101 | 'Search',
102 | name: 'search_title',
103 | desc: '',
104 | args: [],
105 | );
106 | }
107 |
108 | /// `Find Movies、TVs`
109 | String get search_find {
110 | return Intl.message(
111 | 'Find Movies、TVs',
112 | name: 'search_find',
113 | desc: '',
114 | args: [],
115 | );
116 | }
117 |
118 | /// `Enter Keywords`
119 | String get search_hint {
120 | return Intl.message(
121 | 'Enter Keywords',
122 | name: 'search_hint',
123 | desc: '',
124 | args: [],
125 | );
126 | }
127 |
128 | /// `Results`
129 | String get search_results {
130 | return Intl.message(
131 | 'Results',
132 | name: 'search_results',
133 | desc: '',
134 | args: [],
135 | );
136 | }
137 |
138 | /// `Hot`
139 | String get tv_hot {
140 | return Intl.message(
141 | 'Hot',
142 | name: 'tv_hot',
143 | desc: '',
144 | args: [],
145 | );
146 | }
147 |
148 | /// `Domestic`
149 | String get tv_domestic {
150 | return Intl.message(
151 | 'Domestic',
152 | name: 'tv_domestic',
153 | desc: '',
154 | args: [],
155 | );
156 | }
157 |
158 | /// `American`
159 | String get tv_american {
160 | return Intl.message(
161 | 'American',
162 | name: 'tv_american',
163 | desc: '',
164 | args: [],
165 | );
166 | }
167 |
168 | /// `Japanese`
169 | String get tv_japanese {
170 | return Intl.message(
171 | 'Japanese',
172 | name: 'tv_japanese',
173 | desc: '',
174 | args: [],
175 | );
176 | }
177 |
178 | /// `Korean`
179 | String get tv_korean {
180 | return Intl.message(
181 | 'Korean',
182 | name: 'tv_korean',
183 | desc: '',
184 | args: [],
185 | );
186 | }
187 |
188 | /// `Animation`
189 | String get tv_animation {
190 | return Intl.message(
191 | 'Animation',
192 | name: 'tv_animation',
193 | desc: '',
194 | args: [],
195 | );
196 | }
197 |
198 | /// `Hot`
199 | String get show_hot {
200 | return Intl.message(
201 | 'Hot',
202 | name: 'show_hot',
203 | desc: '',
204 | args: [],
205 | );
206 | }
207 |
208 | /// `Domestic`
209 | String get show_domestic {
210 | return Intl.message(
211 | 'Domestic',
212 | name: 'show_domestic',
213 | desc: '',
214 | args: [],
215 | );
216 | }
217 |
218 | /// `Foreign`
219 | String get show_foreign {
220 | return Intl.message(
221 | 'Foreign',
222 | name: 'show_foreign',
223 | desc: '',
224 | args: [],
225 | );
226 | }
227 |
228 | /// `Settings`
229 | String get settings_title {
230 | return Intl.message(
231 | 'Settings',
232 | name: 'settings_title',
233 | desc: '',
234 | args: [],
235 | );
236 | }
237 |
238 | /// `Language`
239 | String get settings_language {
240 | return Intl.message(
241 | 'Language',
242 | name: 'settings_language',
243 | desc: '',
244 | args: [],
245 | );
246 | }
247 |
248 | /// `About`
249 | String get settings_about {
250 | return Intl.message(
251 | 'About',
252 | name: 'settings_about',
253 | desc: '',
254 | args: [],
255 | );
256 | }
257 |
258 | /// `Theme`
259 | String get settings_theme {
260 | return Intl.message(
261 | 'Theme',
262 | name: 'settings_theme',
263 | desc: '',
264 | args: [],
265 | );
266 | }
267 |
268 | /// `Light`
269 | String get settings_theme_light {
270 | return Intl.message(
271 | 'Light',
272 | name: 'settings_theme_light',
273 | desc: '',
274 | args: [],
275 | );
276 | }
277 |
278 | /// `Dark`
279 | String get settings_theme_dark {
280 | return Intl.message(
281 | 'Dark',
282 | name: 'settings_theme_dark',
283 | desc: '',
284 | args: [],
285 | );
286 | }
287 |
288 | /// `Follow System`
289 | String get settings_theme_system {
290 | return Intl.message(
291 | 'Follow System',
292 | name: 'settings_theme_system',
293 | desc: '',
294 | args: [],
295 | );
296 | }
297 |
298 | /// ` Seen`
299 | String get movie_seen {
300 | return Intl.message(
301 | ' Seen',
302 | name: 'movie_seen',
303 | desc: '',
304 | args: [],
305 | );
306 | }
307 |
308 | /// ` Scored`
309 | String get movie_scored {
310 | return Intl.message(
311 | ' Scored',
312 | name: 'movie_scored',
313 | desc: '',
314 | args: [],
315 | );
316 | }
317 |
318 | /// `None`
319 | String get movie_none_rating {
320 | return Intl.message(
321 | 'None',
322 | name: 'movie_none_rating',
323 | desc: '',
324 | args: [],
325 | );
326 | }
327 |
328 | /// `Unrelease`
329 | String get movie_unrelease {
330 | return Intl.message(
331 | 'Unrelease',
332 | name: 'movie_unrelease',
333 | desc: '',
334 | args: [],
335 | );
336 | }
337 |
338 | /// `Review`
339 | String get movie_review {
340 | return Intl.message(
341 | 'Review',
342 | name: 'movie_review',
343 | desc: '',
344 | args: [],
345 | );
346 | }
347 |
348 | /// `Share`
349 | String get movie_share {
350 | return Intl.message(
351 | 'Share',
352 | name: 'movie_share',
353 | desc: '',
354 | args: [],
355 | );
356 | }
357 |
358 | /// `Summary`
359 | String get movie_summary {
360 | return Intl.message(
361 | 'Summary',
362 | name: 'movie_summary',
363 | desc: '',
364 | args: [],
365 | );
366 | }
367 |
368 | /// `Directors / Actors`
369 | String get movie_casts {
370 | return Intl.message(
371 | 'Directors / Actors',
372 | name: 'movie_casts',
373 | desc: '',
374 | args: [],
375 | );
376 | }
377 |
378 | /// `Director`
379 | String get movie_director {
380 | return Intl.message(
381 | 'Director',
382 | name: 'movie_director',
383 | desc: '',
384 | args: [],
385 | );
386 | }
387 |
388 | /// `Trailers`
389 | String get movie_trailers {
390 | return Intl.message(
391 | 'Trailers',
392 | name: 'movie_trailers',
393 | desc: '',
394 | args: [],
395 | );
396 | }
397 |
398 | /// `Comments`
399 | String get movie_comments {
400 | return Intl.message(
401 | 'Comments',
402 | name: 'movie_comments',
403 | desc: '',
404 | args: [],
405 | );
406 | }
407 |
408 | /// `All`
409 | String get movie_comments_all {
410 | return Intl.message(
411 | 'All',
412 | name: 'movie_comments_all',
413 | desc: '',
414 | args: [],
415 | );
416 | }
417 |
418 | /// `Recommended`
419 | String get movie_recommended {
420 | return Intl.message(
421 | 'Recommended',
422 | name: 'movie_recommended',
423 | desc: '',
424 | args: [],
425 | );
426 | }
427 |
428 | /// `Photos`
429 | String get movie_photos {
430 | return Intl.message(
431 | 'Photos',
432 | name: 'movie_photos',
433 | desc: '',
434 | args: [],
435 | );
436 | }
437 |
438 | /// `Release`
439 | String get movie_release {
440 | return Intl.message(
441 | 'Release',
442 | name: 'movie_release',
443 | desc: '',
444 | args: [],
445 | );
446 | }
447 |
448 | /// `Genre`
449 | String get movie_genre {
450 | return Intl.message(
451 | 'Genre',
452 | name: 'movie_genre',
453 | desc: '',
454 | args: [],
455 | );
456 | }
457 |
458 | /// `Duration`
459 | String get movie_duration {
460 | return Intl.message(
461 | 'Duration',
462 | name: 'movie_duration',
463 | desc: '',
464 | args: [],
465 | );
466 | }
467 |
468 | /// `Region`
469 | String get movie_region {
470 | return Intl.message(
471 | 'Region',
472 | name: 'movie_region',
473 | desc: '',
474 | args: [],
475 | );
476 | }
477 |
478 | /// `Language`
479 | String get movie_language {
480 | return Intl.message(
481 | 'Language',
482 | name: 'movie_language',
483 | desc: '',
484 | args: [],
485 | );
486 | }
487 |
488 | /// `Downloading...`
489 | String get file_downloading {
490 | return Intl.message(
491 | 'Downloading...',
492 | name: 'file_downloading',
493 | desc: '',
494 | args: [],
495 | );
496 | }
497 |
498 | /// `Total:`
499 | String get file_total {
500 | return Intl.message(
501 | 'Total:',
502 | name: 'file_total',
503 | desc: '',
504 | args: [],
505 | );
506 | }
507 |
508 | /// `Completed:`
509 | String get file_completed {
510 | return Intl.message(
511 | 'Completed:',
512 | name: 'file_completed',
513 | desc: '',
514 | args: [],
515 | );
516 | }
517 |
518 | /// `No Permission to Access Album`
519 | String get file_permission {
520 | return Intl.message(
521 | 'No Permission to Access Album',
522 | name: 'file_permission',
523 | desc: '',
524 | args: [],
525 | );
526 | }
527 |
528 | /// `Cancel`
529 | String get file_cancel {
530 | return Intl.message(
531 | 'Cancel',
532 | name: 'file_cancel',
533 | desc: '',
534 | args: [],
535 | );
536 | }
537 |
538 | /// `Save to Album`
539 | String get file_save {
540 | return Intl.message(
541 | 'Save to Album',
542 | name: 'file_save',
543 | desc: '',
544 | args: [],
545 | );
546 | }
547 |
548 | /// `Download to Album`
549 | String get file_download {
550 | return Intl.message(
551 | 'Download to Album',
552 | name: 'file_download',
553 | desc: '',
554 | args: [],
555 | );
556 | }
557 |
558 | /// `Open App Settings`
559 | String get file_settings {
560 | return Intl.message(
561 | 'Open App Settings',
562 | name: 'file_settings',
563 | desc: '',
564 | args: [],
565 | );
566 | }
567 |
568 | /// `No Data`
569 | String get refresh_reload {
570 | return Intl.message(
571 | 'No Data',
572 | name: 'refresh_reload',
573 | desc: '',
574 | args: [],
575 | );
576 | }
577 |
578 | /// `Reload`
579 | String get refresh_empty {
580 | return Intl.message(
581 | 'Reload',
582 | name: 'refresh_empty',
583 | desc: '',
584 | args: [],
585 | );
586 | }
587 | }
588 |
589 | class AppLocalizationDelegate extends LocalizationsDelegate {
590 | const AppLocalizationDelegate();
591 |
592 | List get supportedLocales {
593 | return const [
594 | Locale.fromSubtags(languageCode: 'en'),
595 | Locale.fromSubtags(languageCode: 'zh'),
596 | ];
597 | }
598 |
599 | @override
600 | bool isSupported(Locale locale) => _isSupported(locale);
601 | @override
602 | Future load(Locale locale) => S.load(locale);
603 | @override
604 | bool shouldReload(AppLocalizationDelegate old) => false;
605 |
606 | bool _isSupported(Locale locale) {
607 | if (locale != null) {
608 | for (var supportedLocale in supportedLocales) {
609 | if (supportedLocale.languageCode == locale.languageCode) {
610 | return true;
611 | }
612 | }
613 | }
614 | return false;
615 | }
616 | }
--------------------------------------------------------------------------------
/movies/lib/l10n/intl_en.arb:
--------------------------------------------------------------------------------
1 | {
2 | "tab_movies": "Movies",
3 | "tab_tvs": "TVs",
4 | "tab_shows": "Shows",
5 | "tab_settings": "Settings",
6 | "movies_ranks": "Ranks",
7 |
8 | "search_recommended": "Recommended",
9 | "search_title": "Search",
10 | "search_find": "Find Movies、TVs",
11 | "search_hint": "Enter Keywords",
12 | "search_results": "Results",
13 |
14 | "tv_hot": "Hot",
15 | "tv_domestic": "Domestic",
16 | "tv_american": "American",
17 | "tv_japanese": "Japanese",
18 | "tv_korean": "Korean",
19 | "tv_animation": "Animation",
20 | "show_hot": "Hot",
21 | "show_domestic": "Domestic",
22 | "show_foreign": "Foreign",
23 |
24 | "settings_title": "Settings",
25 | "settings_language": "Language",
26 | "settings_about": "About",
27 | "settings_theme": "Theme",
28 | "settings_theme_light": "Light",
29 | "settings_theme_dark": "Dark",
30 | "settings_theme_system": "Follow System",
31 |
32 | "movie_seen": " Seen",
33 | "movie_scored": " Scored",
34 | "movie_none_rating": "None",
35 | "movie_unrelease": "Unrelease",
36 | "movie_review": "Review",
37 | "movie_share": "Share",
38 | "movie_summary": "Summary",
39 | "movie_casts": "Directors / Actors",
40 | "movie_director": "Director",
41 | "movie_trailers": "Trailers",
42 | "movie_comments": "Comments",
43 | "movie_comments_all": "All",
44 | "movie_recommended": "Recommended",
45 | "movie_photos": "Photos",
46 | "movie_release": "Release",
47 | "movie_genre": "Genre",
48 | "movie_duration": "Duration",
49 | "movie_region": "Region",
50 | "movie_language": "Language",
51 |
52 | "file_downloading": "Downloading...",
53 | "file_total": "Total:",
54 | "file_completed": "Completed:",
55 | "file_permission": "No Permission to Access Album",
56 | "file_cancel": "Cancel",
57 | "file_save": "Save to Album",
58 | "file_download": "Download to Album",
59 | "file_settings": "Open App Settings",
60 |
61 | "refresh_reload": "No Data",
62 | "refresh_empty": "Reload"
63 | }
--------------------------------------------------------------------------------
/movies/lib/l10n/intl_zh.arb:
--------------------------------------------------------------------------------
1 | {
2 | "tab_movies": "电影",
3 | "tab_tvs": "电视剧",
4 | "tab_shows": "综艺",
5 | "tab_settings": "设置",
6 | "movies_ranks": "热门榜单",
7 |
8 | "search_recommended": "推荐",
9 | "search_title": "搜索",
10 | "search_find": "找电影、电视剧",
11 | "search_hint": "请输入搜索关键字",
12 | "search_results": "搜索结果",
13 |
14 | "tv_hot": "综合",
15 | "tv_domestic": "国产剧",
16 | "tv_american": "欧美剧",
17 | "tv_japanese": "日剧",
18 | "tv_korean": "韩剧",
19 | "tv_animation": "动画",
20 | "show_hot": "综合",
21 | "show_domestic": "国内",
22 | "show_foreign": "国外",
23 |
24 | "settings_title": "设置",
25 | "settings_language": "语言",
26 | "settings_about": "关于",
27 | "settings_theme": "主题",
28 | "settings_theme_light": "浅色",
29 | "settings_theme_dark": "深色",
30 | "settings_theme_system": "跟随系统",
31 |
32 | "movie_seen": "人看过",
33 | "movie_scored": "人评分",
34 | "movie_none_rating": "暂无评分",
35 | "movie_unrelease": "暂未上映",
36 | "movie_review": "影评",
37 | "movie_share": "分享",
38 | "movie_summary": "简介",
39 | "movie_casts": "导演 / 演员",
40 | "movie_director": "导演",
41 | "movie_trailers": "预告",
42 | "movie_comments": "短评",
43 | "movie_comments_all": "全部",
44 | "movie_recommended": "同类推荐",
45 | "movie_photos": "剧照",
46 | "movie_release": "上映",
47 | "movie_genre": "类型",
48 | "movie_duration": "时长",
49 | "movie_region": "地区",
50 | "movie_language": "语言",
51 |
52 | "file_downloading": "正在下载...",
53 | "file_total": "共计:",
54 | "file_completed": "已下载:",
55 | "file_permission": "无相册访问权限",
56 | "file_cancel": "取消",
57 | "file_save": "保存到相册",
58 | "file_download": "下载到相册",
59 | "file_settings": "去设置",
60 |
61 | "refresh_reload": "重新加载",
62 | "refresh_empty": "暂无数据"
63 | }
--------------------------------------------------------------------------------
/movies/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/services.dart';
2 | import 'package:movies/util/constant.dart';
3 | import 'package:movies/util/router_manager.dart';
4 | import 'package:movies/util/storage_manager.dart';
5 | import 'package:movies/view_model/locale_view_model.dart';
6 | import 'package:movies/view_model/theme_view_model.dart';
7 | import 'package:flutter/material.dart';
8 |
9 | import 'package:flutter_localizations/flutter_localizations.dart';
10 | import 'package:permission_handler/permission_handler.dart';
11 | import 'package:provider/provider.dart';
12 | import 'package:pull_to_refresh/pull_to_refresh.dart';
13 |
14 | import 'generated/l10n.dart';
15 |
16 |
17 | void main() async {
18 |
19 | WidgetsFlutterBinding.ensureInitialized();
20 |
21 | await StorageManager.setup().then((_) {
22 |
23 | RouterManager.setup();
24 | runApp(MovieApp());
25 | Permission.storage.request();
26 |
27 | });
28 |
29 |
30 | }
31 |
32 | class MovieApp extends StatefulWidget {
33 | @override
34 | _MovieAppState createState() => _MovieAppState();
35 | }
36 |
37 | class _MovieAppState extends State {
38 |
39 |
40 | @override
41 | Widget build(BuildContext context) {
42 |
43 | SystemChrome.setPreferredOrientations([
44 | DeviceOrientation.portraitUp,
45 | ]);
46 |
47 |
48 | return MultiProvider(
49 | providers: providers,
50 | child: Consumer2(
51 | builder: (context, viewModel1, viewModel2, widget) {
52 |
53 | return MaterialApp(
54 | debugShowCheckedModeBanner: false,
55 | theme: lightData,
56 | darkTheme: darkData,
57 | themeMode: viewModel1.current,
58 | initialRoute: '/',
59 | onGenerateRoute: RouterManager.router.generator,
60 | localizationsDelegates: [
61 | S.delegate,
62 | RefreshLocalizations.delegate,
63 | GlobalMaterialLocalizations.delegate,
64 | GlobalWidgetsLocalizations.delegate
65 | ],
66 | supportedLocales: S.delegate.supportedLocales,
67 | locale: viewModel2.current,
68 | );
69 | },
70 | )
71 | );
72 | }
73 | }
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/movies/lib/model/base_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:movies/model/photo_model.dart';
2 |
3 | class BaseList {
4 |
5 | num count, start, total;
6 |
7 | List subjects;
8 |
9 | String id, name;
10 |
11 | BaseList.fromJson(json){
12 |
13 | count = json['count'];
14 | start = json['start'];
15 | total = json['total'];
16 |
17 | }
18 |
19 | }
20 |
21 | class BaseMovie {
22 |
23 | String id, title, name, type;
24 | BaseRating rating;
25 |
26 | String get path => '.$type.$id';
27 |
28 | BaseMovie();
29 |
30 | BaseMovie.fromJson(json){
31 |
32 | id = json['id'];
33 | title = json['title'];
34 | name = json['name'];
35 | type = json['type'];
36 | rating = BaseRating.fromJson(json['rating']);
37 | }
38 |
39 | }
40 |
41 | class BaseRating {
42 |
43 | num value = 0, count = 0;
44 |
45 | String stars = '0.0';
46 |
47 | get fullCount {
48 | return num.parse(stars[0]).toInt();
49 | }
50 |
51 | get halfCount {
52 | if (stars.length > 2) {
53 | return num.parse(stars[2] ?? 0) ~/ 5;
54 | }
55 | return 0;
56 | }
57 |
58 | get emptyCount {
59 | return 5 - fullCount - halfCount;
60 | }
61 |
62 |
63 | BaseRating.fromJson(json) {
64 | if (json != null) {
65 | value = json['value'];
66 | stars = json['star_count'].toString();
67 | count = json['count'];
68 | }
69 |
70 | }
71 |
72 | }
73 |
74 | class BaseColor {
75 |
76 | bool isDark;
77 |
78 | String dark, light, secondary;
79 |
80 | get primary {
81 | return '#${isDark ? dark : light}';
82 | }
83 |
84 | BaseColor.fromJson(json) {
85 | isDark = json['is_dark'];
86 | dark = json['primary_color_dark'];
87 | light = json['primary_color_light'];
88 | secondary = json['secondary_color'];
89 | }
90 |
91 | }
92 |
93 | class GalleryItem {
94 |
95 | String id, url, title, subTitle;
96 |
97 | GalleryItem.formStaff(json) {
98 | id = 'staff_${json['id']}';
99 | url = json['cover_url'];
100 | title = json['name'];
101 | subTitle = json['title'];
102 | }
103 |
104 | GalleryItem.formPhoto(PhotoListItem item) {
105 | id = 'photo_${item.id}';
106 | url = item.l.url;
107 | }
108 |
109 | GalleryItem.formUrl(String value) {
110 | id = value;
111 | url = value;
112 | }
113 |
114 | }
115 |
116 |
117 |
--------------------------------------------------------------------------------
/movies/lib/model/comment_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:movies/model/user_model.dart';
2 |
3 | import 'base_model.dart';
4 |
5 | class CommentList extends BaseList {
6 |
7 | CommentList.fromJson(json) : super.fromJson(json) {
8 | subjects = ((json['interests'] ?? json['reviews']) as List)
9 | .map( (json) => CommentListItem.fromJson(json))
10 | .toList();
11 | }
12 |
13 | }
14 |
15 | class CommentListItem extends BaseMovie {
16 |
17 | num useful_count;
18 | String url;
19 | String abstract;
20 | String create_time;
21 | User user;
22 |
23 | CommentListItem.fromJson(json) : super.fromJson(json) {
24 |
25 | useful_count = json['useful_count'] ?? json['vote_count'];
26 | url = json['url'];
27 | abstract = json['abstract'] ?? json['comment'];
28 | create_time = json['create_time'];
29 | user = User.fromJson(json['user']);
30 | }
31 |
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/movies/lib/model/movie_model.dart:
--------------------------------------------------------------------------------
1 | import 'base_model.dart';
2 |
3 | class MovieList extends BaseList {
4 |
5 | MovieList.fromJson(json) : super.fromJson(json) {
6 | id = json['subject_collection']['id'];
7 | name = json['subject_collection']['name'];
8 | subjects = (json['subject_collection_items'] as List)
9 | .map( (json) => MovieListItem.fromJson(json))
10 | .toList();
11 |
12 | }
13 |
14 | }
15 |
16 | class Movie extends BaseMovie {
17 |
18 | String cover;
19 |
20 | BaseColor color;
21 |
22 | List actors;
23 | List directors;
24 |
25 | MovieTrailer trailer;
26 |
27 | bool released;
28 | String intro;
29 | String pubdate;
30 | String durations;
31 | String languages;
32 | String countries;
33 | String genres;
34 | String url;
35 |
36 | List get staffs {
37 | actors.forEach((v) => v.subTitle = '');
38 | directors.forEach((v) => v.subTitle = 'director');
39 | return directors + actors;
40 | }
41 |
42 | Movie.fromJson(json) : super.fromJson(json) {
43 |
44 |
45 | cover = json['pic']['normal'].toString().replaceAll('webp', 'jpg');
46 | actors = (json['actors'] as List).map((v) => GalleryItem.formStaff(v)).toList();
47 | directors = (json['directors'] as List).map((v) => GalleryItem.formStaff(v)).toList();
48 | color = BaseColor.fromJson(json['color_scheme']);
49 | intro = json['intro'];
50 | url = json['info_url'];
51 |
52 |
53 | if (json['trailer'] != null) {
54 | trailer = MovieTrailer.fromJson(json['trailer']);
55 | }
56 |
57 | released = json['is_released'];
58 | pubdate = (json['pubdate'] as List).join(' / ');
59 | durations = (json['durations'] as List).join(' / ');
60 | genres = (json['genres'] as List).join(' / ');
61 | languages = (json['languages'] as List).join(' / ');
62 | countries = (json['countries'] as List).join(' / ');
63 | }
64 |
65 | }
66 |
67 | class MovieListItem extends BaseMovie {
68 |
69 |
70 | String subtitle;
71 | String info;
72 | String cover;
73 |
74 | List actors;
75 | List directors;
76 |
77 | String year;
78 | String release_date;
79 |
80 | String original_title;
81 |
82 | String description;
83 |
84 | String get genre {
85 | final list = info.split('/');
86 | if (list.length > 2) {
87 | return list[1].trim();
88 | }
89 | return info;
90 | }
91 |
92 |
93 | MovieListItem.fromJson(json) : super.fromJson(json) {
94 |
95 | subtitle = json['card_subtitle'];
96 | info = json['info'];
97 | cover = json['cover']['url'].toString().replaceAll('webp', 'jpg');
98 | actors = json['actors'];
99 | directors = json['directors'];
100 | year = json['year'];
101 | release_date = json['release_date'];
102 | original_title = json['original_title'];
103 | description = json['description'];
104 |
105 | }
106 |
107 |
108 | }
109 |
110 | class MovieGridItem extends BaseMovie {
111 |
112 | String cover;
113 | String url;
114 |
115 | MovieGridItem.fromJson(json) : super.fromJson(json) {
116 |
117 | cover = json['pic']['normal'].toString().replaceAll('webp', 'jpg');
118 | url = json['url'];
119 |
120 | }
121 |
122 | MovieGridItem.from(MovieListItem movie) : super() {
123 |
124 | type = movie.type;
125 | id = movie.id;
126 | title = movie.title;
127 | cover = movie.cover;
128 | rating = movie.rating;
129 |
130 | }
131 |
132 | }
133 |
134 | class MovieTrailer extends BaseMovie {
135 |
136 | String cover;
137 | String video;
138 |
139 | MovieTrailer.fromJson(json) : super.fromJson(json) {
140 | cover = json['cover_url'];
141 | video = json['video_url'];
142 | }
143 |
144 | }
145 |
146 |
147 |
--------------------------------------------------------------------------------
/movies/lib/model/movies_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:movies/model/base_model.dart';
2 |
3 | class HomeSearchList extends BaseList {
4 | HomeSearchList.fromJson(json) : super.fromJson(json) {
5 | subjects = (json['subjects'] as List)
6 | .map( (json) => HomeSearchItem.fromJson(json))
7 | .toList();
8 | }
9 | }
10 |
11 | class HomeSearchItem extends BaseMovie {
12 |
13 | String rate, cover;
14 |
15 |
16 | HomeSearchItem.fromJson(json) : super.fromJson(json) {
17 | String _rate = json['rate'];
18 | cover = json['cover'];
19 | rate = _rate.isNotEmpty ? _rate : '0';
20 |
21 | }
22 |
23 |
24 | }
25 |
26 | class MoviesToday {
27 |
28 | String comment, lunar, cover;
29 | BaseMovie movie;
30 | BaseRating rating;
31 | BaseColor color;
32 |
33 |
34 | MoviesToday.fromJson(json) {
35 | comment = json['comment']['content'];
36 | lunar = '农历' + json['today']['description'];
37 | movie = BaseMovie.fromJson(json['subject']);
38 | cover = json['subject']['pic']['normal'].toString().replaceAll('webp', 'jpg');
39 | rating = BaseRating.fromJson(json['subject']['rating']);
40 | color = BaseColor.fromJson(json['subject']['color_scheme']);
41 | }
42 |
43 |
44 | }
--------------------------------------------------------------------------------
/movies/lib/model/photo_model.dart:
--------------------------------------------------------------------------------
1 | import 'base_model.dart';
2 |
3 | class PhotoList extends BaseList {
4 |
5 | List get items {
6 | return subjects.map((item) => GalleryItem.formPhoto(item)).toList();
7 | }
8 |
9 | PhotoList.fromJson(json) : super.fromJson(json) {
10 | subjects = (json['photos'] as List)
11 | .map( (json) => PhotoListItem.fromJson(json))
12 | .toList();
13 | }
14 |
15 | }
16 |
17 | class PhotoListItem {
18 | String id;
19 | PhotoInfo s, m, l;
20 |
21 | PhotoListItem.fromJson(json) {
22 | id = json['id'];
23 | s = PhotoInfo.fromJson(json['image']['small']);
24 | m = PhotoInfo.fromJson(json['image']['normal']);
25 | l = PhotoInfo.fromJson(json['image']['large']);
26 | }
27 | }
28 |
29 | class PhotoInfo {
30 |
31 | String url;
32 | num width;
33 | num height;
34 |
35 | PhotoInfo.fromJson(json) {
36 | url = json['url'];
37 | width = json['width'];
38 | height = json['height'];
39 | }
40 | }
--------------------------------------------------------------------------------
/movies/lib/model/rank_model.dart:
--------------------------------------------------------------------------------
1 | import 'base_model.dart';
2 |
3 | class RankList extends BaseList {
4 |
5 | RankList.fromJson(json) : super.fromJson(json) {
6 | subjects = (json['selected_collections'] as List)
7 | .map( (json) => RankListItem.fromJson(json))
8 | .toList();
9 | }
10 |
11 | }
12 |
13 | class RankListItem extends BaseMovie {
14 |
15 | String header_bg_image;
16 | String cover_url;
17 |
18 | BaseColor color;
19 |
20 | List items;
21 |
22 | RankListItem.fromJson(json) : super.fromJson(json) {
23 | header_bg_image = json['header_bg_image'];
24 | cover_url = json['cover_url'];
25 | items = (json['items'] as List).map((v) => RankMovie.fromJson(v)).toList();
26 | color = BaseColor.fromJson(json['background_color_scheme']);
27 | }
28 | }
29 |
30 | class RankMovie extends BaseMovie {
31 |
32 | String cover;
33 |
34 | RankMovie.fromJson(json) : super.fromJson(json) {
35 | cover = json['pic']['normal'].toString().replaceAll('webp', 'jpg');
36 | }
37 |
38 | }
--------------------------------------------------------------------------------
/movies/lib/model/search_model.dart:
--------------------------------------------------------------------------------
1 | import 'base_model.dart';
2 | import 'movie_model.dart';
3 |
4 | class SearchResults extends BaseList {
5 |
6 | SearchResults.fromJson(json) : super.fromJson(json) {
7 | subjects = (json['subjects'] as List)
8 | .map( (json) => MovieGridItem.fromJson(json))
9 | .toList();
10 | }
11 |
12 | }
13 |
14 | class SearchSuggestions extends BaseList {
15 |
16 | SearchSuggestions.fromJson(json) : super.fromJson(json) {
17 | subjects = (json['items'] as List)
18 | .map( (json) => MovieListItem.fromJson(json))
19 | .toList();
20 | }
21 |
22 | }
--------------------------------------------------------------------------------
/movies/lib/model/user_model.dart:
--------------------------------------------------------------------------------
1 | class User {
2 |
3 | String name;
4 | String avatar;
5 |
6 | User.fromJson(json) {
7 | name = json['name'];
8 | avatar = json['avatar'];
9 | }
10 |
11 | }
--------------------------------------------------------------------------------
/movies/lib/moudule/bottom_tabBar_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:movies/moudule/movies/movies_view.dart';
2 | import 'package:movies/moudule/search/search_view.dart';
3 | import 'package:movies/moudule/settings/settings_view.dart';
4 | import 'package:movies/moudule/tvs/tvs_view.dart';
5 | import 'package:movies/util/constant.dart';
6 | import 'package:flutter/material.dart';
7 |
8 | class BottomTabBarView extends StatefulWidget {
9 | @override
10 | _BottomTabBarViewState createState() => _BottomTabBarViewState();
11 | }
12 |
13 | class _BottomTabBarViewState extends State {
14 |
15 | int _curr = 0;
16 |
17 |
18 | final _items = TabNavigationItem.items;
19 |
20 |
21 | @override
22 | Widget build(BuildContext context) {
23 |
24 |
25 | final _children = _items.map((item) {
26 | return item.page;
27 | }).toList();
28 |
29 | final _bottomItems = _items.map((item) {
30 | return BottomNavigationBarItem(
31 | label: item.title,
32 | icon: item.icon,
33 | );
34 | }).toList();
35 |
36 | return Scaffold(
37 | body: IndexedStack(
38 | index: _curr,
39 | children: _children,
40 | ),
41 | bottomNavigationBar: BottomNavigationBar(
42 | type: BottomNavigationBarType.fixed,
43 | selectedItemColor: ConsColor.theme,
44 | showUnselectedLabels: false,
45 | showSelectedLabels: false,
46 | currentIndex: _curr,
47 | onTap: (index) {
48 | setState(() {
49 | _curr = index;
50 | });
51 | },
52 | items: _bottomItems,
53 | ),
54 | );
55 | }
56 | }
57 |
58 | class TabNavigationItem {
59 |
60 | final Widget page;
61 | final String title;
62 | final Icon icon;
63 |
64 | TabNavigationItem({
65 | @required this.page,
66 | @required this.title,
67 | @required this.icon,
68 | });
69 |
70 | static final List items = [
71 | TabNavigationItem(
72 | page: MoviesView(),
73 | icon: Icon(Icons.movie),
74 | title: '',
75 | ),
76 | TabNavigationItem(
77 | page: TVsView(),
78 | icon: Icon(Icons.tv),
79 | title: '',
80 | ),
81 | TabNavigationItem(
82 | page: SearchView(),
83 | icon: Icon(Icons.search),
84 | title: '',
85 | ),
86 | TabNavigationItem(
87 | page: SettingsView(),
88 | icon: Icon(Icons.settings),
89 | title: '',
90 | ),
91 |
92 | ];
93 | }
94 |
--------------------------------------------------------------------------------
/movies/lib/moudule/movie/movie_comment_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:movies/generated/l10n.dart';
2 | import 'package:movies/model/comment_model.dart';
3 | import 'package:movies/view/base_view.dart';
4 | import 'package:movies/view/item/comment_item_view.dart';
5 | import 'package:movies/view_model/movie_view_model.dart';
6 | import 'package:flutter/cupertino.dart';
7 | import 'package:flutter/material.dart';
8 |
9 |
10 | class MovieCommentView extends BaseRefreshView {
11 |
12 |
13 | MovieCommentView(id)
14 | : super(
15 | title: S.current.movie_comments,
16 | viewModel: MovieCommentViewModel(id),
17 | enablePullUp: true);
18 |
19 | @override
20 | Widget bodyView(BuildContext context) {
21 |
22 |
23 | final list = viewModel.list;
24 |
25 | return ListView.builder(
26 | itemCount: list.subjects.length ?? 0,
27 | itemBuilder: (context, index) {
28 |
29 | CommentListItem item = list.subjects[index];
30 | return CommentItemView(item);
31 |
32 | });
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/movies/lib/moudule/movie/movie_photo_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:movies/generated/l10n.dart';
5 | import 'package:movies/view/base_view.dart';
6 | import 'package:movies/view/gallery_view.dart';
7 | import 'package:movies/view_model/movie_view_model.dart';
8 |
9 |
10 | class MoviePhotoView extends BaseRefreshView {
11 |
12 |
13 | MoviePhotoView(id)
14 | : super(
15 | title: S.current.movie_photos,
16 | viewModel: MoviePhotoViewModel(id),
17 | enablePullUp: true);
18 |
19 |
20 | @override
21 | Widget bodyView(BuildContext context) {
22 |
23 | final list = viewModel.list;
24 |
25 | return StaggeredGridView.countBuilder(
26 | crossAxisCount: 4,
27 | itemCount: list.subjects.length ?? 0,
28 | itemBuilder: (BuildContext context, int index) {
29 |
30 | final item = list.subjects[index];
31 |
32 | return GestureDetector(
33 | onTap: () {
34 | GalleryView.open(context, list.items, index);
35 | },
36 | child: Hero(
37 | tag: item.l.url,
38 | child: CachedNetworkImage(imageUrl: item.s.url, fit: BoxFit.cover),
39 | )
40 | );
41 | },
42 | staggeredTileBuilder: (index) {
43 | return StaggeredTile.fit(2);
44 | },
45 |
46 | );
47 |
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/movies/lib/moudule/movie/movie_recommend_view.dart:
--------------------------------------------------------------------------------
1 |
2 | import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
3 | import 'package:movies/util/router_manager.dart';
4 |
5 | import 'package:movies/view/base_view.dart';
6 |
7 | import 'package:movies/view/item/grid_item_view.dart';
8 | import 'package:movies/view/refresh_view.dart';
9 |
10 | import 'package:movies/view_model/movie_view_model.dart';
11 |
12 | import 'package:flutter/material.dart';
13 |
14 |
15 | class MovieRecommendView extends BaseRefreshView {
16 |
17 |
18 | MovieRecommendView(id)
19 | : super(
20 | enableAppBar: false,
21 | enablePullDown: false,
22 | viewModel: MovieRecommendViewModel(id));
23 |
24 |
25 | @override
26 | Widget bodyView(BuildContext context) {
27 |
28 | final movies = viewModel.movies;
29 |
30 | return AnimationLimiter(
31 | child: GridView.count(
32 | padding: EdgeInsets.all(10),
33 | crossAxisCount: 3,
34 | childAspectRatio: 2 / 3,
35 | children: movies.asMap().entries.map((entry) {
36 |
37 | final index = entry.key, item = entry.value;
38 |
39 | return AnimationConfiguration.staggeredGrid(
40 | duration: const Duration(milliseconds: 500),
41 | position: index,
42 | columnCount: 3,
43 | child: ScaleAnimation(
44 | child: FadeInAnimation(
45 | child: GridItemView(item, () {
46 | RouterManager.toDetail(
47 | context, RouterType.detail, item.path, item.title);
48 | }),
49 | ),
50 | )
51 | );
52 | }).toList(),
53 | )
54 | );
55 |
56 |
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/movies/lib/moudule/movie/movie_review_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:movies/generated/l10n.dart';
2 | import 'package:movies/model/comment_model.dart';
3 | import 'package:movies/view/base_view.dart';
4 | import 'package:movies/view/item/comment_item_view.dart';
5 | import 'package:movies/view/webpage_view.dart';
6 | import 'package:movies/view_model/movie_view_model.dart';
7 | import 'package:flutter/material.dart';
8 |
9 |
10 | class MovieReviewView extends BaseRefreshView {
11 |
12 | MovieReviewView(id)
13 | : super(
14 | title: S.current.movie_review,
15 | viewModel: MovieReviewViewModel(id),
16 | enablePullUp: true);
17 |
18 | @override
19 | Widget bodyView(BuildContext context) {
20 |
21 | final list = viewModel.list;
22 |
23 | return ListView.builder(
24 | itemCount: list.subjects.length ?? 0,
25 | itemBuilder: (context, index) {
26 |
27 | CommentListItem item = list.subjects[index];
28 |
29 | return CommentItemView(item, onTap: (){
30 | if (item.url.isNotEmpty) {
31 | WebpageView.open(context, item.url, title: title);
32 | }
33 | });
34 |
35 | });
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/movies/lib/moudule/movie/movie_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:movies/generated/l10n.dart';
2 | import 'package:movies/util/util.dart';
3 | import 'package:movies/view/error_view.dart';
4 | import 'package:movies/view/movie/movie_cover_view.dart';
5 | import 'package:movies/view/movie/movie_other_view.dart';
6 | import 'package:movies/view/movie/movie_rating_view.dart';
7 |
8 | import 'package:movies/view/movie/movie_staff_view.dart';
9 | import 'package:movies/view/movie/movie_summary_view.dart';
10 | import 'package:movies/view/movie/movie_trailer_view.dart';
11 | import 'package:movies/view/provider_view.dart';
12 | import 'package:movies/view/refresh_view.dart';
13 |
14 | import 'package:movies/view_model/movie_view_model.dart';
15 | import 'package:movies/view_model/base_view_model.dart';
16 |
17 | import 'package:flutter/material.dart';
18 |
19 | import 'movie_recommend_view.dart';
20 |
21 |
22 | class MovieView extends StatelessWidget {
23 |
24 | final String id, title;
25 |
26 |
27 | MovieView(this.id, this.title);
28 |
29 | @override
30 | Widget build(BuildContext context) {
31 |
32 | final _themeData = themeData(context);
33 | final _isDark = isDark(context);
34 |
35 | return ProviderView(
36 | viewModel: MovieViewModel(id),
37 | builder: (context, model, _) {
38 |
39 | final iconColor = _themeData.appBarTheme.textTheme.headline6.color;
40 |
41 | final backgroundColor = _isDark ? _themeData.scaffoldBackgroundColor : model.color;
42 | final titleColor = _isDark ? iconColor: model.titleColor ?? iconColor;
43 |
44 | return Scaffold(
45 | appBar: AppBar(
46 | brightness: Brightness.dark,
47 | backgroundColor: backgroundColor,
48 | elevation: 0,
49 | title: Text(title, style: TextStyle(color: titleColor)),
50 | actions: _actionButtons(context, model),
51 | iconTheme: IconThemeData(color: titleColor),
52 | ),
53 | body: SafeArea(
54 | child: _body(model),
55 | ),
56 | backgroundColor:backgroundColor,
57 | );
58 | },
59 | );
60 | }
61 |
62 | List _actionButtons(BuildContext context, MovieViewModel model) {
63 |
64 | if (model.viewState != ViewState.refreshCompleted) {
65 | return [];
66 | }
67 |
68 | return [
69 | Row(
70 | children: [
71 | IconButton(
72 | icon: Icon(Icons.movie_filter),
73 | onPressed: () => showModalContent(context, S.of(context).movie_recommended, MovieRecommendView(id)))
74 | ],
75 | )
76 | ];
77 |
78 | }
79 |
80 | Widget _body(MovieViewModel model) {
81 |
82 | final state = model.viewState;
83 | final movie = model.movie;
84 |
85 | if (state == ViewState.onRefresh) {
86 | return RefreshCircularIndicator();
87 | }
88 |
89 | if (state == ViewState.refreshError) {
90 | return ErrorView(model.message, onRefresh: model.onRefresh);
91 | }
92 |
93 | return CustomScrollView(
94 | slivers: [
95 | SliverList(
96 | delegate:
97 | SliverChildBuilderDelegate((BuildContext context, int index) {
98 | return Column(
99 | children: [
100 | MovieCoverView(movie.cover),
101 | MovieRatingView(movie),
102 | MovieSummaryView(movie.intro),
103 | MovieStaffView(movie.staffs),
104 | MovieTrailerView(movie),
105 | MovieOtherView(movie)
106 | ],
107 | );
108 | }, childCount: 1),
109 | )
110 | ],
111 | );
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/movies/lib/moudule/movies/movies_list_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:movies/model/movie_model.dart';
3 | import 'package:movies/util/router_manager.dart';
4 | import 'package:movies/view/base_view.dart';
5 | import 'package:movies/view/item/list_item_view.dart';
6 | import 'package:movies/view_model/movies_view_model.dart';
7 |
8 | class MoviesListView extends BaseRefreshView {
9 |
10 | MoviesListView(id, title)
11 | : super(
12 | title: title,
13 | viewModel: MoviesListViewModel(id),
14 | enablePullUp: true);
15 |
16 | @override
17 | Widget bodyView(BuildContext context) {
18 |
19 | final list = viewModel.list;
20 |
21 | return ListView.builder(
22 | itemExtent: 150,
23 | itemCount: list.subjects.length,
24 | itemBuilder: (context, index) {
25 | MovieListItem item = list.subjects[index];
26 | return ListItemView(item, () {
27 | RouterManager.toDetail(
28 | context, RouterType.detail, item.path, item.title);
29 | });
30 | });
31 | }
32 | }
--------------------------------------------------------------------------------
/movies/lib/moudule/movies/movies_ranks_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:movies/generated/l10n.dart';
3 | import 'package:movies/model/rank_model.dart';
4 | import 'package:movies/util/router_manager.dart';
5 | import 'package:movies/view/item/rank_item_view.dart';
6 | class MoviesRanksView extends StatelessWidget {
7 |
8 | final RankList ranks;
9 |
10 | MoviesRanksView(this.ranks);
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | return Column(
15 | crossAxisAlignment: CrossAxisAlignment.start,
16 | children: [
17 | Padding(
18 | padding: EdgeInsets.fromLTRB(15, 15, 10, 5),
19 | child: Text(S.of(context).movies_ranks,
20 | style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold))
21 | ),
22 | _gridView(context)
23 | ],
24 | );
25 |
26 | }
27 |
28 |
29 | Widget _gridView(BuildContext context) {
30 |
31 | return GridView.count(
32 | padding: EdgeInsets.all(10),
33 | shrinkWrap: true,
34 | crossAxisSpacing: 10,
35 | mainAxisSpacing: 10,
36 | crossAxisCount: 3,
37 | childAspectRatio: 1,
38 | physics: NeverScrollableScrollPhysics(),
39 | children: ranks.subjects.map((item) {
40 | return RankItemView(item, () {
41 | RouterManager.toDetail(
42 | context, RouterType.movies_list, item.id, item.name);
43 | });
44 | }).toList(),
45 | );
46 |
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/movies/lib/moudule/movies/movies_today_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:movies/model/movies_model.dart';
4 | import 'package:movies/util/router_manager.dart';
5 | import 'package:movies/view_model/locale_view_model.dart';
6 | import 'package:provider/provider.dart';
7 | import 'package:intl/intl.dart';
8 |
9 | class MoviesTodayView extends StatelessWidget {
10 |
11 |
12 | final MoviesToday today;
13 |
14 | MoviesTodayView(this.today);
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | return Card(
19 | margin: EdgeInsets.fromLTRB(10, 10, 10, 0),
20 | child: InkWell(
21 | child: _coverView,
22 | onTap: () {
23 | RouterManager.toDetail(
24 | context, RouterType.detail, today.movie.path, today.movie.title);
25 | },
26 | ),
27 | );
28 | }
29 |
30 | Widget get _coverView {
31 |
32 | return Row(
33 | children: [
34 | Expanded(
35 | child: Container(
36 | height: 250,
37 | decoration: BoxDecoration(
38 | borderRadius: BorderRadius.only(
39 | topLeft: Radius.circular(3.0),
40 | bottomLeft: Radius.circular(3.0)),
41 | image: DecorationImage(
42 | image: CachedNetworkImageProvider(today.cover),
43 | fit: BoxFit.cover)))),
44 | Expanded(
45 | child: Container(
46 | padding: EdgeInsets.all(15),
47 | height: 250,
48 | child: Column(
49 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
50 | children: [
51 | Consumer(builder: (context, viewModel, _) {
52 |
53 | final now = DateTime.now(),
54 | languageCode = viewModel.current.languageCode,
55 | day = now.day.toString(),
56 | month = DateFormat.MMMM(languageCode).format(now),
57 | weekday = DateFormat.EEEE(languageCode).format(now);
58 |
59 | return Column(children: [
60 | Text(day, style: TextStyle(fontSize: 60)),
61 | Text('$month|$weekday', style: TextStyle(fontSize: 12)),
62 | Text(today.lunar, style: TextStyle(fontSize: 12)),
63 | ]);
64 | }),
65 | Text(" \"${today.comment}\" ", style: TextStyle(fontSize: 16))
66 | ],
67 | )),
68 | )
69 | ],
70 | );
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/movies/lib/moudule/movies/movies_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:movies/generated/l10n.dart';
3 | import 'package:movies/util/router_manager.dart';
4 | import 'package:movies/view/base_view.dart';
5 | import 'package:movies/view/item/movies_item_view.dart';
6 | import 'package:movies/view_model/movies_view_model.dart';
7 |
8 | import 'movies_ranks_view.dart';
9 | import 'movies_today_view.dart';
10 | import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
11 |
12 | class MoviesView extends BaseRefreshView {
13 | MoviesView()
14 | : super(viewModel: MoviesViewModel());
15 |
16 | @override
17 | Widget titleView(BuildContext context) {
18 | return Text(S.of(context).tab_movies);
19 | }
20 |
21 | @override
22 | Widget bodyView(BuildContext context) {
23 |
24 | return ListView(
25 | children: [
26 | MoviesTodayView(viewModel.today),
27 | _itemsView(context),
28 | MoviesRanksView(viewModel.ranks)],
29 | );
30 |
31 | }
32 |
33 |
34 | Widget _itemsView(BuildContext context) {
35 | final lists = viewModel.lists;
36 | return Column(
37 | children: lists.map((list) {
38 | return MoviesItemView(list, onTap: ({id, title}){
39 | if (id != null) {
40 | _toDetail(context, RouterType.detail, id, title);
41 | } else {
42 | _toDetail(context, RouterType.movies_list, list.id, list.name);
43 | }
44 | });
45 | }).toList(),
46 | );
47 | }
48 |
49 |
50 | _toDetail(BuildContext context, RouterType type, String id, String title) {
51 | RouterManager.toDetail(context, type, id, title);
52 | }
53 |
54 | }
--------------------------------------------------------------------------------
/movies/lib/moudule/search/search_results_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:movies/util/router_manager.dart';
3 | import 'package:movies/view/base_view.dart';
4 | import 'package:movies/view/item/grid_item_view.dart';
5 | import 'package:movies/view_model/search_view_model.dart';
6 |
7 | class SearchResultsView extends BaseRefreshView {
8 |
9 |
10 | SearchResultsView(text)
11 | : super(
12 | viewModel: SearchResultsViewModel(text),
13 | enablePullUp: true,
14 | enableAppBar: false);
15 |
16 |
17 | @override
18 | Widget bodyView(BuildContext context) {
19 |
20 | final list = viewModel.list;
21 |
22 | return GridView.count(
23 | padding: EdgeInsets.all(10),
24 | crossAxisCount: 3,
25 | childAspectRatio: 2 / 3,
26 | children: list.subjects.map((item) {
27 | return GridItemView(item, () {
28 | RouterManager.toDetail(
29 | context, RouterType.detail, item.path, item.title);
30 | });
31 | }).toList(),
32 | );
33 |
34 |
35 | }
36 |
37 | }
--------------------------------------------------------------------------------
/movies/lib/moudule/search/search_suggestions_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:movies/model/movie_model.dart';
3 | import 'package:movies/util/router_manager.dart';
4 | import 'package:movies/view/base_view.dart';
5 | import 'package:movies/view/item/list_item_view.dart';
6 | import 'package:movies/view_model/search_view_model.dart';
7 |
8 | class SearchSuggestionsView extends BaseRefreshView {
9 |
10 | SearchSuggestionsView()
11 | : super(
12 | viewModel: SearchSuggestionsViewModel(),
13 | enablePullUp: true,
14 | enableAppBar: false);
15 |
16 |
17 | @override
18 | Widget bodyView(BuildContext context) {
19 |
20 | final list = viewModel.list;
21 |
22 | return ListView.builder(
23 | itemExtent: 150,
24 | itemCount: list.subjects.length,
25 | itemBuilder: (context, index) {
26 | MovieListItem item = list.subjects[index];
27 | return ListItemView(item, () {
28 | RouterManager.toDetail(
29 | context, RouterType.detail, item.path, item.title);
30 | });
31 | });
32 | }
33 |
34 | }
--------------------------------------------------------------------------------
/movies/lib/moudule/search/search_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:movies/generated/l10n.dart';
4 | import 'package:movies/moudule/search/search_results_view.dart';
5 | import 'package:movies/moudule/search/search_suggestions_view.dart';
6 | import 'package:movies/util/constant.dart';
7 | import 'package:movies/util/util.dart';
8 |
9 |
10 | class SearchView extends StatefulWidget {
11 | @override
12 | _SearchViewState createState() => _SearchViewState();
13 | }
14 |
15 | class _SearchViewState extends State {
16 |
17 | bool _isSearching = false;
18 |
19 | final _duration = const Duration(milliseconds: 300);
20 | final _suggestionsView = SearchSuggestionsView();
21 | final _controller = TextEditingController();
22 | final _focusNode = FocusNode();
23 |
24 | @override
25 | Widget build(BuildContext context) {
26 |
27 | return Scaffold(
28 | appBar: AppBar(
29 | title: Stack(
30 | alignment: Alignment.center,
31 | children: [
32 | Row(children: [_leftView]),
33 | _titleView,
34 | Row(
35 | children: [_rightView],
36 | mainAxisAlignment: MainAxisAlignment.end),
37 | ],
38 | ),
39 | ),
40 | body: _suggestionsView,
41 | );
42 | }
43 |
44 | Widget get _titleView {
45 | return AnimatedOpacity(
46 | duration: _duration, opacity: _isSearching ? 0 : 1, child: Text(S.of(context).search_recommended));
47 | }
48 |
49 | Widget get _leftView {
50 | return AnimatedCrossFade(
51 | duration: _duration,
52 | firstChild: ActionChip(
53 | elevation: 1,
54 | backgroundColor: themeData(context).scaffoldBackgroundColor,
55 | avatar: Icon(Icons.search),
56 | label: Text(S.of(context).search_title),
57 | onPressed: () {
58 | setState(() {
59 | FocusScope.of(context).requestFocus(_focusNode);
60 | _isSearching = true;
61 | });
62 | }),
63 | secondChild: Container(
64 | child: TextField(
65 | controller: _controller,
66 | focusNode: _focusNode,
67 | cursorColor: ConsColor.theme,
68 | textInputAction: TextInputAction.search,
69 | maxLines: 1,
70 | onSubmitted: showResults,
71 | decoration: InputDecoration(
72 | border: InputBorder.none,
73 | hintText: S.of(context).search_find,
74 | )),
75 | width: screenWidth(context) * 3 / 4,
76 | ),
77 | crossFadeState:
78 | _isSearching ? CrossFadeState.showSecond : CrossFadeState.showFirst,
79 | );
80 | }
81 |
82 | Widget get _rightView {
83 |
84 | return _isSearching ? InkWell(
85 | child: Icon(Icons.close, size: 30),
86 | onTap: () {
87 | setState(() {
88 | _controller.clear();
89 | _focusNode.unfocus();
90 | _isSearching = false;
91 | });
92 | },
93 | ): SizedBox();
94 |
95 | }
96 |
97 | showResults(String text) {
98 | if (text.isEmpty) {
99 | showSnackBar(context, S.of(context).search_hint);
100 | } else {
101 | showModalContent(context, S.of(context).search_results, SearchResultsView(text));
102 | }
103 | }
104 |
105 | }
106 |
--------------------------------------------------------------------------------
/movies/lib/moudule/settings/settings_detail_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
2 | import 'package:movies/generated/l10n.dart';
3 | import 'package:movies/util/constant.dart';
4 | import 'package:movies/util/router_manager.dart';
5 | import 'package:movies/view_model/locale_view_model.dart';
6 | import 'package:movies/view_model/theme_view_model.dart';
7 | import 'package:flutter/material.dart';
8 | import 'package:provider/provider.dart';
9 |
10 |
11 | class SettingsDetailView extends StatelessWidget {
12 |
13 | final String type;
14 | SettingsDetailView(this.type);
15 |
16 | bool get _localeSetting => StorageKeys.locale == type;
17 |
18 | List get _options {
19 | if (_localeSetting) {
20 | return S.delegate.supportedLocales;
21 | }
22 | return ThemeMode.values;
23 | }
24 |
25 | dynamic _viewModel(BuildContext context) {
26 | if (_localeSetting) {
27 | return Provider.of(context, listen: false);
28 | }
29 | return Provider.of(context, listen: false);
30 | }
31 |
32 | List get _titles {
33 | if (_localeSetting) {
34 | return _options.map((v) => (v as Locale).displayName).toList();
35 | }
36 | return _options.map((v) => (v as ThemeMode).displayName).toList();
37 |
38 | }
39 |
40 | String _title(BuildContext context) {
41 | if (_localeSetting) {
42 | return S.of(context).settings_language;
43 | }
44 | return S.of(context).settings_theme;
45 | }
46 |
47 | int _curr(BuildContext context) {
48 |
49 | return _options.indexOf(_viewModel(context).current);
50 |
51 | }
52 |
53 | _action(BuildContext context, int index) {
54 | _viewModel(context).updateByIndex(index);
55 | }
56 |
57 | @override
58 | Widget build(BuildContext context) {
59 |
60 |
61 | return Scaffold(
62 | appBar: AppBar(title: Text(_title(context))),
63 | body: AnimationLimiter(
64 | child: ListView.separated(
65 | itemBuilder: (context, index) {
66 |
67 | final title = _titles[index],
68 | isSelected = _curr(context) == index;
69 |
70 | return AnimationConfiguration.staggeredList(
71 | duration: const Duration(milliseconds: 500),
72 | position: index,
73 | child: SlideAnimation(
74 | verticalOffset: 50.0,
75 | child: FadeInAnimation(
76 | child: ListTile(
77 | title: Text(title),
78 | trailing: isSelected ? Icon(Icons.check, color: ConsColor.theme) : null,
79 | onTap: (){
80 | if (!isSelected) {
81 | RouterManager.pop(context);
82 | _action(context, index);
83 | }
84 | },
85 | ),
86 | )
87 | )
88 | );
89 | },
90 | separatorBuilder: (_, __) => Divider(),
91 | itemCount: _titles.length),
92 | )
93 | );
94 |
95 | }
96 |
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/movies/lib/moudule/settings/settings_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:movies/generated/l10n.dart';
2 | import 'package:movies/util/constant.dart';
3 | import 'package:movies/util/router_manager.dart';
4 | import 'package:movies/util/storage_manager.dart';
5 | import 'package:flutter/material.dart';
6 |
7 | import 'package:movies/view_model/locale_view_model.dart';
8 | import 'package:movies/view_model/theme_view_model.dart';
9 | import 'package:provider/provider.dart';
10 |
11 | class SettingsView extends StatelessWidget {
12 | @override
13 | Widget build(BuildContext context) {
14 | final version =
15 | 'v ${StorageManager.packageInfo.version}(${StorageManager.packageInfo.buildNumber})';
16 |
17 | final locale = Provider.of(context, listen: false).current,
18 | themeMode = Provider.of(context, listen: false).current;
19 |
20 | final titles = [
21 | S.of(context).settings_language,
22 | S.of(context).settings_theme,
23 | S.of(context).settings_about
24 | ];
25 |
26 | final subtitles = [locale.displayName, themeMode.displayName, ''];
27 |
28 |
29 | final actions = [
30 | ()=>RouterManager.toSetting(context, StorageKeys.locale),
31 | ()=>RouterManager.toSetting(context, StorageKeys.themeMode),
32 | ()=>showAboutDialog(
33 | context: context,
34 | applicationName: StorageManager.packageInfo.appName,
35 | applicationVersion: version,
36 | applicationLegalese: 'By ZzzM')];
37 |
38 | return Scaffold(
39 | appBar: AppBar(title: Text(S.of(context).settings_title)),
40 | body: ListView.separated(
41 | itemBuilder: (_, index) {
42 |
43 | final title = titles[index],
44 | subtitle = subtitles[index],
45 | action = actions[index];
46 |
47 | return ListTile(
48 | title: Text(title),
49 | subtitle: subtitle.isNotEmpty ? Text(subtitle) : null,
50 | trailing: Icon(Icons.chevron_right),
51 | onTap: action,
52 | );
53 | },
54 | separatorBuilder: (_, __) => Divider(),
55 | itemCount: titles.length));
56 |
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/movies/lib/moudule/tvs/tvs_tab_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:movies/generated/l10n.dart';
4 | import 'package:movies/model/movie_model.dart';
5 | import 'package:movies/util/constant.dart';
6 | import 'package:movies/util/router_manager.dart';
7 | import 'package:movies/view/base_view.dart';
8 | import 'package:movies/view/item/list_item_view.dart';
9 | import 'package:movies/view_model/locale_view_model.dart';
10 | import 'package:movies/view_model/tvs_view_model.dart';
11 | import 'package:provider/provider.dart';
12 |
13 | class TVsTabView extends BaseRefreshView {
14 |
15 | final String type;
16 | final List tags;
17 |
18 | TVsTabView(this.type, this.tags)
19 | : super(
20 | viewModel: TVsViewModel(tags.first),
21 | enableAppBar: false,
22 | enablePullUp: true);
23 |
24 |
25 |
26 | List get _tagTitles {
27 | if (type == 'tv') {
28 | return [S.current.tv_hot, S.current.tv_domestic, S.current.tv_american, S.current.tv_japanese, S.current.tv_korean, S.current.tv_animation];
29 | }
30 | return [S.current.show_hot, S.current.show_domestic, S.current.show_foreign];
31 | }
32 |
33 |
34 | @override
35 | Widget bodyView(BuildContext context) {
36 | final list = viewModel.list;
37 |
38 | return ListView.builder(
39 | key: PageStorageKey(tags.first),
40 | itemCount: list.subjects.length + 1,
41 | itemBuilder: (context, index) {
42 |
43 |
44 | if (index == 0) {
45 | return _tagView;
46 | }
47 |
48 | MovieListItem item = list.subjects[index - 1];
49 |
50 | return SizedBox(
51 | child: ListItemView(item, () {
52 | RouterManager.toDetail(
53 | context, RouterType.detail, item.path, item.title);
54 | }),
55 | height: 150,
56 | );
57 | });
58 | }
59 |
60 | Widget get _tagView {
61 |
62 | return Padding(
63 | padding: EdgeInsets.symmetric(horizontal: 20),
64 | child: Wrap(
65 | spacing: 15,
66 | children: tags.asMap().entries.map((entry) {
67 |
68 | final index = entry.key, tag = entry.value, selected = viewModel.id == tag;
69 |
70 | return ChoiceChip(
71 | label: Consumer(
72 | builder: (context, _, __) {
73 | return Text(_tagTitles[index]);
74 | }),
75 | labelStyle: selected ? TextStyle(color: ConsColor.theme) : TextStyle(color: ConsColor.border),
76 | selectedColor: ConsColor.theme.withOpacity(0.3),
77 | backgroundColor: ConsColor.border.withOpacity(0.1),
78 | selected: selected,
79 | onSelected: (_) {
80 | viewModel.update(tag);
81 | },
82 | );
83 |
84 | }).toList(),
85 | ),
86 | );
87 | }
88 |
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/movies/lib/moudule/tvs/tvs_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:movies/generated/l10n.dart';
4 | import 'package:movies/moudule/tvs/tvs_tab_view.dart';
5 | import 'package:movies/util/constant.dart';
6 |
7 | class TVsView extends StatefulWidget {
8 | @override
9 | _TVsViewState createState() => _TVsViewState();
10 | }
11 |
12 | class _TVsViewState extends State {
13 |
14 |
15 |
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 |
20 | final _tabs = [Tab(text: S.of(context).tab_tvs), Tab(text: S.of(context).tab_shows)];
21 |
22 | return DefaultTabController(
23 | length: _tabs.length,
24 | child: Scaffold(
25 | appBar: AppBar(
26 | title:TabBar(
27 | unselectedLabelColor: ConsColor.border,
28 | labelColor: ConsColor.theme,
29 | indicatorColor: ConsColor.theme,
30 | indicatorSize: TabBarIndicatorSize.label,
31 | labelStyle: TextStyle(fontSize: 18),
32 | unselectedLabelStyle: TextStyle(fontSize: 15),
33 | tabs: _tabs,
34 | ),
35 | ),
36 | body: TabBarView(
37 | children: TabItem.tabViews,
38 | ),
39 | )
40 | );
41 | }
42 | }
43 |
44 |
45 | class TabItem {
46 |
47 | final String type;
48 | final List tags;
49 |
50 | TabItem({
51 | @required this.type,
52 | @required this.tags,
53 | });
54 |
55 |
56 | static final List tabViews = _items.map((item) => TVsTabView(item.type, item.tags)).toList();
57 |
58 |
59 | static final List _items = [
60 |
61 | TabItem(
62 | type: 'tv',
63 | tags: ['tv_hot','tv_domestic','tv_american','tv_japanese','tv_korean','tv_animation'],
64 | ),
65 | TabItem(
66 | type: 'show',
67 | tags: ['show_hot','show_domestic','show_foreign'],
68 | ),
69 |
70 | ];
71 | }
--------------------------------------------------------------------------------
/movies/lib/util/constant.dart:
--------------------------------------------------------------------------------
1 | import 'package:movies/util/util.dart';
2 | import 'package:movies/view_model/locale_view_model.dart';
3 | import 'package:movies/view_model/theme_view_model.dart';
4 | import 'package:provider/provider.dart';
5 | import 'package:provider/single_child_widget.dart';
6 |
7 | List providers = [
8 | ChangeNotifierProvider(create: (_) => ThemeViewModel()),
9 | ChangeNotifierProvider(create: (_) => LocaleViewModel())
10 | ];
11 |
12 |
13 | class ConsColor {
14 | static final theme = hexColor('#52BE80');
15 | static final border = hexColor('#657271');
16 | }
17 |
18 | class BaseUrl {
19 |
20 | static const frodo = '';
21 |
22 | }
23 |
24 | class StorageKeys {
25 | static const themeMode = 'storageKeys.themeMode';
26 | static const locale = 'storageKeys.locale';
27 | }
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/movies/lib/util/network_manager.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio/dio.dart';
2 | import 'constant.dart';
3 |
4 |
5 |
6 | class NetworkManager {
7 |
8 | static get cancelToken => CancelToken();
9 |
10 | static Future get(Api api, {String extra, CancelToken cancelToken, Map param}) async {
11 |
12 | final options = api.options;
13 | final path = api.path(extra);
14 | final queryParameters = api.queryParameters(param);
15 |
16 | return await Dio(options).get(path, cancelToken: cancelToken, queryParameters: queryParameters);
17 |
18 | }
19 |
20 |
21 | static Future download(String url, String savePath, {CancelToken cancelToken, ProgressCallback onReceiveProgress}) async {
22 | return await Dio().download(url, savePath,
23 | cancelToken: cancelToken,
24 | onReceiveProgress: onReceiveProgress);
25 | }
26 |
27 |
28 | static Future contentLength(String url, {CancelToken cancelToken}) async {
29 | final req = await Dio().head(url, cancelToken: cancelToken);
30 | return int.parse(req.headers[Headers.contentLengthHeader].first) ?? 0;
31 | }
32 |
33 | static cancel(CancelToken cancelToken) {
34 | cancelToken.cancel();
35 | }
36 |
37 | }
38 |
39 | enum Api {
40 | fetchMovieList,
41 | fetchDetail,
42 | fetchRanks,
43 | fetchSearchResults,
44 | fetchSearchSuggestions,
45 | fetchToday
46 | }
47 |
48 | extension ApiExtension on Api {
49 |
50 | static const Apikey = '';
51 |
52 |
53 | BaseOptions get options {
54 | return BaseOptions(
55 | baseUrl: BaseUrl.frodo,
56 | headers: {
57 | 'User-Agent': ''
58 | },
59 | connectTimeout: 5000,
60 | receiveTimeout: 3000);
61 | }
62 |
63 |
64 | String path(String extra) {
65 |
66 | switch (this) {
67 | case Api.fetchSearchResults: return '/search';
68 | case Api.fetchSearchSuggestions: return '/movie/suggestion';
69 | case Api.fetchDetail: return extra.replaceAll('.', '/');
70 | case Api.fetchMovieList: return '/subject_collection/$extra/items';
71 | case Api.fetchRanks: return '/movie/rank_list';
72 | case Api.fetchToday: return '/calendar/today';
73 |
74 | default: return '';
75 | }
76 |
77 | }
78 |
79 | Map queryParameters(Map param) {
80 |
81 | Map _param = {'apikey': Apikey};
82 | _param.addAll(param ?? {});
83 |
84 | return _param;
85 |
86 | }
87 |
88 | }
89 |
90 | extension IntExtension on int {
91 | String get mb {
92 | return (this / 1024 / 1024).toStringAsFixed(2);
93 | }
94 | }
--------------------------------------------------------------------------------
/movies/lib/util/router_manager.dart:
--------------------------------------------------------------------------------
1 | import 'package:movies/moudule/movie/movie_comment_view.dart';
2 | import 'package:movies/moudule/movie/movie_view.dart';
3 | import 'package:movies/moudule/movie/movie_photo_view.dart';
4 | import 'package:movies/moudule/movie/movie_review_view.dart';
5 | import 'package:movies/moudule/movies/movies_list_view.dart';
6 |
7 |
8 | import 'package:movies/moudule/settings/settings_detail_view.dart';
9 | import 'package:movies/moudule/settings/settings_view.dart';
10 | import 'package:movies/moudule/bottom_tabBar_view.dart';
11 | import 'package:fluro/fluro.dart';
12 |
13 | import 'package:flutter/cupertino.dart';
14 |
15 |
16 | enum RouterType {
17 | root,
18 | detail,
19 | comments,
20 | reviews,
21 | photos,
22 | settings,
23 | settings_detail,
24 | movies_list
25 | }
26 |
27 | extension RouterTypeExtension on RouterType {
28 | String get path {
29 | switch (this) {
30 | case RouterType.detail:
31 | return '/detail';
32 | case RouterType.settings:
33 | return '/settings';
34 | case RouterType.comments:
35 | return 'movie/interests';
36 | case RouterType.reviews:
37 | return 'movie/reviews';
38 | case RouterType.settings_detail:
39 | return '/settings/detail';
40 | case RouterType.photos:
41 | return 'movie/photos';
42 | case RouterType.movies_list:
43 | return '/movies/list';
44 | default:
45 | return '/';
46 | }
47 | }
48 | }
49 |
50 |
51 | Handler handler(RouterType type) {
52 |
53 | return Handler(handlerFunc: (context, params) {
54 |
55 | switch (type) {
56 | case RouterType.detail:
57 | return MovieView(params['id'].first, params['title'].first);
58 | case RouterType.comments:
59 | return MovieCommentView(params['id'].first);
60 | case RouterType.reviews:
61 | return MovieReviewView(params['id'].first);
62 | case RouterType.settings:
63 | return SettingsView();
64 | case RouterType.settings_detail:
65 | return SettingsDetailView(params['type'].first);
66 | case RouterType.movies_list:
67 | return MoviesListView(params['id'].first, params['title'].first);
68 | case RouterType.photos:
69 | return MoviePhotoView(params['id'].first);
70 | default:
71 | return BottomTabBarView();
72 | }
73 | });
74 | }
75 |
76 | TransitionType transitionType(RouterType type) {
77 | switch (type) {
78 |
79 | case RouterType.comments:
80 | return TransitionType.materialFullScreenDialog;
81 | default:
82 | return TransitionType.material;
83 | }
84 | }
85 |
86 | class RouterManager {
87 |
88 | static FluroRouter router = FluroRouter();
89 |
90 | static setup() {
91 |
92 | // router.notFoundHandler
93 |
94 | RouterType.values.forEach((v){
95 | router.define(v.path, handler: handler(v));
96 | });
97 | }
98 |
99 | static pop(BuildContext context) {
100 | Navigator.pop(context);
101 | }
102 |
103 |
104 | static _navigateTo(BuildContext context, RouterType type, {String params = ''}) {
105 |
106 | final _path = type.path;
107 | final _transition = transitionType(type);
108 | switch (type) {
109 | default:
110 | router.navigateTo(
111 | context,
112 | _path + (params.isEmpty ? params : '?$params'),
113 | transition: _transition,
114 | transitionDuration: const Duration(milliseconds: 600));
115 | }
116 |
117 | }
118 |
119 | static toDetail(BuildContext context, RouterType type, String id, String title) {
120 | String params = 'id=$id&title=${Uri.encodeComponent(title)}';
121 | _navigateTo(context, type, params: params);
122 | }
123 |
124 | static toSetting(BuildContext context, String type) {
125 | _navigateTo(context, RouterType.settings_detail, params: 'type=$type');
126 | }
127 |
128 | }
--------------------------------------------------------------------------------
/movies/lib/util/storage_manager.dart:
--------------------------------------------------------------------------------
1 |
2 | import 'package:package_info/package_info.dart';
3 | import 'package:shared_preferences/shared_preferences.dart';
4 |
5 |
6 | class StorageManager {
7 |
8 | static SharedPreferences prefs;
9 | static PackageInfo packageInfo;
10 |
11 | static Future setup() async {
12 | prefs = await SharedPreferences.getInstance();
13 | packageInfo = await PackageInfo.fromPlatform();
14 | }
15 |
16 | static setInt(String key, int value) => prefs.setInt(key, value);
17 |
18 | static getInt(String key) => prefs.getInt(key);
19 |
20 | // static set local(Locale _locale) {
21 | // prefs.setInt(StorageKey.local, _locale.index);
22 | // }
23 | //
24 | // static Locale get local => LocalizationManger.supportedLocales[prefs.getInt(StorageKey.local) ?? 0];
25 | //
26 | //
27 | // static set themeMode(ThemeMode _mode) {
28 | // prefs.setInt(StorageKey.themeMode, _mode.index);
29 | // }
30 | //
31 | // static ThemeMode get themeMode => ThemeMode.values[prefs.getInt(StorageKey.themeMode) ?? 0];
32 |
33 | }
--------------------------------------------------------------------------------
/movies/lib/util/util.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'constant.dart';
3 |
4 | showSnackBar(BuildContext context, String message) {
5 | Scaffold.of(context).showSnackBar(
6 | SnackBar(
7 | content: Text(message, style: TextStyle(color: Colors.white)),
8 | backgroundColor: ConsColor.theme,
9 | behavior: SnackBarBehavior.floating,
10 | shape: RoundedRectangleBorder(
11 | borderRadius: BorderRadius.all(Radius.circular(50))
12 | ),
13 | action: SnackBarAction(label: 'X', textColor: Colors.white, onPressed: () {})
14 | )
15 | );
16 | }
17 |
18 | showModalContent(BuildContext context, String title, Widget child) {
19 |
20 | showModalBottomSheet(
21 | context: context,
22 | shape: RoundedRectangleBorder(
23 | borderRadius: BorderRadius.only(
24 | topLeft: const Radius.circular(10.0),
25 | topRight: const Radius.circular(10.0)
26 | ),
27 | ),
28 | builder: (_) {
29 | return Container(
30 | child: Column(
31 | children: [
32 | SizedBox(height: 15),
33 | Text(title, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
34 | SizedBox(height: 5),
35 | Container(
36 | height: screenWidth(context),
37 | child: child,
38 | )
39 | ],
40 | ));
41 | });
42 | }
43 |
44 | Color hexColor(String code) {
45 | return Color(int.parse(code.substring(1, 7), radix: 16) + 0xFF000000);
46 | }
47 |
48 | Size screenSize(BuildContext context) {
49 | return MediaQuery.of(context).size;
50 | }
51 | double screenHeight(BuildContext context, {double dividedBy = 1}) {
52 | return screenSize(context).height / dividedBy;
53 | }
54 | double screenWidth(BuildContext context, {double dividedBy = 1}) {
55 | return screenSize(context).width / dividedBy;
56 | }
57 |
58 | ThemeData themeData(BuildContext context) {
59 | return Theme.of(context);
60 | }
61 |
62 | bool isDark(BuildContext context) {
63 | return themeData(context).brightness == Brightness.dark;
64 | }
65 |
--------------------------------------------------------------------------------
/movies/lib/view/base_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:movies/generated/l10n.dart';
2 | import 'package:movies/util/util.dart';
3 | import 'package:movies/view/provider_view.dart';
4 |
5 | import 'package:movies/view_model/base_view_model.dart';
6 | import 'package:flutter/material.dart';
7 | import 'package:movies/view_model/locale_view_model.dart';
8 | import 'package:provider/provider.dart';
9 |
10 | import 'package:pull_to_refresh/pull_to_refresh.dart';
11 | import 'package:movies/view/refresh_view.dart';
12 |
13 | import 'error_view.dart';
14 |
15 |
16 | class BaseTitleView extends StatelessWidget {
17 |
18 | final String text;
19 |
20 | BaseTitleView(this.text);
21 |
22 | @override
23 | Widget build(BuildContext context) {
24 | return Container(
25 | child: Text(text,
26 | style: TextStyle(
27 | color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold)),
28 | margin: EdgeInsets.only(bottom: 5),
29 | );
30 | }
31 | }
32 |
33 | class BaseRefreshView extends StatelessWidget {
34 |
35 | final _refreshController = RefreshController();
36 |
37 | final T viewModel;
38 | final bool enablePullUp, enablePullDown, enableAppBar, transparent;
39 | final String title;
40 |
41 | BaseRefreshView({
42 | this.viewModel,
43 | this.title,
44 | this.enablePullDown = true,
45 | this.enablePullUp = false,
46 | this.enableAppBar = true,
47 | this.transparent = false,
48 | });
49 |
50 |
51 | @override
52 | Widget build(BuildContext context) {
53 |
54 | return ProviderView(
55 | viewModel: viewModel,
56 | builder: (context, model, _) {
57 | return Scaffold(
58 | appBar: enableAppBar ? AppBar(
59 | centerTitle: true,
60 | backgroundColor: transparent ? Colors.transparent: null,
61 | title: Consumer(
62 | builder: (context, _, __) {
63 | return _appBarView(context);
64 | }
65 | )
66 | ) : null,
67 | body: SmartRefresher(
68 | controller: _refreshController,
69 | header: RefreshHeader(),
70 | footer: enablePullUp ? RefreshFooter():null,
71 | enablePullDown: enablePullDown ? !model.refreshNoData: false,
72 | enablePullUp: enablePullUp ? !model.refreshNoData : false,
73 | onRefresh: model.onRefresh,
74 | onLoading: enablePullUp ? model.onLoading : null,
75 | child: _refreshChild(context, viewModel),
76 | ),
77 | );
78 | }
79 | );
80 | }
81 |
82 |
83 | Widget _refreshChild(BuildContext context, T viewModel) {
84 |
85 | final state = viewModel.viewState;
86 |
87 | if (state == ViewState.onRefresh && viewModel.refreshNoData) {
88 | return RefreshCircularIndicator();
89 | }
90 |
91 | if (state == ViewState.refreshCompleted) {
92 | _refreshController.resetNoData();
93 | _refreshController.refreshCompleted();
94 | }
95 |
96 | if (state == ViewState.refreshError) {
97 | _refreshController.refreshFailed();
98 | }
99 |
100 | if (state == ViewState.onLoading) {
101 | _refreshController.refreshToIdle();
102 | }
103 |
104 | if (state == ViewState.loadNoData) {
105 | _refreshController.loadNoData();
106 | }
107 |
108 | if (state == ViewState.loadComplete) {
109 | _refreshController.loadComplete();
110 | }
111 |
112 | if (state == ViewState.loadError) {
113 | _refreshController.loadFailed();
114 | }
115 |
116 |
117 | switch (state) {
118 | case ViewState.loadError:
119 | case ViewState.refreshError:
120 | case ViewState.empty:
121 | if (viewModel.refreshNoData) {
122 | return ErrorView(S.of(context).refresh_empty, onRefresh: viewModel.onRefresh);
123 | } else {
124 | showSnackBar(context, S.of(context).refresh_empty);
125 | break;
126 | }
127 | }
128 |
129 | return bodyView(context);
130 |
131 | }
132 |
133 | Widget _appBarView(BuildContext context) {
134 |
135 | List leftItem = [], rightItem = [];
136 |
137 | if (leftView(context) != null) {
138 | leftItem = [leftView(context)];
139 | }
140 |
141 | if (rightView(context) != null) {
142 | rightItem = [rightView(context)];
143 | }
144 |
145 | if (leftItem.isNotEmpty || rightItem.isNotEmpty) {
146 | return Stack(
147 | alignment: Alignment.center,
148 | children: [
149 | Row(
150 | mainAxisAlignment: MainAxisAlignment.start,
151 | children: leftItem,
152 | ),
153 | titleView(context),
154 | Row(
155 | mainAxisAlignment: MainAxisAlignment.end,
156 | children: rightItem,
157 | ),
158 | ],
159 | );
160 | }
161 |
162 | return titleView(context);
163 |
164 | }
165 |
166 | Widget titleView(BuildContext context) {
167 | return Text(title);
168 | }
169 |
170 | Widget leftView(BuildContext context) {
171 | return null;
172 | }
173 |
174 | Widget rightView(BuildContext context) {
175 | return null;
176 | }
177 |
178 |
179 |
180 | Widget bodyView(BuildContext context) {
181 | return RefreshCircularIndicator();
182 | }
183 |
184 |
185 |
186 |
187 |
188 | }
189 |
190 |
--------------------------------------------------------------------------------
/movies/lib/view/error_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:movies/generated/l10n.dart';
2 | import 'package:movies/util/util.dart';
3 | import 'package:flutter/material.dart';
4 |
5 |
6 | class ErrorView extends StatelessWidget {
7 |
8 | String message;
9 | VoidCallback onRefresh;
10 |
11 | ErrorView(this.message, {this.onRefresh});
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 |
16 | final _themeData = themeData(context);
17 |
18 | return Center(
19 | child: Column(
20 | mainAxisAlignment: MainAxisAlignment.center,
21 | children: [
22 | Text(message,
23 | style: TextStyle(fontSize: 15), textAlign: TextAlign.center),
24 | SizedBox(height: 15),
25 | onRefresh != null ?
26 | RaisedButton(
27 | color: _themeData.primaryColor,
28 | child: Text(S.of(context).refresh_reload,
29 | style: TextStyle(color: Colors.white)),
30 | onPressed: onRefresh,
31 | ) : SizedBox()
32 | ],
33 | ),
34 | );
35 | }
36 | }
--------------------------------------------------------------------------------
/movies/lib/view/gallery_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:flutter_cache_manager/flutter_cache_manager.dart';
3 | import 'package:movies/generated/l10n.dart';
4 |
5 | import 'package:movies/model/base_model.dart';
6 |
7 | import 'package:movies/util/router_manager.dart';
8 | import 'package:flutter/material.dart';
9 | import 'package:movies/util/util.dart';
10 | import 'package:movies/view/refresh_view.dart';
11 | import 'package:movies/view/save_view.dart';
12 |
13 | import 'package:photo_view/photo_view.dart';
14 | import 'package:photo_view/photo_view_gallery.dart';
15 |
16 |
17 | class GalleryView extends StatefulWidget {
18 |
19 |
20 | static open(BuildContext context, List galleryItems, int index) {
21 |
22 |
23 | Navigator.push(
24 | context,
25 | PageRouteBuilder(
26 | pageBuilder: (context, animation, __) {
27 | return FadeTransition(
28 | opacity: animation,
29 | child: GalleryView(galleryItems, index),
30 | );
31 | }
32 | )
33 | );
34 | }
35 |
36 | final List galleryItems;
37 | final int index;
38 |
39 | GalleryView(this.galleryItems, this.index);
40 |
41 | @override
42 | _GalleryViewState createState() => _GalleryViewState();
43 |
44 | }
45 |
46 | class _GalleryViewState extends State {
47 |
48 | int _currentIndex;
49 | PageController _pageController;
50 |
51 |
52 | @override
53 | void initState() {
54 | // TODO: implement initState
55 | _currentIndex = widget.index;
56 | _pageController = PageController(initialPage: widget.index);
57 |
58 | super.initState();
59 | }
60 |
61 |
62 | @override
63 | Widget build(BuildContext context) {
64 |
65 | return Scaffold(
66 | backgroundColor: Colors.black,
67 | appBar: AppBar(
68 | backgroundColor: Colors.transparent,
69 | iconTheme: IconThemeData(color: Colors.white),
70 | title: Text(
71 | "${_currentIndex + 1} / ${widget.galleryItems.length}",
72 | style: TextStyle(color: Colors.white),
73 | ),
74 | leading: IconButton(
75 | icon: Icon(Icons.close),
76 | onPressed: () => RouterManager.pop(context)),
77 | actions: [_saveWidget],
78 | ),
79 | body: PhotoViewGallery.builder(
80 | scrollPhysics: BouncingScrollPhysics(),
81 | itemCount: widget.galleryItems.length,
82 | loadingBuilder: (context, event){
83 |
84 | double value = event == null ?
85 | 0 : event.cumulativeBytesLoaded / event.expectedTotalBytes;
86 |
87 | return RefreshCircularIndicator(value: value, backgroundColor: Colors.white);
88 | },
89 | pageController: _pageController,
90 | onPageChanged: _onPageChanged,
91 | builder: _itemView),
92 | );
93 |
94 | }
95 |
96 | Widget get _saveWidget {
97 |
98 | final galleryItem = widget.galleryItems[_currentIndex];
99 |
100 | return IconButton(
101 | icon: Icon(Icons.save),
102 | onPressed: () async {
103 |
104 | FileInfo info = await DefaultCacheManager().getFileFromCache(galleryItem.url);
105 |
106 | if (info != null) {
107 | showModalBottomSheet(context: context,
108 | builder: (_) {
109 | return SaveView(info.file.path);
110 | });
111 |
112 | } else {
113 | showSnackBar(context, S.of(context).file_downloading);
114 | }
115 |
116 | }
117 | );
118 | }
119 |
120 | _onPageChanged(int index) {
121 | setState(() {
122 | _currentIndex = index;
123 | });
124 | }
125 |
126 | PhotoViewGalleryPageOptions _itemView(BuildContext context, int index) {
127 |
128 | final item = widget.galleryItems[index];
129 | final imageProvider = CachedNetworkImageProvider(item.url);
130 |
131 |
132 | return PhotoViewGalleryPageOptions(
133 | heroAttributes: PhotoViewHeroAttributes(tag: item.url),
134 | imageProvider: imageProvider,
135 | initialScale: PhotoViewComputedScale.contained,
136 | minScale: PhotoViewComputedScale.contained * (0.5 + index / 10),
137 | maxScale: PhotoViewComputedScale.covered * 1.1,
138 | );
139 | }
140 |
141 | }
142 |
--------------------------------------------------------------------------------
/movies/lib/view/item/comment_item_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:movies/model/comment_model.dart';
3 | import 'package:movies/util/util.dart';
4 | import 'package:flutter/material.dart';
5 |
6 |
7 | class CommentItemView extends StatelessWidget {
8 |
9 | final CommentListItem item;
10 | final VoidCallback onTap;
11 |
12 | CommentItemView(this.item, {this.onTap});
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 |
17 | return Card(
18 | child: InkWell(
19 | child: Container(
20 | child: Container(
21 | child: Column(
22 | crossAxisAlignment: CrossAxisAlignment.start,
23 | children: [
24 | _headerView(context),
25 | SizedBox(height: 5),
26 | Text(item.abstract, style: TextStyle(fontSize: 13)),
27 | ],
28 | )
29 | ),
30 | padding: EdgeInsets.symmetric(horizontal: 10, vertical: 10),
31 | ),
32 | onTap: onTap,
33 | )
34 | );
35 |
36 | }
37 |
38 |
39 | Widget _headerView(BuildContext context) {
40 |
41 | final _isDark = isDark(context);
42 | final none = _isDark ? Colors.white30: Colors.black12;
43 | final count = _isDark ? Colors.white: Colors.black45;
44 |
45 | return Row(
46 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
47 | crossAxisAlignment: CrossAxisAlignment.end,
48 | children: [
49 | Container(
50 | child: Row(
51 | children: [
52 | _avatarView,
53 | SizedBox(width: 5),
54 | _ratingView(none)
55 | ],
56 | ),
57 | ),
58 | _thumbView(count),
59 | ],
60 | );
61 | }
62 |
63 | Widget get _avatarView {
64 | return CircleAvatar(
65 | radius: 20,
66 | backgroundColor: Colors.transparent,
67 | backgroundImage:
68 | CachedNetworkImageProvider(item.user.avatar),
69 | );
70 | }
71 |
72 | Widget _ratingView(Color color) {
73 |
74 | final stars = List();
75 |
76 | num value = item.rating.value;
77 | stars.addAll(List.filled(value.toInt(), Icon(Icons.star, size: 15, color: Colors.amberAccent)));
78 | stars.addAll(List.filled(5-value.toInt(), Icon(Icons.star, size: 15, color: color)));
79 | stars.add(SizedBox(width: 5));
80 | stars.add(Text(item.create_time.substring(0,10), style: TextStyle(fontSize: 12)));
81 |
82 | return Column(
83 | mainAxisAlignment: MainAxisAlignment.start,
84 | crossAxisAlignment: CrossAxisAlignment.start,
85 | children: [
86 | Text(item.user.name,
87 | style: TextStyle(fontWeight: FontWeight.bold)),
88 | Row(
89 | children: stars,
90 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
91 | crossAxisAlignment: CrossAxisAlignment.center,
92 | ),
93 | ],
94 | );
95 |
96 | }
97 |
98 |
99 | Widget _thumbView(Color color) {
100 | return Row(
101 | children: [
102 | Icon(Icons.thumb_up, size: 15, color: color),
103 | SizedBox(width: 5),
104 | Text(item.useful_count.toString(), style: TextStyle(color: color)),
105 | ],
106 | );
107 | }
108 |
109 |
110 | }
111 |
--------------------------------------------------------------------------------
/movies/lib/view/item/grid_item_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:movies/view/rating_view.dart';
3 | import 'package:movies/model/movie_model.dart';
4 | import 'package:flutter/material.dart';
5 |
6 |
7 | class GridItemView extends StatelessWidget {
8 |
9 | final MovieGridItem item;
10 | final VoidCallback onTap;
11 |
12 | GridItemView(
13 | this.item,
14 | this.onTap
15 | );
16 |
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 |
21 | return Card(
22 | child: InkWell(
23 | onTap: onTap,
24 | child: Column(
25 | crossAxisAlignment: CrossAxisAlignment.start,
26 | children: [
27 | _imageView,
28 | _titleView,
29 | _ratingView
30 | ],
31 | )
32 |
33 | )
34 | );
35 | }
36 |
37 | Widget get _imageView {
38 |
39 | return Expanded(
40 | child: Container(
41 | decoration: BoxDecoration(
42 | borderRadius: BorderRadius.only(
43 | topLeft: Radius.circular(3.0),
44 | topRight: Radius.circular(3.0)
45 | ),
46 | image: DecorationImage(
47 | image: CachedNetworkImageProvider(item.cover),
48 | fit: BoxFit.cover)
49 | )
50 | )
51 | );
52 | }
53 |
54 | Widget get _titleView {
55 |
56 | return Padding(
57 | child: Text(
58 | item.title,
59 | overflow: TextOverflow.ellipsis,
60 | style: TextStyle(
61 | fontSize: 12,
62 | fontWeight: FontWeight.bold)
63 | ),
64 | padding: EdgeInsets.fromLTRB(5, 5, 5, 0),
65 | );
66 | }
67 |
68 | Widget get _ratingView {
69 | return Padding(
70 | child: Row(
71 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
72 | crossAxisAlignment: CrossAxisAlignment.center,
73 | children: [
74 | RatingStarView(
75 | item.rating.fullCount,
76 | Icon(Icons.star, size: 15, color: Colors.amberAccent),
77 | item.rating.emptyCount,
78 | Icon(Icons.star_border, size: 15, color: Colors.amberAccent),
79 | halfCount: item.rating.halfCount,
80 | halfIcon: Icon(Icons.star_half,
81 | size: 15, color: Colors.amberAccent)
82 |
83 | ),
84 | RatingScoreView(item.rating.value != 0 ? item.rating.value.toString() : '',
85 | normalSize: 10,
86 | noneSize: 10,
87 | noneColor: Colors.grey),
88 | ],
89 | ),
90 | padding: EdgeInsets.fromLTRB(5, 0, 5, 5),
91 | );
92 | }
93 | }
--------------------------------------------------------------------------------
/movies/lib/view/item/list_item_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:movies/generated/l10n.dart';
3 | import 'package:movies/view/rating_view.dart';
4 | import 'package:movies/model/movie_model.dart';
5 |
6 | import 'package:movies/view_model/locale_view_model.dart';
7 | import 'package:flutter/material.dart';
8 | import 'package:provider/provider.dart';
9 |
10 |
11 | class ListItemView extends StatelessWidget {
12 |
13 | final MovieListItem item;
14 | final VoidCallback onTap;
15 |
16 | ListItemView(
17 | this.item,
18 | this.onTap
19 | );
20 |
21 |
22 | bool get _isMovieItem => item.directors != null;
23 |
24 | @override
25 | Widget build(BuildContext context) {
26 |
27 | return Card(
28 | child: InkWell(
29 | onTap: onTap,
30 | child: Container(
31 | margin: EdgeInsets.symmetric(vertical: 15, horizontal: 15),
32 | child: Row(
33 | children: [
34 | _imageView,
35 | _titleView,
36 | _ratingView,
37 | ],
38 | ),
39 | ),
40 | ));
41 | }
42 |
43 | get _imageView {
44 | return Container(
45 | width: 80,
46 | margin: EdgeInsets.only(right: 15),
47 | decoration: BoxDecoration(
48 | borderRadius: BorderRadius.circular(3.0),
49 | image: DecorationImage(
50 | image: CachedNetworkImageProvider(item.cover),
51 | fit: BoxFit.fill)));
52 | }
53 |
54 | get _titleView {
55 | return Expanded(
56 | child: Column(
57 | mainAxisAlignment: _isMovieItem ? MainAxisAlignment.start:MainAxisAlignment.spaceEvenly,
58 | children: [
59 | Text(
60 | item.title,
61 | maxLines: 1,
62 | overflow: TextOverflow.ellipsis,
63 | style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
64 | ),
65 | Padding(
66 | padding: EdgeInsets.only(top: 5, bottom: 5),
67 | child: Text(
68 | '${item.year}.${item.release_date}',
69 | maxLines: 1,
70 | style: TextStyle(
71 | color: Colors.grey,
72 | ),
73 | ),
74 | ),
75 | Text(
76 | item.genre,
77 | maxLines: 1,
78 | overflow: TextOverflow.ellipsis,
79 | style: TextStyle(
80 | fontSize: 13,
81 | ),
82 | ),
83 | _isMovieItem ?
84 | Text(
85 | "${item.directors.join(" / ")}",
86 | maxLines: 1,
87 | overflow: TextOverflow.ellipsis,
88 | style: TextStyle(fontSize: 13),
89 | ):
90 | item.description.isNotEmpty ?
91 | Text("\"${item.description}\"",
92 | maxLines: 2,
93 | style: TextStyle(color: Colors.grey),
94 | overflow: TextOverflow.ellipsis):
95 | SizedBox(),
96 | _isMovieItem ?
97 | Text(
98 | "${item.actors.join(" / ")}",
99 | maxLines: 1,
100 | overflow: TextOverflow.ellipsis,
101 | style: TextStyle(fontSize: 13),
102 | ):
103 | SizedBox()
104 | ],
105 | crossAxisAlignment: CrossAxisAlignment.start,
106 | ),
107 | );
108 | }
109 |
110 | Widget get _ratingView {
111 | return Container(
112 | width: 80,
113 | child: Column(children: [
114 | RatingScoreView(item.rating.value.toString(),
115 | normalSize: 24, noneSize: 15, noneColor: Colors.grey),
116 | RatingStarView(
117 | item.rating.fullCount,
118 | Icon(Icons.star, size: 16, color: Colors.amberAccent),
119 | item.rating.emptyCount,
120 | Icon(Icons.star_border, size: 16, color: Colors.amberAccent),
121 | halfCount: item.rating.halfCount,
122 | halfIcon:
123 | Icon(Icons.star_half, size: 16, color: Colors.amberAccent)),
124 | Consumer(builder: (context, _, __) {
125 | return Text(
126 | "${item.rating.count}${S.current.movie_scored}",
127 | style: TextStyle(
128 | fontSize: 10,
129 | ),
130 | );
131 | })
132 |
133 | ],
134 | mainAxisAlignment: MainAxisAlignment.spaceEvenly),
135 | );
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/movies/lib/view/item/movies_item_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/rendering.dart';
3 | import 'package:movies/model/movie_model.dart';
4 |
5 | import '../item/grid_item_view.dart';
6 |
7 | class MoviesItemView extends StatelessWidget {
8 | static const itemHeight = 200.0, itemWidth = itemHeight * 2 / 3;
9 |
10 | final MovieList list;
11 | final void Function({String id, String title}) onTap;
12 |
13 | MoviesItemView(this.list, {this.onTap});
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
18 | _titleView,
19 | Container(
20 | height: itemHeight,
21 | child: ListView(
22 | padding: EdgeInsets.symmetric(horizontal: 10),
23 | scrollDirection: Axis.horizontal,
24 | children: _itemsView,
25 | ),
26 | )
27 | ]);
28 | }
29 |
30 | Widget get _titleView {
31 | return Container(
32 | padding: EdgeInsets.fromLTRB(15, 15, 10, 5),
33 | child: InkWell(
34 | onTap: onTap,
35 | child: Row(
36 | children: [
37 | Text(list.name,
38 | style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
39 | Icon(Icons.chevron_right)
40 | ],
41 | )
42 | )
43 | );
44 | }
45 |
46 | List get _itemsView {
47 |
48 | List subjects =
49 | list.subjects.map((item) => MovieGridItem.from(item)).toList();
50 |
51 | return subjects.map((item) {
52 | return Container(
53 | width: itemWidth,
54 | child: GridItemView(item, () {
55 | onTap(id: item.path, title: item.title);
56 | }),
57 | );
58 | }).toList();
59 |
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/movies/lib/view/item/rank_item_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:movies/model/rank_model.dart';
3 | import 'package:movies/util/constant.dart';
4 | import 'package:movies/util/util.dart';
5 | import 'package:flutter/material.dart';
6 |
7 |
8 | class RankItemView extends StatelessWidget {
9 |
10 | final RankListItem item;
11 | final VoidCallback onTap;
12 |
13 | RankItemView(this.item, this.onTap);
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 |
18 | return InkWell(
19 | child: _coverView,
20 | onTap: onTap,
21 | );
22 |
23 | }
24 |
25 | Widget get _coverView {
26 |
27 | final color = hexColor(item.color.primary);
28 |
29 | return Container(
30 | child: Container(
31 | child: Center(
32 | child: Padding(
33 | child: Text(item.name, style: TextStyle(color: Colors.white), textAlign: TextAlign.center,),
34 | padding: EdgeInsets.all(10),
35 | ),
36 | ),
37 | decoration: BoxDecoration(
38 | borderRadius: BorderRadius.circular(5.0),
39 | gradient: LinearGradient(
40 | begin: Alignment.bottomLeft,
41 | end: Alignment.topRight,
42 | colors: [
43 | color,
44 | color.withOpacity(0.7)
45 | ],
46 | ),
47 | ),
48 | ),
49 | decoration: BoxDecoration(
50 | borderRadius: BorderRadius.circular(5.0),
51 | image: DecorationImage(
52 | image: CachedNetworkImageProvider(item.header_bg_image),
53 | fit: BoxFit.cover
54 | ),
55 | )
56 | );
57 | }
58 |
59 |
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/movies/lib/view/movie/movie_cover_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:movies/model/base_model.dart';
4 | import 'package:movies/view/gallery_view.dart';
5 |
6 | class MovieCoverView extends StatelessWidget {
7 | final String url;
8 |
9 | MovieCoverView(this.url);
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return Row(
14 | mainAxisAlignment: MainAxisAlignment.center,
15 | children: [
16 | GestureDetector(
17 | onTap: () {
18 | GalleryView.open(context, [GalleryItem.formUrl(url)], 0);
19 | },
20 | child: Hero(
21 | tag: url,
22 | child: Container(
23 | decoration: BoxDecoration(
24 | image: DecorationImage(
25 | image: CachedNetworkImageProvider(url), fit: BoxFit.cover),
26 | boxShadow: [
27 | BoxShadow(
28 | color: Colors.black,
29 | blurRadius: 5,
30 | )
31 | ]),
32 | width: 135,
33 | height: 200,
34 | margin: EdgeInsets.only(top: 15),
35 | ),
36 | ),
37 | )
38 | ],
39 | );
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/movies/lib/view/movie/movie_other_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:movies/generated/l10n.dart';
2 | import 'package:movies/model/movie_model.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:movies/util/router_manager.dart';
5 |
6 | import '../base_view.dart';
7 |
8 | class MovieOtherView extends StatelessWidget {
9 | final Movie movie;
10 |
11 | MovieOtherView(this.movie);
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | final types = [RouterType.photos, RouterType.comments, RouterType.reviews];
16 | final titles = [
17 | S.of(context).movie_photos,
18 | S.of(context).movie_comments,
19 | S.of(context).movie_review];
20 |
21 | return Container(
22 | child: Column(
23 | children: List.generate(types.length, (index) {
24 | final type = types[index], title = titles[index];
25 | return ListTile(
26 | title: BaseTitleView(title),
27 | trailing: Icon(Icons.chevron_right, color: Colors.white),
28 | onTap: () {
29 | RouterManager.toDetail(context, type, movie.path, movie.title);
30 | });
31 |
32 | }),
33 | ));
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/movies/lib/view/movie/movie_rating_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:movies/generated/l10n.dart';
2 | import 'package:movies/model/movie_model.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | import '../rating_view.dart';
6 |
7 | class MovieRatingView extends StatelessWidget {
8 |
9 | final Movie movie;
10 |
11 | MovieRatingView(this.movie);
12 |
13 | Widget _textView(String text) {
14 | return Text(text,
15 | overflow: TextOverflow.ellipsis,
16 | style: TextStyle(fontSize: 12, color: Colors.white));
17 | }
18 |
19 | @override
20 | Widget build(BuildContext context) {
21 | return Container(
22 | decoration: BoxDecoration(
23 | color: Colors.white10, borderRadius: BorderRadius.circular(5)),
24 | height: 150,
25 | margin: EdgeInsets.symmetric(vertical: 10, horizontal: 15),
26 | padding: EdgeInsets.symmetric(vertical: 5, horizontal: 15),
27 | child: Row(
28 | // mainAxisAlignment: MainAxisAlignment.spaceBetween,
29 | children: [
30 | Expanded(
31 | child: Column(
32 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
33 | children: [
34 | RatingScoreView(movie.rating.value.toString(),
35 | normalSize: 25, noneSize: 15, normalColor: Colors.white),
36 | RatingStarView(
37 | movie.rating.fullCount,
38 | Icon(Icons.star, size: 20, color: Colors.amberAccent),
39 | movie.rating.emptyCount,
40 | Icon(Icons.star_border, size: 20, color: Colors.amberAccent),
41 | halfCount: movie.rating.halfCount,
42 | halfIcon: Icon(Icons.star_half,
43 | size: 20, color: Colors.amberAccent),
44 | ),
45 | Text(
46 | '${movie.rating.count} ${S.of(context).movie_scored}',
47 | style: TextStyle(fontSize: 10, color: Colors.white))
48 | ],
49 | ),
50 | ),
51 | Expanded(
52 | child: Column(
53 | crossAxisAlignment: CrossAxisAlignment.start,
54 | mainAxisAlignment: MainAxisAlignment.spaceEvenly,
55 | children: [
56 | _textView(
57 | '${S.of(context).movie_release}:${movie.released ? movie.pubdate : S.of(context).movie_unrelease}'),
58 | _textView('${S.of(context).movie_genre}:${movie.genres}'),
59 | _textView('${S.of(context).movie_duration}:${movie.durations}'),
60 | _textView('${S.of(context).movie_region}:${movie.countries}'),
61 | _textView('${S.of(context).movie_language}:${movie.languages}'),
62 | ],
63 | ))
64 | ],
65 | ),
66 | );
67 | }
68 | }
--------------------------------------------------------------------------------
/movies/lib/view/movie/movie_staff_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:movies/generated/l10n.dart';
3 | import 'package:movies/model/base_model.dart';
4 | import 'package:flutter/material.dart';
5 | import '../base_view.dart';
6 | import '../gallery_view.dart';
7 |
8 | class MovieStaffView extends StatelessWidget {
9 |
10 |
11 | final List galleryItems;
12 |
13 |
14 | MovieStaffView(this.galleryItems);
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 |
19 | final items = galleryItems.where((item) {
20 | return item.url != null;
21 | }).toList();
22 |
23 | if (items.isNotEmpty) {
24 | return Container(
25 | padding: EdgeInsets.only(left: 15, right:15, top: 15),
26 | child: Column(
27 | crossAxisAlignment: CrossAxisAlignment.start,
28 | children: [
29 | BaseTitleView(S.of(context).movie_casts),
30 | Container(
31 | margin: EdgeInsets.only(top: 5),
32 | height: 150,
33 | child: GridView.count(
34 | scrollDirection: Axis.horizontal,
35 | crossAxisCount: 1,
36 | childAspectRatio: 3 / 2,
37 | mainAxisSpacing: 10,
38 | children: List.generate(items.length, (index) {
39 |
40 | final item = items[index];
41 |
42 | return Container(
43 | child: Column(
44 | crossAxisAlignment: CrossAxisAlignment.start,
45 | children: [
46 | _imageView(context, item, items),
47 | Text(item.title, style: TextStyle(fontSize: 11, color: Colors.white, fontWeight: FontWeight.bold), overflow: TextOverflow.ellipsis,),
48 | Text(item.subTitle.isNotEmpty ? S.of(context).movie_director : '', style: TextStyle(fontSize: 9, color: Colors.white70))
49 | ],
50 | ),
51 | );
52 | })
53 | )
54 | )
55 | ],
56 | ));
57 | }
58 | return SizedBox();
59 |
60 | }
61 |
62 | Widget _imageView(BuildContext context, GalleryItem item, List items) {
63 |
64 | final index = items.indexOf(item);
65 |
66 | return
67 | Container(
68 | height: 118,
69 | width: 100,
70 | child: GestureDetector(
71 | onTap: () {
72 | GalleryView.open(context, items, index);
73 | },
74 | child: Hero(
75 | tag: item.url,
76 | child: CachedNetworkImage(imageUrl: item.url, fit: BoxFit.cover),
77 | )
78 | ),
79 | );
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/movies/lib/view/movie/movie_summary_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:movies/generated/l10n.dart';
3 | import 'package:movies/util/util.dart';
4 | import '../base_view.dart';
5 |
6 | class MovieSummaryView extends StatelessWidget {
7 |
8 | final String summary;
9 |
10 | MovieSummaryView(this.summary);
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | if (summary.isNotEmpty) {
15 | return Container(
16 | width: screenWidth(context),
17 | padding: EdgeInsets.only(left: 15, right:15, top: 15),
18 | child: Column(
19 | crossAxisAlignment: CrossAxisAlignment.start,
20 | children: [
21 | BaseTitleView(S.of(context).movie_summary),
22 | Text(summary,
23 | style: TextStyle(color: Colors.white, fontSize: 13))
24 | ],
25 | ));
26 | }
27 | return SizedBox();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/movies/lib/view/movie/movie_trailer_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:cached_network_image/cached_network_image.dart';
2 | import 'package:movies/generated/l10n.dart';
3 | import 'package:movies/model/movie_model.dart';
4 | import 'package:movies/view/player_view.dart';
5 | import 'package:flutter/material.dart';
6 |
7 | import '../base_view.dart';
8 |
9 |
10 | class MovieTrailerView extends StatelessWidget {
11 |
12 | final Movie movie;
13 |
14 | MovieTrailerView(this.movie);
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 |
19 | final trailer = movie.trailer;
20 |
21 | if (trailer != null) {
22 | return Container(
23 | padding: EdgeInsets.only(left: 15, right:15),
24 | child: Column(
25 | crossAxisAlignment: CrossAxisAlignment.start,
26 | children: [
27 | BaseTitleView(S.of(context).movie_trailers),
28 | Container(
29 | margin: EdgeInsets.only(top: 5),
30 | height: 180,
31 | child: Container(
32 | child: GestureDetector(
33 | onTap: () {
34 | PlayerView.open(context, trailer.video, title: trailer.id);
35 | },
36 | child: Stack(
37 | alignment: Alignment.center,
38 | fit: StackFit.expand,
39 | children: [
40 | CachedNetworkImage(imageUrl: trailer.cover, fit: BoxFit.cover),
41 | Icon(Icons.play_circle_filled, size: 50, color: Colors.white)
42 | ],
43 | ),
44 | ),
45 | )
46 | )
47 | ],
48 | ));
49 | }
50 | return SizedBox();
51 |
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/movies/lib/view/player_view.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:chewie/chewie.dart';
4 | import 'package:flutter/services.dart';
5 | import 'package:movies/util/router_manager.dart';
6 | import 'package:flutter/material.dart';
7 | import 'package:movies/view/refresh_view.dart';
8 | import 'package:movies/view/save_view.dart';
9 |
10 | import 'package:video_player/video_player.dart';
11 | import 'package:path_provider/path_provider.dart';
12 | import 'package:flutter_cache_manager/flutter_cache_manager.dart';
13 |
14 | class PlayerView extends StatefulWidget {
15 | static open(BuildContext context, String url, {String title}) {
16 | Navigator.push(
17 | context,
18 | PageRouteBuilder(
19 | pageBuilder: (context, animation, __) {
20 | return FadeTransition(
21 | opacity: animation,
22 | child: PlayerView(url, title: title),
23 | );
24 | }
25 | )
26 | );
27 | }
28 |
29 | final String url;
30 | final String title;
31 |
32 | PlayerView(this.url, {this.title});
33 |
34 | @override
35 | _PlayerViewState createState() => _PlayerViewState();
36 | }
37 |
38 | class _PlayerViewState extends State {
39 | VideoPlayerController _videoPlayerController;
40 | ChewieController _chewieController;
41 | String _path;
42 |
43 | @override
44 | void initState() {
45 | getTemporaryDirectory().then((value) async {
46 | _path = '${value.path}/${DefaultCacheManager.key}/${widget.title}.mp4';
47 |
48 | if (await _isExist) {
49 | _videoPlayerController = VideoPlayerController.file(_file);
50 | } else {
51 | _videoPlayerController = VideoPlayerController.network(widget.url);
52 | }
53 |
54 | await _videoPlayerController.initialize();
55 |
56 | _chewieController = ChewieController(
57 | videoPlayerController: _videoPlayerController,
58 | aspectRatio: 16 / 9,
59 | autoPlay: false,
60 | looping: true,
61 | deviceOrientationsAfterFullScreen: [
62 | DeviceOrientation.portraitUp
63 | ],
64 | deviceOrientationsOnEnterFullScreen: [
65 | DeviceOrientation.landscapeRight,
66 | // DeviceOrientation.landscapeLeft,
67 | ]);
68 |
69 | setState(() {});
70 | });
71 |
72 | super.initState();
73 | }
74 |
75 | File get _file => File(_path);
76 |
77 | Future get _isExist async {
78 | return await _file.exists();
79 | }
80 |
81 | @override
82 | Widget build(BuildContext context) {
83 | return Scaffold(
84 | appBar: AppBar(
85 | iconTheme: IconThemeData(color: Colors.white),
86 | backgroundColor: Colors.transparent,
87 | leading: IconButton(
88 | icon: Icon(Icons.close),
89 | onPressed: () => RouterManager.pop(context)),
90 | actions: [_chewieController != null ? _saveWidget : SizedBox()],
91 | ),
92 | backgroundColor: Colors.black,
93 | body: SafeArea(
94 | child: _chewieController != null &&
95 | _chewieController.videoPlayerController.value.initialized
96 | ? Chewie(controller: _chewieController)
97 | : RefreshCircularIndicator()
98 | )
99 | );
100 | }
101 |
102 | Widget get _saveWidget {
103 | return IconButton(
104 | icon: Icon(Icons.save),
105 | onPressed: () async {
106 | final _url = await _isExist ? null : widget.url;
107 | showModalBottomSheet(
108 | context: context,
109 | builder: (_) {
110 | return SaveView(_path, url: _url);
111 | });
112 | });
113 | }
114 |
115 | @override
116 | void dispose() {
117 | _videoPlayerController.pause();
118 | _videoPlayerController.dispose();
119 | _chewieController.dispose();
120 |
121 | SystemChrome.setPreferredOrientations([
122 | DeviceOrientation.portraitUp,
123 | ]);
124 | super.dispose();
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/movies/lib/view/provider_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:provider/provider.dart';
3 |
4 | class ProviderView extends StatefulWidget {
5 | final ValueWidgetBuilder builder;
6 | final T viewModel;
7 | final Widget child;
8 |
9 | ProviderView({
10 | Key key,
11 | @required this.builder,
12 | @required this.viewModel,
13 | this.child,
14 | }) : super(key: key);
15 |
16 | @override
17 | _ProviderViewState createState() => _ProviderViewState();
18 | }
19 |
20 | class _ProviderViewState
21 | extends State> {
22 | T viewModel;
23 |
24 | @override
25 | void initState() {
26 | viewModel = widget.viewModel;
27 |
28 | // TODO: implement initState
29 | super.initState();
30 | }
31 |
32 | @override
33 | Widget build(BuildContext context) {
34 | return ChangeNotifierProvider.value(
35 | value: viewModel,
36 | child: Consumer(
37 | builder: widget.builder,
38 | child: widget.child,
39 | ));
40 | }
41 | }
--------------------------------------------------------------------------------
/movies/lib/view/rating_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:movies/generated/l10n.dart';
3 |
4 |
5 | class RatingScoreView extends StatelessWidget {
6 |
7 | final String text, placeholder;
8 |
9 | final double normalSize, noneSize;
10 |
11 | final Color normalColor, noneColor;
12 |
13 |
14 | RatingScoreView(this.text,
15 | {this.normalSize,
16 | this.noneSize,
17 | this.normalColor,
18 | this.noneColor,
19 | this.placeholder});
20 |
21 | @override
22 | Widget build(BuildContext context) {
23 |
24 | bool isVaild = text != '0';
25 |
26 | return Text(
27 | isVaild
28 | ? text
29 | : placeholder ?? S.of(context).movie_none_rating,
30 | style: TextStyle(
31 | fontSize: isVaild ? normalSize : noneSize,
32 | fontWeight: FontWeight.bold,
33 | color: isVaild ? normalColor : noneColor ?? normalColor),
34 | );
35 | }
36 | }
37 |
38 | class RatingStarView extends StatelessWidget {
39 | final num fullCount, emptyCount, halfCount;
40 |
41 | final Icon fullIcon, emptyIcon, halfIcon;
42 |
43 | RatingStarView(this.fullCount, this.fullIcon, this.emptyCount, this.emptyIcon,
44 | {this.halfCount, this.halfIcon});
45 |
46 | @override
47 | Widget build(BuildContext context) {
48 | final stars = List();
49 |
50 | stars.addAll(List.filled(fullCount, fullIcon));
51 |
52 | if (halfIcon != null) {
53 | stars.addAll(List.filled(halfCount, halfIcon));
54 | }
55 |
56 | stars.addAll(List.filled(emptyCount, emptyIcon));
57 |
58 | return Row(
59 | children: stars,
60 | mainAxisAlignment: MainAxisAlignment.center,
61 | );
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/movies/lib/view/refresh_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:movies/util/constant.dart';
3 | import 'package:pull_to_refresh/pull_to_refresh.dart';
4 |
5 | class RefreshHeader extends StatelessWidget {
6 | @override
7 | Widget build(BuildContext context) {
8 | return ClassicHeader(
9 | failedIcon: RefreshCenterIcon(Icon(Icons.error, color: ConsColor.theme)),
10 | completeIcon: RefreshCenterIcon(Icon(Icons.done, color: ConsColor.theme)),
11 | idleIcon: RefreshCenterIcon(Icon(Icons.arrow_downward, color: ConsColor.theme)) ,
12 | releaseIcon: RefreshCenterIcon(Icon(Icons.refresh, color: ConsColor.theme)),
13 | refreshingIcon: RefreshCenterIcon(
14 | SizedBox(
15 | child: CircularProgressIndicator(
16 | valueColor: AlwaysStoppedAnimation(ConsColor.theme),
17 | strokeWidth: 3),
18 | height: 15.0,
19 | width: 15.0,
20 | )),
21 | idleText: '',
22 | refreshingText: '',
23 | releaseText: '',
24 | completeText: '',
25 | );
26 | }
27 | }
28 |
29 | class RefreshFooter extends StatelessWidget {
30 | @override
31 | Widget build(BuildContext context) {
32 | return ClassicFooter(
33 | loadingIcon: RefreshCenterIcon(
34 | SizedBox(
35 | child: CircularProgressIndicator(
36 | valueColor: AlwaysStoppedAnimation(ConsColor.theme),
37 | strokeWidth: 3),
38 | height: 15.0,
39 | width: 15.0,
40 | )),
41 | canLoadingIcon: RefreshCenterIcon(Icon(Icons.autorenew, color: ConsColor.theme)),
42 | failedIcon: RefreshCenterIcon(Icon(Icons.error, color: ConsColor.theme)),
43 | idleIcon: RefreshCenterIcon(Icon(Icons.arrow_upward, color: ConsColor.theme)),
44 | idleText: '',
45 | loadingText: '',
46 | canLoadingText: '',
47 | );
48 | }
49 | }
50 |
51 | class RefreshCircularIndicator extends StatelessWidget {
52 |
53 | final double value;
54 | final Color backgroundColor;
55 |
56 | RefreshCircularIndicator({this.value, this.backgroundColor});
57 |
58 | @override
59 | Widget build(BuildContext context) {
60 | return Padding(
61 | padding: EdgeInsets.only(bottom: 0),
62 | child: Center(
63 | child: CircularProgressIndicator(
64 | backgroundColor: backgroundColor,
65 | value: value,
66 | valueColor: AlwaysStoppedAnimation(ConsColor.theme)),
67 | ));
68 | }
69 | }
70 |
71 | class RefreshCenterIcon extends StatelessWidget {
72 |
73 | final Widget child;
74 |
75 | RefreshCenterIcon(this.child);
76 |
77 | @override
78 | Widget build(BuildContext context) {
79 | return Center(child: child);
80 | }
81 | }
--------------------------------------------------------------------------------
/movies/lib/view/save_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:image_gallery_saver/image_gallery_saver.dart';
3 | import 'package:movies/generated/l10n.dart';
4 | import 'package:movies/util/constant.dart';
5 |
6 | import 'package:movies/util/network_manager.dart';
7 | import 'package:movies/util/router_manager.dart';
8 | import 'package:movies/view/refresh_view.dart';
9 | import 'package:permission_handler/permission_handler.dart';
10 |
11 |
12 | class SaveView extends StatefulWidget {
13 |
14 | final String path, url;
15 |
16 | const SaveView(this.path, {this.url});
17 |
18 | @override
19 | _SaveViewState createState() => _SaveViewState();
20 | }
21 |
22 | class _SaveViewState extends State {
23 |
24 | bool _isGranted, _isSaved;
25 |
26 | final _cancelToken = NetworkManager.cancelToken;
27 |
28 | String _totalSize, _completedSize;
29 |
30 | @override
31 | void initState() {
32 |
33 | _isSaved = widget.url == null;
34 |
35 | Permission.photos.request().then((value) {
36 | setState(() {
37 | _isGranted = value.isGranted;
38 | });
39 | });
40 |
41 |
42 | if (!_isSaved) {
43 | NetworkManager.contentLength(widget.url, cancelToken: _cancelToken).then((value) {
44 | setState(() {
45 | _totalSize = value.mb;
46 | });
47 | });
48 | }
49 |
50 | super.initState();
51 | }
52 |
53 | @override
54 | Widget build(BuildContext context) {
55 |
56 | return SafeArea(child:
57 | Container(
58 | height: 150,
59 | child: _isGranted != null ? _bodyView: RefreshCircularIndicator()
60 | )
61 | );
62 | }
63 |
64 |
65 | Widget get _titleView {
66 |
67 | if (_isGranted) {
68 | return SizedBox();
69 | }
70 |
71 | return Container(
72 | margin: EdgeInsets.only(bottom: 5),
73 | child: Center(
74 | child: Text(S.of(context).file_permission, style: TextStyle(fontSize: 18)),
75 | ),
76 | );
77 | }
78 |
79 | Widget get _bodyView {
80 | return Column(
81 | mainAxisAlignment: MainAxisAlignment.center,
82 | children: [
83 | _titleView,
84 | Container(
85 | padding: EdgeInsets.symmetric(horizontal: 15),
86 | width: double.infinity,
87 | child: _downloadView
88 | ),
89 | Container(
90 | padding: EdgeInsets.symmetric(horizontal: 15),
91 | width: double.infinity,
92 | child: FlatButton(
93 | onPressed: () {
94 | RouterManager.pop(context);
95 | },
96 | textColor: ConsColor.theme,
97 | child: Text(S.of(context).file_cancel),
98 | color: Colors.transparent,
99 | shape: RoundedRectangleBorder(
100 | side: BorderSide(color: ConsColor.theme)
101 | )
102 | )
103 | )
104 | ],
105 | );
106 | }
107 |
108 | Widget get _downloadView {
109 |
110 | if (_completedSize != null) {
111 | return Padding(
112 | padding: EdgeInsets.only(bottom: 15),
113 | child: Center(
114 | child: Text('${S.of(context).file_total} $_totalSize M, ${S.of(context).file_completed} $_completedSize M',
115 | style: TextStyle(color: ConsColor.theme, fontSize: 18)),
116 | )
117 | //child: RefreshCircularIndicator(value: _indicatorValue, backgroundColor: Colors.black12)
118 | );
119 | }
120 |
121 | return FlatButton(onPressed: () async {
122 |
123 | if (_isGranted) {
124 | _save();
125 | } else {
126 | await openAppSettings();
127 | RouterManager.pop(context);
128 | }
129 |
130 |
131 | },
132 | child: Text(_isGranted ? _isSaved ? S.of(context).file_save : '${S.of(context).file_download} (${_totalSize ?? 0} M)'
133 | : S.of(context).file_settings),
134 | textColor: Colors.white,
135 | color: ConsColor.theme);
136 | }
137 |
138 | _save() async {
139 |
140 | if (_isSaved) {
141 | await ImageGallerySaver.saveFile(widget.path);
142 | RouterManager.pop(context);
143 | } else {
144 |
145 | try {
146 | await NetworkManager
147 | .download(widget.url, widget.path,
148 | cancelToken: _cancelToken,
149 | onReceiveProgress: (count, total) {
150 | setState(() {
151 | _completedSize = count.mb;
152 | });
153 | });
154 | await ImageGallerySaver.saveFile(widget.path);
155 | RouterManager.pop(context);
156 | }
157 | catch (error) {
158 | print(error.toString());
159 | }
160 |
161 |
162 | }
163 |
164 | }
165 |
166 |
167 | @override
168 | void dispose() {
169 | NetworkManager.cancel(_cancelToken);
170 | super.dispose();
171 | }
172 | }
173 |
174 |
--------------------------------------------------------------------------------
/movies/lib/view/webpage_view.dart:
--------------------------------------------------------------------------------
1 |
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:movies/util/router_manager.dart';
5 | import 'package:movies/view/refresh_view.dart';
6 | import 'package:share/share.dart';
7 | import 'package:webview_flutter/webview_flutter.dart';
8 |
9 |
10 | class WebpageView extends StatefulWidget {
11 |
12 | final String url;
13 | final String title;
14 |
15 | static open(BuildContext context, String url, {String title}) {
16 | Navigator.push(
17 | context,
18 | PageRouteBuilder(
19 | pageBuilder: (context, animation, __) {
20 | return FadeTransition(
21 | opacity: animation,
22 | child: WebpageView(url, title: title),
23 | );
24 | }
25 | )
26 | );
27 | }
28 |
29 | WebpageView(this.url, {this.title});
30 |
31 | @override
32 | _WebpageViewState createState() => _WebpageViewState();
33 | }
34 |
35 | class _WebpageViewState extends State {
36 |
37 |
38 | bool _isFinish = false;
39 | WebViewController _controller;
40 |
41 | @override
42 | Widget build(BuildContext context) {
43 |
44 | return Scaffold(
45 | appBar: AppBar(
46 | title: Text(widget.title),
47 | leading:BackButton(
48 | onPressed: () {
49 | _controller
50 | .canGoBack()
51 | .then((value) {
52 | if (value) {
53 | _controller.goBack();
54 | } else {
55 | RouterManager.pop(context);
56 | }
57 |
58 | });
59 | }),
60 | actions: [
61 | IconButton(
62 | icon: Icon(Icons.share),
63 | onPressed: (){
64 | Share.share('${widget.title}\n${widget.url}');
65 | },
66 | )
67 | ],
68 | ),
69 | body: Stack(
70 | children: [
71 | WebView(
72 | onWebViewCreated: (controller) {
73 | _controller = controller;
74 | },
75 | initialUrl: Uri.encodeFull(widget.url),
76 | onPageStarted: (_) {
77 | setState(() {
78 | _isFinish = false;
79 | });
80 | },
81 | onPageFinished: (_) {
82 | setState(() {
83 | _isFinish = true;
84 | });
85 | },
86 | ),
87 | _isFinish ? Stack() : RefreshCircularIndicator()
88 | ],
89 | )
90 |
91 | );
92 | }
93 |
94 | }
95 |
96 |
97 |
--------------------------------------------------------------------------------
/movies/lib/view_model/base_view_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:movies/generated/l10n.dart';
2 | import 'package:movies/model/base_model.dart';
3 | import 'package:dio/dio.dart';
4 | import 'package:movies/util/network_manager.dart';
5 | import 'package:flutter/cupertino.dart';
6 |
7 |
8 | class BaseListViewModel extends BaseViewModel {
9 |
10 | T list;
11 |
12 | String id;
13 |
14 | int pageStart = 0, pageSize = 0;
15 |
16 | int start = 0, count = 10;
17 |
18 | BaseListViewModel({this.id}){
19 | onRefresh();
20 | }
21 |
22 | @override
23 | get param => {
24 | 'start': start,
25 | 'count': count,
26 | };
27 |
28 | @override
29 | bool get refreshNoData {
30 | if (list != null) {
31 | return isEmpty;
32 | }
33 | return true;
34 | }
35 |
36 | @override
37 | bool get isEmpty => list.subjects.isEmpty;
38 |
39 | @override
40 | bool get loadNoData => list.subjects.length >= list.total;
41 |
42 | @override
43 | onRefresh() {
44 | start = pageStart;
45 | super.onRefresh();
46 | }
47 |
48 | @override
49 | onLoading() {
50 | start = list.subjects.length;
51 | super.onLoading();
52 | }
53 |
54 | T modelFromJson(json) {
55 | return BaseList.fromJson(json) as T;
56 | }
57 |
58 | @override
59 | refreshCompleted(json) {
60 | list = modelFromJson(json);
61 | pageSize = list.subjects.length;
62 | }
63 |
64 | @override
65 | loadComplete(json) {
66 | final _list = modelFromJson(json);
67 | list.subjects.addAll(_list.subjects);
68 | pageSize = _list.subjects.length;
69 | }
70 |
71 |
72 | }
73 |
74 |
75 | class BaseViewModel extends StateViewModel {
76 |
77 | bool get isEmpty { return false; }
78 | bool get refreshNoData { return false; }
79 | bool get loadNoData { return false; }
80 |
81 | Api get api { return Api.fetchDetail; }
82 | String get extra { return ''; }
83 | Map get param { return {}; }
84 |
85 | onRefresh() {
86 | setViewState(ViewState.onRefresh);
87 | _response.then((response) {
88 | refreshCompleted(response.data);
89 | setViewState(ViewState.refreshCompleted);
90 | if (isEmpty) {
91 | setViewState(ViewState.empty);
92 | }
93 | }, onError:(error) {
94 | setViewState(ViewState.refreshError, message: error.message);
95 | });
96 | }
97 |
98 | onLoading() {
99 | if (loadNoData) {
100 | setViewState(ViewState.loadNoData);
101 | } else {
102 | setViewState(ViewState.onLoading);
103 | _response.then((response) {
104 | loadComplete(response.data);
105 | setViewState(ViewState.loadComplete);
106 | }, onError: (error) {
107 | setViewState(ViewState.loadError, message: error.message);
108 | });
109 | }
110 | }
111 |
112 |
113 | refreshCompleted(json) {
114 |
115 | }
116 |
117 | loadComplete(json) {
118 |
119 | }
120 |
121 | Future get _response async {
122 | return await NetworkManager.get(api,
123 | extra: extra,
124 | param: param);
125 | }
126 |
127 | }
128 |
129 |
130 |
131 | enum ViewState {
132 | idle,
133 | onRefresh,
134 | refreshCompleted,
135 | refreshError,
136 | onLoading,
137 | loadComplete,
138 | loadNoData,
139 | loadError,
140 | empty
141 | }
142 |
143 | class StateViewModel extends ChangeNotifier {
144 | ViewState _viewState = ViewState.idle;
145 |
146 | String _message;
147 |
148 | setViewState(ViewState viewState, {String message}) {
149 | _viewState = viewState;
150 | _message = message;
151 | notifyListeners();
152 | }
153 |
154 | ViewState get viewState => _viewState;
155 |
156 | String get message => _message;
157 |
158 | }
--------------------------------------------------------------------------------
/movies/lib/view_model/locale_view_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:movies/generated/l10n.dart';
2 | import 'package:movies/util/constant.dart';
3 | import 'package:movies/util/storage_manager.dart';
4 | import 'package:flutter/cupertino.dart';
5 |
6 | class LocaleViewModel extends ChangeNotifier {
7 |
8 | Locale get current => S.delegate.supportedLocales[_index];
9 |
10 | int get _index => StorageManager.getInt(StorageKeys.locale) ?? 1;
11 |
12 | update(Locale locale) {
13 | S.load(locale);
14 | StorageManager.setInt(StorageKeys.locale, locale.index);
15 | notifyListeners();
16 | }
17 |
18 | updateByIndex(int index) {
19 | final locale = S.delegate.supportedLocales[index];
20 | update(locale);
21 | }
22 |
23 | }
24 |
25 | extension LocaleExtension on Locale {
26 |
27 | String get displayName {
28 | switch (languageCode) {
29 | case 'en': return 'English';
30 | default: return '简体中文';
31 | }
32 | }
33 |
34 | int get index {
35 | return S.delegate.supportedLocales.indexOf(this);
36 | }
37 |
38 | }
--------------------------------------------------------------------------------
/movies/lib/view_model/movie_view_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:movies/model/comment_model.dart';
2 | import 'package:movies/model/movie_model.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:movies/model/photo_model.dart';
5 | import 'package:movies/util/util.dart';
6 | import 'base_view_model.dart';
7 |
8 | class MovieViewModel extends BaseViewModel {
9 |
10 | Movie movie;
11 | Color color, titleColor;
12 |
13 | String id;
14 |
15 | MovieViewModel(this.id) {
16 | onRefresh();
17 | }
18 |
19 | @override
20 | String get extra => id;
21 |
22 | @override
23 | refreshCompleted(json) {
24 | movie = Movie.fromJson(json);
25 | color = hexColor(movie.color.primary);
26 | titleColor = Colors.white;
27 | }
28 |
29 | }
30 |
31 | class MovieRecommendViewModel extends BaseViewModel {
32 |
33 | List movies;
34 |
35 | final String id;
36 |
37 | MovieRecommendViewModel(this.id) {
38 | onRefresh();
39 | }
40 |
41 | @override
42 | String get extra => '$id/recommendations';
43 |
44 | @override
45 | refreshCompleted(json) {
46 | movies = (json as List).map((item) => MovieGridItem.fromJson(item)).toList();
47 | }
48 |
49 | @override
50 | bool get isEmpty => movies.isEmpty;
51 |
52 | @override
53 | bool get refreshNoData {
54 | if (movies != null) {
55 | return isEmpty;
56 | }
57 | return true;
58 | }
59 | }
60 |
61 | class MovieCommentViewModel extends BaseListViewModel {
62 |
63 | MovieCommentViewModel(id):super(id: id);
64 |
65 | @override
66 | int get pageStart => 1;
67 |
68 | @override
69 | String get extra => '$id/interests';
70 |
71 | @override
72 | int get count => 15;
73 |
74 | @override
75 | CommentList modelFromJson(json) {
76 | return CommentList.fromJson(json);
77 | }
78 |
79 | }
80 |
81 |
82 | class MovieReviewViewModel extends MovieCommentViewModel {
83 |
84 | MovieReviewViewModel(id) : super(id);
85 |
86 | @override
87 | String get extra => '$id/reviews';
88 |
89 | }
90 |
91 | class MoviePhotoViewModel extends BaseListViewModel {
92 |
93 | PhotoList list;
94 |
95 | MoviePhotoViewModel(id) : super(id: id);
96 |
97 | @override
98 | String get extra => '$id/photos';
99 |
100 | @override
101 | int get pageStart => 1;
102 |
103 | @override
104 | int get count => 20;
105 |
106 | @override
107 | PhotoList modelFromJson(json) {
108 | return PhotoList.fromJson(json);
109 | }
110 |
111 | }
--------------------------------------------------------------------------------
/movies/lib/view_model/movies_view_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio/dio.dart';
2 | import 'package:movies/model/movies_model.dart';
3 | import 'package:movies/model/movie_model.dart';
4 | import 'package:movies/model/rank_model.dart';
5 | import 'package:movies/util/network_manager.dart';
6 |
7 | import 'base_view_model.dart';
8 |
9 | class MoviesViewModel extends BaseViewModel {
10 |
11 | List lists = [];
12 | MoviesToday today;
13 | RankList ranks;
14 |
15 | MoviesViewModel() {
16 | onRefresh();
17 | }
18 |
19 | @override
20 | onRefresh() {
21 |
22 | setViewState(ViewState.onRefresh);
23 | Future.wait([
24 | _fetchToday(),
25 | _fetchList('movie_showing'),
26 | _fetchList('movie_soon'),
27 | _fetchList('movie_hot_gaia'),
28 | _fetchRanks(),
29 | ]).then((result) {
30 |
31 | result.removeWhere((element) => element == null);
32 | lists = result.map((e) => e as MovieList).toList();
33 | setViewState(ViewState.refreshCompleted);
34 | if (isEmpty) {
35 | setViewState(ViewState.empty);
36 | }
37 | }).catchError((error) {
38 | setViewState(ViewState.refreshError, message: error.message);
39 | });
40 |
41 | }
42 |
43 | @override
44 | bool get refreshNoData {
45 | if (lists != null) {
46 | return isEmpty;
47 | }
48 | return true;
49 | }
50 |
51 | @override
52 | bool get isEmpty => lists.isEmpty && ranks == null && today == null;
53 |
54 | @override
55 | Api get api => Api.fetchMovieList;
56 |
57 | @override
58 | get param => {'start': 0, 'count': 5};
59 |
60 |
61 | Future _fetchList(String extra) async {
62 | Response response = await NetworkManager.get(api,
63 | extra: extra,
64 | param: param);
65 |
66 | return MovieList.fromJson(response.data);
67 |
68 | }
69 |
70 | Future _fetchToday() async {
71 |
72 | Response response = await NetworkManager.get(Api.fetchToday);
73 | today = MoviesToday.fromJson(response.data);
74 |
75 | }
76 |
77 | Future _fetchRanks() async {
78 |
79 | Response response = await NetworkManager.get(Api.fetchRanks);
80 | ranks = RankList.fromJson(response.data);
81 |
82 | }
83 |
84 | }
85 |
86 |
87 | class MoviesListViewModel extends BaseListViewModel {
88 |
89 | MoviesListViewModel(id):super(id: id);
90 |
91 | @override
92 | Api get api => Api.fetchMovieList;
93 |
94 | @override
95 | String get extra => id;
96 |
97 | @override
98 | MovieList modelFromJson(json) {
99 | return MovieList.fromJson(json);
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/movies/lib/view_model/search_view_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:movies/model/search_model.dart';
2 | import 'package:movies/util/network_manager.dart';
3 | import 'base_view_model.dart';
4 |
5 | class SearchResultsViewModel extends BaseListViewModel {
6 |
7 | String text;
8 |
9 | SearchResultsViewModel(text):super(id: text);
10 |
11 | @override
12 | Api get api => Api.fetchSearchResults;
13 |
14 | @override
15 | get param => {
16 | 'start': start,
17 | 'count': count,
18 | 'q': id,
19 | 'type': 'movie'
20 | };
21 |
22 |
23 |
24 | @override
25 | SearchResults modelFromJson(json) {
26 | return SearchResults.fromJson(json);
27 | }
28 |
29 | }
30 |
31 | class SearchSuggestionsViewModel extends BaseListViewModel {
32 |
33 | @override
34 | Api get api => Api.fetchSearchSuggestions;
35 |
36 | @override
37 | SearchSuggestions modelFromJson(json) {
38 | return SearchSuggestions.fromJson(json);
39 | }
40 |
41 |
42 | }
--------------------------------------------------------------------------------
/movies/lib/view_model/theme_view_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:movies/generated/l10n.dart';
2 | import 'package:movies/util/constant.dart';
3 | import 'package:movies/util/storage_manager.dart';
4 | import 'package:flutter/material.dart';
5 |
6 |
7 | ThemeData lightData = ThemeData.light().copyWith(
8 | primaryColor: ConsColor.theme,
9 | appBarTheme: AppBarTheme(
10 | color: Colors.white,
11 | textTheme: TextTheme(
12 | headline6: TextStyle(color: ConsColor.theme,
13 | fontSize: 20)
14 | ),
15 | iconTheme: IconThemeData(
16 | color: ConsColor.theme
17 | ),
18 | brightness: Brightness.light,
19 | // elevation: 0
20 | ),
21 | bottomNavigationBarTheme: BottomNavigationBarThemeData(
22 | unselectedItemColor: Colors.grey.shade400,
23 | )
24 | );
25 |
26 | ThemeData darkData = ThemeData.dark().copyWith(
27 | appBarTheme: AppBarTheme(
28 | // elevation: 0,
29 | textTheme: TextTheme(
30 | headline6: TextStyle(color: Colors.white,
31 | fontSize: 20)
32 | ),
33 | iconTheme: IconThemeData(
34 | color: Colors.white
35 | ),
36 | ),
37 | bottomNavigationBarTheme: BottomNavigationBarThemeData(
38 | unselectedItemColor: Colors.grey.shade600,
39 | )
40 | );
41 |
42 | extension ThemeModeExtension on ThemeMode {
43 | String get displayName {
44 | switch (this) {
45 | case ThemeMode.dark: return S.current.settings_theme_dark;
46 | case ThemeMode.light: return S.current.settings_theme_light;
47 | default: return S.current.settings_theme_system;
48 | }
49 | }
50 | }
51 |
52 | class ThemeViewModel extends ChangeNotifier {
53 |
54 | ThemeMode get current => ThemeMode.values[_index];
55 |
56 | int get _index => StorageManager.getInt(StorageKeys.themeMode) ?? 0;
57 |
58 | update(ThemeMode mode) {
59 | StorageManager.setInt(StorageKeys.themeMode, mode.index);
60 | notifyListeners();
61 | }
62 |
63 | updateByIndex(int index) {
64 | final mode = ThemeMode.values[index];
65 | update(mode);
66 | }
67 |
68 | }
--------------------------------------------------------------------------------
/movies/lib/view_model/tvs_view_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:movies/model/movie_model.dart';
2 | import 'package:movies/util/network_manager.dart';
3 |
4 | import 'base_view_model.dart';
5 |
6 | class TVsViewModel extends BaseListViewModel {
7 |
8 | TVsViewModel(id):super(id: id);
9 |
10 | @override
11 | Api get api => Api.fetchMovieList;
12 |
13 | @override
14 | String get extra => id;
15 |
16 | @override
17 | MovieList modelFromJson(json) {
18 | return MovieList.fromJson(json);
19 | }
20 |
21 | update(String _id) {
22 |
23 | if (id != _id) {
24 | id = _id;
25 | onRefresh();
26 | }
27 |
28 | }
29 |
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/movies/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: movies
2 | description: A new Flutter project.
3 |
4 | # The following defines the version and build number for your application.
5 | # A version number is three numbers separated by dots, like 1.2.43
6 | # followed by an optional build number separated by a +.
7 | # Both the version and the builder number may be overridden in flutter
8 | # build by specifying --build-name and --build-number, respectively.
9 | # In Android, build-name is used as versionName while build-number used as versionCode.
10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
12 | # Read more about iOS versioning at
13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
14 | version: 2.0.0+1
15 |
16 | environment:
17 | sdk: ">=2.10.2 <3.0.0"
18 |
19 | dependencies:
20 | flutter:
21 | sdk: flutter
22 |
23 | flutter_localizations:
24 | sdk: flutter
25 |
26 | cached_network_image:
27 | dio:
28 | provider:
29 | package_info:
30 | webview_flutter:
31 | fluro:
32 | pull_to_refresh:
33 | shared_preferences:
34 | photo_view:
35 | video_player:
36 | chewie:
37 | share:
38 | flutter_staggered_grid_view:
39 | permission_handler:
40 | image_gallery_saver:
41 | flutter_staggered_animations:
42 |
43 | dev_dependencies:
44 | flutter_test:
45 | sdk: flutter
46 |
47 |
48 | # For information on the generic Dart part of this file, see the
49 | # following page: https://dart.dev/tools/pub/pubspec
50 |
51 | # The following section is specific to Flutter.
52 | flutter:
53 |
54 | # The following line ensures that the Material Icons font is
55 | # included with your application, so that you can use the icons in
56 | # the material Icons class.
57 | uses-material-design: true
58 |
59 | # To add assets to your application, add an assets section, like this:
60 | # assets:
61 | # - images/a_dot_burr.jpeg
62 | # - images/a_dot_ham.jpeg
63 |
64 | # An image asset can refer to one or more resolution-specific "variants", see
65 | # https://flutter.dev/assets-and-images/#resolution-aware.
66 |
67 | # For details regarding adding assets from package dependencies, see
68 | # https://flutter.dev/assets-and-images/#from-packages
69 |
70 | # To add custom fonts to your application, add a fonts section here,
71 | # in this "flutter" section. Each entry in this list should have a
72 | # "family" key with the font family name, and a "fonts" key with a
73 | # list giving the asset and other descriptors for the font. For
74 | # example:
75 | # fonts:
76 | # - family: Schyler
77 | # fonts:
78 | # - asset: fonts/Schyler-Regular.ttf
79 | # - asset: fonts/Schyler-Italic.ttf
80 | # style: italic
81 | # - family: Trajan Pro
82 | # fonts:
83 | # - asset: fonts/TrajanPro.ttf
84 | # - asset: fonts/TrajanPro_Bold.ttf
85 | # weight: 700
86 | #
87 | # For details regarding fonts from package dependencies,
88 | # see https://flutter.dev/custom-fonts/#from-packages
89 | flutter_intl:
90 | enabled: true
91 |
--------------------------------------------------------------------------------
/movies/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility that Flutter provides. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | import 'package:movies/main.dart';
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // Build our app and trigger a frame.
16 | await tester.pumpWidget(MyApp());
17 |
18 | // Verify that our counter starts at 0.
19 | expect(find.text('0'), findsOneWidget);
20 | expect(find.text('1'), findsNothing);
21 |
22 | // Tap the '+' icon and trigger a frame.
23 | await tester.tap(find.byIcon(Icons.add));
24 | await tester.pump();
25 |
26 | // Verify that our counter has incremented.
27 | expect(find.text('0'), findsNothing);
28 | expect(find.text('1'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/previews/1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZzzM/Flutter-Movies/bd027acf04e989dd86eeb0795afa23d6b84d8df5/previews/1.gif
--------------------------------------------------------------------------------
/previews/2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZzzM/Flutter-Movies/bd027acf04e989dd86eeb0795afa23d6b84d8df5/previews/2.gif
--------------------------------------------------------------------------------
/previews/3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZzzM/Flutter-Movies/bd027acf04e989dd86eeb0795afa23d6b84d8df5/previews/3.gif
--------------------------------------------------------------------------------
/previews/4.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZzzM/Flutter-Movies/bd027acf04e989dd86eeb0795afa23d6b84d8df5/previews/4.gif
--------------------------------------------------------------------------------
/previews/5.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZzzM/Flutter-Movies/bd027acf04e989dd86eeb0795afa23d6b84d8df5/previews/5.gif
--------------------------------------------------------------------------------
/previews/6.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZzzM/Flutter-Movies/bd027acf04e989dd86eeb0795afa23d6b84d8df5/previews/6.gif
--------------------------------------------------------------------------------
/previews/7.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZzzM/Flutter-Movies/bd027acf04e989dd86eeb0795afa23d6b84d8df5/previews/7.gif
--------------------------------------------------------------------------------
/previews/8.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ZzzM/Flutter-Movies/bd027acf04e989dd86eeb0795afa23d6b84d8df5/previews/8.gif
--------------------------------------------------------------------------------