├── .gitignore ├── .metadata ├── README.md ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── shuaishuaimovie │ │ │ │ └── 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 │ │ │ └── logo.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values │ │ │ └── styles.xml │ │ │ └── xml │ │ │ ├── file_paths.xml │ │ │ └── network_security_config.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties └── settings.gradle ├── assets └── fonts │ └── iconfont.ttf ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── lib ├── database │ ├── bean │ │ ├── search_history_bean.dart │ │ └── video_history_bean.dart │ └── sqf_provider.dart ├── generated │ └── json │ │ ├── base │ │ ├── json_convert_content.dart │ │ └── json_filed.dart │ │ ├── common_item_bean_entity_helper.dart │ │ ├── common_tab_item_bean_entity_helper.dart │ │ ├── condition_search_bean_entity_helper.dart │ │ ├── home_detail_bean_entity_helper.dart │ │ ├── home_list_bean_entity_helper.dart │ │ ├── hot_rank_bean_entity_helper.dart │ │ ├── hot_update_bean_entity_helper.dart │ │ ├── select_condition_bean_entity_helper.dart │ │ └── txt_auto_search_bean_entity_helper.dart ├── images.dart ├── main.dart ├── models │ ├── common_item_bean_entity.dart │ ├── common_tab_item_bean_entity.dart │ ├── condition_search_bean_entity.dart │ ├── home_detail_bean_entity.dart │ ├── home_list_bean_entity.dart │ ├── hot_rank_bean_entity.dart │ ├── hot_update_bean_entity.dart │ ├── select_condition_bean_entity.dart │ └── txt_auto_search_bean_entity.dart ├── net │ ├── base_repository.dart │ ├── base_result.dart │ ├── request.dart │ ├── request_const.dart │ └── request_helper.dart ├── provider │ ├── provider_proxy_widget.dart │ ├── provider_setup.dart │ ├── provider_widget.dart │ └── view_state.dart ├── r.g.dart ├── res │ └── app_color.dart ├── routes │ ├── route_handlers.dart │ ├── route_jump.dart │ └── routes.dart ├── sharepreference │ └── share_preference.dart ├── shuai_movie.dart ├── ui │ ├── helper │ │ ├── common_list_helper.dart │ │ ├── db │ │ │ └── db_operate.dart │ │ ├── home_detail_list_helper.dart │ │ ├── home_list_helper.dart │ │ ├── hot_rank_list_helper.dart │ │ ├── hot_update_list_helper.dart │ │ ├── search_list_helper.dart │ │ ├── video_history_list_helper.dart │ │ └── view_state_helper.dart │ └── pages │ │ ├── history │ │ └── video_history_page.dart │ │ ├── hot │ │ ├── rank │ │ │ ├── hot_rank_page.dart │ │ │ └── tab │ │ │ │ ├── month_rank_page.dart │ │ │ │ ├── total_rank_page.dart │ │ │ │ └── week_rank_page.dart │ │ ├── update │ │ │ └── hot_update_page.dart │ │ └── widget │ │ │ ├── hot_rank_widget.dart │ │ │ └── hot_update_widget.dart │ │ ├── maintab │ │ ├── cartoon │ │ │ └── cartoon_page.dart │ │ ├── home │ │ │ ├── home_detail_page.dart │ │ │ ├── home_page.dart │ │ │ └── homewidget │ │ │ │ ├── home_detail_widget.dart │ │ │ │ └── home_list_widget.dart │ │ ├── index_page.dart │ │ ├── movie │ │ │ └── movie_page.dart │ │ ├── show │ │ │ └── show_page.dart │ │ ├── teleplay │ │ │ └── teleplay_page.dart │ │ └── widget │ │ │ └── common_tab_widget.dart │ │ ├── mine │ │ └── mine_page.dart │ │ ├── not_found_page.dart │ │ ├── search │ │ ├── condition_search_page.dart │ │ ├── tab │ │ │ ├── hot_search_page.dart │ │ │ ├── new_search_page.dart │ │ │ └── score_search_page.dart │ │ ├── txt_search_page.dart │ │ └── widget │ │ │ ├── search_widget.dart │ │ │ ├── txt_auto_search_child.dart │ │ │ ├── txt_history_search_child.dart │ │ │ └── txt_normal_search_child.dart │ │ ├── splash_page.dart │ │ └── video │ │ ├── video_page.dart │ │ └── widget │ │ └── shuai_video.dart ├── utils │ ├── app_info_util.dart │ ├── net │ │ └── net_work.dart │ ├── str_util.dart │ ├── system │ │ └── system_chrome.dart │ └── time │ │ └── movie_time_util.dart ├── video │ └── custom_video_panel.dart ├── viewmodels │ ├── app_theme_model.dart │ ├── base_model.dart │ ├── base_refresh_model.dart │ ├── history │ │ └── video_history_model.dart │ ├── hot │ │ ├── hot_rank_model.dart │ │ └── hot_update_model.dart │ ├── search │ │ ├── condition_search_model.dart │ │ ├── select_condition_model.dart │ │ ├── txt_auto_search_model.dart │ │ ├── txt_history_search_model.dart │ │ └── txt_normal_search_model.dart │ ├── tab │ │ ├── commontab │ │ │ └── common_tab_model.dart │ │ └── home │ │ │ ├── home_detail_model.dart │ │ │ ├── home_model.dart │ │ │ └── index_model.dart │ └── video │ │ └── video_model.dart └── widgets │ ├── app_bar.dart │ ├── checkicon.dart │ ├── clip_widget.dart │ ├── custom_chewie_overlay_widget.dart │ ├── custom_controls.dart │ ├── custom_progress_paint.dart │ ├── custom_refresh_widget.dart │ ├── panel_widget.dart │ ├── rating_bar.dart │ ├── refresh_widget.dart │ ├── smarquee_widget.dart │ ├── tag_widget.dart │ ├── text_widget.dart │ └── toggle_rotate.dart ├── pubspec.yaml ├── resource ├── detail_page.gif ├── drop_style.gif ├── history.gif ├── home.gif ├── play_video.gif ├── selection_search.gif └── text_search.gif └── test └── widget_test.dart /.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 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Exceptions to above rules. 43 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 44 | -------------------------------------------------------------------------------- /.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: e6b34c2b5c96bb95325269a29a84e83ed8909b5f 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # shuaishuai_movie 2 | 基于Provider+MVVM的Flutter 视频App项目,帅帅影视是一个含有海量视频的app,各种视频随你来看。 3 | 4 | # 先来点样图 5 | 6 | | ![home.gif](https://i.loli.net/2020/08/04/HwRLQZq7FVyntOd.gif) | ![detail_page.gif](https://i.loli.net/2020/08/04/v4M9zTgKVmwDB6q.gif) | ![play_video.gif](https://i.loli.net/2020/08/04/ZmKUgGxMPIu8vRc.gif)| 7 | | --- | --- | --- | 8 | |![selection_search.gif](https://i.loli.net/2020/08/04/GtEiqpxmU8YyNwn.gif)| ![drop_style.gif](https://i.loli.net/2020/08/04/ymt2baxMTkEerij.gif) | ![text_search.gif](https://i.loli.net/2020/08/04/m7tp85ijWVJTAwf.gif)| 9 | | ![history.gif](https://i.loli.net/2020/08/04/CNueqV4O23ZxKJU.gif) | | 10 | 11 | 12 | **主要使用到的一些三方库:** 13 | 14 | |**第三方库** |**功能** |**github地址** | 15 | | ---- | ---- |---- | 16 | | dio | 网络请求 | https://github.com/flutterchina/dio| 17 | | video_player | 视频播放 |https://github.com/flutter/plugins/tree/master/packages/video_player| 18 | | chewie | 视频播放 | https://github:com/brianegan/chewie | 19 | | fluro | 路由跳转 | https://github.com/theyakka/fluro | 20 | | connectivity | 网络变化监听 | https://pub.dev/packages/connectivity | 21 | | flutter_easyrefresh | 上拉加载下拉刷新 | https://github.com/xuelongqy/flutter_easyrefresh | 22 | | flutter_sticky_header | 粘性头部 | https://github.com/letsar/flutter_sticky_header | 23 | | cached_network_image | 图片缓存 | https://github.com/Baseflow/flutter_cached_network_image | 24 | | fluttertoast | 吐司 | https://github.com/ponnamkarthik/FlutterToast | 25 | | shared_preferences | shared存储 | https://pub.dev/packages?q=shared_preferences | 26 | 27 | # 更新 28 | 29 | ## V1.1.0 `2020-09-08` 30 | - 修复视频快速切换bug 31 | - 视频播放器添加手势滑动调整音量和亮度 32 | - 修改文字搜索历史逻辑 33 | - 修改部分icon图标 34 | 35 | ## V1.0.0 `2020-08-19` 36 | - 添加留言模块 37 | - 添加历史记录可以点击继续观看功能 38 | - 添加控制视频是否自动播放功能 39 | - 添加启动页闪屏动画功能 40 | 41 | ## V0.0.1 `2020-08-04` 42 | - 帅帅影视的基本功能已完成 43 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /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.shuaishuaimovie" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | } 47 | 48 | buildTypes { 49 | release { 50 | // TODO: Add your own signing config for the release build. 51 | // Signing with the debug keys for now, so `flutter run --release` works. 52 | signingConfig signingConfigs.debug 53 | } 54 | } 55 | } 56 | 57 | flutter { 58 | source '../..' 59 | } 60 | 61 | dependencies { 62 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 63 | } 64 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 16 | 20 | 24 | 29 | 33 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/shuaishuaimovie/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.shuaishuaimovie 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/android/app/src/main/res/mipmap-xxhdpi/logo.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /assets/fonts/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/assets/fonts/iconfont.ttf -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /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 parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | generated_key_values = {} 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) do |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | generated_key_values[podname] = podpath 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | end 32 | generated_key_values 33 | end 34 | 35 | target 'Runner' do 36 | use_frameworks! 37 | use_modular_headers! 38 | 39 | # Flutter Pod 40 | 41 | copied_flutter_dir = File.join(__dir__, 'Flutter') 42 | copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') 43 | copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') 44 | unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) 45 | # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. 46 | # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. 47 | # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. 48 | 49 | generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') 50 | unless File.exist?(generated_xcode_build_settings_path) 51 | raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" 52 | end 53 | generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) 54 | cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; 55 | 56 | unless File.exist?(copied_framework_path) 57 | FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) 58 | end 59 | unless File.exist?(copied_podspec_path) 60 | FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) 61 | end 62 | end 63 | 64 | # Keep pod path relative so it can be checked into Podfile.lock. 65 | pod 'Flutter', :path => 'Flutter' 66 | 67 | # Plugin Pods 68 | 69 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 70 | # referring to absolute paths on developers' machines. 71 | system('rm -rf .symlinks') 72 | system('mkdir -p .symlinks/plugins') 73 | plugin_pods = parse_KV_file('../.flutter-plugins') 74 | plugin_pods.each do |name, path| 75 | symlink = File.join('.symlinks', 'plugins', name) 76 | File.symlink(path, symlink) 77 | pod name, :path => File.join(symlink, 'ios') 78 | end 79 | end 80 | 81 | post_install do |installer| 82 | installer.pods_project.targets.each do |target| 83 | target.build_configurations.each do |config| 84 | config.build_settings['ENABLE_BITCODE'] = 'NO' 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@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-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | shuaishuaimovie 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/database/bean/search_history_bean.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | final String tableName = "search_history_table"; 4 | final String columnName = 'name'; 5 | 6 | class SearchHistoryBean { 7 | String name; 8 | 9 | Map toMap() { 10 | var map = { 11 | columnName: name, 12 | }; 13 | 14 | return map; 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /lib/database/bean/video_history_bean.dart: -------------------------------------------------------------------------------- 1 | final String tableName = "video_history_table"; 2 | final String columnPicUrl = 'pic_url'; 3 | final String columnVideoName = 'video_name'; 4 | final String columnVideoId = 'video_id'; 5 | final String columnCurrentPlayTime = "current_play_time"; 6 | final String columnTotalPlayTime = "total_play_time"; 7 | final String columnUpdateInfo = "update_info"; 8 | final String columnMilliseconds = "milliseconds"; 9 | final String columnVideoLevel = "video_level"; 10 | final String columnPlayUrlType = "play_url_type"; 11 | final String columnPlayUrlIndex = "play_url_index"; 12 | final String columnVideoUrl = "video_url"; 13 | final String columnVideoIsPositive = "video_ispositive"; 14 | 15 | class VideoHistoryBean { 16 | String picUrl; 17 | String videoName; 18 | String updateInfo; 19 | String videoLevel; 20 | String playUrlType; 21 | String videoUrl; 22 | String isPositive; 23 | int videoId; 24 | int currentPlayTime; 25 | int totalPlayTime; 26 | int milliseconds; 27 | int playUrlIndex; 28 | 29 | Map toMap() { 30 | var map = { 31 | columnVideoName: videoName, 32 | columnPicUrl: picUrl, 33 | columnVideoId: videoId, 34 | columnCurrentPlayTime: currentPlayTime, 35 | columnTotalPlayTime: totalPlayTime, 36 | columnUpdateInfo: updateInfo, 37 | columnMilliseconds: milliseconds, 38 | columnPlayUrlIndex: playUrlIndex, 39 | columnPlayUrlType: playUrlType, 40 | columnVideoLevel: videoLevel, 41 | columnVideoUrl: videoUrl, 42 | columnVideoIsPositive: isPositive, 43 | }; 44 | 45 | return map; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/database/sqf_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:sqflite/sqflite.dart'; 2 | import 'package:path/path.dart'; 3 | import 'bean/search_history_bean.dart' as searchHistoryTable; 4 | import 'bean/video_history_bean.dart' as videoHistoryTable; 5 | 6 | class SqfProvider { 7 | static Database db; 8 | static const int DB_VERSION = 1; 9 | 10 | static Future initSQF() async { 11 | String databasesPath = await getDatabasesPath(); 12 | // Database Path: /data/user/0/com.package.name/databases 13 | String path = join(databasesPath, 'shuaishuai_movie.db'); 14 | // Path: /data/user/0/com.example.shuaishuaimovie/databases/shuaishuai_movie.db 15 | db = await openDatabase( 16 | path, 17 | version: DB_VERSION, 18 | onCreate: (Database db, int version) async { 19 | // 表格创建等初始化操作, 因为该项目的表格需要的不多,所以就一次性创建 20 | await db.execute( 21 | 'CREATE TABLE ${searchHistoryTable.tableName} (id INTEGER PRIMARY KEY, ${searchHistoryTable.columnName} TEXT)'); 22 | await db.execute( 23 | 'CREATE TABLE ${videoHistoryTable.tableName} (id INTEGER PRIMARY KEY, ${videoHistoryTable.columnVideoName} TEXT, ${videoHistoryTable.columnPicUrl} TEXT, ${videoHistoryTable.columnUpdateInfo} TEXT, ${videoHistoryTable.columnVideoLevel} TEXT, ${videoHistoryTable.columnPlayUrlType} TEXT, ${videoHistoryTable.columnVideoUrl} TEXT, ${videoHistoryTable.columnVideoIsPositive} TEXT, ${videoHistoryTable.columnVideoId} INTEGER, ${videoHistoryTable.columnCurrentPlayTime} INTEGER, ${videoHistoryTable.columnTotalPlayTime} INTEGER, ${videoHistoryTable.columnMilliseconds} INTEGER, ${videoHistoryTable.columnPlayUrlIndex} INTEGER)') 24 | .catchError((error) { 25 | print(error); 26 | }); 27 | }, 28 | onUpgrade: (Database db, int oldVersion, int newVersion) async { 29 | // 数据库升级 30 | }, 31 | ); 32 | } 33 | 34 | // 获取数据库中所有的表 35 | static Future getTables() async { 36 | if (db == null) { 37 | return Future.value([]); 38 | } 39 | List tables = await db 40 | .rawQuery('SELECT name FROM sqlite_master WHERE type = "table"'); 41 | List targetList = []; 42 | tables.forEach((item) { 43 | targetList.add(item['name']); 44 | }); 45 | return targetList; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/generated/json/base/json_filed.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: non_constant_identifier_names 2 | // ignore_for_file: camel_case_types 3 | // ignore_for_file: prefer_single_quotes 4 | 5 | // This file is automatically generated. DO NOT EDIT, all your changes would be lost. 6 | 7 | class JSONField { 8 | //Specify the parse field name 9 | final String name; 10 | 11 | //Specify the time resolution format 12 | final String format; 13 | 14 | //Whether to participate in toJson 15 | final bool serialize; 16 | 17 | //Whether to participate in fromMap 18 | final bool deserialize; 19 | 20 | const JSONField({this.name, this.format, this.serialize, this.deserialize}); 21 | } 22 | -------------------------------------------------------------------------------- /lib/generated/json/common_item_bean_entity_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/models/common_item_bean_entity.dart'; 2 | 3 | commonItemBeanFromJson(CommonItemBean data, Map json) { 4 | if (json['VodID'] != null) { 5 | data.vodID = json['VodID']?.toInt(); 6 | } 7 | if (json['VodName'] != null) { 8 | data.vodName = json['VodName']?.toString(); 9 | } 10 | if (json['VodPic'] != null) { 11 | data.vodPic = json['VodPic']?.toString(); 12 | } 13 | if (json['VodYear'] != null) { 14 | data.vodYear = json['VodYear']?.toString(); 15 | } 16 | if (json['VodArea'] != null) { 17 | data.vodArea = json['VodArea']?.toString(); 18 | } 19 | if (json['VodRemarks'] != null) { 20 | data.vodRemarks = json['VodRemarks']?.toString(); 21 | } 22 | if (json['VodHits'] != null) { 23 | data.vodHits = json['VodHits']?.toInt(); 24 | } 25 | if (json['VodActor'] != null) { 26 | data.vodActor = json['VodActor']?.toString(); 27 | } 28 | if (json['VodDirector'] != null) { 29 | data.vodDirector = json['VodDirector']?.toString(); 30 | } 31 | if (json['VodTime'] != null) { 32 | data.vodTime = json['VodTime']?.toInt(); 33 | } 34 | if (json['VodTypeID'] != null) { 35 | data.vodTypeID = json['VodTypeID']?.toInt(); 36 | } 37 | if (json['VodClass'] != null) { 38 | data.vodClass = new CommonItemBeanVodClass().fromJson(json['VodClass']); 39 | } 40 | return data; 41 | } 42 | 43 | Map commonItemBeanToJson(CommonItemBean entity) { 44 | final Map data = new Map(); 45 | data['VodID'] = entity.vodID; 46 | data['VodName'] = entity.vodName; 47 | data['VodPic'] = entity.vodPic; 48 | data['VodYear'] = entity.vodYear; 49 | data['VodArea'] = entity.vodArea; 50 | data['VodRemarks'] = entity.vodRemarks; 51 | data['VodHits'] = entity.vodHits; 52 | data['VodActor'] = entity.vodActor; 53 | data['VodDirector'] = entity.vodDirector; 54 | data['VodTime'] = entity.vodTime; 55 | data['VodTypeID'] = entity.vodTypeID; 56 | if (entity.vodClass != null) { 57 | data['VodClass'] = entity.vodClass.toJson(); 58 | } 59 | return data; 60 | } 61 | 62 | commonItemBeanVodClassFromJson(CommonItemBeanVodClass data, Map json) { 63 | if (json['TypeID'] != null) { 64 | data.typeID = json['TypeID']?.toInt(); 65 | } 66 | if (json['TypeName'] != null) { 67 | data.typeName = json['TypeName']?.toString(); 68 | } 69 | return data; 70 | } 71 | 72 | Map commonItemBeanVodClassToJson(CommonItemBeanVodClass entity) { 73 | final Map data = new Map(); 74 | data['TypeID'] = entity.typeID; 75 | data['TypeName'] = entity.typeName; 76 | return data; 77 | } -------------------------------------------------------------------------------- /lib/generated/json/common_tab_item_bean_entity_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/models/common_item_bean_entity.dart'; 2 | import 'package:shuaishuaimovie/models/common_tab_item_bean_entity.dart'; 3 | 4 | commonTabItemBeanEntityFromJson(CommonTabItemBeanEntity data, Map json) { 5 | if (json['data'] != null) { 6 | data.data = new List(); 7 | (json['data'] as List).forEach((v) { 8 | data.data.add(new CommonTabItemBeanData().fromJson(v)); 9 | }); 10 | } 11 | if (json['status'] != null) { 12 | data.status = json['status']?.toInt(); 13 | } 14 | return data; 15 | } 16 | 17 | Map commonTabItemBeanEntityToJson(CommonTabItemBeanEntity entity) { 18 | final Map data = new Map(); 19 | if (entity.data != null) { 20 | data['data'] = entity.data.map((v) => v.toJson()).toList(); 21 | } 22 | data['status'] = entity.status; 23 | return data; 24 | } 25 | 26 | commonTabItemBeanDataFromJson(CommonTabItemBeanData data, Map json) { 27 | if (json['ID'] != null) { 28 | data.iD = json['ID']?.toInt(); 29 | } 30 | if (json['Name'] != null) { 31 | data.name = json['Name']?.toString(); 32 | } 33 | if (json['Items'] != null) { 34 | data.items = new List(); 35 | (json['Items'] as List).forEach((v) { 36 | data.items.add(new CommonItemBean().fromJson(v)); 37 | }); 38 | } 39 | return data; 40 | } 41 | 42 | Map commonTabItemBeanDataToJson(CommonTabItemBeanData entity) { 43 | final Map data = new Map(); 44 | data['ID'] = entity.iD; 45 | data['Name'] = entity.name; 46 | if (entity.items != null) { 47 | data['Items'] = entity.items.map((v) => v.toJson()).toList(); 48 | } 49 | return data; 50 | } 51 | -------------------------------------------------------------------------------- /lib/generated/json/condition_search_bean_entity_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/models/common_item_bean_entity.dart'; 2 | import 'package:shuaishuaimovie/models/condition_search_bean_entity.dart'; 3 | import 'package:shuaishuaimovie/models/hot_update_bean_entity.dart'; 4 | 5 | conditionSearchBeanEntityFromJson(ConditionSearchBeanEntity data, Map json) { 6 | if (json['data'] != null) { 7 | data.data = new List(); 8 | (json['data'] as List).forEach((v) { 9 | data.data.add(new CommonItemBean().fromJson(v)); 10 | }); 11 | } 12 | 13 | if (json['status'] != null) { 14 | data.status = json['status']?.toInt(); 15 | } 16 | 17 | if (json['qty'] != null) { 18 | data.qty = json['qty']?.toInt(); 19 | } 20 | 21 | return data; 22 | } 23 | 24 | Map conditionSearchBeanEntityToJson(ConditionSearchBeanEntity entity) { 25 | final Map data = new Map(); 26 | if (entity.data != null) { 27 | data['data'] = entity.data.map((v) => v.toJson()).toList(); 28 | } 29 | 30 | data['status'] = entity.status; 31 | 32 | data['qty'] = entity.qty; 33 | 34 | return data; 35 | } -------------------------------------------------------------------------------- /lib/generated/json/home_list_bean_entity_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/models/common_item_bean_entity.dart'; 2 | import 'package:shuaishuaimovie/models/home_list_bean_entity.dart'; 3 | 4 | homeListBeanEntityFromJson(HomeListBeanEntity data, Map json) { 5 | if (json['c'] != null) { 6 | data.c = new List(); 7 | (json['c'] as List).forEach((v) { 8 | data.c.add(new CommonItemBean().fromJson(v)); 9 | }); 10 | } 11 | if (json['h'] != null) { 12 | data.h = new List(); 13 | (json['h'] as List).forEach((v) { 14 | data.h.add(new CommonItemBean().fromJson(v)); 15 | }); 16 | } 17 | if (json['m'] != null) { 18 | data.m = new List(); 19 | (json['m'] as List).forEach((v) { 20 | data.m.add(new CommonItemBean().fromJson(v)); 21 | }); 22 | } 23 | if (json['s'] != null) { 24 | data.s = new List(); 25 | (json['s'] as List).forEach((v) { 26 | data.s.add(new CommonItemBean().fromJson(v)); 27 | }); 28 | } 29 | if (json['t'] != null) { 30 | data.t = new List(); 31 | (json['t'] as List).forEach((v) { 32 | data.t.add(new CommonItemBean().fromJson(v)); 33 | }); 34 | } 35 | if (json['lastQty'] != null) { 36 | data.lastQty = json['lastQty']?.toInt(); 37 | } 38 | if (json['notice'] != null) { 39 | data.notice = json['notice']?.toString(); 40 | } 41 | if (json['status'] != null) { 42 | data.status = json['status']?.toInt(); 43 | } 44 | return data; 45 | } 46 | 47 | Map homeListBeanEntityToJson(HomeListBeanEntity entity) { 48 | final Map data = new Map(); 49 | if (entity.c != null) { 50 | data['c'] = entity.c.map((v) => v.toJson()).toList(); 51 | } 52 | if (entity.h != null) { 53 | data['h'] = entity.h.map((v) => v.toJson()).toList(); 54 | } 55 | if (entity.m != null) { 56 | data['m'] = entity.m.map((v) => v.toJson()).toList(); 57 | } 58 | if (entity.s != null) { 59 | data['s'] = entity.s.map((v) => v.toJson()).toList(); 60 | } 61 | if (entity.t != null) { 62 | data['t'] = entity.t.map((v) => v.toJson()).toList(); 63 | } 64 | data['lastQty'] = entity.lastQty; 65 | data['notice'] = entity.notice; 66 | data['status'] = entity.status; 67 | return data; 68 | } -------------------------------------------------------------------------------- /lib/generated/json/hot_rank_bean_entity_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/models/common_item_bean_entity.dart'; 2 | import 'package:shuaishuaimovie/models/hot_rank_bean_entity.dart'; 3 | 4 | hotRankBeanEntityFromJson(HotRankBeanEntity data, Map json) { 5 | if (json['cartoon'] != null) { 6 | data.cartoon = new List(); 7 | (json['cartoon'] as List).forEach((v) { 8 | data.cartoon.add(new CommonItemBean().fromJson(v)); 9 | }); 10 | } 11 | 12 | if (json['movie'] != null) { 13 | data.movie = new List(); 14 | (json['movie'] as List).forEach((v) { 15 | data.movie.add(new CommonItemBean().fromJson(v)); 16 | }); 17 | } 18 | 19 | if (json['show'] != null) { 20 | data.show = new List(); 21 | (json['show'] as List).forEach((v) { 22 | data.show.add(new CommonItemBean().fromJson(v)); 23 | }); 24 | } 25 | 26 | if (json['teleplay'] != null) { 27 | data.teleplay = new List(); 28 | (json['teleplay'] as List).forEach((v) { 29 | data.teleplay.add(new CommonItemBean().fromJson(v)); 30 | }); 31 | } 32 | 33 | if (json['status'] != null) { 34 | data.status = json['status']?.toInt(); 35 | } 36 | return data; 37 | } 38 | 39 | Map hotRankBeanEntityToJson(HotRankBeanEntity entity) { 40 | final Map data = new Map(); 41 | if (entity.cartoon != null) { 42 | data['cartoon'] = entity.cartoon.map((v) => v.toJson()).toList(); 43 | } 44 | 45 | if (entity.movie != null) { 46 | data['movie'] = entity.movie.map((v) => v.toJson()).toList(); 47 | } 48 | 49 | if (entity.show != null) { 50 | data['show'] = entity.show.map((v) => v.toJson()).toList(); 51 | } 52 | 53 | if (entity.teleplay != null) { 54 | data['teleplay'] = entity.teleplay.map((v) => v.toJson()).toList(); 55 | } 56 | data['status'] = entity.status; 57 | return data; 58 | } 59 | -------------------------------------------------------------------------------- /lib/generated/json/hot_update_bean_entity_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/models/common_item_bean_entity.dart'; 2 | import 'package:shuaishuaimovie/models/hot_update_bean_entity.dart'; 3 | 4 | hotUpdateBeanEntityFromJson(HotUpdateBeanEntity data, Map json) { 5 | if (json['data'] != null) { 6 | data.data = new List(); 7 | (json['data'] as List).forEach((v) { 8 | data.data.add(new CommonItemBean().fromJson(v)); 9 | }); 10 | } 11 | 12 | if (json['status'] != null) { 13 | data.status = json['status']?.toInt(); 14 | } 15 | 16 | return data; 17 | } 18 | 19 | Map hotUpdateBeanEntityToJson(HotUpdateBeanEntity entity) { 20 | final Map data = new Map(); 21 | if (entity.data != null) { 22 | data['data'] = entity.data.map((v) => v.toJson()).toList(); 23 | } 24 | 25 | data['status'] = entity.status; 26 | 27 | return data; 28 | } -------------------------------------------------------------------------------- /lib/generated/json/select_condition_bean_entity_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/models/select_condition_bean_entity.dart'; 2 | 3 | selectConditionBeanEntityFromJson(SelectConditionBeanEntity data, Map json) { 4 | if (json['areas'] != null) { 5 | data.areas = new SelectConditionCommonBean().fromJson(json['areas']); 6 | } 7 | if (json['classes'] != null) { 8 | data.classes = new SelectConditionCommonBean().fromJson(json['classes']); 9 | } 10 | if (json['languages'] != null) { 11 | data.languages = new List(); 12 | (json['languages'] as List).forEach((v) { 13 | data.languages.add(new SelectConditionInnerBean().fromJson(v)); 14 | }); 15 | } 16 | if (json['status'] != null) { 17 | data.status = json['status']?.toInt(); 18 | } 19 | if (json['types'] != null) { 20 | data.types = new SelectConditionCommonBean().fromJson(json['types']); 21 | } 22 | return data; 23 | } 24 | 25 | Map selectConditionBeanEntityToJson(SelectConditionBeanEntity entity) { 26 | final Map data = new Map(); 27 | if (entity.areas != null) { 28 | data['areas'] = entity.areas.toJson(); 29 | } 30 | if (entity.classes != null) { 31 | data['classes'] = entity.classes.toJson(); 32 | } 33 | if (entity.languages != null) { 34 | data['languages'] = entity.languages.map((v) => v.toJson()).toList(); 35 | } 36 | data['status'] = entity.status; 37 | if (entity.types != null) { 38 | data['types'] = entity.types.toJson(); 39 | } 40 | return data; 41 | } 42 | 43 | selectConditionCommonBeanFromJson(SelectConditionCommonBean data, Map json) { 44 | if (json['电影'] != null) { 45 | data.movie = new List(); 46 | (json['电影'] as List).forEach((v) { 47 | data.movie.add(new SelectConditionInnerBean().fromJson(v)); 48 | }); 49 | } 50 | 51 | if (json['动漫'] != null) { 52 | data.cartoon = new List(); 53 | (json['动漫'] as List).forEach((v) { 54 | data.cartoon.add(new SelectConditionInnerBean().fromJson(v)); 55 | }); 56 | } 57 | 58 | if (json['综艺'] != null) { 59 | data.show = new List(); 60 | (json['综艺'] as List).forEach((v) { 61 | data.show.add(new SelectConditionInnerBean().fromJson(v)); 62 | }); 63 | } 64 | 65 | if (json['连续剧'] != null) { 66 | data.teleplay = new List(); 67 | (json['连续剧'] as List).forEach((v) { 68 | data.teleplay.add(new SelectConditionInnerBean().fromJson(v)); 69 | }); 70 | } 71 | return data; 72 | } 73 | 74 | Map selectConditionCommonBeanToJson(SelectConditionCommonBean entity) { 75 | final Map data = new Map(); 76 | if (entity.movie != null) { 77 | data['电影'] = entity.movie.map((v) => v.toJson()).toList(); 78 | } 79 | 80 | if (entity.cartoon != null) { 81 | data['动漫'] = entity.cartoon.map((v) => v.toJson()).toList(); 82 | } 83 | 84 | if (entity.show != null) { 85 | data['综艺'] = entity.show.map((v) => v.toJson()).toList(); 86 | } 87 | 88 | if (entity.teleplay != null) { 89 | data['连续剧'] = entity.teleplay.map((v) => v.toJson()).toList(); 90 | } 91 | 92 | return data; 93 | } 94 | 95 | selectConditionInnerBeanFromJson(SelectConditionInnerBean data, Map json) { 96 | if (json['name'] != null) { 97 | data.name = json['name']; 98 | } 99 | 100 | if (json['id'] != null) { 101 | data.id = json['id']; 102 | } 103 | return data; 104 | } 105 | 106 | Map selectConditionInnerBeanToJson(SelectConditionInnerBean entity) { 107 | final Map data = new Map(); 108 | data['name'] = entity.name; 109 | data['id'] = entity.id; 110 | return data; 111 | } 112 | 113 | -------------------------------------------------------------------------------- /lib/generated/json/txt_auto_search_bean_entity_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/models/common_item_bean_entity.dart'; 2 | import 'package:shuaishuaimovie/models/txt_auto_search_bean_entity.dart'; 3 | 4 | txtAutoSearchBeanEntityFromJson(TxtAutoSearchBeanEntity data, Map json) { 5 | if (json['data'] != null) { 6 | data.data = new List(); 7 | (json['data'] as List).forEach((v) { 8 | data.data.add(new CommonItemBean().fromJson(v)); 9 | }); 10 | } 11 | 12 | if (json['status'] != null) { 13 | data.status = json['status']?.toInt(); 14 | } 15 | 16 | return data; 17 | } 18 | 19 | Map txtAutoSearchBeanEntityToJson(TxtAutoSearchBeanEntity entity) { 20 | final Map data = new Map(); 21 | if (entity.data != null) { 22 | data['data'] = entity.data.map((v) => v.toJson()).toList(); 23 | } 24 | 25 | data['status'] = entity.status; 26 | 27 | return data; 28 | } -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:provider/provider.dart'; 4 | import 'package:shuaishuaimovie/database/sqf_provider.dart'; 5 | import 'package:shuaishuaimovie/res/app_color.dart'; 6 | import 'package:shuaishuaimovie/ui/pages/splash_page.dart'; 7 | import 'package:shuaishuaimovie/viewmodels/app_theme_model.dart'; 8 | import 'package:shuaishuaimovie/provider/provider_setup.dart'; 9 | import 'package:shuaishuaimovie/routes/routes.dart'; 10 | import 'package:shuaishuaimovie/shuai_movie.dart'; 11 | 12 | import 'sharepreference/share_preference.dart'; 13 | import 'utils/system/system_chrome.dart'; 14 | 15 | void main() { 16 | runApp(MyApp()); 17 | } 18 | 19 | class MyApp extends StatefulWidget { 20 | @override 21 | _MyAppState createState() => _MyAppState(); 22 | } 23 | 24 | class _MyAppState extends State { 25 | _MyAppState() { 26 | final router = Router(); 27 | Routes.configureRoutes(router); 28 | Application.router = router; 29 | 30 | //初始化数据库 31 | _initDB(); 32 | _initSharePreference(); 33 | } 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | return MultiProvider( 38 | providers: providers, 39 | child: Builder( 40 | builder: (context) => MaterialApp( 41 | title: "帅帅影视", 42 | color: AppColor.black, 43 | theme: ThemeData( 44 | accentColor: AppColor.icon_yellow, 45 | scaffoldBackgroundColor: AppColor.white, 46 | appBarTheme: AppBarTheme( 47 | color: AppColor.black, 48 | ), 49 | iconTheme: IconThemeData( 50 | color: context.watch().iconThemeColor, 51 | ), 52 | ), 53 | debugShowCheckedModeBanner: false, 54 | onGenerateRoute: Application.router.generator, 55 | home: AnnotatedRegion( 56 | value: MovieSystemChrome.statusDark, child: SplashPage()), 57 | ), 58 | ), 59 | ); 60 | } 61 | 62 | void _initDB() { 63 | SqfProvider.initSQF(); 64 | } 65 | 66 | void _initSharePreference() { 67 | //没有找到设置默认值的地方,所以通过判断返回是空的话设置默认值。 68 | MovieSharePreference.getAutoPlayValue().then((value) { 69 | if (value == null) MovieSharePreference.saveAutoPlayValue(true); 70 | }); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/models/common_item_bean_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/generated/json/base/json_convert_content.dart'; 2 | import 'package:shuaishuaimovie/generated/json/base/json_filed.dart'; 3 | 4 | class CommonItemBean with JsonConvert { 5 | @JSONField(name: "VodID") 6 | int vodID; 7 | @JSONField(name: "VodName") 8 | String vodName; 9 | @JSONField(name: "VodPic") 10 | String vodPic; 11 | @JSONField(name: "VodYear") 12 | String vodYear; 13 | @JSONField(name: "VodArea") 14 | String vodArea; 15 | @JSONField(name: "VodRemarks") 16 | String vodRemarks; 17 | @JSONField(name: "VodHits") 18 | int vodHits; 19 | @JSONField(name: "VodActor") 20 | String vodActor; 21 | @JSONField(name: "VodDirector") 22 | String vodDirector; 23 | @JSONField(name: "VodTime") 24 | int vodTime; 25 | @JSONField(name: "VodTypeID") 26 | int vodTypeID; 27 | @JSONField(name: "VodClass") 28 | CommonItemBeanVodClass vodClass; 29 | } 30 | 31 | 32 | class CommonItemBeanVodClass with JsonConvert { 33 | @JSONField(name: "TypeID") 34 | int typeID; 35 | @JSONField(name: "TypeName") 36 | String typeName; 37 | } -------------------------------------------------------------------------------- /lib/models/common_tab_item_bean_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/generated/json/base/json_convert_content.dart'; 2 | import 'package:shuaishuaimovie/generated/json/base/json_filed.dart'; 3 | import 'package:shuaishuaimovie/models/common_item_bean_entity.dart'; 4 | 5 | class CommonTabItemBeanEntity with JsonConvert { 6 | List data; 7 | int status; 8 | } 9 | 10 | class CommonTabItemBeanData with JsonConvert { 11 | @JSONField(name: "ID") 12 | int iD; 13 | @JSONField(name: "Name") 14 | String name; 15 | @JSONField(name: "Items") 16 | List items; 17 | } 18 | 19 | -------------------------------------------------------------------------------- /lib/models/condition_search_bean_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/generated/json/base/json_convert_content.dart'; 2 | 3 | import 'common_item_bean_entity.dart'; 4 | 5 | class ConditionSearchBeanEntity with JsonConvert { 6 | List data; 7 | int status; 8 | int qty; 9 | 10 | } -------------------------------------------------------------------------------- /lib/models/home_detail_bean_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/generated/json/base/json_convert_content.dart'; 2 | import 'package:shuaishuaimovie/generated/json/base/json_filed.dart'; 3 | import 'package:shuaishuaimovie/models/common_item_bean_entity.dart'; 4 | 5 | class HomeDetailBeanEntity with JsonConvert { 6 | int count; 7 | String domain; 8 | List rand; 9 | List relate; 10 | String shareLink; 11 | int status; 12 | HomeDetailBeanVod vod; 13 | } 14 | 15 | 16 | class HomeDetailBeanVod with JsonConvert { 17 | @JSONField(name: "VodID") 18 | int vodID; 19 | @JSONField(name: "VodLevel") 20 | int vodLevel; 21 | @JSONField(name: "TypeID") 22 | int typeID; 23 | @JSONField(name: "TypeID1") 24 | int typeID1; 25 | @JSONField(name: "GroupID") 26 | int groupID; 27 | @JSONField(name: "VodUp") 28 | int vodUp; 29 | @JSONField(name: "VodName") 30 | String vodName; 31 | @JSONField(name: "VodPic") 32 | String vodPic; 33 | @JSONField(name: "VodActor") 34 | String vodActor; 35 | @JSONField(name: "VodDirector") 36 | String vodDirector; 37 | @JSONField(name: "VodBlurb") 38 | String vodBlurb; 39 | @JSONField(name: "VodContent") 40 | String vodContent; 41 | @JSONField(name: "VodYear") 42 | String vodYear; 43 | @JSONField(name: "VodScore") 44 | int vodScore; 45 | @JSONField(name: "VodScoreAll") 46 | int vodScoreAll; 47 | @JSONField(name: "VodHits") 48 | int vodHits; 49 | @JSONField(name: "VodScoreNum") 50 | int vodScoreNum; 51 | @JSONField(name: "VodArea") 52 | String vodArea; 53 | @JSONField(name: "VodRemarks") 54 | String vodRemarks; 55 | @JSONField(name: "Vps") 56 | String vps; 57 | @JSONField(name: "Vpf") 58 | String vpf; 59 | @JSONField(name: "Vpl") 60 | String vpl; 61 | @JSONField(name: "VodHitsWeek") 62 | String vodHitsWeek; 63 | @JSONField(name: "VodTime") 64 | int vodTime; 65 | @JSONField(name: "VodPlayServer") 66 | List vodPlayServer; 67 | @JSONField(name: "VodPlayUrls") 68 | HomeDetailBeanVodVodPlayUrls vodPlayUrls; 69 | @JSONField(name: "VodClass") 70 | HomeDetailBeanVodVodClass vodClass; 71 | } 72 | 73 | class HomeDetailBeanVodVodPlayServer with JsonConvert { 74 | @JSONField(name: "Status") 75 | String status; 76 | @JSONField(name: "From") 77 | String from; 78 | @JSONField(name: "Show") 79 | String xShow; 80 | @JSONField(name: "Des") 81 | String des; 82 | @JSONField(name: "Ps") 83 | String ps; 84 | @JSONField(name: "Target") 85 | String target; 86 | @JSONField(name: "Parse") 87 | String parse; 88 | @JSONField(name: "Sort") 89 | String sort; 90 | @JSONField(name: "Tip") 91 | String tip; 92 | @JSONField(name: "ID") 93 | String iD; 94 | } 95 | 96 | class HomeDetailBeanVodVodPlayUrls with JsonConvert { 97 | List bjm3u8; 98 | List kuyun; 99 | List youku; 100 | List zuidam3u8; 101 | } 102 | 103 | class HomeDetailBeanVodVodClass with JsonConvert { 104 | @JSONField(name: "TypeID") 105 | int typeID; 106 | @JSONField(name: "TypeName") 107 | String typeName; 108 | } 109 | -------------------------------------------------------------------------------- /lib/models/home_list_bean_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/generated/json/base/json_convert_content.dart'; 2 | 3 | import 'common_item_bean_entity.dart'; 4 | 5 | class HomeListBeanEntity with JsonConvert { 6 | List c; 7 | List h; 8 | List m; 9 | List s; 10 | List t; 11 | int lastQty; 12 | String notice; 13 | int status; 14 | } 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /lib/models/hot_rank_bean_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/generated/json/base/json_convert_content.dart'; 2 | import 'package:shuaishuaimovie/generated/json/base/json_filed.dart'; 3 | import 'package:shuaishuaimovie/models/common_item_bean_entity.dart'; 4 | 5 | class HotRankBeanEntity with JsonConvert { 6 | List cartoon; 7 | List movie; 8 | List show; 9 | List teleplay; 10 | int status; 11 | } 12 | 13 | -------------------------------------------------------------------------------- /lib/models/hot_update_bean_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/generated/json/base/json_convert_content.dart'; 2 | 3 | import 'common_item_bean_entity.dart'; 4 | 5 | class HotUpdateBeanEntity with JsonConvert { 6 | List data; 7 | int status; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /lib/models/select_condition_bean_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/generated/json/base/json_convert_content.dart'; 2 | import 'package:shuaishuaimovie/generated/json/base/json_filed.dart'; 3 | 4 | class SelectConditionBeanEntity with JsonConvert { 5 | SelectConditionCommonBean areas; 6 | SelectConditionCommonBean classes; 7 | List languages; 8 | int status; 9 | SelectConditionCommonBean types; 10 | } 11 | 12 | class SelectConditionCommonBean with JsonConvert { 13 | @JSONField(name: "动漫") 14 | List cartoon; 15 | @JSONField(name: "电影") 16 | List movie; 17 | @JSONField(name: "综艺") 18 | List show; 19 | @JSONField(name: "连续剧") 20 | List teleplay; 21 | } 22 | 23 | class SelectConditionInnerBean with JsonConvert { 24 | String name; 25 | int id; 26 | } 27 | 28 | 29 | -------------------------------------------------------------------------------- /lib/models/txt_auto_search_bean_entity.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/generated/json/base/json_convert_content.dart'; 2 | import 'package:shuaishuaimovie/models/common_item_bean_entity.dart'; 3 | 4 | class TxtAutoSearchBeanEntity with JsonConvert { 5 | List data; 6 | int status; 7 | } 8 | 9 | 10 | -------------------------------------------------------------------------------- /lib/net/base_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'base_result.dart'; 3 | 4 | /// 父类repository对象 5 | class BaseRepository { 6 | Dio _dio; 7 | 8 | BaseRepository(); 9 | 10 | void setDio(Dio dio) { 11 | this._dio = dio; 12 | } 13 | 14 | /// 通用get请求 15 | Future get(String path, {Map params}) async { 16 | try { 17 | Response response = await _dio.get(path, queryParameters: params); 18 | return BaseResult( 19 | data: response.data, code: 0); 20 | } catch (e) { 21 | print('异常信息:' + e.toString()); 22 | return BaseResult(message: e.toString(), code: 500); 23 | } 24 | } 25 | 26 | /// 通用post请求 27 | Future post(String path, Map params) async { 28 | try { 29 | Response response = await _dio.post(path, data: params); 30 | return BaseResult( 31 | data: response.data, code: response.data.status); 32 | } catch (e) { 33 | print('异常信息:' + e.toString()); 34 | return BaseResult(message: e.toString(), code: 500); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/net/base_result.dart: -------------------------------------------------------------------------------- 1 | /// 通用返回类 2 | class BaseResult { 3 | /// 返回数据bean 4 | dynamic data; 5 | /// 状态码 6 | int code; 7 | /// 返回信息 8 | String message; 9 | BaseResult({this.data, this.code, this.message}); 10 | } 11 | -------------------------------------------------------------------------------- /lib/net/request_const.dart: -------------------------------------------------------------------------------- 1 | class RequestConstApi{ 2 | static const SERVICE_API = 'https://vip.88-spa.com:8443/v1/'; 3 | static const HOME_API = SERVICE_API + "home-list"; 4 | static const HOME_DETAIL_API = SERVICE_API + "vod-details"; 5 | static const HOT_UPDATE_API = SERVICE_API + "map"; 6 | static const RANK_WEEK_API = SERVICE_API + "rank-list?cate=vod_hits_week"; 7 | static const RANK_MONTH_API = SERVICE_API + "rank-list?cate=vod_hits_month"; 8 | static const RANK_TOTAL_API = SERVICE_API + "rank-list?cate=vod_hits"; 9 | static const MOVIE_API = SERVICE_API + "list-by-type?type=1"; 10 | static const TELEPLAY_API = SERVICE_API + "list-by-type?type=2"; 11 | static const SHOW_API = SERVICE_API + "list-by-type?type=3"; 12 | static const CARTOON_API = SERVICE_API + "list-by-type?type=4"; 13 | static const SELECT_CONDITION_API = SERVICE_API + "vod-list-options"; 14 | static const CONDITION_SEARCH_API = SERVICE_API + "vod-list?"; 15 | static const TXT_AUTO_SEARCH_API = SERVICE_API + "auto-search?"; 16 | static const TXT_NORMAL_SEARCH_API = SERVICE_API + "search?"; 17 | } -------------------------------------------------------------------------------- /lib/net/request_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'request_const.dart'; 3 | 4 | class HttpHelper { 5 | Dio _dio; 6 | 7 | factory HttpHelper() => _getProvider(); 8 | 9 | HttpHelper._internal(); 10 | 11 | static HttpHelper get instance => _getProvider(); 12 | static HttpHelper _httpHelper; 13 | 14 | static HttpHelper _getProvider() { 15 | if (_httpHelper == null) { 16 | _httpHelper = new HttpHelper._internal(); 17 | } 18 | return _httpHelper; 19 | } 20 | 21 | ///创建dio网络请求对象 22 | Dio _createDio() { 23 | final options = BaseOptions( 24 | baseUrl: RequestConstApi.SERVICE_API, 25 | connectTimeout: 30 * 1000, 26 | receiveTimeout: 30 * 1000); 27 | final dio = new Dio(options); 28 | // 添加通用拦截器 29 | // for (Interceptor interceptor in getInterceptors()) { 30 | // dio.interceptors.add(interceptor); 31 | // } 32 | return dio; 33 | } 34 | 35 | // 子类可以复写该方法 自定义添加拦截器 36 | List getInterceptors() { 37 | 38 | } 39 | 40 | Dio getDio() { 41 | if (_dio == null) { 42 | _dio = _createDio(); 43 | } 44 | return _dio; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/provider/provider_proxy_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | 4 | /// 负责创建viewModel对象和初始化数据加载 5 | class ProviderProxyWidget 6 | extends StatefulWidget { 7 | /// viewModel 8 | final T1 model; 9 | final ValueWidgetBuilder builder; 10 | final Widget child; 11 | final ProxyProviderBuilder proxyUpdate; 12 | 13 | /// 初始化数据 14 | final Function(T1 model) initData; 15 | 16 | @override 17 | State createState() => _ProviderProxyWidgetState(); 18 | 19 | ProviderProxyWidget( 20 | {@required this.model, 21 | @required this.builder, 22 | @required this.proxyUpdate, 23 | this.child, 24 | this.initData}); 25 | } 26 | 27 | class _ProviderProxyWidgetState extends State> { 29 | T1 model; 30 | 31 | @override 32 | void initState() { 33 | model = widget.model; 34 | super.initState(); 35 | WidgetsBinding.instance.addPostFrameCallback((timeStamp) { 36 | widget.initData?.call(model); 37 | }); 38 | } 39 | 40 | @override 41 | void dispose() { 42 | model.dispose(); 43 | super.dispose(); 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | return ChangeNotifierProxyProvider( 49 | create: (context) => widget.model, 50 | update: widget.proxyUpdate, 51 | child: Consumer( 52 | builder: widget.builder, 53 | child: widget.child, 54 | ), 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/provider/provider_setup.dart: -------------------------------------------------------------------------------- 1 | import 'package:provider/provider.dart'; 2 | import 'package:provider/single_child_widget.dart'; 3 | import 'package:shuaishuaimovie/viewmodels/app_theme_model.dart'; 4 | 5 | List providers = [ 6 | ...independentServices, 7 | ...dependentServices, 8 | ]; 9 | 10 | List independentServices = [ 11 | ChangeNotifierProvider(create: (_) => AppTheme()), 12 | ]; 13 | 14 | List dependentServices = [ 15 | //这里使用ProxyProvider来定义需要依赖其他Provider的服务 16 | ]; -------------------------------------------------------------------------------- /lib/provider/provider_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | 4 | /// 负责创建viewModel对象和初始化数据加载 5 | class ProviderWidget 6 | extends StatefulWidget { 7 | /// viewModel 8 | final T model; 9 | final ValueWidgetBuilder builder; 10 | final Widget child; 11 | 12 | /// 初始化数据 13 | final Function(T model) initData; 14 | 15 | @override 16 | State createState() => _ProviderWidgetState(); 17 | 18 | ProviderWidget( 19 | {@required this.model, 20 | @required this.builder, 21 | this.child, 22 | this.initData}); 23 | } 24 | 25 | class _ProviderWidgetState 26 | extends State> { 27 | T model; 28 | 29 | @override 30 | void initState() { 31 | model = widget.model; 32 | widget.initData?.call(model); 33 | super.initState(); 34 | } 35 | 36 | @override 37 | void dispose() { 38 | model.dispose(); 39 | super.dispose(); 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return ChangeNotifierProvider.value( 45 | value: model, 46 | child: Consumer( 47 | builder: widget.builder, 48 | child: widget.child, 49 | ), 50 | ); 51 | } 52 | } 53 | 54 | class ProviderWidget2 55 | extends StatefulWidget { 56 | /// viewModel 57 | final A modelA; 58 | final B modelB; 59 | final Widget Function(BuildContext context, A model1, B model2, Widget child) builder; 60 | final Widget child; 61 | 62 | /// 初始化数据 63 | final Future Function(A modelA, B modelB) initData; 64 | 65 | @override 66 | State createState() => _ProviderWidget2State(); 67 | 68 | ProviderWidget2( 69 | {@required this.modelA, 70 | @required this.modelB, 71 | @required this.builder, 72 | this.child, 73 | this.initData}); 74 | } 75 | 76 | class _ProviderWidget2State 77 | extends State> { 78 | A modelA; 79 | B modelB; 80 | 81 | @override 82 | void initState() { 83 | modelA = widget.modelA; 84 | modelB = widget.modelB; 85 | widget.initData?.call(modelA, modelB); 86 | super.initState(); 87 | } 88 | 89 | @override 90 | void dispose() { 91 | modelA.dispose(); 92 | modelB.dispose(); 93 | super.dispose(); 94 | } 95 | 96 | @override 97 | Widget build(BuildContext context) { 98 | return MultiProvider( 99 | providers: [ 100 | ChangeNotifierProvider.value(value: modelA), 101 | ChangeNotifierProvider.value(value: modelB), 102 | ], 103 | child: Consumer2( 104 | builder: widget.builder, 105 | child: widget.child, 106 | ), 107 | ); 108 | } 109 | } -------------------------------------------------------------------------------- /lib/provider/view_state.dart: -------------------------------------------------------------------------------- 1 | /// 状态枚举类 2 | enum ViewState { 3 | /// 加载中 4 | loading, 5 | 6 | /// 数据返回成功 7 | success, 8 | 9 | /// 空数据 10 | empty, 11 | 12 | /// 数据返回失败 13 | error, 14 | 15 | ///no net 16 | noNetwork, 17 | } 18 | 19 | class ViewStateError { 20 | Error error; 21 | String message; 22 | 23 | ViewStateError({this.error, this.message}) { 24 | this.message ??= '服务器异常'; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/r.g.dart: -------------------------------------------------------------------------------- 1 | // IT IS GENERATED BY FLR - DO NOT MODIFY BY HAND 2 | // YOU CAN GET MORE DETAILS ABOUT FLR FROM: 3 | // - https://github.com/Fly-Mix/flr-cli 4 | // - https://github.com/Fly-Mix/flr-vscode-extension 5 | // - https://github.com/Fly-Mix/flr-as-plugin 6 | // 7 | 8 | // ignore: unused_import 9 | import 'package:flutter/widgets.dart'; 10 | // ignore: unused_import 11 | import 'package:flutter/services.dart' show rootBundle; 12 | // ignore: unused_import 13 | import 'package:path/path.dart' as path; 14 | // ignore: unused_import 15 | import 'package:flutter_svg/flutter_svg.dart'; 16 | // ignore: unused_import 17 | import 'package:r_dart_library/asset_svg.dart'; 18 | 19 | /// This `R` class is generated and contains references to static asset resources. 20 | class R { 21 | /// package name: shuaishuaimovie 22 | static const package = "shuaishuaimovie"; 23 | 24 | /// This `R.image` struct is generated, and contains static references to static non-svg type image asset resources. 25 | static const image = _R_Image(); 26 | 27 | /// This `R.svg` struct is generated, and contains static references to static svg type image asset resources. 28 | static const svg = _R_Svg(); 29 | 30 | /// This `R.text` struct is generated, and contains static references to static text asset resources. 31 | static const text = _R_Text(); 32 | 33 | /// This `R.fontFamily` struct is generated, and contains static references to static font asset resources. 34 | static const fontFamily = _R_FontFamily(); 35 | } 36 | /// Asset resource’s metadata class. 37 | /// For example, here is the metadata of `packages/flutter_demo/assets/images/example.png` asset: 38 | /// - packageName:flutter_demo 39 | /// - assetName:assets/images/example.png 40 | /// - fileDirname:assets/images 41 | /// - fileBasename:example.png 42 | /// - fileBasenameNoExtension:example 43 | /// - fileExtname:.png 44 | class AssetResource { 45 | /// Creates an object to hold the asset resource’s metadata. 46 | const AssetResource(this.assetName, {this.packageName}) : assert(assetName != null); 47 | 48 | /// The name of the main asset from the set of asset resources to choose from. 49 | final String assetName; 50 | 51 | /// The name of the package from which the asset resource is included. 52 | final String packageName; 53 | 54 | /// The name used to generate the key to obtain the asset resource. For local assets 55 | /// this is [assetName], and for assets from packages the [assetName] is 56 | /// prefixed 'packages//'. 57 | String get keyName => packageName == null ? assetName : "packages/$packageName/$assetName"; 58 | 59 | /// The file basename of the asset resource. 60 | String get fileBasename { 61 | final basename = path.basename(assetName); 62 | return basename; 63 | } 64 | 65 | /// The no extension file basename of the asset resource. 66 | String get fileBasenameNoExtension { 67 | final basenameWithoutExtension = path.basenameWithoutExtension(assetName); 68 | return basenameWithoutExtension; 69 | } 70 | 71 | /// The file extension name of the asset resource. 72 | String get fileExtname { 73 | final extension = path.extension(assetName); 74 | return extension; 75 | } 76 | 77 | /// The directory path name of the asset resource. 78 | String get fileDirname { 79 | var dirname = assetName; 80 | if (packageName != null) { 81 | final packageStr = "packages/$packageName/"; 82 | dirname = dirname.replaceAll(packageStr, ""); 83 | } 84 | final filenameStr = "$fileBasename/"; 85 | dirname = dirname.replaceAll(filenameStr, ""); 86 | return dirname; 87 | } 88 | } 89 | // ignore: camel_case_types 90 | class _R_Image_AssetResource { 91 | const _R_Image_AssetResource(); 92 | 93 | } 94 | // ignore: camel_case_types 95 | class _R_Svg_AssetResource { 96 | const _R_Svg_AssetResource(); 97 | 98 | } 99 | // ignore: camel_case_types 100 | class _R_Text_AssetResource { 101 | const _R_Text_AssetResource(); 102 | 103 | } 104 | /// This `_R_Image` class is generated and contains references to static non-svg type image asset resources. 105 | // ignore: camel_case_types 106 | class _R_Image { 107 | const _R_Image(); 108 | 109 | final asset = const _R_Image_AssetResource(); 110 | 111 | } 112 | /// This `_R_Svg` class is generated and contains references to static svg type image asset resources. 113 | // ignore: camel_case_types 114 | class _R_Svg { 115 | const _R_Svg(); 116 | 117 | final asset = const _R_Svg_AssetResource(); 118 | 119 | } 120 | /// This `_R_Text` class is generated and contains references to static text asset resources. 121 | // ignore: camel_case_types 122 | class _R_Text { 123 | const _R_Text(); 124 | 125 | final asset = const _R_Text_AssetResource(); 126 | 127 | } 128 | /// This `_R_FontFamily` class is generated and contains references to static font asset resources. 129 | // ignore: camel_case_types 130 | class _R_FontFamily { 131 | const _R_FontFamily(); 132 | 133 | } -------------------------------------------------------------------------------- /lib/res/app_color.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | /// app颜色定义统一管理 6 | class AppColor{ 7 | static const Color primary = Color(0xFF5C5C5C); 8 | static const Color secondary = Color(0xFF51DEC6); 9 | static const Color red = Color(0xFFFF2B45); 10 | static const Color orange = Color(0xFFF67264); 11 | static const Color white = Color(0xFFFFFFFF); 12 | static const Color transparent = Color(0x00FFFFFF); 13 | static const Color paper = Color(0xFFF5F5F5); 14 | static const Color lightGrey = Color(0xFFDDDDDD); 15 | static const Color darkGrey = Color(0xFF333333); 16 | static const Color grey = Color(0xFF888888); 17 | static const Color blue = Color(0xFF3688FF); 18 | static const Color golden = Color(0xff8B7961); 19 | static const Color black = Color(0xff000000); 20 | static const Color black_99 = Color(0xff999999); 21 | static const Color black_66 = Color(0xff666666); 22 | static const Color black_33 = Color(0xff333333); 23 | static const Color black_cc = Color(0xffcccccc); 24 | 25 | static const Color default_txt_grey = Color(0xFF757575); 26 | static const Color default_icon_grey = Color(0xFF616161); 27 | static const Color icon_yellow = Color(0xFFFBC02D); 28 | static const Color search_bg_grey = Color(0x44cccccc); 29 | static const Color line_grey = Color(0xFFF5F5F5); 30 | } -------------------------------------------------------------------------------- /lib/routes/route_handlers.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:shuaishuaimovie/ui/pages/history/video_history_page.dart'; 4 | import 'package:shuaishuaimovie/ui/pages/hot/rank/hot_rank_page.dart'; 5 | import 'package:shuaishuaimovie/ui/pages/hot/update/hot_update_page.dart'; 6 | import 'package:shuaishuaimovie/ui/pages/maintab/cartoon/cartoon_page.dart'; 7 | import 'package:shuaishuaimovie/ui/pages/maintab/home/home_detail_page.dart'; 8 | import 'package:shuaishuaimovie/ui/pages/maintab/index_page.dart'; 9 | import 'package:shuaishuaimovie/ui/pages/search/condition_search_page.dart'; 10 | import 'package:shuaishuaimovie/ui/pages/search/txt_search_page.dart'; 11 | import 'package:shuaishuaimovie/ui/pages/splash_page.dart'; 12 | import 'package:shuaishuaimovie/ui/pages/video/video_page.dart'; 13 | 14 | var splashHandler = Handler( 15 | handlerFunc: (BuildContext context, Map> params) { 16 | return SplashPage(); 17 | }); 18 | 19 | var indexHandler = Handler( 20 | handlerFunc: (BuildContext context, Map> params) { 21 | var currentTabName = params["type"][0]; 22 | return IndexPage(currentTabName); 23 | }); 24 | 25 | var homeDetailHandler = Handler( 26 | handlerFunc: (BuildContext context, Map> params) { 27 | var vodId = params["id"][0]; 28 | var imageUrl = params["imageUrl"][0]; 29 | return HomeDetailPage( 30 | vodId: vodId, 31 | imageUrl: imageUrl, 32 | ); 33 | }); 34 | 35 | var hotUpdateHandler = Handler( 36 | handlerFunc: (BuildContext context, Map> params) { 37 | return TodayHotUpdatePage(); 38 | }); 39 | 40 | var rankHandler = Handler( 41 | handlerFunc: (BuildContext context, Map> params) { 42 | return HotRankPage(); 43 | }); 44 | 45 | var cartoonHandler = Handler( 46 | handlerFunc: (BuildContext context, Map> params) { 47 | return CartoonPage(); 48 | }); 49 | 50 | var conditionSearchHandler = Handler( 51 | handlerFunc: (BuildContext context, Map> params) { 52 | var tabType = params["tabType"][0]; 53 | var classes = params["classes"][0]; 54 | return ConditionSearchPage(tabType, classes: classes); 55 | }); 56 | 57 | var txtSearchHandler = Handler( 58 | handlerFunc: (BuildContext context, Map> params) { 59 | return TxtSearchPage(); 60 | }); 61 | 62 | var videoHandler = Handler( 63 | handlerFunc: (BuildContext context, Map> params) { 64 | var videoId = params["videoId"][0]; 65 | var videoUrl = params["videoUrl"][0]; 66 | var playUrlType = params["playUrlType"][0]; 67 | var playUrlIndex = params["playUrlIndex"][0]; 68 | var videoName = params["videoName"][0]; 69 | var videoLevel = params["videoLevel"][0]; 70 | var currentTime = params["currentTime"][0]; 71 | var isPositive = params["isPositive"][0]; 72 | 73 | return VideoPage( 74 | videoId: videoId, 75 | videoUrl: videoUrl, 76 | videoName: videoName, 77 | videoLevel: videoLevel, 78 | playUrlType: playUrlType, 79 | playUrlIndex: int.parse(playUrlIndex), 80 | currentTime: currentTime, 81 | isPositive: isPositive, 82 | ); 83 | }); 84 | 85 | var videoHistoryHandler = Handler( 86 | handlerFunc: (BuildContext context, Map> params) { 87 | return VideoHistoryPage(); 88 | }); 89 | -------------------------------------------------------------------------------- /lib/routes/route_jump.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:shuaishuaimovie/routes/routes.dart'; 4 | import 'package:shuaishuaimovie/shuai_movie.dart'; 5 | import 'package:shuaishuaimovie/ui/pages/maintab/index_page.dart'; 6 | import 'package:shuaishuaimovie/widgets/clip_widget.dart'; 7 | 8 | void jumpHomeDetail(context, String vodId, String imageUrl, {replace = false}) { 9 | Application.router.navigateTo( 10 | context, 11 | Routes.home_detail + "/$vodId/${Uri.encodeComponent(imageUrl)}", 12 | replace: replace, 13 | transition: TransitionType.cupertino, 14 | ); 15 | } 16 | 17 | void jumpRank(context) { 18 | Application.router.navigateTo( 19 | context, 20 | Routes.rank, 21 | transition: TransitionType.cupertino, 22 | ); 23 | } 24 | 25 | void jumpIndex(context, 26 | {String type = IndexPage.TAB_HOME, clearStack = false, replace = false}) { 27 | Application.router.navigateTo( 28 | context, 29 | Routes.index + "/$type", 30 | clearStack: clearStack, 31 | replace: replace, 32 | transition: TransitionType.cupertino, 33 | ); 34 | } 35 | 36 | void jumpCartoon(context) { 37 | Application.router.navigateTo( 38 | context, 39 | Routes.cartoon, 40 | transition: TransitionType.cupertino, 41 | ); 42 | } 43 | 44 | jumpVideo(context, 45 | {String videoId, 46 | String videoUrl, 47 | String playUrlType, 48 | String playUrlIndex, 49 | String videoName, 50 | String videoLevel, 51 | String isPositive, 52 | String currentTime = ""}) { 53 | Application.router.navigateTo( 54 | context, 55 | Routes.video + 56 | "/$videoId/${Uri.encodeComponent(videoUrl)}/$playUrlType/$playUrlIndex/$videoName/$videoLevel/$currentTime/$isPositive", 57 | transition: TransitionType.cupertino, 58 | ); 59 | } 60 | 61 | void jumpConditionSearch(context, String tabType, {String classes = ""}) { 62 | Application.router.navigateTo( 63 | context, 64 | Routes.condition_search + '/$tabType/$classes', 65 | transition: TransitionType.cupertino, 66 | ); 67 | } 68 | 69 | void jumpTxtSearch(context) { 70 | Application.router.navigateTo(context, Routes.txt_search, 71 | transition: TransitionType.custom, 72 | transitionDuration: const Duration(milliseconds: 500), transitionBuilder: 73 | (BuildContext context, Animation animation, 74 | Animation secondaryAnimation, Widget child) { 75 | return AnimatedBuilder( 76 | animation: animation, 77 | builder: (context, child) { 78 | return ClipPath( 79 | clipper: CirclePath(animation.value), 80 | child: child, 81 | ); 82 | }, 83 | child: child, 84 | ); 85 | }); 86 | } 87 | 88 | void jumpVideoHistory(context) { 89 | Application.router.navigateTo( 90 | context, 91 | Routes.video_history, 92 | transition: TransitionType.cupertino, 93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /lib/routes/routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:shuaishuaimovie/ui/pages/not_found_page.dart'; 4 | import 'package:shuaishuaimovie/routes/route_handlers.dart'; 5 | 6 | class Routes { 7 | static String splash = '/splash'; 8 | static String index = '/index'; 9 | static String home_detail = '/home/detail'; 10 | static String video = '/video'; 11 | static String hot_update = '/home/hotUpdate'; 12 | static String rank = '/rank'; 13 | static String cartoon = '/cartoon'; 14 | static String condition_search = '/condition_search'; 15 | static String txt_search = '/txt_search'; 16 | static String video_history = '/video_history'; 17 | 18 | static void configureRoutes(Router router) { 19 | router.notFoundHandler = Handler( 20 | handlerFunc: (BuildContext context, Map> params) { 21 | print("ROUTE WAS NOT FOUND !!!"); 22 | return NotFoundPage(); 23 | } 24 | ); 25 | 26 | router.define(splash, handler: splashHandler); 27 | //type为0,1,2,3,4分别对应底部的tab。 28 | router.define(index + "/:type", handler: indexHandler); 29 | router.define(home_detail + "/:id/:imageUrl", handler: homeDetailHandler); 30 | router.define(video + "/:videoId/:videoUrl/:playUrlType/:playUrlIndex/:videoName/:videoLevel/:currentTime/:isPositive", handler: videoHandler); 31 | router.define(hot_update, handler: hotUpdateHandler); 32 | router.define(rank, handler: rankHandler); 33 | router.define(cartoon, handler: cartoonHandler); 34 | router.define(condition_search + "/:tabType/:classes", handler: conditionSearchHandler); 35 | router.define(txt_search, handler: txtSearchHandler); 36 | router.define(video_history, handler: videoHistoryHandler); 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /lib/sharepreference/share_preference.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | 3 | class MovieSharePreference { 4 | static const String AUTO_PLAY = "auto_play"; 5 | static Future _prefs = SharedPreferences.getInstance(); 6 | 7 | static void saveAutoPlayValue(bool isAutoPlay) async { 8 | final SharedPreferences prefs = await _prefs; 9 | await prefs.setBool(AUTO_PLAY, isAutoPlay); 10 | } 11 | 12 | static Future getAutoPlayValue() async { 13 | final SharedPreferences prefs = await _prefs; 14 | return prefs.getBool(AUTO_PLAY); 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /lib/shuai_movie.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | 3 | class Application { 4 | static Router router; 5 | } -------------------------------------------------------------------------------- /lib/ui/helper/db/db_operate.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/database/bean/search_history_bean.dart'; 2 | import 'package:shuaishuaimovie/database/sqf_provider.dart'; 3 | import 'package:shuaishuaimovie/database/bean/search_history_bean.dart' as historySearch; 4 | 5 | void insertHistorySearchDBTxt(String searchTxt) async { 6 | await SqfProvider.db.transaction((txn) async { 7 | //如果当前搜索条件在table中,跳过再一次存储 8 | List list = await txn.query(historySearch.tableName, 9 | distinct: false, 10 | orderBy: 'id desc', 11 | columns: [historySearch.columnName], 12 | where: '${historySearch.columnName}=?', 13 | whereArgs: [searchTxt]); 14 | print(list.length); 15 | if (list.length > 0) return; 16 | SearchHistoryBean searchHistoryBean = SearchHistoryBean(); 17 | searchHistoryBean.name = searchTxt; 18 | await txn.insert(historySearch.tableName, searchHistoryBean.toMap()); 19 | List results = await txn.rawQuery( 20 | 'SELECT id FROM ${historySearch.tableName}', 21 | ); 22 | //默认只存16条数据 23 | for(int i = 0; i < results.length - 16; i++) { 24 | await txn.delete(historySearch.tableName, where: 'id=${results[i]['id']}'); 25 | } 26 | }); 27 | } -------------------------------------------------------------------------------- /lib/ui/helper/home_detail_list_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuaishuaimovie/models/common_item_bean_entity.dart'; 3 | import 'package:shuaishuaimovie/routes/route_jump.dart'; 4 | import 'package:shuaishuaimovie/ui/helper/common_list_helper.dart'; 5 | 6 | 7 | class DetailListTile extends StatelessWidget { 8 | const DetailListTile(this.title); 9 | 10 | final String title; 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Container( 15 | alignment: Alignment.centerLeft, 16 | height: 40, 17 | child: Text( 18 | title, 19 | style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), 20 | ) 21 | ); 22 | } 23 | } 24 | 25 | class NarrowList extends StatelessWidget { 26 | NarrowList(this.list); 27 | 28 | List list; 29 | 30 | 31 | int itemCount() { 32 | return list.length; 33 | } 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | debugPrint("NarrowList"); 38 | return SizedBox( 39 | height: 120, 40 | child: ListView.separated( 41 | scrollDirection: Axis.horizontal, 42 | itemBuilder: (context, index) { 43 | CommonItemBean commonItemBean = list[index]; 44 | return GestureDetector( 45 | onTap: () { 46 | print(commonItemBean.vodID); 47 | jumpHomeDetail( 48 | context, commonItemBean.vodID.toString(), commonItemBean.vodPic, 49 | replace: true); 50 | }, 51 | child: SizedBox( 52 | height: 120, 53 | width: 130, 54 | child: CommonNarrowListItem(commonItemBean), 55 | ), 56 | ); 57 | }, 58 | separatorBuilder: (BuildContext context, int index) { 59 | return SizedBox( 60 | width: 5, 61 | ); 62 | }, 63 | itemCount: itemCount(), 64 | ), 65 | ); 66 | } 67 | } 68 | 69 | class CommonNarrowListItem extends StatelessWidget { 70 | CommonNarrowListItem(this.commonItemBean); 71 | 72 | final CommonItemBean commonItemBean; 73 | 74 | @override 75 | Widget build(BuildContext context) { 76 | return Column( 77 | crossAxisAlignment: CrossAxisAlignment.start, 78 | children: [ 79 | Expanded( 80 | child: _buildNarrowItemImgModule(), 81 | ), 82 | buildItemTxtModule(commonItemBean.vodName, commonItemBean.vodActor), 83 | ], 84 | ); 85 | } 86 | 87 | _buildNarrowItemImgModule() { 88 | return ClipRRect( 89 | borderRadius: BorderRadius.circular(4), 90 | child: Stack( 91 | children: [ 92 | buildItemImg(commonItemBean.vodPic), 93 | buildUpdateInfoTxt(commonItemBean.vodRemarks), 94 | ], 95 | ), 96 | ); 97 | } 98 | } 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /lib/ui/helper/home_list_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:shuaishuaimovie/res/app_color.dart'; 4 | import 'package:shuaishuaimovie/widgets/tag_widget.dart'; 5 | import 'package:shuaishuaimovie/models/home_list_bean_entity.dart'; 6 | import 'package:shuaishuaimovie/ui/helper/common_list_helper.dart'; 7 | 8 | import 'common_list_helper.dart'; 9 | 10 | class HotListTile extends StatefulWidget { 11 | const HotListTile(this.title, 12 | {@required this.updateCount, 13 | this.iconData, 14 | this.onMorePressed, 15 | this.onRefreshPressed}); 16 | 17 | final IconData iconData; 18 | final String title; 19 | final String updateCount; 20 | final VoidCallback onMorePressed; 21 | final VoidCallback onRefreshPressed; 22 | 23 | @override 24 | _HotListTileState createState() => _HotListTileState(); 25 | } 26 | 27 | class _HotListTileState extends State 28 | with SingleTickerProviderStateMixin { 29 | AnimationController _controller; 30 | Animation _animation; 31 | 32 | @override 33 | void initState() { 34 | super.initState(); 35 | _controller = AnimationController(duration: Duration(milliseconds: 500), vsync: this); 36 | _animation = Tween(begin: 0, end: 2).animate(_controller); 37 | } 38 | 39 | @override 40 | void dispose() { 41 | _controller.dispose(); 42 | super.dispose(); 43 | } 44 | 45 | @override 46 | Widget build(BuildContext context) { 47 | return Container( 48 | margin: EdgeInsets.symmetric(horizontal: 15), 49 | height: 50, 50 | child: Row( 51 | children: [ 52 | buildListTileTitle(widget.iconData, widget.title), 53 | GestureDetector( 54 | onTap: widget.onMorePressed, 55 | child: Container( 56 | padding: EdgeInsets.symmetric(horizontal: 10, vertical: 10), 57 | child: Row( 58 | children: [ 59 | Text( 60 | "今日更新", 61 | style: TextStyle( 62 | fontSize: 11, 63 | ), 64 | ), 65 | SizedBox( 66 | width: 4, 67 | ), 68 | movieYellowTag(widget.updateCount), 69 | ], 70 | ), 71 | ), 72 | ), 73 | Spacer(), 74 | GestureDetector( 75 | onTap: () { 76 | if (_controller.isAnimating) return; 77 | if (_controller.isDismissed || _controller.isCompleted) { 78 | _controller.reset(); 79 | _controller.forward(); 80 | widget.onRefreshPressed(); 81 | } 82 | }, 83 | child: Row( 84 | mainAxisSize: MainAxisSize.min, 85 | children: [ 86 | RotationTransition( 87 | turns: _animation, 88 | child: Icon( 89 | Icons.refresh, 90 | color: AppColor.icon_yellow, 91 | size: 15, 92 | ), 93 | ), 94 | SizedBox( 95 | width: 2, 96 | ), 97 | Text( 98 | "换一换", 99 | style: TextStyle( 100 | fontSize: 11, 101 | ), 102 | ), 103 | ], 104 | ), 105 | ), 106 | ], 107 | ), 108 | ); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /lib/ui/helper/view_state_helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:fluttertoast/fluttertoast.dart'; 4 | import 'package:shuaishuaimovie/provider/view_state.dart'; 5 | import 'package:shuaishuaimovie/res/app_color.dart'; 6 | import 'package:shuaishuaimovie/viewmodels/base_model.dart'; 7 | 8 | /// 针对加载中 数据为空 数据加载失败做统一处理 9 | class CommonViewStateHelper extends StatelessWidget { 10 | final T model; 11 | final VoidCallback onEmptyPressed; 12 | final VoidCallback onErrorPressed; 13 | final VoidCallback onNoNetworkPressed; 14 | 15 | CommonViewStateHelper({ 16 | @required this.model, 17 | this.onEmptyPressed, 18 | this.onErrorPressed, 19 | this.onNoNetworkPressed, 20 | }); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | if (model.isLoading()) { 25 | return new ViewStateLoadingWidget(); 26 | } else if (model.isEmpty()) { 27 | return new ViewStateEmptyWidget(onPressed: onEmptyPressed); 28 | } else if(model.isNoNetWork()) { 29 | Fluttertoast.showToast(msg: "当前没有网络"); 30 | return new ViewStateNoNetworkWidget(onPressed: onNoNetworkPressed); 31 | } else if (model.isError()) { 32 | Fluttertoast.showToast(msg: "请求失败"); 33 | return new ViewStateErrorWidget( 34 | error: model.viewStateError, onPressed: onErrorPressed); 35 | } else { 36 | throw new Exception('状态异常,请核查状态'); 37 | } 38 | } 39 | } 40 | 41 | /// 初始化 加载中 42 | class ViewStateLoadingWidget extends StatelessWidget { 43 | @override 44 | Widget build(BuildContext context) { 45 | return new Scaffold( 46 | backgroundColor: AppColor.white, 47 | body: new Center( 48 | child: CupertinoActivityIndicator(), 49 | ), 50 | ); 51 | } 52 | } 53 | 54 | /// 页面无数据 55 | class ViewStateEmptyWidget extends StatelessWidget { 56 | final String message; 57 | final VoidCallback onPressed; 58 | 59 | ViewStateEmptyWidget({this.message, this.onPressed}); 60 | 61 | @override 62 | Widget build(BuildContext context) { 63 | return Scaffold( 64 | body: GestureDetector( 65 | onTap: () { 66 | onPressed(); 67 | }, 68 | child: Center( 69 | child: Container( 70 | padding: EdgeInsets.all(20), 71 | child: Text( 72 | message ?? '暂无数据,点击重试', 73 | textAlign: TextAlign.center, 74 | style: TextStyle(fontSize: 16, color: AppColor.black_33), 75 | ), 76 | ), 77 | )), 78 | ); 79 | } 80 | } 81 | 82 | /// 页面数据请求异常 83 | class ViewStateErrorWidget extends StatelessWidget { 84 | final ViewStateError error; 85 | final VoidCallback onPressed; 86 | 87 | ViewStateErrorWidget({this.error, this.onPressed}); 88 | 89 | @override 90 | Widget build(BuildContext context) { 91 | return Scaffold( 92 | body: GestureDetector( 93 | onTap: () { 94 | onPressed(); 95 | }, 96 | child: Center( 97 | child: Container( 98 | padding: EdgeInsets.all(20), 99 | child: Text( 100 | '数据请求异常,点击重试', 101 | textAlign: TextAlign.center, 102 | style: TextStyle(fontSize: 16, color: AppColor.black_33), 103 | ), 104 | ), 105 | )), 106 | ); 107 | } 108 | } 109 | 110 | /// 页面没有网络 111 | class ViewStateNoNetworkWidget extends StatelessWidget { 112 | final String message; 113 | final VoidCallback onPressed; 114 | 115 | ViewStateNoNetworkWidget({this.message, this.onPressed}); 116 | 117 | @override 118 | Widget build(BuildContext context) { 119 | return Scaffold( 120 | body: GestureDetector( 121 | onTap: () { 122 | onPressed(); 123 | }, 124 | child: Center( 125 | child: Container( 126 | padding: EdgeInsets.all(20), 127 | child: Text( 128 | message ?? '没有网络,点击重试', 129 | textAlign: TextAlign.center, 130 | style: TextStyle(fontSize: 16, color: AppColor.black_33), 131 | ), 132 | ), 133 | )), 134 | ); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /lib/ui/pages/hot/rank/hot_rank_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuaishuaimovie/res/app_color.dart'; 3 | import 'package:shuaishuaimovie/ui/pages/hot/rank/tab/month_rank_page.dart'; 4 | import 'package:shuaishuaimovie/ui/pages/hot/rank/tab/total_rank_page.dart'; 5 | import 'package:shuaishuaimovie/ui/pages/hot/rank/tab/week_rank_page.dart'; 6 | import 'package:shuaishuaimovie/ui/pages/search/widget/search_widget.dart'; 7 | 8 | class HotRankPage extends StatefulWidget { 9 | @override 10 | _HotRankPageState createState() => _HotRankPageState(); 11 | } 12 | 13 | class _HotRankPageState extends State with SingleTickerProviderStateMixin { 14 | List tabs = ["周榜", "月榜", "总榜"]; 15 | TabController _tabController; 16 | 17 | @override 18 | void initState() { 19 | super.initState(); 20 | _tabController = TabController(length: tabs.length, vsync: this); 21 | } 22 | @override 23 | Widget build(BuildContext context) { 24 | return Scaffold( 25 | appBar: AppBar( 26 | title: Text("排行榜"), 27 | centerTitle: true, 28 | actions: [ 29 | JumpHeroTxtSearchIconWidget(), 30 | ], 31 | bottom: TabBar( 32 | labelColor: AppColor.icon_yellow, 33 | unselectedLabelColor: AppColor.white, 34 | controller: _tabController, 35 | tabs: tabs.map((e) => Tab(text: e,)).toList(), 36 | ), 37 | ), 38 | body: Padding( 39 | padding: EdgeInsets.symmetric(horizontal: 10), 40 | child: TabBarView( 41 | controller: _tabController, 42 | children: [ 43 | WeekRankPage(), 44 | MonthRankPage(), 45 | TotalRankPage(), 46 | ], 47 | ), 48 | ), 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/ui/pages/hot/rank/tab/month_rank_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuaishuaimovie/provider/provider_widget.dart'; 3 | import 'package:shuaishuaimovie/ui/helper/view_state_helper.dart'; 4 | import 'package:shuaishuaimovie/ui/pages/hot/widget/hot_rank_widget.dart'; 5 | import 'package:shuaishuaimovie/viewmodels/hot/hot_rank_model.dart'; 6 | 7 | class MonthRankPage extends StatefulWidget { 8 | @override 9 | _MonthRankPageState createState() => _MonthRankPageState(); 10 | } 11 | 12 | class _MonthRankPageState extends State 13 | with AutomaticKeepAliveClientMixin { 14 | @override 15 | Widget build(BuildContext context) { 16 | return ProviderWidget( 17 | model: MonthRankModel(), 18 | initData: (model) { 19 | loadData(model); 20 | }, 21 | builder: (context, model, child) { 22 | if (!model.isSuccess()) { 23 | return CommonViewStateHelper( 24 | model: model, 25 | onEmptyPressed: () => loadData(model), 26 | onErrorPressed: () => loadData(model), 27 | onNoNetworkPressed: () => loadData(model), 28 | ); 29 | } 30 | return _buildContent(model); 31 | }, 32 | ); 33 | } 34 | 35 | Widget _buildContent(model) { 36 | return HotRankModule(model); 37 | } 38 | 39 | void loadData(MonthRankModel model) { 40 | model.getRankApiData(); 41 | } 42 | 43 | @override 44 | bool get wantKeepAlive => true; 45 | } 46 | -------------------------------------------------------------------------------- /lib/ui/pages/hot/rank/tab/total_rank_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuaishuaimovie/provider/provider_widget.dart'; 3 | import 'package:shuaishuaimovie/ui/helper/view_state_helper.dart'; 4 | import 'package:shuaishuaimovie/ui/pages/hot/widget/hot_rank_widget.dart'; 5 | import 'package:shuaishuaimovie/viewmodels/hot/hot_rank_model.dart'; 6 | 7 | class TotalRankPage extends StatefulWidget { 8 | @override 9 | _TotalRankPageState createState() => _TotalRankPageState(); 10 | } 11 | 12 | class _TotalRankPageState extends State with AutomaticKeepAliveClientMixin{ 13 | @override 14 | Widget build(BuildContext context) { 15 | return ProviderWidget( 16 | model: TotalRankModel(), 17 | initData: (model) { 18 | loadData(model); 19 | }, 20 | builder: (context, model, child) { 21 | if (!model.isSuccess()) { 22 | return CommonViewStateHelper( 23 | model: model, 24 | onEmptyPressed: () => loadData(model), 25 | onErrorPressed: () => loadData(model), 26 | onNoNetworkPressed: () => loadData(model), 27 | ); 28 | } 29 | return _buildContent(model); 30 | }, 31 | ); 32 | } 33 | 34 | Widget _buildContent(model) { 35 | return HotRankModule(model); 36 | } 37 | 38 | void loadData(TotalRankModel model) { 39 | model.getRankApiData(); 40 | 41 | } 42 | 43 | @override 44 | bool get wantKeepAlive => true; 45 | } 46 | -------------------------------------------------------------------------------- /lib/ui/pages/hot/rank/tab/week_rank_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuaishuaimovie/provider/provider_widget.dart'; 3 | import 'package:shuaishuaimovie/ui/helper/view_state_helper.dart'; 4 | import 'package:shuaishuaimovie/ui/pages/hot/widget/hot_rank_widget.dart'; 5 | import 'package:shuaishuaimovie/viewmodels/hot/hot_rank_model.dart'; 6 | 7 | class WeekRankPage extends StatefulWidget { 8 | @override 9 | _WeekRankPageState createState() => _WeekRankPageState(); 10 | } 11 | 12 | class _WeekRankPageState extends State with AutomaticKeepAliveClientMixin { 13 | @override 14 | Widget build(BuildContext context) { 15 | return ProviderWidget( 16 | model: WeekRankModel(), 17 | initData: (model) { 18 | loadData(model); 19 | }, 20 | builder: (context, model, child) { 21 | if (!model.isSuccess()) { 22 | return CommonViewStateHelper( 23 | model: model, 24 | onEmptyPressed: () => loadData(model), 25 | onErrorPressed: () => loadData(model), 26 | onNoNetworkPressed: () => loadData(model), 27 | ); 28 | } 29 | return _buildContent(model); 30 | }, 31 | ); 32 | } 33 | 34 | Widget _buildContent(model) { 35 | return HotRankModule(model); 36 | } 37 | 38 | void loadData(WeekRankModel model) { 39 | model.getRankApiData(); 40 | 41 | } 42 | 43 | @override 44 | bool get wantKeepAlive => true; 45 | } 46 | -------------------------------------------------------------------------------- /lib/ui/pages/hot/update/hot_update_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuaishuaimovie/provider/provider_widget.dart'; 3 | import 'package:shuaishuaimovie/ui/helper/hot_update_list_helper.dart'; 4 | import 'package:shuaishuaimovie/ui/helper/view_state_helper.dart'; 5 | import 'package:shuaishuaimovie/ui/pages/search/widget/search_widget.dart'; 6 | import 'package:shuaishuaimovie/viewmodels/hot/hot_update_model.dart'; 7 | import 'package:shuaishuaimovie/widgets/custom_refresh_widget.dart'; 8 | 9 | class TodayHotUpdatePage extends StatelessWidget { 10 | @override 11 | Widget build(BuildContext context) { 12 | return Scaffold( 13 | appBar: AppBar( 14 | title: Text("热播更新"), 15 | centerTitle: true, 16 | actions: [ 17 | JumpHeroTxtSearchIconWidget(), 18 | ], 19 | ), 20 | body: ProviderWidget( 21 | initData: (model) { 22 | loadData(model); 23 | }, 24 | model: HotUpdateModel(), 25 | builder: (context, model, child) { 26 | if (!model.isSuccess()) { 27 | return CommonViewStateHelper( 28 | model: model, 29 | onEmptyPressed: () => loadData(model), 30 | onErrorPressed: () => loadData(model), 31 | onNoNetworkPressed: () => loadData(model), 32 | ); 33 | } 34 | return Padding( 35 | padding: EdgeInsets.symmetric(horizontal: 10), 36 | child: _buildContent(model)); 37 | }, 38 | ), 39 | ); 40 | } 41 | 42 | Widget _buildContent(HotUpdateModel model) { 43 | return CustomHeaderRefreshWidget( 44 | onRefresh: model.refreshHotUpdateData, 45 | easyRefreshController: model.easyRefreshController, 46 | slivers: [ 47 | SliverToBoxAdapter( 48 | child: HotUpdateTile( 49 | hotUpdateModel: model, 50 | onRefreshPress: () { 51 | model.refreshLocalData(); 52 | }, 53 | ), 54 | ), 55 | SliverPadding( 56 | padding: EdgeInsets.only(top: 15), 57 | sliver: HotUpdateList(model.currentHotDatas), 58 | ), 59 | ], 60 | ); 61 | } 62 | 63 | loadData(HotUpdateModel model) { 64 | model.getHotUpdateApiData(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/ui/pages/hot/widget/hot_rank_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuaishuaimovie/routes/route_jump.dart'; 3 | import 'package:shuaishuaimovie/ui/helper/hot_rank_list_helper.dart'; 4 | import 'package:shuaishuaimovie/ui/pages/maintab/index_page.dart'; 5 | import 'package:shuaishuaimovie/viewmodels/hot/hot_rank_model.dart'; 6 | import 'package:shuaishuaimovie/widgets/custom_refresh_widget.dart'; 7 | 8 | class HotRankModule extends StatelessWidget { 9 | HotRankModule(this.model); 10 | 11 | HotRankModel model; 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return CustomHeaderRefreshWidget( 16 | onRefresh: () async { 17 | await model.refreshRankData(); 18 | }, 19 | easyRefreshController: model.easyRefreshController, 20 | slivers: [ 21 | SliverToBoxAdapter( 22 | child: HotRankHeaderTile( 23 | IndexPage.TAB_MOVIE, 24 | onMorePress: () { 25 | jumpIndex(context, type: IndexPage.TAB_MOVIE, clearStack: true); 26 | }, 27 | )), 28 | SliverPadding( 29 | padding: EdgeInsets.symmetric(vertical: 10), 30 | sliver: CommonSliverHotRankList(model.hotRankBeanEntity.movie)), 31 | SliverToBoxAdapter( 32 | child: HotRankHeaderTile( 33 | IndexPage.TAB_TELEPLAY, 34 | onMorePress: () { 35 | jumpIndex(context, type: IndexPage.TAB_TELEPLAY, clearStack: true); 36 | }, 37 | )), 38 | SliverPadding( 39 | padding: EdgeInsets.symmetric(vertical: 10), 40 | sliver: CommonSliverHotRankList(model.hotRankBeanEntity.teleplay), 41 | ), 42 | SliverToBoxAdapter( 43 | child: HotRankHeaderTile( 44 | IndexPage.TAB_SHOW, 45 | onMorePress: () { 46 | jumpIndex(context, type: IndexPage.TAB_SHOW, clearStack: true); 47 | }, 48 | )), 49 | SliverPadding( 50 | padding: EdgeInsets.symmetric(vertical: 10), 51 | sliver: CommonSliverHotRankList(model.hotRankBeanEntity.show), 52 | ), 53 | SliverToBoxAdapter( 54 | child: HotRankHeaderTile( 55 | IndexPage.TAB_CARTOON, 56 | onMorePress: () { 57 | jumpCartoon(context); 58 | }, 59 | )), 60 | SliverPadding( 61 | padding: EdgeInsets.symmetric(vertical: 10), 62 | sliver: CommonSliverHotRankList(model.hotRankBeanEntity.cartoon), 63 | ), 64 | ], 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/ui/pages/hot/widget/hot_update_widget.dart: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /lib/ui/pages/maintab/cartoon/cartoon_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuaishuaimovie/provider/provider_widget.dart'; 3 | import 'package:shuaishuaimovie/res/app_color.dart'; 4 | import 'package:shuaishuaimovie/routes/route_jump.dart'; 5 | import 'package:shuaishuaimovie/ui/helper/view_state_helper.dart'; 6 | import 'package:shuaishuaimovie/ui/pages/maintab/index_page.dart'; 7 | import 'package:shuaishuaimovie/ui/pages/maintab/widget/common_tab_widget.dart'; 8 | import 'package:shuaishuaimovie/ui/pages/search/txt_search_page.dart'; 9 | import 'package:shuaishuaimovie/ui/pages/search/widget/search_widget.dart'; 10 | import 'package:shuaishuaimovie/viewmodels/tab/commontab/common_tab_model.dart'; 11 | 12 | class CartoonPage extends StatelessWidget { 13 | @override 14 | Widget build(BuildContext context) { 15 | return Scaffold( 16 | appBar: AppBar( 17 | title: Text("动漫剧场"), 18 | actions: [ 19 | JumpHeroTxtSearchIconWidget(), 20 | ], 21 | ), 22 | body: ProviderWidget( 23 | initData: (model) { 24 | loadData(model); 25 | }, 26 | model: CartoonModel(), 27 | builder: (context, model, child) { 28 | if (!model.isSuccess()) { 29 | return CommonViewStateHelper( 30 | model: model, 31 | onEmptyPressed: () => loadData(model), 32 | onErrorPressed: () => loadData(model), 33 | onNoNetworkPressed: () => loadData(model), 34 | ); 35 | } 36 | return CommonTabWidget(model, IndexPage.TAB_CARTOON); 37 | }, 38 | ), 39 | ); 40 | } 41 | 42 | loadData(CartoonModel model) { 43 | model.getCommonTabApiData(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/ui/pages/maintab/home/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuaishuaimovie/provider/provider_widget.dart'; 3 | import 'package:shuaishuaimovie/ui/helper/view_state_helper.dart'; 4 | import 'package:shuaishuaimovie/ui/pages/maintab/home/homewidget/home_list_widget.dart'; 5 | import 'package:shuaishuaimovie/viewmodels/tab/home/home_model.dart'; 6 | import 'package:shuaishuaimovie/widgets/custom_refresh_widget.dart'; 7 | 8 | class HomePage extends StatefulWidget { 9 | @override 10 | _HomePageState createState() => _HomePageState(); 11 | } 12 | 13 | class _HomePageState extends State with AutomaticKeepAliveClientMixin{ 14 | @override 15 | Widget build(BuildContext context) { 16 | return ProviderWidget( 17 | initData: (model) { 18 | loadData(model); 19 | }, 20 | model: HomeViewModel(), 21 | child: HomeContentHeaderWidget(), 22 | builder: (context, model, child) { 23 | if (!model.isSuccess()) { 24 | return CommonViewStateHelper( 25 | model: model, 26 | onEmptyPressed: () => loadData(model), 27 | onErrorPressed: () => loadData(model), 28 | onNoNetworkPressed: () => loadData(model), 29 | ); 30 | } 31 | 32 | return CustomHeaderRefreshWidget( 33 | onRefresh: model.refreshHomeListData, 34 | easyRefreshController: model.easyRefreshController, 35 | slivers: [ 36 | SliverToBoxAdapter( 37 | child: child, 38 | ), 39 | SliverHomeHotModuleWidget(model), 40 | SliverHomeMovieModuleWidget(model), 41 | SliverHomeSitcomModuleWidget(model), 42 | SliverHomeVarietyModuleWidget(model), 43 | SliverHomeAnimatedModuleWidget(model), 44 | ], 45 | ); 46 | }, 47 | ); 48 | } 49 | 50 | loadData(HomeViewModel model) { 51 | model.getHomeListApiData(); 52 | } 53 | 54 | @override 55 | // TODO: implement wantKeepAlive 56 | bool get wantKeepAlive => true; 57 | } 58 | -------------------------------------------------------------------------------- /lib/ui/pages/maintab/movie/movie_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuaishuaimovie/provider/provider_widget.dart'; 3 | import 'package:shuaishuaimovie/ui/helper/view_state_helper.dart'; 4 | import 'package:shuaishuaimovie/ui/pages/maintab/index_page.dart'; 5 | import 'package:shuaishuaimovie/ui/pages/maintab/widget/common_tab_widget.dart'; 6 | import 'package:shuaishuaimovie/viewmodels/tab/commontab/common_tab_model.dart'; 7 | 8 | class MoviePage extends StatefulWidget { 9 | @override 10 | _MoviePageState createState() => _MoviePageState(); 11 | } 12 | 13 | class _MoviePageState extends State with AutomaticKeepAliveClientMixin{ 14 | @override 15 | Widget build(BuildContext context) { 16 | return ProviderWidget( 17 | initData: (model) { 18 | loadData(model); 19 | }, 20 | model: MovieTabModel(), 21 | builder: (context, model, child) { 22 | if (!model.isSuccess()) { 23 | return CommonViewStateHelper( 24 | model: model, 25 | onEmptyPressed: () => loadData(model), 26 | onErrorPressed: () => loadData(model), 27 | onNoNetworkPressed: () => loadData(model), 28 | ); 29 | } 30 | return CommonTabWidget(model, IndexPage.TAB_MOVIE); 31 | }, 32 | ); 33 | } 34 | 35 | loadData(MovieTabModel model) { 36 | model.getCommonTabApiData(); 37 | } 38 | 39 | @override 40 | // TODO: implement wantKeepAlive 41 | bool get wantKeepAlive => true; 42 | } 43 | -------------------------------------------------------------------------------- /lib/ui/pages/maintab/show/show_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuaishuaimovie/provider/provider_widget.dart'; 3 | import 'package:shuaishuaimovie/ui/helper/view_state_helper.dart'; 4 | import 'package:shuaishuaimovie/ui/pages/maintab/index_page.dart'; 5 | import 'package:shuaishuaimovie/ui/pages/maintab/widget/common_tab_widget.dart'; 6 | import 'package:shuaishuaimovie/viewmodels/tab/commontab/common_tab_model.dart'; 7 | 8 | class ShowPage extends StatefulWidget { 9 | @override 10 | _ShowPageState createState() => _ShowPageState(); 11 | } 12 | 13 | class _ShowPageState extends State with AutomaticKeepAliveClientMixin{ 14 | @override 15 | Widget build(BuildContext context) { 16 | return ProviderWidget( 17 | initData: (model) { 18 | loadData(model); 19 | }, 20 | model: ShowTabModel(), 21 | builder: (context, model, child) { 22 | if (!model.isSuccess()) { 23 | return CommonViewStateHelper( 24 | model: model, 25 | onEmptyPressed: () => loadData(model), 26 | onErrorPressed: () => loadData(model), 27 | onNoNetworkPressed: () => loadData(model), 28 | ); 29 | } 30 | return CommonTabWidget(model, IndexPage.TAB_SHOW); 31 | }, 32 | ); 33 | } 34 | 35 | loadData(ShowTabModel model) { 36 | model.getCommonTabApiData(); 37 | } 38 | 39 | @override 40 | // TODO: implement wantKeepAlive 41 | bool get wantKeepAlive => true; 42 | } 43 | -------------------------------------------------------------------------------- /lib/ui/pages/maintab/teleplay/teleplay_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuaishuaimovie/provider/provider_widget.dart'; 3 | import 'package:shuaishuaimovie/ui/helper/view_state_helper.dart'; 4 | import 'package:shuaishuaimovie/ui/pages/maintab/index_page.dart'; 5 | import 'package:shuaishuaimovie/ui/pages/maintab/widget/common_tab_widget.dart'; 6 | import 'package:shuaishuaimovie/viewmodels/tab/commontab/common_tab_model.dart'; 7 | 8 | class TeleplayPage extends StatefulWidget { 9 | @override 10 | _TeleplayPageState createState() => _TeleplayPageState(); 11 | } 12 | 13 | class _TeleplayPageState extends State with AutomaticKeepAliveClientMixin { 14 | @override 15 | Widget build(BuildContext context) { 16 | return ProviderWidget( 17 | initData: (model) { 18 | loadData(model); 19 | }, 20 | model: TeleplayTabModel(), 21 | builder: (context, model, child) { 22 | if (!model.isSuccess()) { 23 | return CommonViewStateHelper( 24 | model: model, 25 | onEmptyPressed: () => loadData(model), 26 | onErrorPressed: () => loadData(model), 27 | onNoNetworkPressed: () => loadData(model), 28 | ); 29 | } 30 | return CommonTabWidget(model, IndexPage.TAB_TELEPLAY); 31 | }, 32 | ); 33 | } 34 | 35 | loadData(TeleplayTabModel model) { 36 | model.getCommonTabApiData(); 37 | } 38 | 39 | @override 40 | // TODO: implement wantKeepAlive 41 | bool get wantKeepAlive => true; 42 | } 43 | -------------------------------------------------------------------------------- /lib/ui/pages/mine/mine_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuaishuaimovie/res/app_color.dart'; 3 | import 'package:shuaishuaimovie/sharepreference/share_preference.dart'; 4 | import 'package:shuaishuaimovie/utils/app_info_util.dart'; 5 | import 'package:shuaishuaimovie/widgets/text_widget.dart'; 6 | 7 | class MinePage extends StatefulWidget { 8 | @override 9 | _MinePageState createState() => _MinePageState(); 10 | } 11 | 12 | class _MinePageState extends State { 13 | bool isAutoPlay = true; 14 | String appVersion = ""; 15 | 16 | @override 17 | void initState() { 18 | super.initState(); 19 | WidgetsBinding.instance.addPostFrameCallback( 20 | (timeStamp) => AppInfoUtil.getAppVersion().then((value) { 21 | setState(() { 22 | appVersion = value; 23 | }); 24 | })); 25 | } 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | print(appVersion); 30 | 31 | return Scaffold( 32 | appBar: AppBar( 33 | title: Text("留言页面"), 34 | centerTitle: true, 35 | backgroundColor: AppColor.black, 36 | ), 37 | body: SingleChildScrollView( 38 | physics: BouncingScrollPhysics(), 39 | child: Column( 40 | children: [ 41 | ListTile( 42 | title: Text("视频自动播放"), 43 | trailing: Switch( 44 | value: isAutoPlay, 45 | onChanged: (bool value) { 46 | setState(() { 47 | isAutoPlay = !isAutoPlay; 48 | MovieSharePreference.saveAutoPlayValue(isAutoPlay); 49 | }); 50 | }, 51 | ), 52 | ), 53 | ListTile( 54 | title: Text("作者微信号:"), 55 | trailing: SelectableText("MS_miaoshuai"), 56 | ), 57 | ListTile( 58 | title: Text("当前版本号:"), 59 | trailing: Text(appVersion), 60 | ), 61 | SizedBox( 62 | height: 20, 63 | ), 64 | Padding( 65 | padding: EdgeInsets.symmetric(horizontal: 15), 66 | child: Column( 67 | mainAxisSize: MainAxisSize.min, 68 | crossAxisAlignment: CrossAxisAlignment.start, 69 | children: [ 70 | CommonText( 71 | "结束语:", 72 | txtSize: 16, 73 | txtWeight: FontWeight.bold, 74 | ), 75 | CommonText( 76 | " Flutter的框架和社区已经算是非常的成熟了,对于Flutter的流行度你可以通过github的star不难看出这个框架不亚于RN等主流的跨平台开发的其他的框架。而且Flutter出道比较晚,当前作者基于开发的版本才1.20。所以作者认为对于Flutter接下来的路还是相当客观的。在这里也不多说Flutter的优点了,如果不知道的小伙伴可以看官网或者搜索一些其他对Flutter的认知的文章进行阅读。", 77 | txtSize: 15, 78 | ), 79 | CommonText( 80 | " 作者开发了这个帅帅影视的开源项目的目的在于1.可以在项目中对于flutter进行跟深入的学习。2.同样为了帮助那些想要学习或者正要进行学习Flutter的同学的提供一个开源的Flutter项目用于参考", 81 | txtSize: 15, 82 | ), 83 | CommonText( 84 | " 在开发项目中,作者不得不在一次感慨一下开发起来特别爽,开发一款Flutter的项目的开发进度作者认为至少比原生提高百分之20%,更何况一套代码两端运行呢!", 85 | txtSize: 15, 86 | ), 87 | CommonText( 88 | " 最后如果小伙伴拥有好的idea,并且可以进行用Flutter开发的话。愿意参与进行共同维护这块开源项目,为Flutter社区做一些贡献的欢迎私聊我哦。", 89 | txtSize: 15, 90 | ), 91 | SizedBox( 92 | height: 50, 93 | ), 94 | ], 95 | ), 96 | ), 97 | ], 98 | ), 99 | ), 100 | ); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/ui/pages/not_found_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class NotFoundPage extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Container(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lib/ui/pages/search/tab/hot_search_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuaishuaimovie/provider/provider_widget.dart'; 3 | import 'package:shuaishuaimovie/ui/helper/common_list_helper.dart'; 4 | import 'package:shuaishuaimovie/ui/helper/view_state_helper.dart'; 5 | import 'package:shuaishuaimovie/viewmodels/search/condition_search_model.dart'; 6 | import 'package:shuaishuaimovie/viewmodels/search/select_condition_model.dart'; 7 | import 'package:shuaishuaimovie/widgets/refresh_widget.dart'; 8 | 9 | class HotSearchPage extends StatefulWidget { 10 | HotSearchPage({Key key, this.model, this.tabType}) : super(key: key); 11 | 12 | String tabType; 13 | SelectionConditionModel model; 14 | 15 | @override 16 | HotSearchPageState createState() => HotSearchPageState(); 17 | } 18 | 19 | class HotSearchPageState extends State { 20 | ConditionSearchModel conditionSearchModel; 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return ProviderWidget( 25 | initData: (model) { 26 | conditionSearchModel = model; 27 | loadData(model); 28 | }, 29 | model: HotConditionModel(widget.tabType) 30 | ..selectionConditionModel = widget.model, 31 | builder: (_, HotConditionModel model, __) { 32 | debugPrint("build_new_search_page"); 33 | if (!model.isSuccess()) { 34 | return CommonViewStateHelper( 35 | model: model, 36 | onEmptyPressed: () => loadData(model), 37 | onErrorPressed: () => loadData(model), 38 | onNoNetworkPressed: () => loadData(model), 39 | ); 40 | } 41 | 42 | return FooterLoadMoreWidget( 43 | topBouncing: false, 44 | bottomBouncing: false, 45 | easyRefreshController: model.easyRefreshController, 46 | onLoadMore: (model.qty ?? 0) <= 36 47 | ? null 48 | : () async { 49 | await model.loadMoreConditionSearchData(); 50 | setState(() {}); 51 | }, 52 | child: Column( 53 | children: [ 54 | SizedBox( 55 | height: 67, 56 | ), 57 | CommonGrid( 58 | model.conditionSearchBeanDatas, 59 | isShowTag: false, 60 | ), 61 | ], 62 | ), 63 | ); 64 | }, 65 | ); 66 | } 67 | 68 | void loadData(HotConditionModel model) { 69 | model.getConditionSearchApiData(); 70 | } 71 | 72 | void refreshData() { 73 | conditionSearchModel.refreshConditionSearchApiData(); 74 | } 75 | 76 | bool isLoading() { 77 | return conditionSearchModel.isLoading(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/ui/pages/search/tab/new_search_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import 'package:shuaishuaimovie/provider/provider_widget.dart'; 4 | import 'package:shuaishuaimovie/ui/helper/common_list_helper.dart'; 5 | import 'package:shuaishuaimovie/ui/helper/view_state_helper.dart'; 6 | import 'package:shuaishuaimovie/viewmodels/search/condition_search_model.dart'; 7 | import 'package:shuaishuaimovie/viewmodels/search/select_condition_model.dart'; 8 | import 'package:shuaishuaimovie/widgets/refresh_widget.dart'; 9 | 10 | class NewSearchPage extends StatefulWidget { 11 | NewSearchPage({Key key, this.model, this.tabType}) : super(key: key); 12 | 13 | String tabType; 14 | SelectionConditionModel model; 15 | 16 | @override 17 | NewSearchPageState createState() => NewSearchPageState(); 18 | } 19 | 20 | class NewSearchPageState extends State { 21 | ConditionSearchModel conditionSearchModel; 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return ProviderWidget( 26 | initData: (model) { 27 | conditionSearchModel = model; 28 | loadData(model); 29 | }, 30 | model: TimeConditionModel(widget.tabType) 31 | ..selectionConditionModel = widget.model, 32 | builder: (_, TimeConditionModel model, __) { 33 | debugPrint("build_new_search_page"); 34 | if (!model.isSuccess()) { 35 | return CommonViewStateHelper( 36 | model: model, 37 | onEmptyPressed: () => loadData(model), 38 | onErrorPressed: () => loadData(model), 39 | onNoNetworkPressed: () => loadData(model), 40 | ); 41 | } 42 | 43 | Provider.of(context).qty = model.qty.toString(); 44 | return FooterLoadMoreWidget( 45 | topBouncing: false, 46 | bottomBouncing: false, 47 | easyRefreshController: model.easyRefreshController, 48 | onLoadMore: (model.qty ?? 0) <= 36 49 | ? null 50 | : () async { 51 | await model.loadMoreConditionSearchData(); 52 | setState(() {}); 53 | }, 54 | child: Column( 55 | children: [ 56 | SizedBox( 57 | height: 67, 58 | ), 59 | CommonGrid( 60 | model.conditionSearchBeanDatas, 61 | isShowTag: false, 62 | ), 63 | ], 64 | ), 65 | ); 66 | }, 67 | ); 68 | } 69 | 70 | void loadData(TimeConditionModel model) { 71 | model.getConditionSearchApiData(); 72 | } 73 | 74 | void refreshData() { 75 | conditionSearchModel.refreshConditionSearchApiData(); 76 | } 77 | 78 | bool isLoading() { 79 | return conditionSearchModel.isLoading(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/ui/pages/search/tab/score_search_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | import 'package:shuaishuaimovie/provider/provider_widget.dart'; 4 | import 'package:shuaishuaimovie/ui/helper/common_list_helper.dart'; 5 | import 'package:shuaishuaimovie/ui/helper/view_state_helper.dart'; 6 | import 'package:shuaishuaimovie/viewmodels/search/condition_search_model.dart'; 7 | import 'package:shuaishuaimovie/viewmodels/search/select_condition_model.dart'; 8 | import 'package:shuaishuaimovie/widgets/refresh_widget.dart'; 9 | 10 | class ScoreSearchPage extends StatefulWidget { 11 | ScoreSearchPage({Key key, this.model, this.tabType}) : super(key: key); 12 | 13 | String tabType; 14 | SelectionConditionModel model; 15 | 16 | @override 17 | ScoreSearchPageState createState() => ScoreSearchPageState(); 18 | } 19 | 20 | class ScoreSearchPageState extends State { 21 | ConditionSearchModel conditionSearchModel; 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return ProviderWidget( 26 | initData: (model) { 27 | conditionSearchModel = model; 28 | loadData(model); 29 | }, 30 | model: ScoreConditionModel(widget.tabType) 31 | ..selectionConditionModel = widget.model, 32 | builder: (context, ScoreConditionModel model, __) { 33 | debugPrint("build_new_search_page"); 34 | if (!model.isSuccess()) { 35 | return CommonViewStateHelper( 36 | model: model, 37 | onEmptyPressed: () => loadData(model), 38 | onErrorPressed: () => loadData(model), 39 | onNoNetworkPressed: () => loadData(model), 40 | ); 41 | } 42 | 43 | Provider.of(context).qty = model.qty.toString(); 44 | return FooterLoadMoreWidget( 45 | topBouncing: false, 46 | bottomBouncing: false, 47 | easyRefreshController: model.easyRefreshController, 48 | onLoadMore: (model.qty ?? 0) <= 36 49 | ? null 50 | : () async { 51 | await model.loadMoreConditionSearchData(); 52 | setState(() {}); 53 | }, 54 | child: Column( 55 | children: [ 56 | SizedBox( 57 | height: 67, 58 | ), 59 | CommonGrid( 60 | model.conditionSearchBeanDatas, 61 | isShowTag: false, 62 | ), 63 | ], 64 | ), 65 | ); 66 | }, 67 | ); 68 | } 69 | 70 | void loadData(ScoreConditionModel model) { 71 | model.getConditionSearchApiData(); 72 | } 73 | 74 | void refreshData() { 75 | conditionSearchModel.refreshConditionSearchApiData(); 76 | } 77 | 78 | bool isLoading() { 79 | return conditionSearchModel.isLoading(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/ui/pages/search/widget/search_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuaishuaimovie/res/app_color.dart'; 3 | import 'package:shuaishuaimovie/routes/route_jump.dart'; 4 | import 'package:shuaishuaimovie/ui/pages/search/txt_search_page.dart'; 5 | 6 | class JumpHeroTxtSearchIconWidget extends StatelessWidget { 7 | @override 8 | Widget build(BuildContext context) { 9 | return InkWell( 10 | onTap: () { 11 | jumpTxtSearch(context); 12 | }, 13 | child: Hero( 14 | tag: TxtSearchPage.TXT_SEARCH, 15 | child: Padding( 16 | padding: EdgeInsets.all(10), 17 | child: Icon(Icons.search)), 18 | transitionOnUserGestures: true, 19 | flightShuttleBuilder: (flightContext, animation, direction, 20 | fromContext, toContext) { 21 | return Icon(Icons.arrow_back, color: AppColor.white,); 22 | }, 23 | ), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/ui/pages/search/widget/txt_auto_search_child.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuaishuaimovie/models/common_item_bean_entity.dart'; 3 | import 'package:shuaishuaimovie/provider/provider_widget.dart'; 4 | import 'package:shuaishuaimovie/res/app_color.dart'; 5 | import 'package:shuaishuaimovie/routes/route_jump.dart'; 6 | import 'package:shuaishuaimovie/ui/helper/db/db_operate.dart'; 7 | import 'package:shuaishuaimovie/viewmodels/search/txt_auto_search_model.dart'; 8 | 9 | class TxtAutoSearchChild extends StatefulWidget { 10 | TxtAutoSearchChild({Key key, this.jumpCallback}) : super(key: key); 11 | VoidCallback jumpCallback; 12 | 13 | @override 14 | TxtAutoSearchChildState createState() => TxtAutoSearchChildState(); 15 | } 16 | 17 | class TxtAutoSearchChildState extends State { 18 | TxtAutoSearchModel model; 19 | String keyword = ""; 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return ProviderWidget( 24 | initData: (model) { 25 | this.model = model; 26 | }, 27 | model: TxtAutoSearchModel(), 28 | builder: (_, TxtAutoSearchModel model, child) { 29 | return Container( 30 | color: AppColor.white, 31 | child: ListView.separated( 32 | itemBuilder: (BuildContext context, int index) { 33 | CommonItemBean bean = model.txtAutoSearchBeanDatas[index]; 34 | return ListTile( 35 | onTap: () { 36 | widget.jumpCallback(); 37 | jumpHomeDetail(context, bean.vodID.toString(), bean.vodPic); 38 | insertHistorySearchDBTxt(bean.vodName); 39 | }, 40 | title: Text.rich( 41 | TextSpan(children: [ 42 | for (String txt 43 | in model.processedTxt(bean.vodName, keyword)) 44 | TextSpan( 45 | text: txt, 46 | style: TextStyle( 47 | color: txt == keyword 48 | ? AppColor.icon_yellow 49 | : AppColor.black, 50 | ), 51 | ), 52 | ]), 53 | ), 54 | ); 55 | }, 56 | itemCount: model.txtAutoSearchBeanDatas?.length ?? 0, 57 | separatorBuilder: (BuildContext context, int index) { 58 | return Container( 59 | height: 1, 60 | color: AppColor.line_grey, 61 | ); 62 | }, 63 | ), 64 | ); 65 | }, 66 | ); 67 | } 68 | 69 | 70 | Future loadData(String keyword) async{ 71 | this.keyword = keyword; 72 | await model.getTxtAutoSearchApiData(keyword); 73 | } 74 | 75 | void resetData() { 76 | model.clearData(); 77 | } 78 | 79 | 80 | } 81 | -------------------------------------------------------------------------------- /lib/ui/pages/search/widget/txt_normal_search_child.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuaishuaimovie/provider/provider_widget.dart'; 3 | import 'package:shuaishuaimovie/res/app_color.dart'; 4 | import 'package:shuaishuaimovie/ui/helper/search_list_helper.dart'; 5 | import 'package:shuaishuaimovie/ui/helper/view_state_helper.dart'; 6 | import 'package:shuaishuaimovie/viewmodels/search/txt_normal_search_model.dart'; 7 | import 'package:shuaishuaimovie/widgets/custom_refresh_widget.dart'; 8 | 9 | class TxtNormalSearchChild extends StatefulWidget { 10 | TxtNormalSearchChild({this.jumpCallback, Key key}) : super(key: key); 11 | VoidCallback jumpCallback; 12 | 13 | @override 14 | TxtNormalSearchChildState createState() => TxtNormalSearchChildState(); 15 | } 16 | 17 | class TxtNormalSearchChildState extends State { 18 | String keyword; 19 | NormalSearchModel model; 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return ProviderWidget( 24 | initData: (model) { 25 | this.model = model; 26 | }, 27 | model: NormalSearchModel(), 28 | builder: (_, NormalSearchModel model, child) { 29 | print(model.isSuccess()); 30 | if (!model.isSuccess() && 31 | (keyword == null ? false : keyword.isNotEmpty)) { 32 | return CommonViewStateHelper( 33 | model: model, 34 | onEmptyPressed: () => loadData(keyword), 35 | onErrorPressed: () => loadData(keyword), 36 | onNoNetworkPressed: () => loadData(keyword), 37 | ); 38 | } 39 | 40 | return Padding( 41 | padding: EdgeInsets.symmetric(horizontal: 10), 42 | child: CustomFooterLoadMoreWidget( 43 | easyRefreshController: model.easyRefreshController, 44 | onLoadMore: (model.qty ?? 0) <= 36 45 | ? null 46 | : () async { 47 | await model.loadMoreNormalSearchData(); 48 | }, 49 | slivers: [ 50 | SliverToBoxAdapter( 51 | child: Padding( 52 | padding: EdgeInsets.symmetric(vertical: 15), 53 | child: Text.rich(TextSpan( 54 | text: "搜索到与", 55 | style: TextStyle( 56 | color: AppColor.black, 57 | fontSize: 17, 58 | ), 59 | children: [ 60 | TextSpan( 61 | text: "\"$keyword\"", 62 | style: TextStyle( 63 | color: AppColor.icon_yellow, 64 | ), 65 | ), 66 | TextSpan( 67 | text: "相关的", 68 | style: TextStyle( 69 | color: AppColor.black, 70 | ), 71 | ), 72 | TextSpan( 73 | text: model.qty.toString(), 74 | style: TextStyle( 75 | color: AppColor.icon_yellow, 76 | ), 77 | ), 78 | TextSpan( 79 | text: "条结果", 80 | style: TextStyle( 81 | color: AppColor.black, 82 | ), 83 | ), 84 | ], 85 | )), 86 | ), 87 | ), 88 | NormalSearchList( 89 | model.conditionSearchBeanDatas, 90 | jumpCallback: widget.jumpCallback, 91 | ), 92 | ], 93 | ), 94 | ); 95 | }, 96 | ); 97 | } 98 | 99 | void loadData(String keyword) { 100 | this.keyword = keyword; 101 | model.getNormalSearchApiData(keyword); 102 | } 103 | 104 | void resetData() { 105 | model.currentPage = 1; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /lib/ui/pages/splash_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuaishuaimovie/res/app_color.dart'; 3 | import 'package:shuaishuaimovie/routes/route_jump.dart'; 4 | 5 | class SplashPage extends StatefulWidget { 6 | @override 7 | _SplashPageState createState() => _SplashPageState(); 8 | } 9 | 10 | class _SplashPageState extends State 11 | with SingleTickerProviderStateMixin { 12 | AnimationController _animationController; 13 | Animation _animLeft; 14 | Animation _animRight; 15 | Gradient _textGradient; 16 | 17 | @override 18 | void initState() { 19 | super.initState(); 20 | 21 | _animationController = AnimationController( 22 | duration: Duration(milliseconds: 1000), vsync: this); 23 | 24 | Future.delayed(Duration(milliseconds: 300)).then((value) { 25 | _animationController.forward(); 26 | Future.delayed(Duration(milliseconds: 2000)).then((value) { 27 | jumpIndex(context, replace: true); 28 | }); 29 | }); 30 | 31 | _textGradient = LinearGradient( 32 | begin: Alignment.centerLeft, 33 | end: Alignment.centerRight, 34 | colors: [Colors.blue[300], Colors.blue]); 35 | } 36 | 37 | @override 38 | void didChangeDependencies() { 39 | super.didChangeDependencies(); 40 | 41 | final offset = getTextWidth("帅帅影视") - getTextWidth("Flutter"); 42 | final screenWidth = MediaQuery.of(context).size.width; 43 | _animLeft = Tween(begin: -screenWidth / 2 - offset / 2, end: 0.toDouble()) 44 | .animate(CurvedAnimation( 45 | parent: _animationController, curve: Curves.easeIn)); 46 | _animRight = Tween(begin: screenWidth / 2 - offset / 2, end: 0.toDouble()) 47 | .animate(CurvedAnimation( 48 | parent: _animationController, curve: Curves.easeIn)); 49 | } 50 | 51 | @override 52 | Widget build(BuildContext context) { 53 | return Scaffold( 54 | backgroundColor: AppColor.black, 55 | body: Container( 56 | color: AppColor.white, 57 | alignment: Alignment.center, 58 | child: Row( 59 | mainAxisSize: MainAxisSize.min, 60 | children: [ 61 | if (_animLeft != null) 62 | AnimatedBuilder( 63 | animation: _animLeft, 64 | builder: (BuildContext context, Widget child) { 65 | return Transform.translate( 66 | offset: Offset(_animLeft.value, 0), 67 | child: Text( 68 | 'Flutter', 69 | style: TextStyle( 70 | fontSize: 30, 71 | color: Colors.black, 72 | fontWeight: FontWeight.w600, 73 | ), 74 | ), 75 | ); 76 | }, 77 | ), 78 | SizedBox( 79 | width: 5, 80 | ), 81 | if (_animRight != null) _buildLogoTxt(), 82 | ], 83 | ), 84 | ), 85 | ); 86 | } 87 | 88 | _buildLogoTxt() { 89 | return AnimatedBuilder( 90 | animation: _animRight, 91 | builder: (BuildContext context, Widget child) { 92 | return Transform.translate( 93 | offset: Offset(_animRight.value, 0), 94 | child: Text.rich( 95 | TextSpan( 96 | text: "帅帅", 97 | style: TextStyle( 98 | foreground: Paint() 99 | ..shader = 100 | _textGradient.createShader(Rect.fromLTRB(0, 0, 40, 40)), 101 | fontSize: 30, 102 | fontWeight: FontWeight.w600, 103 | ), 104 | children: [ 105 | TextSpan( 106 | text: "影视", 107 | style: TextStyle( 108 | fontSize: 30, 109 | fontWeight: FontWeight.w600, 110 | foreground: Paint()..color = Colors.pink[400], 111 | ), 112 | ), 113 | ], 114 | ), 115 | ), 116 | ); 117 | }, 118 | ); 119 | } 120 | 121 | double getTextWidth(String text) { 122 | final textPainter = TextPainter( 123 | text: TextSpan( 124 | text: text, 125 | style: TextStyle( 126 | fontSize: 30, 127 | fontWeight: FontWeight.w600, 128 | ), 129 | ), 130 | textDirection: TextDirection.ltr, 131 | ); 132 | textPainter.layout(minWidth: 0, maxWidth: double.infinity); 133 | return textPainter.width; 134 | } 135 | 136 | @override 137 | void dispose() { 138 | _animationController.dispose(); 139 | super.dispose(); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /lib/ui/pages/video/video_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:shuaishuaimovie/provider/provider_widget.dart'; 4 | import 'package:shuaishuaimovie/res/app_color.dart'; 5 | import 'package:shuaishuaimovie/ui/helper/view_state_helper.dart'; 6 | import 'package:shuaishuaimovie/ui/pages/maintab/home/homewidget/home_detail_widget.dart'; 7 | import 'package:shuaishuaimovie/ui/pages/video/widget/shuai_video.dart'; 8 | import 'package:shuaishuaimovie/viewmodels/video/video_model.dart'; 9 | 10 | class VideoPage extends StatefulWidget { 11 | VideoPage({ 12 | @required this.videoId, 13 | @required this.videoUrl, 14 | @required this.videoName, 15 | @required this.videoLevel, 16 | @required this.playUrlType, 17 | @required this.playUrlIndex, 18 | @required this.isPositive, 19 | this.currentTime, 20 | }); 21 | 22 | String videoId; 23 | String videoUrl; 24 | String playUrlType; 25 | int playUrlIndex; 26 | String videoName; 27 | String videoLevel; 28 | String currentTime; 29 | String isPositive; 30 | 31 | static const String BASE_VIDEO_URL = "https://vip1.sp-flv.com/p2p/?v="; 32 | 33 | @override 34 | State createState() { 35 | return _VideoPageState(); 36 | } 37 | } 38 | 39 | class _VideoPageState extends State { 40 | @override 41 | void initState() { 42 | super.initState(); 43 | //将videoUrl 解码 44 | widget.videoUrl = Uri.decodeComponent(widget.videoUrl); 45 | } 46 | 47 | @override 48 | void dispose() { 49 | super.dispose(); 50 | } 51 | 52 | @override 53 | Widget build(BuildContext context) { 54 | return Scaffold( 55 | backgroundColor: AppColor.black, 56 | body: SafeArea( 57 | child: Container( 58 | color: AppColor.white, 59 | child: ProviderWidget( 60 | initData: (model) { 61 | loadData(model); 62 | }, 63 | model: VideoViewModel( 64 | videoId: widget.videoId, 65 | videoUrl: widget.videoUrl, 66 | playUrlType: widget.playUrlType, 67 | playUrlIndex: widget.playUrlIndex, 68 | videoName: widget.videoName, 69 | videoLevel: widget.videoLevel, 70 | currentTime: widget.currentTime, 71 | isPositive: widget.isPositive, 72 | ), 73 | builder: 74 | (BuildContext context, VideoViewModel model, Widget child) { 75 | return Column( 76 | children: [ 77 | ShuaiVideo(model), 78 | Expanded(child: _buildVideoSelectionContent(model)), 79 | ], 80 | ); 81 | }, 82 | ), 83 | ), 84 | ), 85 | ); 86 | } 87 | 88 | Widget _buildVideoSelectionContent(VideoViewModel model) { 89 | if (!model.isSuccess()) { 90 | return CommonViewStateHelper( 91 | model: model, 92 | onEmptyPressed: () => loadData(model), 93 | onErrorPressed: () => loadData(model), 94 | onNoNetworkPressed: () => loadData(model), 95 | ); 96 | } 97 | return SingleChildScrollView( 98 | physics: BouncingScrollPhysics(), 99 | child: Padding( 100 | padding: EdgeInsets.all(15), 101 | child: MovieSelectionModuleWidget( 102 | model.playUrls, 103 | selectedIndex: model.playUrlIndex, 104 | isPositive: model.isPositive, 105 | onAssembleTap: (index, isPositive) { 106 | model.changeVideo(index, isPositive); 107 | }, 108 | onSortTap: (index, flag) { 109 | model.playUrlIndex = index; 110 | model.setPositive(flag ? "1" : "0"); 111 | }, 112 | ), 113 | ), 114 | ); 115 | } 116 | 117 | Future loadData(VideoViewModel model) async { 118 | await model.getHomeDetailApiData(widget.videoId); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /lib/utils/app_info_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:package_info/package_info.dart'; 2 | 3 | class AppInfoUtil { 4 | static Future _initPageInfo() async { 5 | return await PackageInfo.fromPlatform(); 6 | } 7 | 8 | static Future getAppVersion() async { 9 | final PackageInfo packageInfo = await _initPageInfo(); 10 | return packageInfo.version; 11 | } 12 | } -------------------------------------------------------------------------------- /lib/utils/net/net_work.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:connectivity/connectivity.dart'; 3 | 4 | 5 | Future isConnected() async { 6 | var connectivityResult = await (Connectivity().checkConnectivity()); 7 | print(connectivityResult != ConnectivityResult.none); 8 | return connectivityResult != ConnectivityResult.none; 9 | } 10 | 11 | Future checkNetMobile() async { 12 | var connectivityResult = await (Connectivity().checkConnectivity()); 13 | return connectivityResult == ConnectivityResult.mobile; 14 | } 15 | 16 | Future checkNetWifi() async { 17 | var connectivityResult = await (Connectivity().checkConnectivity()); 18 | return connectivityResult == ConnectivityResult.wifi; 19 | } 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /lib/utils/str_util.dart: -------------------------------------------------------------------------------- 1 | class StrUtil { 2 | static isNotEmpty(String txt) { 3 | return txt != null && txt.length > 0; 4 | } 5 | } -------------------------------------------------------------------------------- /lib/utils/system/system_chrome.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:shuaishuaimovie/res/app_color.dart'; 4 | 5 | class MovieSystemChrome { 6 | static const SystemUiOverlayStyle statusDark = SystemUiOverlayStyle( 7 | statusBarColor: Color(0xFF000000), 8 | statusBarIconBrightness: Brightness.light, 9 | statusBarBrightness: Brightness.dark, 10 | ); 11 | 12 | static const SystemUiOverlayStyle statusTransparent = SystemUiOverlayStyle( 13 | statusBarColor: AppColor.transparent, 14 | statusBarBrightness: Brightness.dark, 15 | ); 16 | } -------------------------------------------------------------------------------- /lib/utils/time/movie_time_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:date_format/date_format.dart'; 2 | 3 | class MovieTimeUtil { 4 | static String getVodTime(int vodTime) { 5 | var date = 6 | DateTime.fromMillisecondsSinceEpoch(vodTime * 1000); 7 | return formatDate(date, [mm, '-', dd]); 8 | } 9 | 10 | static int currentTimeMillis() { 11 | return DateTime.now().millisecondsSinceEpoch; 12 | } 13 | } -------------------------------------------------------------------------------- /lib/viewmodels/app_theme_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:shuaishuaimovie/res/app_color.dart'; 3 | 4 | class AppTheme extends ChangeNotifier{ 5 | Color iconThemeColor = AppColor.default_icon_grey; 6 | Color txtColor = AppColor.default_txt_grey; 7 | 8 | void setIconThemeColor(Color iconThemeColor) { 9 | this.iconThemeColor = iconThemeColor; 10 | } 11 | 12 | void setTxtColor(Color txtColor) { 13 | this.txtColor = txtColor; 14 | } 15 | 16 | void setHomeDetailPageThemeColor() { 17 | setIconThemeColor(AppColor.white); 18 | setTxtColor(AppColor.white); 19 | } 20 | 21 | void setHomePageThemeColor() { 22 | setIconThemeColor(AppColor.default_icon_grey); 23 | setTxtColor(AppColor.default_txt_grey); 24 | } 25 | 26 | void setDynamicActionBarColor({Color iconThemeColor, Color txtColor}) { 27 | setIconThemeColor(iconThemeColor ?? AppColor.default_icon_grey); 28 | setTxtColor(txtColor ?? AppColor.default_txt_grey); 29 | } 30 | } -------------------------------------------------------------------------------- /lib/viewmodels/base_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:shuaishuaimovie/net/base_repository.dart'; 4 | import 'package:shuaishuaimovie/utils/net/net_work.dart' as net; 5 | 6 | import '../provider/view_state.dart'; 7 | 8 | /// 父类ViewModel 9 | abstract class BaseViewModel with ChangeNotifier { 10 | /// 防止页面销毁后,异步任务才完成,导致报错 11 | bool disposed = false; 12 | 13 | /// 初始化状态为加载中 14 | ViewState _state = ViewState.loading; 15 | 16 | ViewState get state => _state; 17 | 18 | /// 错误状态类 19 | ViewStateError _viewStateError; 20 | 21 | ViewStateError get viewStateError => _viewStateError; 22 | 23 | T mRepository; 24 | 25 | BaseViewModel({ViewState state}) { 26 | this._state = state ?? ViewState.loading; 27 | mRepository = createRepository(); 28 | } 29 | 30 | /// 通用请求数据方法 子类可以复写 31 | Future requestData(dynamic f) async { 32 | var result; 33 | try { 34 | setLoading(); 35 | result = await f; 36 | if(result == null) setError(Error()); 37 | } catch (e) { 38 | setError(Error(), message: "请求失败"); 39 | throw Exception(e); 40 | } 41 | return result; 42 | } 43 | 44 | Future requestNoStatusData(dynamic f) async { 45 | var result; 46 | try { 47 | result = await f; 48 | } catch (e) { 49 | throw Exception(e); 50 | } 51 | return result; 52 | } 53 | 54 | Future checkNet() async { 55 | return await net.isConnected(); 56 | } 57 | 58 | /// 提供一个创建Repository对象的抽象方法T createRepository(); 59 | T createRepository() { 60 | } 61 | 62 | @override 63 | void notifyListeners() { 64 | if (!disposed) { 65 | super.notifyListeners(); 66 | } 67 | } 68 | 69 | @override 70 | void dispose() { 71 | disposed = true; 72 | super.dispose(); 73 | } 74 | 75 | /// 初始化状态 76 | void setLoading() { 77 | debugPrint("build_${this.toString()}_loading"); 78 | setState(ViewState.loading); 79 | } 80 | 81 | /// 数据成功不为空 82 | void setSuccess() { 83 | debugPrint("build_${this.toString()}_success"); 84 | setState(ViewState.success); 85 | } 86 | 87 | /// 数据成功且为空 88 | void setEmpty() { 89 | debugPrint("build_${this.toString()}_empty"); 90 | setState(ViewState.empty); 91 | } 92 | 93 | /// 数据异常 94 | void setError(e, {String message}) { 95 | debugPrint("build_${this.toString()}_error"); 96 | if (e is DioError) { 97 | e = e.error; 98 | message = e.message; 99 | } 100 | _viewStateError = new ViewStateError(error: e, message: message); 101 | setState(ViewState.error); 102 | } 103 | 104 | ///没有网络 105 | void setNoNetWork() { 106 | debugPrint("build_${this.toString()}_noNetwork"); 107 | setState(ViewState.noNetwork); 108 | } 109 | 110 | /// 设置状态改变 111 | void setState(ViewState state) { 112 | this._state = state; 113 | notifyListeners(); 114 | } 115 | 116 | /// 加载中状态 117 | bool isLoading() { 118 | return this._state == ViewState.loading; 119 | } 120 | 121 | /// 数据为空状态 122 | bool isEmpty() { 123 | return this._state == ViewState.empty; 124 | } 125 | 126 | /// 数据异常状态 127 | bool isError() { 128 | return this._state == ViewState.error; 129 | } 130 | 131 | /// 数据加载成功状态 不为空 132 | bool isSuccess() { 133 | return this._state == ViewState.success; 134 | } 135 | 136 | bool isNoNetWork() { 137 | return this._state == ViewState.noNetwork; 138 | } 139 | 140 | /// 是否是成功显示数据状态 141 | /// false 表示数据状态为加载中 数据为空 或者数据加载失败 142 | /// true 数据成功 or 刷新状态 or 加载更多状态 143 | bool isSuccessShowDataState() { 144 | return isSuccess(); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /lib/viewmodels/base_refresh_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 2 | import 'package:shuaishuaimovie/net/base_repository.dart'; 3 | import 'package:shuaishuaimovie/viewmodels/base_model.dart'; 4 | 5 | abstract class BaseRefreshViewModel 6 | extends BaseViewModel { 7 | 8 | EasyRefreshController _easyRefreshController = EasyRefreshController(); 9 | EasyRefreshController get easyRefreshController => _easyRefreshController; 10 | 11 | /// 通用请求数据方法 子类可以复写 12 | Future refreshData(Future requestApi) async { 13 | var result; 14 | try { 15 | result = await requestApi; 16 | } catch (e) { 17 | print(e.toString()); 18 | } 19 | return result; 20 | } 21 | 22 | Future loadMoreData(Future requestApi) async { 23 | var result; 24 | try { 25 | result = await requestApi; 26 | } catch (e) { 27 | print(e.toString()); 28 | } 29 | return result; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /lib/viewmodels/hot/hot_rank_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/models/hot_rank_bean_entity.dart'; 2 | import 'package:shuaishuaimovie/net/request.dart'; 3 | import 'package:shuaishuaimovie/viewmodels/base_refresh_model.dart'; 4 | 5 | abstract class HotRankModel extends BaseRefreshViewModel { 6 | HotRankBeanEntity _hotRankBeanEntity; 7 | HotRankBeanEntity get hotRankBeanEntity => _hotRankBeanEntity; 8 | 9 | Future getRankApiData() async { 10 | _hotRankBeanEntity = await requestData(requestRankTypeData()); 11 | if (_hotRankBeanEntity?.status == 0) { 12 | setSuccess(); 13 | } else { 14 | setError(new Error(), message: "请求失败"); 15 | } 16 | } 17 | 18 | Future refreshRankData() async { 19 | var hotRankBeanEntity = await refreshData(requestRankTypeData()); 20 | if (hotRankBeanEntity != null && hotRankBeanEntity.status == 0) { 21 | _hotRankBeanEntity = hotRankBeanEntity; 22 | setSuccess(); 23 | easyRefreshController.resetLoadState(); 24 | easyRefreshController.finishRefresh(success: true); 25 | } else { 26 | easyRefreshController.resetLoadState(); 27 | easyRefreshController.finishRefresh(success: false); 28 | } 29 | } 30 | 31 | requestRankTypeData(); 32 | 33 | @override 34 | MovieRepository createRepository() { 35 | return MovieRepository(); 36 | } 37 | } 38 | 39 | class WeekRankModel extends HotRankModel { 40 | 41 | @override 42 | requestRankTypeData() { 43 | return mRepository.requestRankWeek(); 44 | } 45 | } 46 | 47 | class MonthRankModel extends HotRankModel { 48 | @override 49 | requestRankTypeData() { 50 | return mRepository.requestRankMonth(); 51 | } 52 | } 53 | 54 | class TotalRankModel extends HotRankModel { 55 | @override 56 | requestRankTypeData() { 57 | return mRepository.requestRankTotal(); 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /lib/viewmodels/hot/hot_update_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/models/common_item_bean_entity.dart'; 2 | import 'package:shuaishuaimovie/models/hot_update_bean_entity.dart'; 3 | import 'package:shuaishuaimovie/net/request.dart'; 4 | import 'package:shuaishuaimovie/viewmodels/base_refresh_model.dart'; 5 | 6 | class HotUpdateModel extends BaseRefreshViewModel { 7 | HotUpdateBeanEntity _hotUpdateBeanEntity; 8 | 9 | List get hotUpdateBeanDatas => _hotUpdateBeanEntity.data; 10 | 11 | int currentPage = 0; 12 | 13 | List get currentHotDatas => _currentHotDatas; 14 | List _currentHotDatas; 15 | 16 | int get hotUpdateTotals => hotUpdateBeanDatas.length ~/ 20; 17 | 18 | Future getHotUpdateApiData() async { 19 | _hotUpdateBeanEntity = await requestData(mRepository.requestHotUpdate()); 20 | if (_hotUpdateBeanEntity?.status == 0) { 21 | refreshLocalData(true); 22 | setSuccess(); 23 | } else { 24 | setError(new Error(), message: "请求失败"); 25 | } 26 | } 27 | 28 | Future refreshHotUpdateData() async { 29 | var hotUpdateBeanEntity = await refreshData(mRepository.requestHotUpdate()); 30 | if (hotUpdateBeanEntity != null && hotUpdateBeanEntity.status == 0) { 31 | _hotUpdateBeanEntity = hotUpdateBeanEntity; 32 | refreshLocalData(true); 33 | setSuccess(); 34 | easyRefreshController.resetLoadState(); 35 | easyRefreshController.finishRefresh(success: true); 36 | } else { 37 | easyRefreshController.resetLoadState(); 38 | easyRefreshController.finishRefresh(success: false); 39 | } 40 | } 41 | 42 | void refreshLocalData([bool initFlag = false]) { 43 | if (initFlag) { 44 | currentPage = 0; 45 | _currentHotDatas = hotUpdateBeanDatas.sublist(0, 20); 46 | return; 47 | } 48 | currentPage = currentPage >= hotUpdateTotals - 1 ? 0 : currentPage + 1; 49 | _currentHotDatas = 50 | hotUpdateBeanDatas.sublist(currentPage * 20, currentPage * 20 + 20); 51 | notifyListeners(); 52 | } 53 | 54 | @override 55 | MovieRepository createRepository() { 56 | return MovieRepository(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/viewmodels/search/txt_auto_search_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/models/common_item_bean_entity.dart'; 2 | import 'package:shuaishuaimovie/models/txt_auto_search_bean_entity.dart'; 3 | import 'package:shuaishuaimovie/net/request.dart'; 4 | import 'package:shuaishuaimovie/viewmodels/base_model.dart'; 5 | 6 | class TxtAutoSearchModel extends BaseViewModel { 7 | TxtAutoSearchBeanEntity _autoSearchBeanEntity; 8 | 9 | List get txtAutoSearchBeanDatas => 10 | _autoSearchBeanEntity?.data; 11 | 12 | Future getTxtAutoSearchApiData(String keyword) async { 13 | _autoSearchBeanEntity = 14 | await requestNoStatusData(mRepository.requestAutoSearch(keyword)); 15 | if (_autoSearchBeanEntity?.status == 0) { 16 | setSuccess(); 17 | } 18 | } 19 | 20 | void clearData() { 21 | _autoSearchBeanEntity = null; 22 | } 23 | 24 | List processedTxt(String str, String highTxt) { 25 | List list = List(); 26 | if(str.contains(highTxt)) { 27 | var preTxt = str.split(highTxt)[0]; 28 | return list..add(preTxt)..add(highTxt)..add( 29 | str.replaceFirst(preTxt + highTxt, "")); 30 | } else { 31 | return list..add(str); 32 | } 33 | } 34 | 35 | @override 36 | MovieRepository createRepository() { 37 | return MovieRepository(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/viewmodels/search/txt_history_search_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/database/sqf_provider.dart'; 2 | import 'package:shuaishuaimovie/viewmodels/base_model.dart'; 3 | import 'package:shuaishuaimovie/database/bean/search_history_bean.dart' 4 | as historySearch; 5 | 6 | class TxtHistorySearchModel extends BaseViewModel { 7 | List> _historySearchList; 8 | bool _isShowCloseIcon = false; 9 | 10 | String get columnName => historySearch.columnName; 11 | List> get historySearchList => _historySearchList; 12 | bool get isShowCloseIcon => _isShowCloseIcon; 13 | 14 | void getLocalHistorySearchData() async { 15 | await SqfProvider.db.query(historySearch.tableName, orderBy: 'id desc').then((value) { 16 | _historySearchList = List.from(value); 17 | notifyListeners(); 18 | }); 19 | 20 | } 21 | 22 | void delSearchHistoryData(int index) { 23 | SqfProvider.db.delete(historySearch.tableName, 24 | where: 'id=${_historySearchList[index]['id']}'); 25 | _historySearchList.removeAt(index); 26 | if (_historySearchList.length == 0) { 27 | changeShowCloseStatus(); 28 | } 29 | } 30 | 31 | void delSearchHistoryAllDatas() { 32 | SqfProvider.db.delete(historySearch.tableName); 33 | _historySearchList.clear(); 34 | changeShowCloseStatus(); 35 | } 36 | 37 | void changeShowCloseStatus() { 38 | _isShowCloseIcon = !_isShowCloseIcon; 39 | notifyListeners(); 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /lib/viewmodels/search/txt_normal_search_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/database/bean/search_history_bean.dart'; 2 | import 'package:shuaishuaimovie/database/bean/search_history_bean.dart' 3 | as historySearch; 4 | import 'package:shuaishuaimovie/database/sqf_provider.dart'; 5 | import 'package:shuaishuaimovie/models/common_item_bean_entity.dart'; 6 | import 'package:shuaishuaimovie/models/condition_search_bean_entity.dart'; 7 | import 'package:shuaishuaimovie/net/request.dart'; 8 | import 'package:shuaishuaimovie/ui/helper/db/db_operate.dart'; 9 | import 'package:shuaishuaimovie/viewmodels/base_refresh_model.dart'; 10 | 11 | class NormalSearchModel extends BaseRefreshViewModel { 12 | ConditionSearchBeanEntity _conditionSearchBeanEntity; 13 | 14 | List get conditionSearchBeanDatas => 15 | _conditionSearchBeanEntity?.data; 16 | 17 | int get qty => _conditionSearchBeanEntity?.qty ?? 0; 18 | 19 | int currentPage = 1; 20 | bool isLoadingMore = false; 21 | String keyword; 22 | 23 | Future getNormalSearchApiData(String keyword) async { 24 | this.keyword = keyword; 25 | _conditionSearchBeanEntity = 26 | await requestData(mRepository.requestNormalSearch( 27 | keyword: keyword, 28 | page: currentPage.toString(), 29 | )); 30 | 31 | if (_conditionSearchBeanEntity?.status == 0) { 32 | if (_conditionSearchBeanEntity.data == null) { 33 | setEmpty(); 34 | } else { 35 | //将搜索成功的数据存入数据库中 36 | insertHistorySearchDBTxt(keyword); 37 | setSuccess(); 38 | } 39 | } else { 40 | setError(new Error(), message: "请求失败"); 41 | } 42 | } 43 | 44 | Future loadMoreNormalSearchData() async { 45 | if (isLoadingMore) return; 46 | 47 | //默认每一页36条数据 48 | if (currentPage >= (qty / 36.toDouble()).ceil()) { 49 | easyRefreshController.resetLoadState(); 50 | easyRefreshController.finishLoad(noMore: true); 51 | return; 52 | } 53 | 54 | isLoadingMore = true; 55 | var conditionSearchBeanEntity = 56 | await loadMoreData(mRepository.requestNormalSearch( 57 | keyword: keyword, 58 | page: (currentPage + 1).toString(), 59 | )); 60 | if (conditionSearchBeanEntity?.status == 0) { 61 | _conditionSearchBeanEntity.data.addAll(conditionSearchBeanEntity.data); 62 | currentPage++; 63 | easyRefreshController.resetLoadState(); 64 | easyRefreshController.finishLoad(success: true); 65 | } else { 66 | easyRefreshController.resetLoadState(); 67 | easyRefreshController.finishLoad(success: false); 68 | } 69 | isLoadingMore = false; 70 | notifyListeners(); 71 | } 72 | 73 | @override 74 | MovieRepository createRepository() { 75 | return MovieRepository(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/viewmodels/tab/commontab/common_tab_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/models/common_tab_item_bean_entity.dart'; 2 | import 'package:shuaishuaimovie/net/request.dart'; 3 | import 'package:shuaishuaimovie/viewmodels/base_refresh_model.dart'; 4 | 5 | abstract class CommonTabModel extends BaseRefreshViewModel { 6 | CommonTabItemBeanEntity _commonTabItemBeanEntity; 7 | 8 | CommonTabItemBeanEntity get commonTabItemBeanEntity => 9 | _commonTabItemBeanEntity; 10 | 11 | Future getCommonTabApiData() async { 12 | _commonTabItemBeanEntity = await requestData(requestTabTypeData()); 13 | if (_commonTabItemBeanEntity?.status == 0) { 14 | setSuccess(); 15 | } else { 16 | setError(new Error(), message: "请求失败"); 17 | } 18 | } 19 | 20 | Future refreshCommonTabData() async { 21 | var commonTabItemBeanEntity = await refreshData(requestTabTypeData()); 22 | if (commonTabItemBeanEntity != null && 23 | commonTabItemBeanEntity.status == 0) { 24 | _commonTabItemBeanEntity = commonTabItemBeanEntity; 25 | setSuccess(); 26 | easyRefreshController.resetLoadState(); 27 | easyRefreshController.finishRefresh(success: true); 28 | } else { 29 | easyRefreshController.resetLoadState(); 30 | easyRefreshController.finishRefresh(success: false); 31 | } 32 | } 33 | 34 | requestTabTypeData(); 35 | 36 | @override 37 | MovieRepository createRepository() { 38 | return MovieRepository(); 39 | } 40 | } 41 | 42 | class MovieTabModel extends CommonTabModel { 43 | @override 44 | requestTabTypeData() { 45 | return mRepository.requestMovie(); 46 | } 47 | } 48 | 49 | class TeleplayTabModel extends CommonTabModel { 50 | @override 51 | requestTabTypeData() { 52 | return mRepository.requestTeleplay(); 53 | } 54 | } 55 | 56 | class ShowTabModel extends CommonTabModel { 57 | @override 58 | requestTabTypeData() { 59 | return mRepository.requestShow(); 60 | } 61 | } 62 | 63 | class CartoonModel extends CommonTabModel { 64 | @override 65 | requestTabTypeData() { 66 | return mRepository.requestCartoon(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/viewmodels/tab/home/home_detail_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuaishuaimovie/models/common_item_bean_entity.dart'; 3 | import 'package:shuaishuaimovie/models/home_detail_bean_entity.dart'; 4 | import 'package:shuaishuaimovie/net/request.dart'; 5 | import 'package:shuaishuaimovie/res/app_color.dart'; 6 | import 'package:shuaishuaimovie/viewmodels/base_model.dart'; 7 | 8 | class HomeDetailViewModel extends BaseViewModel { 9 | HomeDetailBeanEntity _homeDetailBeanEntity; 10 | String _playUrlType; 11 | 12 | HomeDetailBeanEntity get homeDetailBeanEntity => _homeDetailBeanEntity; 13 | 14 | HomeDetailBeanVod get homeDetailBeanVod => _homeDetailBeanEntity.vod; 15 | 16 | List get homeDetailBeanRands => _homeDetailBeanEntity.rand; 17 | 18 | List get homeDetailBeanRelate => _homeDetailBeanEntity.relate; 19 | 20 | String get playUrlType => _playUrlType; 21 | 22 | List get playUrls { 23 | //todo临时解决的,后期在通过数据看看能不能进一步优化。因为数据是抓的,所以先遍历最常用的 24 | var list; 25 | _playUrlType = "bjm3u8"; 26 | list = homeDetailBeanVod.vodPlayUrls.toJson()[_playUrlType]; 27 | if(list != null) return list; 28 | 29 | _playUrlType = "zuidam3u8"; 30 | list = homeDetailBeanVod.vodPlayUrls.toJson()[_playUrlType]; 31 | if(list != null) return list; 32 | 33 | _playUrlType = "zkm3u8"; 34 | list = homeDetailBeanVod.vodPlayUrls.toJson()[_playUrlType]; 35 | if(list != null) return list; 36 | 37 | _playUrlType = "dbm3u8"; 38 | list = homeDetailBeanVod.vodPlayUrls.toJson()[_playUrlType]; 39 | if(list != null) return list; 40 | 41 | _playUrlType = homeDetailBeanVod.vodPlayServer?.last?.iD; 42 | return _playUrlType == null ? null : homeDetailBeanVod.vodPlayUrls.toJson()[_playUrlType]; 43 | 44 | } 45 | 46 | Color bgColor = AppColor.white; 47 | 48 | @override 49 | MovieRepository createRepository() { 50 | return MovieRepository(); 51 | } 52 | 53 | Future getHomeDetailApiData(String vodId) async { 54 | if (!await checkNet()) { 55 | setNoNetWork(); 56 | return null; 57 | } 58 | _homeDetailBeanEntity = 59 | await requestData(mRepository.requestHomeDetail(vodId)); 60 | if (_homeDetailBeanEntity?.status == 0) { 61 | setSuccess(); 62 | } else { 63 | setError(new Error(), message: "请求失败"); 64 | } 65 | } 66 | 67 | void setBgColor(Color bgColor) { 68 | this.bgColor = bgColor; 69 | notifyListeners(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/viewmodels/tab/home/home_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/models/common_item_bean_entity.dart'; 2 | import 'package:shuaishuaimovie/models/home_list_bean_entity.dart'; 3 | import 'package:shuaishuaimovie/net/request.dart'; 4 | import 'package:shuaishuaimovie/viewmodels/base_refresh_model.dart'; 5 | 6 | class HomeViewModel extends BaseRefreshViewModel { 7 | HomeListBeanEntity _homeListBeanEntity; 8 | 9 | HomeListBeanEntity get homeListBeanEntity => _homeListBeanEntity; 10 | 11 | List get homeHotBeans => _homeListBeanEntity.h; 12 | 13 | List get homeMovieBeans => _homeListBeanEntity.m; 14 | 15 | List get homeSitcomBeans => _homeListBeanEntity.t; 16 | 17 | List get homeVarietyBeans => _homeListBeanEntity.s; 18 | 19 | List get homeAnimatedBeans => _homeListBeanEntity.c; 20 | 21 | //用来重置home页面的最热数据, 22 | bool isRequestApi = false; 23 | 24 | void setRequestApi(bool isRequestApi) { 25 | this.isRequestApi = isRequestApi; 26 | } 27 | 28 | getHomeListApiData() async { 29 | _homeListBeanEntity = await requestData(mRepository.requestHomeList()); 30 | if (_homeListBeanEntity?.status == 0) { 31 | isRequestApi = true; 32 | setSuccess(); 33 | } else { 34 | setError(new Error(), message: "请求失败"); 35 | } 36 | } 37 | 38 | Future refreshHomeListData() async { 39 | var homeListBeanEntity = await refreshData(mRepository.requestHomeList()); 40 | if (homeListBeanEntity != null && homeListBeanEntity.status == 0) { 41 | _homeListBeanEntity = homeListBeanEntity; 42 | isRequestApi = true; 43 | setSuccess(); 44 | easyRefreshController.resetLoadState(); 45 | easyRefreshController.finishRefresh(success: true); 46 | } else { 47 | easyRefreshController.resetLoadState(); 48 | easyRefreshController.finishRefresh(success: false); 49 | } 50 | } 51 | 52 | 53 | @override 54 | MovieRepository createRepository() { 55 | return MovieRepository(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/viewmodels/tab/home/index_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | class IndexModel with ChangeNotifier { 4 | PageController _pageController; 5 | int navBarCurrentIndex = 0; 6 | bool isShowIndexAppBar = true; 7 | 8 | void setNavBarCurrentIndex(int navBarCurrentIndex) { 9 | this.navBarCurrentIndex = navBarCurrentIndex; 10 | isShowIndexAppBar = this.navBarCurrentIndex != 4; 11 | _pageController.jumpToPage(navBarCurrentIndex); 12 | notifyListeners(); 13 | } 14 | 15 | IndexModel(this._pageController); 16 | } -------------------------------------------------------------------------------- /lib/viewmodels/video/video_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:shuaishuaimovie/models/home_detail_bean_entity.dart'; 2 | import 'package:shuaishuaimovie/net/request.dart'; 3 | import 'package:shuaishuaimovie/viewmodels/base_model.dart'; 4 | 5 | class VideoViewModel extends BaseViewModel { 6 | String videoId; 7 | String videoUrl; 8 | String playUrlType; 9 | int playUrlIndex; 10 | String videoName; 11 | String videoLevel; 12 | String currentTime; 13 | String isPositive; 14 | 15 | VideoViewModel({this.videoId, this.videoUrl, this.playUrlType, this.playUrlIndex, this.videoName, this.videoLevel, this.currentTime, this.isPositive}); 16 | 17 | HomeDetailBeanEntity _homeDetailBeanEntity; 18 | 19 | HomeDetailBeanEntity get homeDetailBeanEntity => _homeDetailBeanEntity; 20 | 21 | HomeDetailBeanVod get homeDetailBeanVod => _homeDetailBeanEntity?.vod; 22 | 23 | List get playUrls { 24 | return homeDetailBeanVod.vodPlayUrls.toJson()[playUrlType]; 25 | } 26 | 27 | String get videoTitle { 28 | if(homeDetailBeanVod == null) return ""; 29 | final tempPlayUrls = 30 | (isPositive == "0" ? false : true) ? playUrls.reversed.toList() : playUrls; 31 | return homeDetailBeanVod.vodName + " " + tempPlayUrls[playUrlIndex][0]; 32 | } 33 | 34 | void changeVideo(int index, bool isPositive) { 35 | final tempPlayUrls = 36 | isPositive ? playUrls.reversed.toList() : playUrls; 37 | 38 | playUrlIndex = index; 39 | videoUrl = tempPlayUrls[index][1]; 40 | videoLevel = tempPlayUrls[index][0]; 41 | 42 | notifyListeners(); 43 | } 44 | 45 | Future getHomeDetailApiData(String vodId) async { 46 | if (!await checkNet()) { 47 | setNoNetWork(); 48 | return null; 49 | } 50 | _homeDetailBeanEntity = 51 | await requestData(mRepository.requestHomeDetail(vodId)); 52 | if (_homeDetailBeanEntity?.status == 0) { 53 | setSuccess(); 54 | } else { 55 | setError(new Error(), message: "请求失败"); 56 | } 57 | } 58 | 59 | void setPositive(String isPositive) { 60 | this.isPositive = isPositive; 61 | } 62 | 63 | @override 64 | MovieRepository createRepository() { 65 | return MovieRepository(); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /lib/widgets/checkicon.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuaishuaimovie/res/app_color.dart'; 3 | 4 | class CheckIcon extends StatefulWidget { 5 | bool value; 6 | ValueChanged onChanged; 7 | IconData iconData; 8 | Color checkColor; 9 | Color activeColor; 10 | 11 | CheckIcon({ 12 | @required this.value, 13 | @required this.iconData, 14 | this.onChanged, 15 | this.activeColor, 16 | this.checkColor, 17 | }); 18 | 19 | @override 20 | _CheckIconState createState() => _CheckIconState(); 21 | } 22 | 23 | class _CheckIconState extends State { 24 | @override 25 | Widget build(BuildContext context) { 26 | widget.checkColor = widget.checkColor ?? AppColor.default_txt_grey; 27 | widget.activeColor = widget.activeColor ?? Theme.of(context).accentColor; 28 | 29 | return GestureDetector( 30 | behavior: HitTestBehavior.opaque, 31 | onTap: () { 32 | setState(() { 33 | widget.value = !widget.value; 34 | print(widget.value); 35 | if (widget.onChanged != null) widget.onChanged(widget.value); 36 | }); 37 | }, 38 | child: Padding( 39 | padding: EdgeInsets.all(5), 40 | child: Icon( 41 | widget.iconData, 42 | size: 20, 43 | color: widget.value ? widget.activeColor : widget.checkColor, 44 | ), 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/widgets/clip_widget.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:math'; 3 | 4 | import 'package:flutter/material.dart'; 5 | 6 | class CirclePath extends CustomClipper { 7 | CirclePath(this.value); 8 | 9 | final double value; 10 | 11 | @override 12 | Path getClip(Size size) { 13 | var path = Path(); 14 | double radius = 15 | value * sqrt(size.height * size.height + size.width * size.width); 16 | path.addOval(Rect.fromLTRB( 17 | size.width - radius, -radius, size.width + radius, radius)); 18 | return path; 19 | } 20 | 21 | @override 22 | bool shouldReclip(CustomClipper oldClipper) { 23 | return true; 24 | } 25 | } -------------------------------------------------------------------------------- /lib/widgets/custom_chewie_overlay_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuaishuaimovie/res/app_color.dart'; 3 | import 'package:shuaishuaimovie/widgets/text_widget.dart'; 4 | 5 | class CustomChewieOverlayWidget extends StatelessWidget { 6 | CustomChewieOverlayWidget({this.onTap, this.tipsMsg, this.tapMsg}); 7 | VoidCallback onTap; 8 | String tipsMsg; 9 | String tapMsg; 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Container( 14 | color: AppColor.black, 15 | alignment: Alignment.center, 16 | child: Column( 17 | mainAxisSize: MainAxisSize.min, 18 | children: [ 19 | CommonText( 20 | tipsMsg, 21 | txtSize: 14, 22 | txtColor: AppColor.white, 23 | ), 24 | GestureDetector( 25 | onTap: onTap, 26 | child: Container( 27 | margin: EdgeInsets.only(top: 20), 28 | padding: EdgeInsets.symmetric(vertical: 7, horizontal: 20), 29 | decoration: BoxDecoration( 30 | borderRadius: BorderRadius.circular(15), 31 | color: AppColor.grey.withOpacity(.5)), 32 | child: CommonText( 33 | tapMsg, 34 | txtSize: 14, 35 | txtColor: AppColor.white, 36 | ), 37 | ), 38 | ), 39 | ], 40 | ), 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/widgets/custom_progress_paint.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:shuaishuaimovie/res/app_color.dart'; 4 | 5 | class CustomProgressPaint extends CustomPainter { 6 | CustomProgressPaint({ 7 | this.currentProgress = 0, 8 | this.bgColor = AppColor.grey, 9 | this.progressColor = AppColor.icon_yellow, 10 | }); 11 | int currentProgress = 0; 12 | Color bgColor; 13 | Color progressColor; 14 | 15 | @override 16 | void paint(Canvas canvas, Size size) { 17 | double eWidth = size.width; 18 | double eHeight = size.height; 19 | 20 | var bgPaint = Paint() 21 | ..isAntiAlias = true 22 | ..style = PaintingStyle.fill 23 | ..color = bgColor; 24 | 25 | //绘制背景色 26 | canvas.drawRect(Offset.zero & size, bgPaint); 27 | 28 | //绘制进度 29 | var progressPaint = bgPaint..color = progressColor; 30 | canvas.drawRect(Offset.zero & Size(eWidth * currentProgress / 100.0, eHeight), progressPaint); 31 | 32 | } 33 | 34 | @override 35 | bool shouldRepaint(CustomProgressPaint oldDelegate) { 36 | return oldDelegate.currentProgress != this.currentProgress; 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /lib/widgets/custom_refresh_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:date_format/date_format.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 4 | 5 | typedef OnRefreshCallback = Future Function(); 6 | typedef OnLoadMoreCallback = Future Function(); 7 | 8 | class CustomHeaderRefreshWidget extends StatelessWidget { 9 | CustomHeaderRefreshWidget({@required this.slivers, @required this.onRefresh, @required this.easyRefreshController}); 10 | List slivers; 11 | OnRefreshCallback onRefresh; 12 | EasyRefreshController easyRefreshController; 13 | 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | debugPrint("build_header_refresh"); 18 | return CustomCommonRefreshWidget(slivers: slivers, easyRefreshController: easyRefreshController, onRefresh: onRefresh, enableRefresh: true); 19 | } 20 | } 21 | 22 | class CustomFooterLoadMoreWidget extends StatelessWidget { 23 | CustomFooterLoadMoreWidget({@required this.slivers, @required this.onLoadMore, @required this.easyRefreshController, }); 24 | List slivers; 25 | OnLoadMoreCallback onLoadMore; 26 | EasyRefreshController easyRefreshController; 27 | 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | debugPrint("build_footer_onLoad"); 32 | return CustomCommonRefreshWidget(slivers: slivers, easyRefreshController: easyRefreshController, onLoadMore: onLoadMore, enableLoad: true,); 33 | } 34 | } 35 | 36 | class CustomCommonRefreshWidget extends StatefulWidget { 37 | CustomCommonRefreshWidget({@required this.slivers, @required this.easyRefreshController, this.onRefresh, this.onLoadMore, this.enableRefresh = false, this.enableLoad = false}); 38 | List slivers; 39 | bool enableRefresh; 40 | bool enableLoad; 41 | int count; 42 | OnRefreshCallback onRefresh; 43 | OnLoadMoreCallback onLoadMore; 44 | EasyRefreshController easyRefreshController; 45 | 46 | @override 47 | _CustomCommonRefreshWidgetState createState() => _CustomCommonRefreshWidgetState(); 48 | } 49 | 50 | class _CustomCommonRefreshWidgetState extends State { 51 | _CustomCommonRefreshWidgetState(); 52 | // 反向 53 | bool _reverse = false; 54 | // 方向 55 | Axis _direction = Axis.vertical; 56 | // Header浮动 57 | bool _headerFloat = false; 58 | // 无限加载 59 | bool _enableInfiniteLoad = true; 60 | // 控制结束 61 | bool _enableControlFinish = false; 62 | // 任务独立 63 | bool _taskIndependence = false; 64 | // 震动 65 | bool _vibration = true; 66 | // 是否开启刷新 67 | bool _enableRefresh = true; 68 | // 是否开启加载 69 | bool _enableLoad = true; 70 | // 顶部回弹 71 | bool _topBouncing = true; 72 | // 底部回弹 73 | bool _bottomBouncing = true; 74 | 75 | ScrollController _scrollController; 76 | 77 | @override 78 | void initState() { 79 | super.initState(); 80 | _scrollController = ScrollController(); 81 | 82 | } 83 | @override 84 | Widget build(BuildContext context) { 85 | 86 | return EasyRefresh.custom( 87 | enableControlFinishRefresh: true, 88 | enableControlFinishLoad: true, 89 | taskIndependence: _taskIndependence, 90 | controller: widget.easyRefreshController, 91 | scrollController: _scrollController, 92 | reverse: _reverse, 93 | scrollDirection: _direction, 94 | topBouncing: _topBouncing, 95 | bottomBouncing: _bottomBouncing, 96 | header: widget.enableRefresh ? ClassicalHeader( 97 | enableInfiniteRefresh: false, 98 | bgColor: _headerFloat ? Theme.of(context).primaryColor : null, 99 | infoColor: _headerFloat ? Colors.black87 : Colors.teal, 100 | float: _headerFloat, 101 | enableHapticFeedback: _vibration, 102 | refreshText: "下拉刷新...", 103 | refreshReadyText: "释放即可刷新...", 104 | refreshingText: "加载中...", 105 | refreshedText: "加载成功...", 106 | refreshFailedText: "加载失败...", 107 | noMoreText: "没有更多数据了...", 108 | infoText: "更新时间: ${getCurrentTime()}", 109 | ) : null, 110 | footer: widget.enableLoad ? ClassicalFooter( 111 | enableInfiniteLoad: _enableInfiniteLoad, 112 | enableHapticFeedback: _vibration, 113 | loadText: "加载中...", 114 | loadReadyText: "加载中...", 115 | loadingText: "加载中...", 116 | loadedText: "加载成功...", 117 | loadFailedText: "加载失败...", 118 | noMoreText: "没有更多数据了...", 119 | infoText: "更新时间: ${getCurrentTime()}", 120 | ) : null, 121 | onRefresh: widget.enableRefresh ? widget.onRefresh: null, 122 | onLoad: widget.enableLoad ? widget.onLoadMore : null, 123 | slivers: widget.slivers,); 124 | } 125 | 126 | String getCurrentTime() { 127 | var now = DateTime.now(); 128 | return formatDate(now, [hh, ':', nn]); 129 | } 130 | } 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /lib/widgets/panel_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuaishuaimovie/res/app_color.dart'; 3 | import 'package:shuaishuaimovie/widgets/toggle_rotate.dart'; 4 | 5 | class IntroducePanelWidget extends StatefulWidget { 6 | final String content; 7 | 8 | const IntroducePanelWidget({Key key, this.content}) : super(key: key); 9 | @override 10 | _IntroducePanelWidgetState createState() => _IntroducePanelWidgetState(); 11 | } 12 | 13 | class _IntroducePanelWidgetState extends State { 14 | CrossFadeState _crossFadeState = CrossFadeState.showFirst; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Column( 19 | children: [ 20 | _introducePlot(), 21 | _buildShowIntroducePlotBtn(), 22 | ], 23 | ); 24 | } 25 | 26 | Widget _introducePlot() { 27 | return AnimatedCrossFade( 28 | crossFadeState: _crossFadeState, 29 | firstChild: Text( 30 | widget.content, 31 | maxLines: 2, 32 | overflow: TextOverflow.fade, 33 | ), 34 | secondChild: Text(widget.content), 35 | duration: Duration(microseconds: 500), 36 | ); 37 | } 38 | 39 | Widget _buildShowIntroducePlotBtn() { 40 | return Padding( 41 | padding: EdgeInsets.all(5), 42 | child: ToggleRotate( 43 | durationMs: 300, 44 | onTap: () => _toggleIntroducePlotPanel(), 45 | child: Icon(Icons.keyboard_arrow_down, color: AppColor.black,), 46 | ), 47 | ); 48 | } 49 | 50 | void _toggleIntroducePlotPanel() { 51 | setState(() { 52 | _crossFadeState = _crossFadeState == CrossFadeState.showFirst 53 | ? CrossFadeState.showSecond 54 | : CrossFadeState.showFirst; 55 | }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/widgets/rating_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class StarRatingBar extends StatefulWidget { 4 | final double starSize; 5 | final int startCount; 6 | final int selectedCount; 7 | final double starSpace; 8 | final Color defaultStarColor; 9 | final Color selectedStarColor; 10 | 11 | const StarRatingBar.onlyShow({ 12 | Key key, 13 | this.starSize = 15, 14 | this.startCount = 5, 15 | this.starSpace = 2, 16 | this.selectedCount = 0, 17 | this.defaultStarColor = Colors.grey, 18 | this.selectedStarColor = Colors.red, 19 | }) : super(key: key); 20 | 21 | @override 22 | _StarRatingBarState createState() => _StarRatingBarState(); 23 | } 24 | 25 | class _StarRatingBarState extends State { 26 | @override 27 | Widget build(BuildContext context) { 28 | return Row( 29 | children: [ 30 | ..._buildStarList(), 31 | ], 32 | ); 33 | } 34 | 35 | List _buildStarList() { 36 | var starList = List(); 37 | for (int i = 0; i < widget.startCount; i++) { 38 | Color starColor = i < widget.selectedCount 39 | ? widget.selectedStarColor 40 | : widget.defaultStarColor; 41 | starList.add(_buildStar(starColor)); 42 | if (i != widget.startCount - 1) 43 | starList.add(SizedBox( 44 | width: widget.starSpace, 45 | )); 46 | } 47 | return starList; 48 | } 49 | 50 | Widget _buildStar(Color color) { 51 | return Icon( 52 | Icons.star, 53 | size: widget.starSize, 54 | color: color, 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/widgets/refresh_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:date_format/date_format.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 4 | 5 | typedef OnRefreshCallback = Future Function(); 6 | typedef OnLoadMoreCallback = Future Function(); 7 | 8 | class HeaderRefreshWidget extends StatelessWidget { 9 | HeaderRefreshWidget( 10 | {@required this.child, 11 | @required this.onRefresh, 12 | @required this.easyRefreshController}); 13 | 14 | Widget child; 15 | OnRefreshCallback onRefresh; 16 | EasyRefreshController easyRefreshController; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | debugPrint("build_header_refresh"); 21 | return CommonRefreshWidget( 22 | child: child, 23 | easyRefreshController: easyRefreshController, 24 | onRefresh: onRefresh, 25 | enableRefresh: true); 26 | } 27 | } 28 | 29 | class FooterLoadMoreWidget extends StatelessWidget { 30 | FooterLoadMoreWidget( 31 | {@required this.child, 32 | @required this.onLoadMore, 33 | @required this.easyRefreshController, 34 | this.topBouncing = true, 35 | this.bottomBouncing = true}); 36 | 37 | Widget child; 38 | OnLoadMoreCallback onLoadMore; 39 | EasyRefreshController easyRefreshController; 40 | bool topBouncing; 41 | bool bottomBouncing; 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | debugPrint("build_footer_onLoad"); 46 | return CommonRefreshWidget( 47 | child: child, 48 | easyRefreshController: easyRefreshController, 49 | onLoadMore: onLoadMore, 50 | enableLoad: true, 51 | bottomBouncing: bottomBouncing, 52 | topBouncing: topBouncing, 53 | ); 54 | } 55 | } 56 | 57 | class CommonRefreshWidget extends StatefulWidget { 58 | CommonRefreshWidget({ 59 | @required this.child, 60 | @required this.easyRefreshController, 61 | this.onRefresh, 62 | this.onLoadMore, 63 | this.enableRefresh = false, 64 | this.enableLoad = false, 65 | this.topBouncing = true, 66 | this.bottomBouncing = true, 67 | }); 68 | 69 | Widget child; 70 | bool enableRefresh; 71 | bool enableLoad; 72 | bool topBouncing; 73 | bool bottomBouncing; 74 | int count; 75 | OnRefreshCallback onRefresh; 76 | OnLoadMoreCallback onLoadMore; 77 | EasyRefreshController easyRefreshController; 78 | 79 | @override 80 | _CommonRefreshWidgetState createState() => _CommonRefreshWidgetState(); 81 | } 82 | 83 | class _CommonRefreshWidgetState extends State { 84 | _CommonRefreshWidgetState(); 85 | 86 | @override 87 | void initState() { 88 | super.initState(); 89 | } 90 | 91 | @override 92 | Widget build(BuildContext context) { 93 | return EasyRefresh( 94 | enableControlFinishRefresh: true, 95 | enableControlFinishLoad: true, 96 | controller: widget.easyRefreshController, 97 | topBouncing: widget.topBouncing, 98 | bottomBouncing: widget.bottomBouncing, 99 | header: widget.enableRefresh 100 | ? ClassicalHeader( 101 | enableInfiniteRefresh: false, 102 | infoColor: Colors.black87, 103 | refreshText: "下拉刷新...", 104 | refreshReadyText: "释放即可刷新...", 105 | refreshingText: "加载中...", 106 | refreshedText: "加载成功...", 107 | refreshFailedText: "加载失败...", 108 | noMoreText: "没有更多数据了...", 109 | infoText: "更新时间: ${getCurrentTime()}", 110 | ) 111 | : null, 112 | footer: widget.enableLoad 113 | ? ClassicalFooter( 114 | loadText: "加载中...", 115 | loadReadyText: "加载中...", 116 | loadingText: "加载中...", 117 | loadedText: "加载成功...", 118 | loadFailedText: "加载失败...", 119 | noMoreText: "没有更多数据了...", 120 | infoText: "更新时间: ${getCurrentTime()}", 121 | ) 122 | : null, 123 | onRefresh: widget.enableRefresh ? widget.onRefresh : null, 124 | onLoad: widget.enableLoad ? widget.onLoadMore : null, 125 | child: widget.child, 126 | ); 127 | } 128 | 129 | String getCurrentTime() { 130 | var now = DateTime.now(); 131 | return formatDate(now, [hh, ':', nn]); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /lib/widgets/smarquee_widget.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | class SmarqueeWidget extends StatefulWidget { 6 | SmarqueeWidget( 7 | {@required this.child, 8 | this.marginLeft, 9 | this.betweenSpacing, 10 | this.width = double.infinity, 11 | this.speedRate = 1}); 12 | 13 | /// marginLeft代表着第一个文字控件距离最左边的距离 14 | /// *默认的宽度是控件的宽度 15 | double marginLeft; 16 | 17 | ///betweenSpacing 代表文字控件之间的间距 18 | /// *默认的宽度是控件的宽度 19 | double betweenSpacing; 20 | 21 | //子控件 22 | final Widget child; 23 | 24 | final double width; 25 | 26 | final double speedRate; 27 | 28 | @override 29 | _SmarqueeWidgetState createState() => _SmarqueeWidgetState(); 30 | } 31 | 32 | class _SmarqueeWidgetState extends State { 33 | static const double NORMAL_SPEED = 150.0; 34 | 35 | Timer _smarqueeTimer; 36 | ScrollController _scrollController; 37 | 38 | @override 39 | void initState() { 40 | super.initState(); 41 | _scrollController = ScrollController(); 42 | WidgetsBinding.instance.addPostFrameCallback((timeStamp) { 43 | startTimer(); 44 | }); 45 | } 46 | 47 | void startTimer() { 48 | _smarqueeTimer = Timer.periodic(Duration(microseconds: 16), (timer) { 49 | final distance = _scrollController.offset ?? 0; 50 | if(_scrollController.hasClients) 51 | _scrollController.jumpTo(distance + (1 / NORMAL_SPEED) * widget.speedRate); 52 | }); 53 | } 54 | 55 | @override 56 | Widget build(BuildContext context) { 57 | return ScrollNotificationInterceptor( 58 | child: LayoutBuilder( 59 | builder: (BuildContext context, BoxConstraints constraints) { 60 | debugPrint("build_layout_smarquee_widget"); 61 | final biggestWidth = constraints.biggest.width; 62 | widget.marginLeft ??= biggestWidth; 63 | widget.betweenSpacing ??= biggestWidth; 64 | 65 | return ListView.builder( 66 | scrollDirection: Axis.horizontal, 67 | itemCount: double.maxFinite.toInt(), 68 | itemBuilder: (context, index) { 69 | final distance = 70 | (index == 0 ? widget.marginLeft : widget.betweenSpacing); 71 | return Container( 72 | padding: EdgeInsets.only(left: distance), 73 | alignment: Alignment.center, 74 | child: widget.child, 75 | ); 76 | }, 77 | controller: _scrollController, 78 | physics: NeverScrollableScrollPhysics(), 79 | ); 80 | }, 81 | ), 82 | ); 83 | } 84 | 85 | @override 86 | void dispose() { 87 | _smarqueeTimer.cancel(); 88 | _scrollController.dispose(); 89 | super.dispose(); 90 | } 91 | } 92 | 93 | /// 滚动通知拦截器(用于拦截其他UI组件的滑动事件) 94 | /// 由于一些需求在父组件里进行scrollNotification的监听操作, 95 | /// 而SMarqueeWidget会影响到父组件的逻辑。 96 | /// 例如:本项目集成了EasyRefresh库,会影响到它的下拉刷新功能。 97 | /// 因此额外做了阻止SmarqueeWidget的滚动事件向上传递。 98 | class ScrollNotificationInterceptor extends StatelessWidget { 99 | final Widget child; 100 | 101 | // 构造函数 102 | ScrollNotificationInterceptor({ 103 | Key key, 104 | @required this.child, 105 | }) : super(key: key); 106 | 107 | @override 108 | Widget build(BuildContext context) { 109 | return NotificationListener( 110 | onNotification: (ScrollNotification notification) { 111 | return true; 112 | }, 113 | child: this.child, 114 | ); 115 | } 116 | } -------------------------------------------------------------------------------- /lib/widgets/tag_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuaishuaimovie/res/app_color.dart'; 3 | import 'package:shuaishuaimovie/widgets/text_widget.dart'; 4 | 5 | movieCustomTag(String txt, {Color color = AppColor.icon_yellow, double radius = 10}) { 6 | return Container( 7 | padding: EdgeInsets.symmetric(horizontal: 5,), 8 | alignment: Alignment.center, 9 | height: 19, 10 | decoration: BoxDecoration( 11 | color: color, 12 | borderRadius: BorderRadius.all(Radius.circular(radius)), 13 | ), 14 | constraints: BoxConstraints( 15 | minWidth: 25 16 | ), 17 | child: Text( 18 | txt, 19 | style: TextStyle( 20 | fontSize: 10, 21 | color: Colors.white, 22 | ), 23 | ), 24 | ); 25 | } 26 | 27 | movieYellowTag(String txt) { 28 | return movieCustomTag(txt, color: AppColor.icon_yellow); 29 | } 30 | 31 | movieBlueTag(String txt) { 32 | return movieCustomTag(txt, color: AppColor.blue); 33 | } 34 | 35 | rankTag({Color color = AppColor.default_txt_grey, @required String txt}) { 36 | return Container( 37 | width: 18, 38 | height: 18, 39 | alignment: Alignment.center, 40 | decoration: BoxDecoration( 41 | borderRadius: BorderRadius.only( 42 | topLeft: Radius.circular(5), 43 | bottomLeft: Radius.circular(5), 44 | bottomRight: Radius.circular(5), 45 | ), 46 | color: color, 47 | ), 48 | child: CommonText( 49 | txt, 50 | txtSize: 11, 51 | txtWeight: FontWeight.bold, 52 | txtColor: AppColor.white, 53 | ), 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /lib/widgets/text_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:shuaishuaimovie/res/app_color.dart'; 3 | 4 | class CommonText extends StatelessWidget { 5 | final String txt; 6 | final Color txtColor; 7 | final FontWeight txtWeight; 8 | final double txtSize; 9 | final int maxLine; 10 | 11 | const CommonText(this.txt, 12 | {Key key, 13 | this.txtColor = AppColor.black, 14 | this.txtWeight = FontWeight.normal, 15 | this.txtSize = 12, 16 | this.maxLine,}) 17 | : super(key: key); 18 | 19 | CommonText.singleline( 20 | this.txt, { 21 | Key key, 22 | this.txtColor = AppColor.black, 23 | this.txtWeight = FontWeight.normal, 24 | this.txtSize = 12, 25 | this.maxLine = 1, 26 | }) : super(key: key); 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return Text( 31 | txt, 32 | style: TextStyle( 33 | color: txtColor, 34 | fontWeight: txtWeight, 35 | fontSize: txtSize, 36 | ), 37 | overflow: maxLine == 1 ? TextOverflow.ellipsis : DefaultTextStyle.of(context).overflow, 38 | maxLines: maxLine ?? DefaultTextStyle.of(context).maxLines, 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/widgets/toggle_rotate.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | class ToggleRotate extends StatefulWidget { 6 | final Widget child; 7 | final Function onTap; 8 | final double rad; 9 | final int durationMs; 10 | final bool clockwise; 11 | final Curve curve; 12 | 13 | ToggleRotate({this.child, @required this.onTap, this.rad = pi, this.clockwise = true,this.durationMs=200, this.curve = Curves.fastOutSlowIn}); 14 | 15 | @override 16 | _ToggleRotateState createState() => _ToggleRotateState(); 17 | } 18 | 19 | class _ToggleRotateState extends State with SingleTickerProviderStateMixin { 20 | double _rad = 0; 21 | bool _rotated = false; 22 | AnimationController _controller; 23 | Animation _rotate; 24 | 25 | @override 26 | void initState() { 27 | _controller = 28 | AnimationController(duration: Duration(milliseconds: widget.durationMs), vsync: this) 29 | ..addListener(() => setState(() => _rad = (_rotated?(1 - _rotate.value):_rotate.value) * widget.rad)) 30 | ..addStatusListener((status) { 31 | if (status == AnimationStatus.completed) { 32 | _rotated = !_rotated; 33 | } 34 | }); 35 | _rotate = CurvedAnimation(parent: _controller, curve: widget.curve); 36 | super.initState(); 37 | } 38 | @override 39 | void dispose() { 40 | _controller.dispose(); 41 | super.dispose(); 42 | } 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | return GestureDetector( 47 | onTap: () { 48 | _controller.reset(); 49 | _controller.forward(); 50 | widget.onTap(); 51 | }, 52 | child: Transform( 53 | transform: Matrix4.rotationZ(widget.clockwise ? _rad : -_rad), 54 | alignment: Alignment.center, 55 | child: widget.child, 56 | ), 57 | ); 58 | } 59 | } -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: shuaishuaimovie 2 | description: A new Flutter application. 3 | publish_to: none 4 | version: 1.1.0 5 | environment: 6 | sdk: '>=2.7.0 <3.0.0' 7 | dependencies: 8 | flutter: 9 | sdk: flutter 10 | cupertino_icons: ^0.1.3 11 | connectivity: ^0.4.9 12 | dio: ^3.0.9 13 | provider: ^4.1.3 14 | fluro: ^1.6.3 15 | sqflite: ^1.3.1 16 | cached_network_image: ^2.2.0+1 17 | fluttertoast: ^4.0.0 18 | flutter_easyrefresh: ^2.1.1 19 | flutter_sticky_header: ^0.4.5 20 | date_format: ^1.0.8 21 | animated_text_kit: ^2.2.0 22 | chewie: ^0.9.10 23 | video_player: ^0.10.11+2 24 | shared_preferences: ^0.5.8 25 | package_info: ^0.4.3 26 | fijkplayer: ^0.8.7 27 | 28 | r_dart_library: 29 | git: 30 | url: https://github.com/YK-Unit/r_dart_library.git 31 | ref: 0.2.1 32 | dev_dependencies: 33 | flutter_test: 34 | sdk: flutter 35 | flutter: 36 | uses-material-design: true 37 | fonts: 38 | - family: appIconFonts 39 | fonts: 40 | - asset: assets/fonts/iconfont.ttf 41 | flr: 42 | core_version: 3.1.0 43 | dartfmt_line_length: 80 44 | assets: 45 | 46 | 47 | -------------------------------------------------------------------------------- /resource/detail_page.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/resource/detail_page.gif -------------------------------------------------------------------------------- /resource/drop_style.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/resource/drop_style.gif -------------------------------------------------------------------------------- /resource/history.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/resource/history.gif -------------------------------------------------------------------------------- /resource/home.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/resource/home.gif -------------------------------------------------------------------------------- /resource/play_video.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/resource/play_video.gif -------------------------------------------------------------------------------- /resource/selection_search.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/resource/selection_search.gif -------------------------------------------------------------------------------- /resource/text_search.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridfish/shuaishuai_film/00c0255b9be9a864cafa7efcdb9c71dd9ef861de/resource/text_search.gif -------------------------------------------------------------------------------- /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:shuaishuaimovie/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 | --------------------------------------------------------------------------------