├── .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 --------------------------------------------------------------------------------