├── .gitignore
├── .gradle
├── 6.8
│ ├── fileChanges
│ │ └── last-build.bin
│ ├── fileHashes
│ │ └── fileHashes.lock
│ └── gc.properties
├── buildOutputCleanup
│ ├── buildOutputCleanup.lock
│ └── cache.properties
├── checksums
│ └── checksums.lock
├── configuration-cache
│ └── gc.properties
└── vcs-1
│ └── gc.properties
├── .metadata
├── README.md
├── analysis_options.yaml
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── example
│ │ │ │ └── flutter_video_player
│ │ │ │ └── MainActivity.kt
│ │ └── res
│ │ │ ├── drawable-v21
│ │ │ └── launch_background.xml
│ │ │ ├── drawable
│ │ │ └── launch_background.xml
│ │ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ │ ├── values-night
│ │ │ └── styles.xml
│ │ │ └── values
│ │ │ └── styles.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
└── settings.gradle
├── ios
├── .gitignore
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── Podfile
├── Podfile.lock
├── Runner.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── WorkspaceSettings.xcsettings
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── 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
├── abstracts
│ └── abstract_interface.dart
├── custom
│ └── navigationbar.dart
├── database
│ └── hv_manager.dart
├── extension
│ └── extension.dart
├── http
│ ├── http_config.dart
│ ├── http_manager.dart
│ └── http_response_model.dart
├── main.dart
├── models
│ ├── models.dart
│ └── user
│ │ └── user_post.dart
├── pages
│ ├── cache
│ │ └── cache_page.dart
│ ├── category
│ │ ├── model
│ │ │ └── category_model.dart
│ │ ├── page
│ │ │ └── category_page.dart
│ │ ├── view
│ │ │ ├── category_item_view.dart
│ │ │ └── cateogry_header_view.dart
│ │ └── view_model
│ │ │ └── category_view_model.dart
│ ├── drama_detail
│ │ ├── model
│ │ │ ├── play_info_model.dart
│ │ │ └── video_info_model.dart
│ │ ├── page
│ │ │ └── drama_detail_page.dart
│ │ ├── view_model
│ │ │ └── drama_detail_view_model.dart
│ │ └── views
│ │ │ ├── small_screen_placeholder_view.dart
│ │ │ ├── video_brief_detail_view.dart
│ │ │ ├── video_brief_view.dart
│ │ │ ├── video_episode_view.dart
│ │ │ └── video_series_view.dart
│ ├── drama_list
│ │ └── drama_list_page.dart
│ ├── haokan_video
│ │ ├── haokan_home
│ │ │ ├── haokan_home_model.dart
│ │ │ ├── haokan_home_page.dart
│ │ │ ├── haokan_home_view_model.dart
│ │ │ └── haokan_tab_page.dart
│ │ ├── haokan_video_detail
│ │ │ └── haokan_video_detail_model.dart
│ │ └── short_video
│ │ │ ├── haokan_short_video_item_view.dart
│ │ │ ├── haokan_short_video_model.dart
│ │ │ ├── haokan_short_video_page.dart
│ │ │ └── haokan_short_video_view_model.dart
│ ├── home
│ │ ├── model
│ │ │ └── home_model.dart
│ │ ├── page
│ │ │ └── home_page.dart
│ │ ├── view_model
│ │ │ └── home_view_model.dart
│ │ └── views
│ │ │ ├── big_eye_view.dart
│ │ │ ├── cell_big_eye.dart
│ │ │ ├── cell_guide.dart
│ │ │ ├── cell_list.dart
│ │ │ ├── cell_multi.dart
│ │ │ └── cell_single_image.dart
│ ├── hot_video
│ │ ├── bilibili_model.dart
│ │ ├── card_view.dart
│ │ ├── hot_video_page.dart
│ │ └── hot_video_view_model.dart
│ ├── login
│ │ ├── login_input_text_view.dart
│ │ ├── login_page.dart
│ │ └── login_view_model.dart
│ ├── mine
│ │ ├── mine_page.dart
│ │ └── mine_view_model.dart
│ ├── player
│ │ ├── controller_overlay.dart
│ │ └── video_player.dart
│ ├── search
│ │ └── search_page.dart
│ ├── setting
│ │ └── setting_page.dart
│ ├── splash
│ │ └── splash_page.dart
│ ├── tab_controller
│ │ └── tab_controller.dart
│ └── web
│ │ └── web_page.dart
├── routes
│ └── route_manager.dart
├── user
│ ├── user_info_model.dart
│ └── user_manager.dart
└── util
│ ├── device_info.dart
│ ├── r_sources.dart
│ ├── toast.dart
│ └── util.dart
├── local.properties
├── pubspec.lock
├── pubspec.yaml
├── resource
├── image
│ ├── 2.0x
│ │ ├── ic_vip.png
│ │ ├── pic_Avatar_h.png
│ │ ├── pic_Avatar_n.png
│ │ └── pic_banner_shadow.png
│ ├── 3.0x
│ │ ├── ic_vip.png
│ │ ├── pic_Avatar_h.png
│ │ ├── pic_Avatar_n.png
│ │ ├── pic_banner_shadow.png
│ │ └── splash_logo.png
│ ├── cover_img.png
│ ├── haokan_logo.png
│ ├── ic_playing.gif
│ └── splash_shake.png
└── json
│ ├── category_filters.json
│ ├── category_list_page1.json
│ ├── category_list_page2.json
│ ├── drama_info_detail.json
│ ├── home_page.json
│ ├── login_send_code.json
│ ├── mine_page.json
│ ├── play1_info_detail.json
│ ├── play2_info_detail.json
│ ├── play3_info_detail.json
│ ├── play4_info_detail.json
│ └── user_info.json
└── 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 | **/ios/Flutter/.last_build_id
26 | .dart_tool/
27 | .flutter-plugins
28 | .flutter-plugins-dependencies
29 | .packages
30 | .pub-cache/
31 | .pub/
32 | /build/
33 |
34 | # Web related
35 | lib/generated_plugin_registrant.dart
36 |
37 | # Symbolication related
38 | app.*.symbols
39 |
40 | # Obfuscation related
41 | app.*.map.json
42 |
43 | # Android Studio will place build artifacts here
44 | /android/app/debug
45 | /android/app/profile
46 | /android/app/release
47 |
--------------------------------------------------------------------------------
/.gradle/6.8/fileChanges/last-build.bin:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gradle/6.8/fileHashes/fileHashes.lock:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/.gradle/6.8/fileHashes/fileHashes.lock
--------------------------------------------------------------------------------
/.gradle/6.8/gc.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/.gradle/6.8/gc.properties
--------------------------------------------------------------------------------
/.gradle/buildOutputCleanup/buildOutputCleanup.lock:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/.gradle/buildOutputCleanup/buildOutputCleanup.lock
--------------------------------------------------------------------------------
/.gradle/buildOutputCleanup/cache.properties:
--------------------------------------------------------------------------------
1 | #Fri Feb 25 11:26:15 CST 2022
2 | gradle.version=6.8
3 |
--------------------------------------------------------------------------------
/.gradle/checksums/checksums.lock:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/.gradle/checksums/checksums.lock
--------------------------------------------------------------------------------
/.gradle/configuration-cache/gc.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/.gradle/configuration-cache/gc.properties
--------------------------------------------------------------------------------
/.gradle/vcs-1/gc.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/.gradle/vcs-1/gc.properties
--------------------------------------------------------------------------------
/.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: 77d935af4db863f6abd0b9c31c7e6df2a13de57b
8 | channel: stable
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # flutter_video_player 后面在provider_dev分支上更新
2 |
3 | # provider_dev分支 重构版
4 |
5 | # 完整的flutter项目,包含首页,好看视频,分类筛选,bilibili瀑布流漫画,抖音短视频,登录,数据库sql,hive,视频播放,全屏播放,历史记录等功能,video_player,在iOS平台上开发,好看短视频接口需要cookie,请自行抓接口添加测试
6 | !
7 | !
8 | !
9 | !
10 | !
11 | !
12 | !
13 | !
14 |
15 |
16 | ## Getting Started
17 | This project is a starting point for a Flutter application.
18 |
19 | A few resources to get you started if this is your first Flutter project:
20 |
21 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
22 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
23 |
24 | For help getting started with Flutter, view our
25 | [online documentation](https://flutter.dev/docs), which offers tutorials,
26 | samples, guidance on mobile development, and a full API reference.
27 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at
17 | # https://dart-lang.github.io/linter/lints/index.html.
18 | #
19 | # Instead of disabling a lint rule for the entire project in the
20 | # section below, it can also be suppressed for a single line of code
21 | # or a specific dart file by using the `// ignore: name_of_lint` and
22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
23 | # producing the lint.
24 | rules:
25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
27 |
28 | # Additional information about this file can be found at
29 | # https://dart.dev/guides/language/analysis-options
30 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/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 flutter.compileSdkVersion
30 |
31 | compileOptions {
32 | sourceCompatibility JavaVersion.VERSION_1_8
33 | targetCompatibility JavaVersion.VERSION_1_8
34 | }
35 |
36 | kotlinOptions {
37 | jvmTarget = '1.8'
38 | }
39 |
40 | sourceSets {
41 | main.java.srcDirs += 'src/main/kotlin'
42 | }
43 |
44 | defaultConfig {
45 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
46 | applicationId "com.example.flutter_video_player"
47 | minSdkVersion flutter.minSdkVersion
48 | targetSdkVersion flutter.targetSdkVersion
49 | versionCode flutterVersionCode.toInteger()
50 | versionName flutterVersionName
51 | }
52 |
53 | buildTypes {
54 | release {
55 | // TODO: Add your own signing config for the release build.
56 | // Signing with the debug keys for now, so `flutter run --release` works.
57 | signingConfig signingConfigs.debug
58 | }
59 | }
60 | }
61 |
62 | flutter {
63 | source '../..'
64 | }
65 |
66 | dependencies {
67 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
68 | }
69 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
15 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
30 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/example/flutter_video_player/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.flutter_video_player
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.6.10'
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:7.1.2'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | mavenCentral()
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.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
7 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
4 | def properties = new Properties()
5 |
6 | assert localPropertiesFile.exists()
7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
8 |
9 | def flutterSdkPath = properties.getProperty("flutter.sdk")
10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
12 |
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
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 | 11.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, '11.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | use_frameworks!
32 | use_modular_headers!
33 |
34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
35 | end
36 |
37 | post_install do |installer|
38 | installer.pods_project.targets.each do |target|
39 | flutter_additional_ios_build_settings(target)
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Flutter (1.0.0)
3 | - package_info_plus (0.4.5):
4 | - Flutter
5 | - path_provider_ios (0.0.1):
6 | - Flutter
7 | - shared_preferences_ios (0.0.1):
8 | - Flutter
9 | - video_player_avfoundation (0.0.1):
10 | - Flutter
11 |
12 | DEPENDENCIES:
13 | - Flutter (from `Flutter`)
14 | - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
15 | - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
16 | - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`)
17 | - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`)
18 |
19 | EXTERNAL SOURCES:
20 | Flutter:
21 | :path: Flutter
22 | package_info_plus:
23 | :path: ".symlinks/plugins/package_info_plus/ios"
24 | path_provider_ios:
25 | :path: ".symlinks/plugins/path_provider_ios/ios"
26 | shared_preferences_ios:
27 | :path: ".symlinks/plugins/shared_preferences_ios/ios"
28 | video_player_avfoundation:
29 | :path: ".symlinks/plugins/video_player_avfoundation/ios"
30 |
31 | SPEC CHECKSUMS:
32 | Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
33 | package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
34 | path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
35 | shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad
36 | video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff
37 |
38 | PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
39 |
40 | COCOAPODS: 1.11.3
41 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/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 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/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/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/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/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/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/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/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/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/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/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/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/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/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/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/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/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/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/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/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/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/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/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/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/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/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/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/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/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/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/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/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/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/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 | CFBundleDisplayName
8 | 剧天堂
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | flutter_video_player
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(FLUTTER_BUILD_NUMBER)
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIMainStoryboardFile
30 | Main
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeRight
35 | UIInterfaceOrientationLandscapeLeft
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 | UIViewControllerBasedStatusBarAppearance
45 |
46 | CADisableMinimumFrameDurationOnPhone
47 |
48 | UIApplicationSupportsIndirectInputEvents
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/lib/abstracts/abstract_interface.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_video_player/http/http_response_model.dart';
2 |
3 | abstract class Request {
4 | Future requestData({
5 | int pageNum = 1,
6 | Map queryParams = const {},
7 | });
8 |
9 | Future handleData(ResponseModel model);
10 | }
11 |
--------------------------------------------------------------------------------
/lib/custom/navigationbar.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: must_be_immutable
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_video_player/util/util.dart';
5 |
6 | class UINavigationBar extends StatelessWidget implements PreferredSizeWidget {
7 | UINavigationBar({
8 | Key? key,
9 | this.title = '',
10 | this.isCenterTitle = true,
11 | this.isSecondPage = true,
12 | this.leading = const [],
13 | this.middleView,
14 | this.tailing = const [],
15 | this.color = Colors.white,
16 | this.height = 44.0,
17 | }) : super(key: key);
18 |
19 | final String? title;
20 | final Widget? middleView;
21 | final List? leading;
22 | final List? tailing;
23 | Color? color;
24 | final double? height;
25 |
26 | bool? isCenterTitle;
27 | bool? isSecondPage;
28 | EdgeInsets insets = const EdgeInsets.fromLTRB(12, 0, 12, 0);
29 |
30 | @override
31 | Size get preferredSize => const Size.fromHeight(Util.navBarHeight);
32 |
33 | @override
34 | Widget build(BuildContext context) {
35 | List views = [];
36 |
37 | if (leading?.isNotEmpty == true) {
38 | // for (var i = 0; i < widget.leading!.length; i++) {
39 | // views.add(
40 | // Positioned.fromRect(
41 | // rect: Rect.fromLTWH(widget.insets.left + i * 100, 0, 100, 40),
42 | // child: widget.leading![i],
43 | // ),
44 | // );
45 | // }
46 | views.add(
47 | Row(
48 | mainAxisAlignment: MainAxisAlignment.start,
49 | children: leading!,
50 | ),
51 | );
52 | }
53 |
54 | if (title?.isNotEmpty == true) {
55 | if (isCenterTitle == true) {
56 | views.add(
57 | Center(
58 | child: Text(
59 | title!,
60 | style: TextStyle(
61 | fontSize: 14,
62 | color: const Color(0xff32383a),
63 | fontWeight:
64 | isSecondPage == true ? FontWeight.w500 : FontWeight.normal,
65 | ),
66 | ),
67 | ),
68 | );
69 | } else {
70 | views.add(
71 | Positioned(
72 | left: insets.left,
73 | child: Text(
74 | title!,
75 | style: TextStyle(
76 | color: const Color(0xffFAFbFC),
77 | fontSize: 16,
78 | fontWeight:
79 | isSecondPage == true ? FontWeight.w500 : FontWeight.normal,
80 | ),
81 | ),
82 | ),
83 | );
84 | }
85 | }
86 |
87 | if (middleView != null) {
88 | views.add(
89 | Center(
90 | child: middleView!,
91 | ),
92 | );
93 | }
94 |
95 | if (tailing?.isNotEmpty == true) {
96 | views.add(
97 | Row(
98 | mainAxisAlignment: MainAxisAlignment.end,
99 | children: tailing!,
100 | ),
101 | );
102 | }
103 | return Container(
104 | color: color,
105 | child: Column(
106 | children: [
107 | Container(
108 | height: Util.statusBarHeight,
109 | ),
110 | SizedBox(
111 | height: Util.navBarHeight,
112 | child: Stack(
113 | alignment: Alignment.center,
114 | children: views,
115 | ),
116 | )
117 | ],
118 | ),
119 | );
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/lib/database/hv_manager.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: empty_catches, control_flow_in_finally
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:hive_flutter/hive_flutter.dart';
5 | import 'package:flutter_video_player/user/user_info_model.dart';
6 | import 'package:path/path.dart' as p;
7 | import 'package:path_provider/path_provider.dart';
8 |
9 | class HiveManager {
10 | HiveManager._();
11 | static final HiveManager _instance = HiveManager._();
12 | static HiveManager get instance => _instance;
13 |
14 | @protected
15 | factory HiveManager() => _instance;
16 |
17 | static var boxPath = '';
18 | final userBox = 'UserBox';
19 | final userInfo = 'UserInfo';
20 |
21 | static Future initHive() async {
22 | try {
23 | final directory = await getApplicationDocumentsDirectory();
24 | boxPath = p.join(directory.path, 'JQQHive');
25 | await Hive.initFlutter(boxPath);
26 | } catch (e) {}
27 | }
28 |
29 | ///User
30 | Future saveUserInfo(UserInfoModel model) async {
31 | try {
32 | final box = await Hive.openBox(userBox);
33 | await box.put(userInfo, model.toJson());
34 | return true;
35 | } catch (e) {
36 | return false;
37 | }
38 | }
39 |
40 | Future getUserInfo() async {
41 | try {
42 | final box = await Hive.openBox(userBox);
43 | if (box.isEmpty) {
44 | return null;
45 | }
46 | Map? map = await box.getAt(0);
47 | if (map != null) {
48 | UserInfoModel model = UserInfoModel(map);
49 | if (model.token != null) {
50 | return model;
51 | }
52 | }
53 | } catch (e) {}
54 | return null;
55 | }
56 |
57 | Future deleteUserInfo() async {
58 | try {
59 | final box = await Hive.openBox(userBox);
60 | await box.clear();
61 | return true;
62 | } catch (e) {
63 | return false;
64 | }
65 | }
66 |
67 | ////
68 | ///
69 | final homeBox = 'HomeBox';
70 | final homeKey = 'HomeKey';
71 | Future saveHomeData(UserInfoModel model) async {
72 | try {
73 | final box = await Hive.openBox(homeBox);
74 | await box.put(homeKey, model.toJson());
75 | return true;
76 | } catch (e) {
77 | return false;
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/lib/extension/extension.dart:
--------------------------------------------------------------------------------
1 | extension StringConvert on int {
2 | String get stringify {
3 | String ret = '';
4 | int quot = this ~/ 10;
5 | int rem = this % 10;
6 | if (quot > 0) {
7 | ret += (quot * 10).toParse;
8 | }
9 | if (rem > 0) {
10 | ret += rem.toParse;
11 | }
12 | return ret;
13 | }
14 |
15 | String get toParse {
16 | String ret = '';
17 | switch (this) {
18 | case 0:
19 | ret = '零';
20 | break;
21 | case 1:
22 | ret = '一';
23 | break;
24 | case 2:
25 | ret = '二';
26 | break;
27 | case 3:
28 | ret = '三';
29 | break;
30 | case 4:
31 | ret = '四';
32 | break;
33 | case 5:
34 | ret = '五';
35 | break;
36 | case 6:
37 | ret = '六';
38 | break;
39 | case 7:
40 | ret = '七';
41 | break;
42 | case 8:
43 | ret = '八';
44 | break;
45 | case 9:
46 | ret = '九';
47 | break;
48 | case 10:
49 | ret = '十';
50 | break;
51 | case 20:
52 | ret = '二十';
53 | break;
54 | default:
55 | }
56 | return ret;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/lib/http/http_config.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/services.dart' show rootBundle;
2 | import 'dart:convert' show jsonDecode;
3 | import 'package:flutter_video_player/util/r_sources.dart';
4 | import 'http_response_model.dart';
5 |
6 | enum Api {
7 | home,
8 | dramaDetail,
9 | dramaList,
10 | search,
11 | mine,
12 | sendVerifyCode,
13 | verifyCodeLogin,
14 | logout,
15 | categoryHeader,
16 | categoryPage,
17 | dramaPlay,
18 | }
19 |
20 | class HttpConfig {
21 | static Future request({
22 | required Api api,
23 | Map queryParams = const {},
24 | }) async {
25 | String jsp = '';
26 | switch (api) {
27 | case Api.home:
28 | jsp = R.Jsp.homePage;
29 | break;
30 | case Api.categoryHeader:
31 | jsp = R.Jsp.categoryFilter;
32 | break;
33 | case Api.categoryPage:
34 | if (queryParams['page'] == 1) {
35 | jsp = R.Jsp.categoryPage1;
36 | } else {
37 | jsp = R.Jsp.categoryPage2;
38 | }
39 | break;
40 | case Api.dramaDetail:
41 | jsp = R.Jsp.dramaDetail;
42 | break;
43 | case Api.dramaPlay:
44 | String episodeId = queryParams['episodeId'];
45 | switch (int.parse(episodeId)) {
46 | case 1:
47 | jsp = R.Jsp.play1Detail;
48 | break;
49 | case 2:
50 | jsp = R.Jsp.play2Detail;
51 | break;
52 | case 3:
53 | jsp = R.Jsp.play3Detail;
54 | break;
55 | case 4:
56 | jsp = R.Jsp.play4Detail;
57 | break;
58 | default:
59 | }
60 | break;
61 | case Api.mine:
62 | jsp = R.Jsp.minePage;
63 | break;
64 | case Api.sendVerifyCode:
65 | jsp = R.Jsp.loginSendCode;
66 | break;
67 | case Api.verifyCodeLogin:
68 | jsp = R.Jsp.loginUserInfo;
69 | break;
70 | default:
71 | break;
72 | }
73 |
74 | if (jsp.isNotEmpty) {
75 | try {
76 | String jsonString = await rootBundle.loadString(jsp);
77 | final json = jsonDecode(jsonString);
78 | return ResponseModel.fromJson(json);
79 | } catch (e) {
80 | return null;
81 | }
82 | }
83 | return null;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/lib/http/http_manager.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 | import 'package:dio/dio.dart';
3 | import 'http_response_model.dart';
4 |
5 | enum Method {
6 | get('get'),
7 | post('post'),
8 | delete('delete'),
9 | put('put');
10 |
11 | final String name;
12 | const Method(this.name);
13 | }
14 |
15 | enum NetApi {
16 | haokanHome,
17 | haokanDramaDetail,
18 | haokanShortVideoList,
19 | bilibiliHotVideo,
20 | }
21 |
22 | enum BaseUrl {
23 | bilibili('https://api.vc.bilibili.com'),
24 | haokan('https://haokan.baidu.com'),
25 | test('');
26 |
27 | final String url;
28 | const BaseUrl(this.url);
29 | }
30 |
31 | extension ApiOption on NetApi {
32 | /// The target's base `URL`.
33 | String get url {
34 | switch (this) {
35 | case NetApi.haokanHome:
36 | case NetApi.haokanDramaDetail:
37 | case NetApi.haokanShortVideoList:
38 | return BaseUrl.haokan.url;
39 | case NetApi.bilibiliHotVideo:
40 | return BaseUrl.bilibili.url;
41 | default:
42 | }
43 | return BaseUrl.test.url;
44 | }
45 |
46 | /// The path to be appended to `baseURL` to form the full `URL`.
47 | String get path {
48 | var path = '';
49 | switch (this) {
50 | case NetApi.haokanHome:
51 | path = '/web/video/longpage';
52 | break;
53 | case NetApi.haokanDramaDetail:
54 | path = '/v';
55 | break;
56 | case NetApi.haokanShortVideoList:
57 | path = '/web/video/feed';
58 | break;
59 | case NetApi.bilibiliHotVideo:
60 | path = '/link_draw/v2/Doc/index';
61 | break;
62 | }
63 | return path;
64 | }
65 |
66 | /// The HTTP method used in the request.
67 | Method get method {
68 | switch (this) {
69 | default:
70 | return Method.get;
71 | }
72 | }
73 | }
74 |
75 | class ResponseCallBack {
76 | ResponseModel? model;
77 | DioError? error;
78 |
79 | ResponseCallBack(this.model, this.error);
80 | }
81 |
82 | // 限定泛型的类型
83 | class HttpManager {
84 | // 私有化构造方法
85 | HttpManager._() {
86 | var options = BaseOptions(
87 | baseUrl: BaseUrl.test.url,
88 | connectTimeout: 5000,
89 | receiveTimeout: 3000,
90 | );
91 | dio = Dio(options);
92 | // dio.interceptors.add(LogInterceptor(
93 | // requestBody: true,
94 | // responseBody: true,
95 | // error: true,
96 | // ));
97 | }
98 |
99 | static final HttpManager _instance = HttpManager._();
100 |
101 | static HttpManager get instance => _instance;
102 |
103 | late NetApi api;
104 | late Dio dio;
105 |
106 | late int timeOffset = 0;
107 |
108 | Map handleHeader({
109 | required NetApi req,
110 | Map queryParams = const {},
111 | }) {
112 | api = req;
113 | dio.options.baseUrl = api.url;
114 | var map = {
115 | 'Connection': 'keep-alive',
116 | 'User-Agent':
117 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:99.0) Gecko/20100101 Firefox/99.0',
118 | };
119 | return map;
120 | }
121 |
122 | Future sendRequest({
123 | required NetApi req,
124 | Map queryParams = const {},
125 | }) async {
126 | try {
127 | var headers = handleHeader(req: req, queryParams: queryParams);
128 | Response response = await dio.request(
129 | req.path,
130 | queryParameters: queryParams,
131 | options: Options(
132 | headers: headers,
133 | method: api.method.name,
134 | ),
135 | );
136 | if (response.statusCode == HttpStatus.ok) {
137 | ResponseModel model = ResponseModel.fromJson(response.data);
138 | return ResponseCallBack(model, null);
139 | } else {
140 | return ResponseCallBack(
141 | null,
142 | DioError(
143 | requestOptions: response.requestOptions,
144 | type: DioErrorType.response,
145 | ),
146 | );
147 | }
148 | } on DioError catch (e) {
149 | return ResponseCallBack(null, e);
150 | }
151 | }
152 |
153 | static Future request({
154 | required NetApi req,
155 | Map queryParams = const {},
156 | }) async {
157 | return await HttpManager.instance
158 | .sendRequest(req: req, queryParams: queryParams);
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/lib/http/http_response_model.dart:
--------------------------------------------------------------------------------
1 | class ResponseModel {
2 | String? code;
3 | String? msg;
4 | final dynamic _data;
5 |
6 | ///data数据是对象,字典,map类型
7 | Map get map {
8 | if (_data is Map) {
9 | return _data;
10 | } else {
11 | return {};
12 | }
13 | }
14 |
15 | ///data数据是数组,list类型
16 | List get list {
17 | if (_data is List) {
18 | return _data;
19 | } else {
20 | return [];
21 | }
22 | }
23 |
24 | ///data数据是字符串类型
25 | String get string {
26 | if (_data is String) {
27 | return _data;
28 | }
29 | return '';
30 | }
31 |
32 | ResponseModel(this.code, this.msg, this._data);
33 | factory ResponseModel.fromJson(Map json) {
34 | return ResponseModel('${json['code']}', json['msg'], json['data']);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2014 The Flutter Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | import 'dart:async';
6 |
7 | import 'package:flutter/gestures.dart';
8 | import 'package:flutter/material.dart';
9 | import 'package:flutter/services.dart';
10 | import 'package:flutter_video_player/database/hv_manager.dart';
11 | import 'pages/splash/splash_page.dart';
12 | import 'routes/route_manager.dart';
13 |
14 | export 'package:flutter_video_player/util/r_sources.dart';
15 |
16 | void main() {
17 | runZonedGuarded(
18 | () async {
19 | WidgetsFlutterBinding.ensureInitialized();
20 | SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual,
21 | overlays: [SystemUiOverlay.bottom, SystemUiOverlay.top]);
22 | //滚动性能优化
23 | // GestureBinding.instance.resamplingEnabled = true;
24 | SystemChrome.setPreferredOrientations([
25 | DeviceOrientation.portraitUp,
26 | ]);
27 | await HiveManager.initHive();
28 | runApp(const MyApp());
29 | },
30 | (error, StackTrace stack) {
31 | // ignore: avoid_print
32 | print(error);print(stack);
33 | },
34 | zoneSpecification: ZoneSpecification(
35 | // 拦截print
36 | print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
37 | parent.print(zone, "Interceptor: $line");
38 | },
39 | // 拦截未处理的异步错误
40 | handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone,
41 | Object error, StackTrace stackTrace) {
42 | parent.print(zone, '${error.toString()} $stackTrace');
43 | },
44 | ),
45 | );
46 | }
47 |
48 | class MyApp extends StatelessWidget {
49 | const MyApp({Key? key}) : super(key: key);
50 |
51 | @override
52 | Widget build(BuildContext context) {
53 | return MaterialApp(
54 | home: const SplashScreen(),
55 | onGenerateRoute: RouteManager.generateRoute,
56 | showPerformanceOverlay: false,
57 | debugShowCheckedModeBanner: false,
58 | navigatorObservers: [CFNavigatorObservers()],
59 | );
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/lib/models/models.dart:
--------------------------------------------------------------------------------
1 | // 页面传递数据,临时用的
2 | class DramaCoverModel {
3 | String? dramaId;
4 | String? coverUrl;
5 |
6 | DramaCoverModel({required this.dramaId, this.coverUrl});
7 | }
8 |
9 | class DramaPageModel {
10 | DramaModel? drama;
11 | }
12 |
13 | class DramaModel {
14 | int? dramaId;
15 | int? seasonNo;
16 | String? area;
17 | String? brief;
18 | String? cover;
19 | String? cat;
20 | String? createTime;
21 | String? enName;
22 | String? relatedName;
23 | bool? vipFlag;
24 | double? score;
25 | String? title;
26 | String? updateTime;
27 | String? year;
28 | String? serializedStatus;
29 | String? serializedTips;
30 | String? dramaType;
31 | String? catString;
32 | bool? dramaMovie;
33 |
34 | String? dramaInfo;
35 | String? dramaTypeStr;
36 |
37 | DramaModel.fromJsom(Map map) : dramaId = map['dramaId'];
38 | }
39 |
--------------------------------------------------------------------------------
/lib/models/user/user_post.dart:
--------------------------------------------------------------------------------
1 | class UserPost {
2 | int? userId;
3 | int? id;
4 | String? title;
5 | String? body;
6 |
7 | UserPost({this.userId, this.id, this.title, this.body});
8 |
9 | factory UserPost.fromJson(Map json) => UserPost(
10 | userId: json['userId'] as int?,
11 | id: json['id'] as int?,
12 | title: json['title'] as String?,
13 | body: json['body'] as String?,
14 | );
15 |
16 | Map toJson() => {
17 | 'userId': userId,
18 | 'id': id,
19 | 'title': title,
20 | 'body': body,
21 | };
22 | }
23 |
--------------------------------------------------------------------------------
/lib/pages/cache/cache_page.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: must_be_immutable
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_video_player/custom/navigationbar.dart';
5 |
6 | class CacheViewPage extends StatefulWidget {
7 | const CacheViewPage({
8 | Key? key,
9 | }) : super(key: key);
10 |
11 | @override
12 | _CacheViewPageState createState() => _CacheViewPageState();
13 | }
14 |
15 | class _CacheViewPageState extends State {
16 | @override
17 | void initState() {
18 | super.initState();
19 | }
20 |
21 | @override
22 | Widget build(BuildContext context) {
23 | return Scaffold(
24 | appBar: UINavigationBar(
25 | title: '设置',
26 | isCenterTitle: false,
27 | tailing: [
28 | MaterialButton(
29 | padding: EdgeInsets.zero,
30 | minWidth: 42,
31 | onPressed: () {
32 | Navigator.pop(context);
33 | },
34 | child: const Text('编辑'),
35 | ),
36 | ],
37 | ),
38 | body: Container(
39 | color: Colors.blueGrey,
40 | ),
41 | );
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/lib/pages/category/model/category_model.dart:
--------------------------------------------------------------------------------
1 | class AllCategoriesGroupModel {
2 | String groupType = '';
3 | List itemArray = [];
4 | int groupIndex = 0;
5 | String drameType = '';
6 |
7 | AllCategoriesGroupModel.fromJson(Map map) {
8 | groupType = map['filterType'];
9 | itemArray = (map['dramaFilterItemList'] as List)
10 | .map((e) => AllCategoriesItemModel.fromJson(e))
11 | .toList()
12 | ..first.selected = true;
13 | }
14 | }
15 |
16 | class AllCategoriesItemModel {
17 | String title = '';
18 | String idStr = '';
19 | bool selected = false;
20 |
21 | AllCategoriesItemModel.fromJson(Map map)
22 | : title = map['displayName'],
23 | idStr = map['value'];
24 | }
25 |
--------------------------------------------------------------------------------
/lib/pages/category/view/category_item_view.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: must_be_immutable, prefer_const_constructors
2 |
3 | import 'package:flutter/material.dart';
4 | import '../model/category_model.dart';
5 |
6 | /*
7 | 获取 widget位置
8 | RenderObject? box = ctx.findRenderObject();
9 | double x = box?.getTransformTo(null).getTranslation().x ?? 0;
10 | ctx.size;
11 | */
12 | class CategoryItemView extends StatefulWidget {
13 | CategoryItemView({
14 | Key? key,
15 | required this.model,
16 | this.onTap,
17 | this.enable = true,
18 | }) : super(key: key);
19 |
20 | AllCategoriesGroupModel model;
21 | final ValueChanged? onTap;
22 | bool enable;
23 |
24 | @override
25 | _CategoryItemViewState createState() => _CategoryItemViewState();
26 | }
27 |
28 | class _CategoryItemViewState extends State
29 | with SingleTickerProviderStateMixin {
30 | late TabController _tabController;
31 | @override
32 | void initState() {
33 | super.initState();
34 | _tabController =
35 | TabController(length: widget.model.itemArray.length, vsync: this);
36 | }
37 |
38 | @override
39 | void dispose() {
40 | _tabController.dispose();
41 | super.dispose();
42 | }
43 |
44 | @override
45 | Widget build(BuildContext context) {
46 | List itemArray = widget.model.itemArray;
47 | return Theme(
48 | data: Theme.of(context).copyWith(
49 | splashColor: Colors.transparent,
50 | highlightColor: Colors.transparent,
51 | ),
52 | child: TabBar(
53 | controller: _tabController,
54 | isScrollable: true,
55 | padding: EdgeInsets.symmetric(horizontal: 6),
56 | labelPadding: EdgeInsets.symmetric(horizontal: 3, vertical: 6),
57 | indicatorWeight: 0.0001,
58 | indicatorColor: Colors.white,
59 | enableFeedback: false,
60 | onTap: !widget.enable
61 | ? null
62 | : (index) {
63 | AllCategoriesItemModel e = itemArray[index];
64 | setState(() {
65 | for (var item in itemArray) {
66 | item.selected = false;
67 | }
68 | e.selected = true;
69 | });
70 | widget.onTap?.call(index);
71 | },
72 | tabs: itemArray
73 | .map(
74 | (e) => Tab(
75 | height: 32,
76 | child: Container(
77 | padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
78 | decoration: !widget.enable
79 | ? null
80 | : BoxDecoration(
81 | color: e.selected ? Color(0xfff3f6f8) : Colors.white,
82 | borderRadius: BorderRadius.circular(16),
83 | ),
84 | child: Text(
85 | e.title,
86 | style: TextStyle(
87 | color: !widget.enable
88 | ? Color(0xffcbcdd4)
89 | : (e.selected
90 | ? Color(0xfffb6060)
91 | : Color(0xff868996)),
92 | fontSize: 14,
93 | fontWeight: !widget.enable
94 | ? FontWeight.normal
95 | : (e.selected ? FontWeight.w500 : FontWeight.normal),
96 | ),
97 | ),
98 | ),
99 | ),
100 | )
101 | .toList(),
102 | ),
103 | );
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/lib/pages/category/view/cateogry_header_view.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: must_be_immutable
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_video_player/pages/category/view/category_item_view.dart';
5 | import 'package:flutter_video_player/util/util.dart';
6 |
7 | import '../view_model/category_view_model.dart';
8 |
9 | class CategoryHeaderView extends StatefulWidget {
10 | CategoryHeaderView({
11 | Key? key,
12 | required this.viewModel,
13 | this.filterCallback,
14 | }) : super(key: key);
15 |
16 | CategoryViewModel viewModel;
17 | VoidCallback? filterCallback;
18 |
19 | @override
20 | _CategoryHeaderViewState createState() => _CategoryHeaderViewState();
21 | }
22 |
23 | class _CategoryHeaderViewState extends State {
24 | @override
25 | Widget build(BuildContext context) {
26 | return SizedBox(
27 | width: Util.appWidth,
28 | height: widget.viewModel.groupArray.length * 44,
29 | child: Column(
30 | crossAxisAlignment: CrossAxisAlignment.start,
31 | children: widget.viewModel.groupArray.map((e) {
32 | return CategoryItemView(
33 | model: e,
34 | enable:
35 | !(e.drameType == 'MOVIE' && e.groupType == 'serializedStatus'),
36 | onTap: (index) {
37 | e.groupIndex = index;
38 | var key = e.groupType;
39 | var value = e.itemArray[index].idStr;
40 | widget.viewModel.filterParams[key] = value;
41 | if (e.groupType == 'dramaType') {
42 | widget.viewModel.groupArray
43 | .firstWhere((element) =>
44 | element.groupType == 'serializedStatus')
45 | .drameType =
46 | (key == 'dramaType' && value == 'MOVIE') ? 'MOVIE' : '';
47 | }
48 | widget.filterCallback?.call();
49 | },
50 | );
51 | }).toList(),
52 | ),
53 | );
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/lib/pages/category/view_model/category_view_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_video_player/http/http_config.dart';
2 | import 'package:flutter_video_player/http/http_response_model.dart';
3 | import 'package:flutter_video_player/pages/home/model/home_model.dart';
4 |
5 | import '../model/category_model.dart';
6 |
7 | class CategoryViewModel {
8 | List groupArray = [];
9 | List contentArray = [];
10 | Map filterParams = {};
11 |
12 | String get filterTitle {
13 | var str0 = '';
14 | var str1 = '';
15 | var str2 = '';
16 |
17 | for (var item in groupArray) {
18 | var sectionType = item.groupType;
19 | var model = item.itemArray[item.groupIndex];
20 | if (sectionType == 'sort') {
21 | str1 = model.title;
22 | } else if (sectionType == 'area') {
23 | str0 = model.title;
24 | } else if (sectionType == 'plotType') {
25 | str2 = model.title;
26 | }
27 | }
28 |
29 | var title = '';
30 | if (str0 == '') {
31 | title = '$str1 · $str2';
32 | } else {
33 | title = '$str0 · $str1 · $str2';
34 | }
35 | return title;
36 | }
37 |
38 | /// 获取分类相关筛选项
39 | Future fetchCategory() async {
40 | ResponseModel? model = await HttpConfig.request(api: Api.categoryHeader);
41 | if (model != null) {
42 | groupArray = model.list
43 | .map(
44 | (e) => AllCategoriesGroupModel.fromJson(e),
45 | )
46 | .toList();
47 | }
48 | }
49 |
50 | /// 获取筛选搜索结果
51 | Future fetchCategoryContent({int page = 1}) async {
52 | ResponseModel? model =
53 | await HttpConfig.request(api: Api.categoryPage, queryParams: {
54 | 'page': page,
55 | });
56 | if (model != null) {
57 | contentArray =
58 | model.list.map((e) => SectionContentModel.fromJson(e)).toList();
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/lib/pages/drama_detail/model/play_info_model.dart:
--------------------------------------------------------------------------------
1 | import 'video_info_model.dart';
2 |
3 | class PlayInfoModel {
4 | PlayInfo? playInfo;
5 | List? qualityList;
6 | Quality? currentQuality;
7 |
8 | PlayInfoModel.fromJson(Map json) {
9 | playInfo = PlayInfo.fromJson(json['playInfo']);
10 | try {
11 | qualityList =
12 | (((json['qualityList'] is List ? json['qualityList'] : []) as List)
13 | .map((e) => Quality.fromJson(e))).toList();
14 | if (qualityList?.isNotEmpty == true) {
15 | currentQuality =
16 | qualityList?.firstWhere((element) => element.quality == 'LD');
17 | }
18 | } catch (e) {
19 | // (e);
20 | }
21 | }
22 | }
23 |
24 | class Quality {
25 | Quality({
26 | this.quality,
27 | this.qualityName,
28 | this.vipFlag,
29 | this.qualityShortName,
30 | this.qualityCode,
31 | this.qualityDescription,
32 | });
33 |
34 | String? quality;
35 | String? qualityName;
36 | bool? vipFlag;
37 | String? qualityDescription;
38 | String? qualityShortName;
39 | String? qualityCode;
40 |
41 | factory Quality.fromJson(Map json) {
42 | return Quality(
43 | vipFlag: json["vipFlag"],
44 | qualityDescription: json["qualityDescription"],
45 | quality: json["quality"],
46 | qualityName: json["qualityName"],
47 | qualityShortName: json["qualityShortName"],
48 | qualityCode: json["qualityCode"],
49 | );
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/lib/pages/drama_detail/model/video_info_model.dart:
--------------------------------------------------------------------------------
1 | class VideoInfoModel {
2 | List? seriesList;
3 | List? episodeList;
4 | DramaInfo? drama;
5 | PlayInfo? playInfo;
6 |
7 | Series? currentSeries;
8 | Episode? currentEpisode;
9 | Episode? nextEpisode;
10 |
11 | VideoInfoModel.fromJson(Map json) {
12 | drama = DramaInfo.fromJson(json['drama']);
13 | playInfo = PlayInfo.fromJson(json['playInfo']);
14 | try {
15 | episodeList =
16 | (((json['episodeList'] is List ? json['episodeList'] : []) as List)
17 | .map((e) => Episode.fromJson(e))).toList();
18 | seriesList =
19 | (((json['seriesList'] is List ? json['seriesList'] : []) as List)
20 | .map((e) => Series.fromJson(e))).toList();
21 |
22 | if (seriesList?.isNotEmpty == true) {
23 | currentSeries = seriesList?.first;
24 | }
25 | if (episodeList?.isNotEmpty == true) {
26 | currentEpisode = episodeList?.firstWhere(
27 | (element) => element.episodeSid == playInfo?.episodeSid);
28 | }
29 | } catch (e) {
30 | //(e);
31 | }
32 | }
33 | }
34 |
35 | class DramaInfo {
36 | DramaInfo({
37 | this.id,
38 | this.createTime,
39 | this.title,
40 | this.cover,
41 | this.brief,
42 | this.score,
43 | this.year,
44 | this.area,
45 | this.enName,
46 | this.cat,
47 | this.dramaType,
48 | this.serializedStatus,
49 | this.episodeCount,
50 | });
51 |
52 | int? id;
53 | String? createTime;
54 | String? title;
55 | String? cover;
56 | String? brief;
57 | double? score;
58 | String? year;
59 | String? area;
60 | String? enName;
61 | String? cat;
62 | String? dramaType;
63 | String? serializedStatus;
64 | int? episodeCount;
65 |
66 | String get info => '$dramaType/$year/$area/$cat';
67 |
68 | factory DramaInfo.fromJson(Map json) => DramaInfo(
69 | id: json["id"],
70 | createTime: json["createTime"],
71 | title: json["title"],
72 | cover: json["cover"],
73 | brief: json["brief"],
74 | score: json["score"].toDouble(),
75 | year: json["year"],
76 | area: json["area"],
77 | enName: json["enName"],
78 | cat: json["cat"],
79 | dramaType: json["dramaType"],
80 | serializedStatus: json["serializedStatus"],
81 | episodeCount: json["episodeCount"],
82 | );
83 |
84 | Map toJson() => {
85 | "id": id,
86 | "createTime": createTime,
87 | "title": title,
88 | "cover": cover,
89 | "brief": brief,
90 | "score": score,
91 | "year": year,
92 | "area": area,
93 | "enName": enName,
94 | "cat": cat,
95 | "dramaType": dramaType,
96 | "serializedStatus": serializedStatus,
97 | "episodeCount": episodeCount,
98 | };
99 | }
100 |
101 | class Series {
102 | Series({
103 | this.dramaId,
104 | this.seasonNo,
105 | this.relatedName,
106 | this.id,
107 | });
108 |
109 | int? dramaId;
110 | int? seasonNo;
111 | String? relatedName;
112 | int? id;
113 |
114 | bool isSelected = false;
115 |
116 | factory Series.fromJson(Map json) => Series(
117 | dramaId: json["dramaId"],
118 | seasonNo: json["seasonNo"],
119 | relatedName: json["relatedName"],
120 | id: json["id"],
121 | );
122 |
123 | Map toJson() => {
124 | "dramaId": dramaId,
125 | "seasonNo": seasonNo,
126 | "relatedName": relatedName,
127 | "id": id,
128 | };
129 | }
130 |
131 | class Episode {
132 | Episode({
133 | this.episodeNo,
134 | this.text,
135 | this.episodeSid,
136 | });
137 |
138 | int? episodeNo;
139 | String? text;
140 | int? episodeSid;
141 | bool isSelected = false;
142 |
143 | factory Episode.fromJson(Map json) => Episode(
144 | episodeNo: json["episodeNo"],
145 | text: json["text"],
146 | episodeSid: json["episodeSid"],
147 | );
148 |
149 | Map toJson() => {
150 | "episodeNo": episodeNo,
151 | "text": text,
152 | "episodeSid": episodeSid,
153 | };
154 | }
155 |
156 | class PlayInfo {
157 | PlayInfo({
158 | this.mediaId,
159 | this.episodeSid,
160 | this.url,
161 | this.startingLength,
162 | this.currentQuality,
163 | });
164 |
165 | int? mediaId;
166 | int? episodeSid;
167 | String? url;
168 | String? currentQuality;
169 | int? startingLength;
170 |
171 | factory PlayInfo.fromJson(Map json) {
172 | return PlayInfo(
173 | url: json["url"],
174 | currentQuality: json["currentQuality"],
175 | mediaId: json["mediaId"],
176 | episodeSid: json["episodeSid"],
177 | startingLength: json["startingLength"],
178 | );
179 | }
180 |
181 | Map toJson() => {
182 | "url": url,
183 | "currentQuality": currentQuality,
184 | "mediaId": mediaId,
185 | "episodeSid": episodeSid,
186 | "startingLength": startingLength,
187 | };
188 | }
189 |
--------------------------------------------------------------------------------
/lib/pages/drama_detail/view_model/drama_detail_view_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_video_player/http/http_config.dart';
2 | // import 'package:flutter_video_player/http/http_manager.dart';
3 | import 'package:flutter_video_player/http/http_response_model.dart';
4 | import 'package:flutter_video_player/pages/drama_detail/model/play_info_model.dart';
5 | import 'package:flutter_video_player/pages/drama_detail/model/video_info_model.dart';
6 |
7 | class DramaDetailViewModel {
8 | VideoInfoModel? videoInfoModel;
9 | PlayInfoModel? playInfoModel;
10 |
11 | /// 请求剧集信息
12 | Future requestData({required String dramaId}) async {
13 | // if (dramaId.length > 10) {
14 | // ResponseCallBack? data = await HttpManager.request(req: NetApi.haokanDramaDetail, queryParams: {
15 | // 'vid': dramaId,
16 | // '_format': 'json',
17 | // },);
18 | // if (data.model != null) {
19 |
20 | // }
21 | // }
22 | ResponseModel? model = await HttpConfig.request(api: Api.dramaDetail);
23 | if (model != null) {
24 | videoInfoModel = VideoInfoModel.fromJson(model.map);
25 | }
26 | return videoInfoModel;
27 | }
28 |
29 | /// 请求播放数据
30 | Future requestPlayInfo({
31 | required String? dramaId,
32 | required int? episodeId,
33 | String? quality = 'LD',
34 | }) async {
35 | ResponseModel? model =
36 | await HttpConfig.request(api: Api.dramaPlay, queryParams: {
37 | 'dramaId': dramaId,
38 | 'episodeId': '${episodeId! - 10000}',
39 | });
40 | if (model != null) {
41 | playInfoModel = PlayInfoModel.fromJson(model.map);
42 | }
43 |
44 | videoInfoModel?.currentEpisode = videoInfoModel?.episodeList?.firstWhere(
45 | (element) => element.episodeSid == playInfoModel?.playInfo?.episodeSid);
46 |
47 | int index = videoInfoModel?.episodeList?.indexWhere((element) =>
48 | element.episodeSid == videoInfoModel?.currentEpisode?.episodeSid) ??
49 | 0;
50 | if (videoInfoModel?.currentEpisode != null &&
51 | (index + 1) < (videoInfoModel?.episodeList?.length ?? 0)) {
52 | videoInfoModel?.nextEpisode = videoInfoModel?.episodeList?[index + 1];
53 | } else {
54 | videoInfoModel?.nextEpisode = null;
55 | }
56 |
57 | return playInfoModel;
58 | }
59 |
60 | @override
61 | bool operator ==(Object other) {
62 | if (other is DramaDetailViewModel) {
63 | return false;
64 | }
65 | return false;
66 | }
67 |
68 | @override
69 | // ignore: unnecessary_overrides
70 | int get hashCode => super.hashCode;
71 | }
72 |
--------------------------------------------------------------------------------
/lib/pages/drama_detail/views/small_screen_placeholder_view.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: must_be_immutable
2 |
3 | import 'package:flutter/material.dart';
4 | import 'dart:ui' show ImageFilter;
5 |
6 | class SmallScreenPlayerPlaceHolderView extends StatelessWidget {
7 | const SmallScreenPlayerPlaceHolderView({
8 | Key? key,
9 | required this.coverUrl,
10 | }) : super(key: key);
11 |
12 | final String coverUrl;
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | var width = MediaQuery.of(context).size.width;
17 | var height = width * 0.56;
18 | return SizedBox(
19 | height: height,
20 | child: Stack(
21 | children: [
22 | Positioned(
23 | child: Hero(
24 | tag: coverUrl,
25 | child: Image.network(
26 | coverUrl,
27 | width: width,
28 | height: height,
29 | cacheWidth: width.toInt(),
30 | cacheHeight: height.toInt(),
31 | fit: BoxFit.cover,
32 | ),
33 | ),
34 | ),
35 | Positioned(
36 | top: 0,
37 | left: 0,
38 | right: 0,
39 | height: height,
40 | child: ClipRect(
41 | child: BackdropFilter(
42 | filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
43 | blendMode: BlendMode.modulate,
44 | child: Container(
45 | color: Colors.black.withOpacity(0.3),
46 | ),
47 | ),
48 | ),
49 | ),
50 | Center(
51 | child: ClipRRect(
52 | borderRadius: BorderRadius.circular(4),
53 | child: Image.network(
54 | coverUrl,
55 | height: height - 52,
56 | ),
57 | ),
58 | ),
59 | ],
60 | ),
61 | );
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/lib/pages/drama_detail/views/video_brief_detail_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_video_player/pages/drama_detail/model/video_info_model.dart';
3 |
4 | class VideoBriefDetailView extends StatelessWidget {
5 | const VideoBriefDetailView({
6 | Key? key,
7 | required this.model,
8 | }) : super(key: key);
9 |
10 | final VideoInfoModel? model;
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | return Container(
15 | color: Colors.white,
16 | padding: const EdgeInsets.all(12),
17 | child: Column(
18 | crossAxisAlignment: CrossAxisAlignment.start,
19 | children: [
20 | Padding(
21 | padding: const EdgeInsets.only(bottom: 12),
22 | child: Row(
23 | children: [
24 | const Text(
25 | '简介',
26 | maxLines: 1,
27 | style: TextStyle(
28 | fontSize: 16,
29 | fontWeight: FontWeight.w500,
30 | color: Color(0xff32383a),
31 | ),
32 | ),
33 | const Spacer(),
34 | GestureDetector(
35 | child: const Icon(Icons.close),
36 | onTap: () {
37 | Navigator.pop(context);
38 | },
39 | )
40 | ],
41 | )),
42 | Padding(
43 | padding: const EdgeInsets.only(right: 12, bottom: 6),
44 | child: Text(
45 | model?.drama?.title ?? '剧名',
46 | maxLines: 2,
47 | style: const TextStyle(
48 | fontSize: 24,
49 | fontWeight: FontWeight.w500,
50 | color: Color(0xff3b4051),
51 | ),
52 | ),
53 | ),
54 | Padding(
55 | padding: EdgeInsets.zero,
56 | child: Text(
57 | model?.drama?.enName ?? '',
58 | maxLines: 1,
59 | style: const TextStyle(
60 | fontSize: 10,
61 | color: Color(0xffadb6c2),
62 | ),
63 | ),
64 | ),
65 | Padding(
66 | padding: const EdgeInsets.only(right: 12, top: 4, bottom: 24),
67 | child: Row(
68 | crossAxisAlignment: CrossAxisAlignment.start,
69 | children: [
70 | Padding(
71 | padding: const EdgeInsets.only(top: 4),
72 | child: Text(
73 | model?.drama?.info ?? '',
74 | maxLines: 1,
75 | style: const TextStyle(
76 | fontSize: 10,
77 | color: Color(0xffadb6c2),
78 | ),
79 | ),
80 | ),
81 | ],
82 | ),
83 | ),
84 | Padding(
85 | padding: const EdgeInsets.only(right: 10, bottom: 28),
86 | child: Text(
87 | model?.drama?.brief ?? '',
88 | style: const TextStyle(
89 | fontSize: 14,
90 | color: Color(0xff868996),
91 | height: 1.5,
92 | ),
93 | ),
94 | ),
95 | ],
96 | ),
97 | );
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/lib/pages/drama_detail/views/video_episode_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_video_player/pages/drama_detail/model/video_info_model.dart';
3 |
4 | import '../../../util/util.dart';
5 |
6 | class ExplosidListView extends StatefulWidget {
7 | const ExplosidListView({
8 | Key? key,
9 | required this.model,
10 | }) : super(key: key);
11 |
12 | final VideoInfoModel? model;
13 |
14 | @override
15 | _ExplosidListViewState createState() => _ExplosidListViewState();
16 | }
17 |
18 | class _ExplosidListViewState extends State {
19 | VideoInfoModel? get model => widget.model;
20 |
21 | bool get hasText => (model?.episodeList?.first.text?.length ?? 0) > 2;
22 |
23 | @override
24 | Widget build(BuildContext context) {
25 | var serializedStatus = model?.drama?.serializedStatus?.isNotEmpty == true
26 | ? model?.drama?.serializedStatus
27 | : '未开播';
28 | return Container(
29 | color: Colors.white,
30 | padding: const EdgeInsets.all(12),
31 | child: Column(
32 | crossAxisAlignment: CrossAxisAlignment.start,
33 | children: [
34 | Padding(
35 | padding: const EdgeInsets.only(bottom: 12),
36 | child: Row(
37 | children: [
38 | const Text(
39 | '简介',
40 | maxLines: 1,
41 | style: TextStyle(
42 | fontSize: 16,
43 | fontWeight: FontWeight.w500,
44 | color: Color(0xff32383a),
45 | ),
46 | ),
47 | const Spacer(),
48 | GestureDetector(
49 | child: const Icon(Icons.close),
50 | onTap: () {
51 | Navigator.pop(context);
52 | },
53 | )
54 | ],
55 | )),
56 | Padding(
57 | padding: const EdgeInsets.only(bottom: 8),
58 | child: Text(
59 | serializedStatus ?? '',
60 | maxLines: 1,
61 | style: const TextStyle(
62 | fontSize: 10,
63 | color: Color(0xffadb6c2),
64 | ),
65 | ),
66 | ),
67 | Expanded(
68 | child: GridView.builder(
69 | padding: EdgeInsets.zero,
70 | gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
71 | maxCrossAxisExtent: hasText ? Util.appWidth - 24 : 54,
72 | mainAxisSpacing: 6,
73 | crossAxisSpacing: 6,
74 | childAspectRatio: hasText ? ((Util.appWidth - 24) / 54) : 1,
75 | ),
76 | itemCount: model?.episodeList?.length ?? 0,
77 | itemBuilder: (ctx, index) {
78 | Episode? episode = model?.episodeList?[index];
79 | String? text =
80 | hasText ? episode?.text : '${episode?.episodeNo}';
81 | episode?.isSelected =
82 | (model?.currentEpisode?.episodeSid == episode.episodeSid);
83 | Widget textWidget = Text(
84 | '$text',
85 | maxLines: 2,
86 | softWrap: false,
87 | overflow: TextOverflow.ellipsis,
88 | style: TextStyle(
89 | fontSize: 16,
90 | fontWeight: FontWeight.w500,
91 | color: episode?.isSelected == true
92 | ? const Color(0xffFB6060)
93 | : const Color(0xff3b4051),
94 | ),
95 | );
96 | return Container(
97 | clipBehavior: Clip.hardEdge,
98 | padding: !hasText
99 | ? EdgeInsets.zero
100 | : const EdgeInsets.symmetric(horizontal: 20),
101 | alignment: hasText ? Alignment.centerLeft : Alignment.center,
102 | decoration: BoxDecoration(
103 | color: const Color(0xfff3f6f8),
104 | borderRadius: BorderRadius.circular(4),
105 | ),
106 | child: (episode?.isSelected == true && !hasText)
107 | ? Image.asset(R.Img.ic_playing)
108 | : textWidget,
109 | );
110 | },
111 | ),
112 | ),
113 | ],
114 | ),
115 | );
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/lib/pages/drama_list/drama_list_page.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: must_be_immutable
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter/services.dart';
5 | import 'package:flutter_video_player/custom/navigationbar.dart';
6 | import 'package:flutter_video_player/http/http_response_model.dart';
7 | import 'package:flutter_video_player/models/models.dart';
8 | import 'package:flutter_video_player/pages/home/model/home_model.dart';
9 | import 'package:flutter_video_player/pages/home/views/cell_list.dart';
10 |
11 | import '../../abstracts/abstract_interface.dart';
12 |
13 | class DramaListPageView extends StatefulWidget {
14 | DramaListPageView({
15 | Key? key,
16 | required this.model,
17 | }) : super(key: key);
18 |
19 | DramaCoverModel? model;
20 |
21 | @override
22 | _DramaListPageViewState createState() => _DramaListPageViewState();
23 | }
24 |
25 | class _DramaListPageViewState extends State with Request {
26 | late List itemArray = [];
27 |
28 | @override
29 | void initState() {
30 | super.initState();
31 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark);
32 | requestData();
33 | }
34 |
35 | @override
36 | Widget build(BuildContext context) {
37 | return Scaffold(
38 | appBar: UINavigationBar(
39 | title: widget.model?.coverUrl,
40 | leading: [
41 | MaterialButton(
42 | padding: EdgeInsets.zero,
43 | minWidth: 42,
44 | onPressed: () {
45 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light);
46 | Navigator.pop(context);
47 | },
48 | child: const Icon(
49 | Icons.arrow_back_ios_new,
50 | size: 24,
51 | color: Color(0xff32383a),
52 | ),
53 | ),
54 | ],
55 | ),
56 | body: GridView.builder(
57 | padding: const EdgeInsets.only(left: 12, right: 12),
58 | gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
59 | crossAxisCount: 3,
60 | mainAxisSpacing: 0,
61 | crossAxisSpacing: 6,
62 | childAspectRatio: 0.564,
63 | ),
64 | itemCount: itemArray.length,
65 | itemBuilder: (ctx, index) => ListCell(
66 | model: itemArray[index],
67 | ),
68 | ),
69 | );
70 | }
71 |
72 | @override
73 | Future handleData(ResponseModel model) async {
74 | setState(() {
75 | var list = model.map['content'];
76 | if (list is List) {
77 | for (var element in list) {
78 | itemArray.add(SectionContentModel.fromJson(element));
79 | }
80 | }
81 | });
82 | }
83 |
84 | @override
85 | Future requestData({
86 | int pageNum = 1,
87 | Map queryParams = const {},
88 | }) async {
89 | // final url = widget.model?.dramaId ?? '';
90 | // final decoder = Uri.decodeFull(url);
91 | // final uri = Uri.parse(decoder);
92 | // final seriesId = uri.queryParameters['seriesId'] ?? '';
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/lib/pages/haokan_video/haokan_home/haokan_home_model.dart:
--------------------------------------------------------------------------------
1 |
2 | class HomeResponseModel {
3 | HomeResponseModel({
4 | this.pageData,
5 | this.hasMore,
6 | });
7 |
8 | final List? pageData;
9 | final int? hasMore;
10 |
11 | factory HomeResponseModel.fromJson(Map json) => HomeResponseModel(
12 | pageData: List.from(json["page_data"].map((x) => DramaItemModel.fromJson(x))),
13 | hasMore: json["has_more"],
14 | );
15 | }
16 |
17 | class DramaItemModel {
18 | DramaItemModel({
19 | this.videoName,
20 | this.verticalImage,
21 | this.seriesNum,
22 | this.introduction,
23 | this.firstEpisodes,
24 | this.sumPlayCnt,
25 | this.horizontalImage,
26 | this.isFinish,
27 | this.previewUrlHttp,
28 | this.sumPlayCntText,
29 | });
30 |
31 | final String? videoName;
32 | final String? verticalImage;
33 | final String? seriesNum;
34 | final String? introduction;
35 | final String? firstEpisodes;
36 | final String? sumPlayCnt;
37 | final String? horizontalImage;
38 | final String? isFinish;
39 | final String? previewUrlHttp;
40 | final String? sumPlayCntText;
41 |
42 | factory DramaItemModel.fromJson(Map json) => DramaItemModel(
43 | videoName: json["videoName"],
44 | verticalImage: json["verticalImage"],
45 | seriesNum: json["seriesNum"],
46 | introduction: json["introduction"],
47 | firstEpisodes: json["firstEpisodes"],
48 | sumPlayCnt: json["sumPlayCnt"],
49 | horizontalImage: json["horizontalImage"],
50 | isFinish: json["isFinish"],
51 | previewUrlHttp: json["previewUrlHttp"],
52 | sumPlayCntText: json["sumPlayCntText"],
53 | );
54 | }
55 |
--------------------------------------------------------------------------------
/lib/pages/haokan_video/haokan_home/haokan_home_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import '../../../util/util.dart';
3 | import 'haokan_home_view_model.dart';
4 | import 'haokan_tab_page.dart';
5 |
6 | class HaoKanHomePage extends StatelessWidget {
7 | const HaoKanHomePage({Key? key}) : super(key: key);
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return DefaultTabController(
12 | length: DramaType.values.length,
13 | child: Scaffold(
14 | // backgroundColor: const Color(0xff2d2d2d),
15 | appBar: AppBar(
16 | toolbarHeight: Util.navBarHeight,
17 | backgroundColor: const Color(0xff2d2d2d),
18 | leadingWidth: 200,
19 | elevation: 0.1,
20 | title: Row(
21 | children: [
22 | Expanded(
23 | child: TabBar(
24 | isScrollable: true,
25 | indicatorColor: const Color(0xFFF93759),
26 | indicatorWeight: 6,
27 | indicatorPadding: kTabLabelPadding,
28 | enableFeedback: false,
29 | splashFactory: NoSplash.splashFactory,
30 | overlayColor: MaterialStateProperty.resolveWith(
31 | (Set states) {
32 | return states.contains(MaterialState.focused)
33 | ? null
34 | : Colors.transparent;
35 | },
36 | ),
37 | labelColor: const Color(0xffF93759),
38 | labelStyle: const TextStyle(
39 | fontSize: 15,
40 | fontWeight: FontWeight.bold,
41 | ),
42 | unselectedLabelColor: const Color(0xff8d8d8d),
43 | unselectedLabelStyle: const TextStyle(
44 | fontSize: 12,
45 | ),
46 | tabs: DramaType.values
47 | .map(
48 | (e) => Tab(text: e.name),
49 | )
50 | .toList(),
51 | ),
52 | ),
53 | ],
54 | ),
55 | ),
56 | body: TabBarView(
57 | children: DramaType.values
58 | .map(
59 | (e) => HaoKanHomeTabPage(type: e),
60 | )
61 | .toList(),
62 | ),
63 | ),
64 | );
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/lib/pages/haokan_video/haokan_home/haokan_home_view_model.dart:
--------------------------------------------------------------------------------
1 | import '../../../http/http_manager.dart';
2 | import 'haokan_home_model.dart';
3 |
4 |
5 | enum DramaType {
6 | ///全部
7 | all(0, '全部'),
8 |
9 | ///古装剧
10 | costume(1, '古装剧'),
11 |
12 | ///家庭剧
13 | family(2, '家庭剧'),
14 |
15 | ///爱情剧
16 | love(3, '爱情剧'),
17 |
18 | ///悬疑剧
19 | crux(4, '悬疑剧'),
20 |
21 | ///武侠剧
22 | kungfu(5, '武侠剧'),
23 |
24 | ///喜剧
25 | comedy(6, '喜剧'),
26 |
27 | ///战争剧
28 | war(7, '战争剧');
29 |
30 | final int value;
31 | final String name;
32 |
33 | const DramaType(this.value, this.name);
34 | }
35 |
36 | class HaoKanHomeViewModel {
37 | late List pageModelList = [];
38 |
39 | int pageNumber = 1;
40 |
41 | Future requestData({
42 | required DramaType type,
43 | int pageNum = 1,
44 | }) async {
45 | ResponseCallBack data = await HttpManager.request(
46 | req: NetApi.haokanHome,
47 | queryParams: {
48 | 'rn': 20,
49 | 'pn': pageNum,
50 | 'type': type.value,
51 | },
52 | );
53 | pageNumber = pageNum + 1;
54 |
55 | if (pageNum == 1) {
56 | pageModelList.clear();
57 | }
58 |
59 | if (data.model == null) {
60 | return [];
61 | }
62 |
63 | HomeResponseModel resModel =
64 | HomeResponseModel.fromJson(data.model!.map['response']);
65 | pageModelList.addAll(resModel.pageData ?? []);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/lib/pages/haokan_video/haokan_home/haokan_tab_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:pull_to_refresh/pull_to_refresh.dart';
3 | import '../../../models/models.dart';
4 | import '../../../routes/route_manager.dart';
5 | import '../../home/model/home_model.dart';
6 | import '../../home/views/cell_list.dart';
7 | import 'haokan_home_model.dart';
8 | import 'haokan_home_view_model.dart';
9 |
10 | class HaoKanHomeTabPage extends StatefulWidget {
11 | const HaoKanHomeTabPage({
12 | Key? key,
13 | required this.type,
14 | }) : super(key: key);
15 |
16 | final DramaType type;
17 |
18 | @override
19 | State createState() => _HaoKanHomeTabPageState();
20 | }
21 |
22 | class _HaoKanHomeTabPageState extends State
23 | with AutomaticKeepAliveClientMixin {
24 | late HaoKanHomeViewModel viewModel;
25 |
26 | late final RefreshController _controller;
27 |
28 | @override
29 | void initState() {
30 | super.initState();
31 | viewModel = HaoKanHomeViewModel();
32 | _controller = RefreshController(initialRefresh: false);
33 | WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
34 | viewModel.requestData(type: widget.type).then((value) {
35 | if (mounted) {
36 | setState(() {});
37 | }
38 | });
39 | });
40 | }
41 |
42 | @override
43 | void dispose() {
44 | _controller.dispose();
45 | super.dispose();
46 | }
47 |
48 | @override
49 | Widget build(BuildContext context) {
50 | super.build(context);
51 | return SmartRefresher(
52 | enablePullUp: true,
53 | controller: _controller,
54 | onRefresh: () async {
55 | viewModel.requestData(type: widget.type, pageNum: 1).then((value) {
56 | if (mounted) {
57 | _controller.refreshCompleted();
58 | setState(() {});
59 | }
60 | });
61 | },
62 | onLoading: () async {
63 | viewModel
64 | .requestData(type: widget.type, pageNum: viewModel.pageNumber)
65 | .then((value) {
66 | if (mounted) {
67 | _controller.loadComplete();
68 | setState(() {});
69 | }
70 | });
71 | },
72 | child: GridView.builder(
73 | padding: const EdgeInsets.all(12),
74 | gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
75 | crossAxisCount: 3,
76 | mainAxisSpacing: 0,
77 | crossAxisSpacing: 6,
78 | childAspectRatio: 0.564,
79 | ),
80 | itemCount: viewModel.pageModelList.length,
81 | itemBuilder: (ctx, index) {
82 | DramaItemModel model = viewModel.pageModelList[index];
83 | return GestureDetector(
84 | onTap: () {
85 | Navigator.pushNamed(
86 | context,
87 | RouteManager.dramaDetail,
88 | arguments: DramaCoverModel(
89 | dramaId: model.firstEpisodes,
90 | coverUrl: model.verticalImage,
91 | ),
92 | );
93 | },
94 | child: ListCell(
95 | model: SectionContentModel(
96 | int.tryParse('${model.firstEpisodes}'),
97 | model.videoName,
98 | model.verticalImage,
99 | '${model.seriesNum}集${model.isFinish == '1' ? '全' : ' 连载中'}',
100 | ),
101 | ),
102 | );
103 | },
104 | ),
105 | );
106 | }
107 |
108 | @override
109 | bool get wantKeepAlive => true;
110 | }
111 |
--------------------------------------------------------------------------------
/lib/pages/haokan_video/haokan_video_detail/haokan_video_detail_model.dart:
--------------------------------------------------------------------------------
1 | class Data {
2 | Data({
3 | this.apiData,
4 | });
5 |
6 | final ApiData? apiData;
7 |
8 | factory Data.fromJson(Map json) => Data(
9 | apiData: ApiData.fromJson(json["apiData"]),
10 | );
11 | }
12 |
13 | class ApiData {
14 | ApiData({
15 | this.curVideoMeta,
16 | this.longVideoSideBar,
17 | });
18 |
19 | final CurVideoMeta? curVideoMeta;
20 | final LongVideoSideBar? longVideoSideBar;
21 |
22 | factory ApiData.fromJson(Map json) => ApiData(
23 | curVideoMeta: CurVideoMeta.fromJson(json["curVideoMeta"]),
24 | longVideoSideBar: LongVideoSideBar.fromJson(json["longVideoSideBar"]),
25 | );
26 | }
27 |
28 | class CurVideoMeta {
29 | CurVideoMeta({
30 | this.id,
31 | this.title,
32 | this.poster,
33 | this.fmplaycnt,
34 | this.date,
35 | this.timeLength,
36 | this.duration,
37 | this.playurl,
38 | this.clarityUrl,
39 | this.isLongVideo,
40 | this.dramaInfo,
41 | this.allowPlay,
42 | });
43 |
44 | final String? id;
45 | final String? title;
46 | final String? poster;
47 | final String? fmplaycnt;
48 | final String? date;
49 | final String? timeLength;
50 | final int? duration;
51 | final String? playurl;
52 | final List? clarityUrl;
53 | final bool? isLongVideo;
54 | final DramaInfo? dramaInfo;
55 | final int? allowPlay;
56 |
57 | factory CurVideoMeta.fromJson(Map json) => CurVideoMeta(
58 | id: json["id"],
59 | title: json["title"],
60 | poster: json["poster"],
61 | fmplaycnt: json["fmplaycnt"],
62 | date: json["date"],
63 | timeLength: json["time_length"],
64 | duration: json["duration"],
65 | playurl: json["playurl"],
66 | clarityUrl: List.from(
67 | json["clarityUrl"].map((x) => ClarityUrl.fromJson(x))),
68 | isLongVideo: json["isLongVideo"],
69 | dramaInfo: DramaInfo.fromJson(json["dramaInfo"]),
70 | allowPlay: json["allow_play"],
71 | );
72 | }
73 |
74 | ///当前播放信息模型
75 | class ClarityUrl {
76 | ClarityUrl({
77 | this.key,
78 | this.rank,
79 | this.title,
80 | this.url,
81 | this.videoBps,
82 | this.vodVideoHw,
83 | this.videoSize,
84 | this.vodMoovSize,
85 | this.codecType,
86 | });
87 |
88 | final String? key;
89 | final int? rank;
90 | final String? title;
91 | final String? url;
92 | final int? videoBps;
93 | final String? vodVideoHw;
94 | final int? videoSize;
95 | final int? vodMoovSize;
96 | final String? codecType;
97 |
98 | factory ClarityUrl.fromJson(Map json) => ClarityUrl(
99 | key: json["key"],
100 | rank: json["rank"],
101 | title: json["title"],
102 | url: json["url"],
103 | videoBps: json["videoBps"],
104 | vodVideoHw: json["vodVideoHW"],
105 | videoSize: json["videoSize"],
106 | vodMoovSize: json["vodMoovSize"],
107 | codecType: json["codec_type"],
108 | );
109 |
110 | Map toJson() => {
111 | "key": key,
112 | "rank": rank,
113 | "title": title,
114 | "url": url,
115 | "videoBps": videoBps,
116 | "vodVideoHW": vodVideoHw,
117 | "videoSize": videoSize,
118 | "vodMoovSize": vodMoovSize,
119 | "codec_type": codecType,
120 | };
121 | }
122 |
123 | /// 影视信息模型
124 | class DramaInfo {
125 | DramaInfo({
126 | this.videoName,
127 | this.seriesNum,
128 | this.verticalImage,
129 | this.sumPlayCnt,
130 | this.typeValue,
131 | this.typeName,
132 | this.introduction,
133 | this.currentNum,
134 | this.isFinish,
135 | });
136 |
137 | final String? videoName;
138 | final String? seriesNum;
139 | final String? verticalImage;
140 | final String? sumPlayCnt;
141 | final String? typeValue;
142 | final String? typeName;
143 | final String? introduction;
144 | final String? currentNum;
145 | final String? isFinish;
146 |
147 | factory DramaInfo.fromJson(Map json) => DramaInfo(
148 | videoName: json["videoName"],
149 | seriesNum: json["seriesNum"],
150 | verticalImage: json["verticalImage"],
151 | sumPlayCnt: json["sumPlayCnt"],
152 | typeValue: json["typeValue"],
153 | typeName: json["typeName"],
154 | introduction: json["introduction"],
155 | currentNum: json["currentNum"],
156 | isFinish: json["isFinish"],
157 | );
158 | }
159 |
160 | ///选集信息模型
161 | class LongVideoSideBar {
162 | LongVideoSideBar({
163 | required this.episodes,
164 | });
165 |
166 | final List episodes;
167 |
168 | factory LongVideoSideBar.fromJson(Map json) =>
169 | LongVideoSideBar(
170 | episodes: List.from(
171 | json["episodes"].map((x) => Episode.fromJson(x))),
172 | );
173 | }
174 |
175 | ///集信息模型
176 | class Episode {
177 | Episode({
178 | this.no,
179 | this.vid,
180 | });
181 |
182 | final String? no;
183 | final String? vid;
184 |
185 | factory Episode.fromJson(Map json) => Episode(
186 | no: json["no"],
187 | vid: json["vid"],
188 | );
189 | }
190 |
--------------------------------------------------------------------------------
/lib/pages/haokan_video/short_video/haokan_short_video_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class HaoKanShortVideoModel {
4 | HaoKanShortVideoModel({
5 | this.videos,
6 | });
7 |
8 | final List? videos;
9 |
10 | factory HaoKanShortVideoModel.fromJson(Map json) =>
11 | HaoKanShortVideoModel(
12 | videos: List.from(
13 | json["videos"].map(
14 | (x) => ShortVideoItem.fromJson(x),
15 | ),
16 | ),
17 | );
18 | }
19 |
20 | class ShortVideoItem {
21 | ShortVideoItem({
22 | this.id,
23 | this.title,
24 | this.posterSmall,
25 | this.posterBig,
26 | this.posterPc,
27 | this.sourceName,
28 | this.playUrl,
29 | this.duration,
30 | this.url,
31 | this.showTag,
32 | this.publishTime,
33 | this.isPayColumn,
34 | this.like,
35 | this.comment,
36 | this.playcnt,
37 | this.fmplaycnt,
38 | this.fmplaycnt2,
39 | this.outstandTag,
40 | this.previewUrlHttp,
41 | this.thirdId,
42 | this.vip,
43 | this.authorAvatar,
44 | });
45 |
46 | final String? id;
47 | final String? title;
48 | final String? posterSmall;
49 | final String? posterBig;
50 | final String? posterPc;
51 | final String? sourceName;
52 | final String? playUrl;
53 | final String? duration;
54 | final String? url;
55 | final int? showTag;
56 | final String? publishTime;
57 | final int? isPayColumn;
58 | final String? like;
59 | final String? comment;
60 | final String? playcnt;
61 | final String? fmplaycnt;
62 | final String? fmplaycnt2;
63 | final String? outstandTag;
64 | final String? previewUrlHttp;
65 | final String? thirdId;
66 | final int? vip;
67 | final String? authorAvatar;
68 |
69 | Size get size {
70 | List? list = posterSmall?.split(',');
71 | String? ws =
72 | list?.firstWhere((element) => element.startsWith('w_')).substring(2) ??
73 | '1';
74 | String? hs =
75 | list?.firstWhere((element) => element.startsWith('h_')).substring(2) ??
76 | '1';
77 | double w = double.tryParse(ws) ?? 1;
78 | double h = double.tryParse(hs) ?? 1;
79 | return Size(w, h);
80 | }
81 |
82 | double get aspectRatio => size.width / size.height;
83 |
84 | factory ShortVideoItem.fromJson(Map json) => ShortVideoItem(
85 | id: json["id"],
86 | title: json["title"],
87 | posterSmall: json["poster_small"],
88 | posterBig: json["poster_big"],
89 | posterPc: json["poster_pc"],
90 | sourceName: json["source_name"],
91 | playUrl: json["play_url"],
92 | duration: json["duration"],
93 | url: json["url"],
94 | showTag: json["show_tag"],
95 | publishTime: json["publish_time"],
96 | isPayColumn: json["is_pay_column"],
97 | like: json["like"],
98 | comment: json["comment"],
99 | playcnt: json["playcnt"],
100 | fmplaycnt: json["fmplaycnt"],
101 | fmplaycnt2: json["fmplaycnt_2"],
102 | outstandTag: json["outstand_tag"],
103 | previewUrlHttp: json["previewUrlHttp"],
104 | thirdId: json["third_id"],
105 | vip: json["vip"],
106 | authorAvatar: json["author_avatar"],
107 | );
108 | }
109 |
--------------------------------------------------------------------------------
/lib/pages/haokan_video/short_video/haokan_short_video_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'haokan_short_video_item_view.dart';
3 | import 'haokan_short_video_view_model.dart';
4 |
5 | class HaoKanShortVideoPage extends StatefulWidget {
6 | const HaoKanShortVideoPage({
7 | super.key,
8 | required this.active,
9 | });
10 |
11 | final bool active;
12 |
13 | @override
14 | State createState() => _HaoKanShortVideoPageState();
15 | }
16 |
17 | class _HaoKanShortVideoPageState extends State {
18 | late final HaoKanShortVideoViewModel viewModel;
19 | late final PageController _pageController;
20 |
21 | @override
22 | void initState() {
23 | super.initState();
24 | viewModel = HaoKanShortVideoViewModel();
25 | _pageController = PageController();
26 | _pageController.addListener(() {
27 | if (_pageController.position.pixels ==
28 | _pageController.position.maxScrollExtent) {
29 | viewModel.requestData(pageNum: viewModel.pageNumber).then((value) {
30 | if (mounted) {
31 | setState(() {});
32 | }
33 | });
34 | }
35 | });
36 | WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
37 | viewModel.requestData().then((value) {
38 | if (mounted) {
39 | setState(() {});
40 | }
41 | });
42 | });
43 | }
44 |
45 | @override
46 | void dispose() {
47 | _pageController.dispose();
48 | super.dispose();
49 | }
50 |
51 | @override
52 | Widget build(BuildContext context) {
53 | return Scaffold(
54 | backgroundColor: Colors.black,
55 | body: RefreshIndicator(
56 | onRefresh: () async {
57 | viewModel.requestData().then((value) {
58 | if (mounted) {
59 | setState(() {});
60 | }
61 | });
62 | },
63 | child: PageView.builder(
64 | itemCount: viewModel.videoList.length,
65 | scrollDirection: Axis.vertical,
66 | physics: const ClampingScrollPhysics(),
67 | controller: _pageController,
68 | itemBuilder: (context, index) {
69 | return HaoKanShortVideoItemView(
70 | model: viewModel.videoList[index],
71 | active: widget.active,
72 | );
73 | },
74 | ),
75 | ),
76 | );
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/lib/pages/haokan_video/short_video/haokan_short_video_view_model.dart:
--------------------------------------------------------------------------------
1 | import '../../../http/http_manager.dart';
2 | import 'haokan_short_video_model.dart';
3 |
4 | class HaoKanShortVideoViewModel {
5 | late List videoList = [];
6 |
7 | int pageNumber = 1;
8 |
9 | // https://haokan.baidu.com/web/video/feed?tab=yingshi_new&act=pcFeed&pd=pc&num=20&shuaxin_id=9653361786441
10 | Future requestData({
11 | int pageNum = 1,
12 | }) async {
13 | ResponseCallBack data = await HttpManager.request(
14 | req: NetApi.haokanShortVideoList,
15 | queryParams: {
16 | 'tab': 'recommend',
17 | 'act': 'pcFeed',
18 | 'pd': 'pc',
19 | 'num': 10,
20 | },
21 | );
22 | pageNumber = pageNum + 1;
23 | if (pageNum == 1) {
24 | videoList.clear();
25 | }
26 |
27 | if (data.model == null) {
28 | return [];
29 | }
30 |
31 | HaoKanShortVideoModel resModel =
32 | HaoKanShortVideoModel.fromJson(data.model!.map['response']);
33 | videoList.addAll(resModel.videos ?? []);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lib/pages/home/model/home_model.dart:
--------------------------------------------------------------------------------
1 | enum SectionType {
2 | bigEye,
3 | guide,
4 | mutilEntry,
5 | singleImage,
6 | }
7 |
8 | class HomePageModel {
9 | ///cell类型
10 | SectionType? type;
11 |
12 | ///cell内容
13 | List itemArray;
14 |
15 | ///cell标题
16 | String title;
17 |
18 | String? redirectUrl;
19 |
20 | HomePageModel({
21 | required this.type,
22 | required this.itemArray,
23 | this.title = '',
24 | this.redirectUrl,
25 | });
26 | }
27 |
28 | class HomeDataModel {
29 | BannerTop? top;
30 | List? sections;
31 | bool isEnd = false;
32 |
33 | HomeDataModel.fromJson(Map map) {
34 | if (map.isEmpty) {
35 | return;
36 | }
37 | if (map['top'] != null) {
38 | top = BannerTop.fromJson(map['top']);
39 | }
40 | if (map['isEnd'] != null) {
41 | isEnd = map['isEnd'];
42 | }
43 |
44 | sections =
45 | (map['sections'] as List).map((e) => SectionModel.fromJson(e)).toList();
46 | }
47 | }
48 |
49 | class BannerTop {
50 | List? eyeList;
51 | List? guideList;
52 |
53 | BannerTop.fromJson(Map map) {
54 | if (map.isEmpty) {
55 | return;
56 | }
57 | // [{id: 6800, name: 华灯初上 第一季, title: 小酒馆妈妈桑的爱恨情仇,谁杀了谁, imgUrl: http://img.juquanquanapp.com/img/img/20211224/o_bf0eba1ea4b1430f9a6f707889ad4ce4.jpg, targetUrl: 38331, type: season, sequence: 1, redirectUrl: jgjg.jqq://season?seasonId=38331&isMovie=false, position: jqq_eye, filmSubtitle: 好口碑高人气新剧, filmTitle: 华灯初上, filmScore: 8.1}]
58 | // [{id: 6642, name: 美丽的他, title: 自闭少年恋上清冷美少年, imgUrl: http://img.juquanquanapp.com/img/img/20211119/o_93bfe0455e8c4b79a369ed4a0ad427fb.jpg, targetUrl: 38242, type: season, sequence: 1, redirectUrl: jgjg.jqq://season?seasonId=38242&isMovie=false, position: jqq_eye, filmSubtitle: 深陷一见钟情, filmTitle: 美丽的他, filmScore: 8.8}]
59 | // [{id: 6799, name: 那年,我们的夏天, title: 《魔女》再组CP,这颗糖我死磕到底, imgUrl: http://img.juquanquanapp.com/img/img/20211224/o_b4f33018f2bc4c74811988d5e3e5e1c4.jpg, targetUrl: 38217, type: season, sequence: 1, redirectUrl: jgjg.jqq://season?seasonId=38217&isMovie=false, position: jqq_eye, filmSubtitle: 崔宇植×金多美, filmTitle: 那年,我们, filmScore: 9.0}]
60 | // [{id: 6802, name: 猎魔人 第二季, title: 杰洛特再度华丽回归!, imgUrl: http://img.juquanquanapp.com/img/img/20211224/o_30fd136b7d454780b29da16cbdf4d030.jpg, targetUrl: 38253, type: season, sequence: 1, redirectUrl: jgjg.jqq://season?seasonId=38253&isMovie=false, position: jqq_eye, filmSubtitle: 巫师, filmTitle: 猎魔人, filmScore: 8.3}]
61 | List guides = map['guide'];
62 | List eyes =
63 | map['eye'] + (guides.length <= 2 ? guides : guides.sublist(2));
64 | eyeList = eyes.map((e) => BigEyeModel.fromJson(e)).toList();
65 | guideList = (guides.map((e) => GuideModel.fromJson(e))).toList();
66 | }
67 | }
68 |
69 | class BigEyeModel {
70 | int? id;
71 | String? imgUrl;
72 | String? name;
73 | String? title;
74 | String? position;
75 | String? redirectUrl;
76 | String? type;
77 | String? targetUrl;
78 | String? filmSubtitle;
79 | String? filmTitle;
80 |
81 | BigEyeModel.fromJson(Map map)
82 | : id = map['id'],
83 | imgUrl = map['imgUrl'],
84 | name = map['name'],
85 | position = map['position'],
86 | redirectUrl = map['redirectUrl'],
87 | type = map['type'],
88 | title = map['title'],
89 | filmTitle = map['filmTitle'],
90 | filmSubtitle = map['filmSubtitle'];
91 |
92 | @override
93 | String toString() {
94 | return 'title:$title, subTitle:$title, filmTitle:$filmTitle, filmSubtitle:$filmSubtitle';
95 | }
96 | }
97 |
98 | class GuideModel {
99 | int? id;
100 | double? filmScore;
101 | String? filmSubtitle;
102 | String? filmTitle;
103 | String? imgUrl;
104 | String? name;
105 | String? position;
106 | String? redirectUrl;
107 | String? type;
108 | String? targetUrl;
109 |
110 | GuideModel.fromJson(Map map)
111 | : id = map['id'],
112 | imgUrl = map['imgUrl'],
113 | name = map['name'],
114 | position = map['position'],
115 | redirectUrl = map['redirectUrl'],
116 | type = map['type'],
117 | filmTitle = map['filmTitle'],
118 | filmSubtitle = map['filmSubtitle'],
119 | filmScore = map['filmScore'];
120 | }
121 |
122 | class SectionModel {
123 | String? moreText;
124 | String? name;
125 | int? position;
126 | List? sectionContents;
127 | String? sectionType;
128 | int? sequence;
129 | String? targetId;
130 |
131 | SectionModel.fromJson(Map map) {
132 | moreText = map['moreText'];
133 | name = map['name'];
134 | position = map['position'];
135 | sectionType = map['sectionType'];
136 | sequence = map['sequence'];
137 | targetId = map['targetId'];
138 |
139 | sectionContents = (map['sectionContents'] as List)
140 | .map((e) => SectionContentModel.fromJson(e))
141 | .toList();
142 | }
143 | }
144 |
145 | class SectionContentModel {
146 | String? coverUrl;
147 | int? dramaId;
148 | String? dramaType;
149 | String? icon;
150 | String? score;
151 | String? subTitle;
152 | String? targetId;
153 | String? targetType;
154 | String? title;
155 | String? pictureHeight;
156 | String? pictureWidth;
157 | bool? vipFlag;
158 |
159 | SectionContentModel(
160 | this.dramaId,
161 | this.title,
162 | this.coverUrl,
163 | this.score,
164 | );
165 |
166 | SectionContentModel.fromJson(Map map) {
167 | coverUrl = map['coverUrl'];
168 | dramaId = map['dramaId'];
169 | dramaType = map['dramaType'];
170 | icon = map['icon'];
171 | score = '${map['score']}';
172 | subTitle = map['subTitle'];
173 | targetId = map['targetId'];
174 | targetType = map['targetType'];
175 | title = map['title'];
176 | pictureHeight = map['pictureHeight'];
177 | pictureWidth = map['pictureWidth'];
178 | vipFlag = map['vipFlag'];
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/lib/pages/home/view_model/home_view_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_video_player/abstracts/abstract_interface.dart';
2 | import 'package:flutter_video_player/http/http_config.dart';
3 | import 'package:flutter_video_player/http/http_response_model.dart';
4 |
5 | import '../model/home_model.dart';
6 |
7 | class HomeViewModel implements Request {
8 | late List pageModelList = [];
9 |
10 | @override
11 | Future handleData(ResponseModel data) async {
12 | var model = HomeDataModel.fromJson(data.map);
13 | pageModelList.clear();
14 | if (model.top != null) {
15 | BannerTop top = model.top!;
16 | if (top.eyeList?.isNotEmpty == true) {
17 | pageModelList.add(
18 | HomePageModel(
19 | type: SectionType.bigEye,
20 | itemArray: top.eyeList!,
21 | ),
22 | );
23 | }
24 | if (top.guideList != null) {
25 | pageModelList.add(
26 | HomePageModel(
27 | type: SectionType.guide,
28 | itemArray: top.guideList!,
29 | ),
30 | );
31 | }
32 | }
33 | if (model.sections != null) {
34 | model.sections?.forEach(
35 | (e) {
36 | if (e.sectionContents?.isEmpty == true) {
37 | return;
38 | }
39 | if (e.sectionType == 'SINGLE_IMAGE') {
40 | pageModelList.add(HomePageModel(
41 | type: SectionType.singleImage,
42 | itemArray: e.sectionContents ?? [],
43 | title: e.name ?? '',
44 | redirectUrl: e.targetId,
45 | ));
46 | } else if (e.sectionType == 'VIDEO' ||
47 | e.sectionType == 'VIDEO_AUTO') {
48 | pageModelList.add(HomePageModel(
49 | type: SectionType.mutilEntry,
50 | itemArray: e.sectionContents ?? [],
51 | title: e.name ?? '',
52 | redirectUrl: e.targetId,
53 | ));
54 | }
55 | },
56 | );
57 | }
58 | return model.top?.eyeList?.first;
59 | }
60 |
61 | @override
62 | Future requestData({
63 | int pageNum = 1,
64 | Map queryParams = const {},
65 | }) async {
66 | ResponseModel? model = await HttpConfig.request(api: Api.home);
67 | if (model != null) {
68 | return await handleData(model);
69 | }
70 | return null;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/lib/pages/home/views/big_eye_view.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui' show ImageFilter;
2 |
3 | import 'package:extended_image/extended_image.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_video_player/util/util.dart';
6 |
7 | class HomeBigEyeView extends StatelessWidget {
8 | HomeBigEyeView({
9 | Key? key,
10 | this.imgUrl,
11 | }) : super(key: key);
12 |
13 | final String? imgUrl;
14 | set imageUrl(String? url) {
15 | if (url?.isEmpty == true) {
16 | return;
17 | }
18 | imgUrlNotify.value = url;
19 | }
20 |
21 | late final ValueNotifier imgUrlNotify =
22 | ValueNotifier(imgUrl);
23 |
24 | @override
25 | Widget build(BuildContext context) {
26 | final size = MediaQuery.of(context).size;
27 | final width = size.width;
28 | final height = width / Util.bigEyeImgRatio;
29 | return SizedBox(
30 | height: size.height,
31 | child: Stack(
32 | children: [
33 | Positioned(
34 | top: 0,
35 | left: 0,
36 | right: 0,
37 | height: height,
38 | child: ValueListenableBuilder(
39 | valueListenable: imgUrlNotify,
40 | builder: (context, url, child) {
41 | if (url?.isEmpty == true) {
42 | return const SizedBox.shrink();
43 | }
44 | return ExtendedImage.network(
45 | url ?? '',
46 | fit: BoxFit.cover,
47 | width: width,
48 | height: height,
49 | cacheWidth: width.toInt(),
50 | cacheHeight: height.toInt(),
51 | cacheMaxAge: const Duration(days: 7),
52 | enableLoadState: false,
53 | filterQuality: FilterQuality.none,
54 | );
55 | },
56 | ),
57 | ),
58 | Positioned(
59 | child: Container(
60 | height: 120,
61 | decoration: const BoxDecoration(
62 | gradient: LinearGradient(
63 | begin: Alignment.topCenter,
64 | end: Alignment.bottomCenter,
65 | colors: [
66 | Color(0xFF383B4B),
67 | Color(0x99383B4B),
68 | Color(0x00383B4B),
69 | ],
70 | stops: [0.60, 0.79, 1],
71 | ),
72 | ),
73 | ),
74 | ),
75 | Positioned(
76 | top: 0,
77 | left: 0,
78 | right: 0,
79 | height: height,
80 | child: ClipRect(
81 | child: BackdropFilter(
82 | filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
83 | child: Container(
84 | color: Colors.black45.withOpacity(0),
85 | ),
86 | ),
87 | ),
88 | ),
89 | Positioned(
90 | top: height - 200,
91 | left: 0,
92 | right: 0,
93 | height: 200,
94 | child: Container(
95 | decoration: const BoxDecoration(
96 | gradient: LinearGradient(
97 | begin: Alignment.topCenter,
98 | end: Alignment.bottomCenter,
99 | colors: [Color(0x00FFFFFF), Colors.white],
100 | stops: [0, 0.72],
101 | ),
102 | ),
103 | ),
104 | ),
105 | ],
106 | ),
107 | );
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/lib/pages/home/views/cell_guide.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: must_be_immutable
2 |
3 | import 'package:extended_image/extended_image.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_video_player/models/models.dart';
6 | import 'package:flutter_video_player/routes/route_manager.dart';
7 |
8 | import '../model/home_model.dart';
9 |
10 | class GuideViewCell extends StatefulWidget {
11 | GuideViewCell({
12 | Key? key,
13 | required this.itemArray,
14 | }) : super(key: key);
15 |
16 | List? itemArray;
17 |
18 | @override
19 | _GuideViewCellState createState() => _GuideViewCellState();
20 | }
21 |
22 | class _GuideViewCellState extends State {
23 | @override
24 | Widget build(BuildContext context) {
25 | return SizedBox(
26 | height: 200,
27 | child: GridView.count(
28 | padding: const EdgeInsets.only(
29 | left: 12,
30 | right: 12,
31 | ),
32 | scrollDirection: Axis.horizontal,
33 | crossAxisSpacing: 0,
34 | mainAxisSpacing: 6,
35 | crossAxisCount: 1,
36 | childAspectRatio: 200 / 160,
37 | children: ((widget.itemArray ?? []).map(
38 | (e) => GestureDetector(
39 | child: GuideViewItemCell(model: e),
40 | onTap: () {
41 | Navigator.pushNamed(
42 | context,
43 | RouteManager.dramaDetail,
44 | arguments: DramaCoverModel(
45 | dramaId: '${e.id}',
46 | coverUrl: e.imgUrl,
47 | ),
48 | );
49 | },
50 | ),
51 | )).toList(),
52 | ),
53 | );
54 | }
55 | }
56 |
57 | class GuideViewItemCell extends StatefulWidget {
58 | GuideViewItemCell({
59 | Key? key,
60 | required this.model,
61 | }) : super(key: key);
62 |
63 | GuideModel? model;
64 | @override
65 | _GuideViewItemCellState createState() => _GuideViewItemCellState();
66 | }
67 |
68 | class _GuideViewItemCellState extends State {
69 | @override
70 | Widget build(BuildContext context) {
71 | num score = widget.model?.filmScore ?? 0;
72 | List stars = List.generate(
73 | (score.ceil() ~/ 2),
74 | (index) => const Icon(
75 | Icons.star,
76 | color: Color(0xfffb6060),
77 | size: 10,
78 | ),
79 | );
80 |
81 | return SizedBox(
82 | child: Column(
83 | children: [
84 | Container(
85 | alignment: Alignment.topLeft,
86 | padding: EdgeInsets.zero,
87 | clipBehavior: Clip.hardEdge,
88 | decoration: BoxDecoration(
89 | borderRadius: BorderRadius.circular(4),
90 | ),
91 | child: ExtendedImage.network(
92 | widget.model?.imgUrl ?? '',
93 | fit: BoxFit.cover,
94 | width: 160,
95 | height: 120,
96 | cacheWidth: 160,
97 | cacheHeight: 120,
98 | cacheMaxAge: const Duration(days: 7),
99 | enableLoadState: false,
100 | ),
101 | ),
102 | const SizedBox(
103 | height: 6,
104 | ),
105 | Align(
106 | alignment: Alignment.topLeft,
107 | child: Text(
108 | widget.model?.filmTitle ?? '',
109 | maxLines: 1,
110 | overflow: TextOverflow.ellipsis,
111 | style: const TextStyle(
112 | color: Color(0xff32383A),
113 | fontSize: 14,
114 | ),
115 | ),
116 | ),
117 | const SizedBox(
118 | height: 4,
119 | ),
120 | Align(
121 | alignment: Alignment.topLeft,
122 | child: Text(
123 | widget.model?.filmSubtitle ?? '',
124 | maxLines: 1,
125 | overflow: TextOverflow.ellipsis,
126 | style: const TextStyle(
127 | color: Color(0xff868996),
128 | fontSize: 10,
129 | ),
130 | ),
131 | ),
132 | const SizedBox(
133 | height: 6,
134 | ),
135 | Row(
136 | crossAxisAlignment: CrossAxisAlignment.end,
137 | children: [
138 | Text(
139 | '${widget.model?.filmScore ?? ''}',
140 | maxLines: 1,
141 | overflow: TextOverflow.ellipsis,
142 | style: const TextStyle(
143 | color: Color(0xffFB6060),
144 | fontSize: 10 * 4 / 3,
145 | // fontFamily: 'Bebas',
146 | ),
147 | ),
148 | ...stars, //展开操作符 ... 能够把 list、set、map 字面量里的元素插入到一个集合中。一个对象是否可用于展开操作符取决于是否继承了Iterable,Map集合例外,对 map 进行展开操作 实际上是 调用了 Map 的 entries.iterator(),如果被展开的对象可能为 null,需要在展开操作符后面加上 ? 号 (...?)
149 | ],
150 | ),
151 | ],
152 | ),
153 | );
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/lib/pages/home/views/cell_list.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: must_be_immutable
2 |
3 | import 'package:extended_image/extended_image.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_video_player/util/r_sources.dart';
6 |
7 | import '../model/home_model.dart';
8 |
9 | class ListCell extends StatelessWidget {
10 | const ListCell({
11 | Key? key,
12 | this.model,
13 | }) : super(key: key);
14 | final SectionContentModel? model;
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | final width = (MediaQuery.of(context).size.width - 36) / 3.0;
19 | final height = width * 1.4;
20 | return SizedBox(
21 | width: width,
22 | child: Column(
23 | children: [
24 | Stack(
25 | children: [
26 | Container(
27 | clipBehavior: Clip.hardEdge,
28 | padding: EdgeInsets.zero,
29 | decoration: const BoxDecoration(
30 | borderRadius: BorderRadius.all(
31 | Radius.circular(4),
32 | ),
33 | ),
34 | child: Hero(
35 | tag: model!.coverUrl!,
36 | child: ExtendedImage.network(
37 | model?.coverUrl ?? '',
38 | fit: BoxFit.cover,
39 | width: width,
40 | height: height,
41 | cacheWidth: width.toInt(),
42 | cacheHeight: height.toInt(),
43 | cacheMaxAge: const Duration(days: 7),
44 | enableLoadState: false,
45 | ),
46 | ),
47 | ),
48 | Positioned(
49 | left: 0,
50 | top: 0,
51 | child: Visibility(
52 | visible: model?.vipFlag == true,
53 | child: Image.asset(
54 | R.Img.ic_vip,
55 | ),
56 | ),
57 | ),
58 | Positioned(
59 | left: 0,
60 | right: 0,
61 | bottom: 0,
62 | child: Container(
63 | height: 28,
64 | clipBehavior: Clip.hardEdge,
65 | decoration: BoxDecoration(
66 | borderRadius: const BorderRadius.only(
67 | bottomLeft: Radius.circular(4),
68 | bottomRight: Radius.circular(4),
69 | ),
70 | gradient: LinearGradient(
71 | begin: Alignment.topCenter,
72 | end: Alignment.bottomCenter,
73 | colors: [
74 | Colors.black.withAlpha(0),
75 | Colors.black.withAlpha(153),
76 | ],
77 | ),
78 | ),
79 | ),
80 | ),
81 | Positioned(
82 | right: 4,
83 | bottom: 4,
84 | child: Text(
85 | '${model?.score}',
86 | textScaleFactor: 1.0,
87 | style: const TextStyle(
88 | color: Colors.white,
89 | fontSize: 13,
90 | ),
91 | ),
92 | ),
93 | ],
94 | ),
95 | Container(
96 | alignment: Alignment.topLeft,
97 | margin: const EdgeInsets.only(top: 6),
98 | child: Text(
99 | model?.title ?? '',
100 | maxLines: 1,
101 | overflow: TextOverflow.ellipsis,
102 | textScaleFactor: 1.0,
103 | style: const TextStyle(
104 | color: Color(0xFF32383A),
105 | fontSize: 14,
106 | ),
107 | ),
108 | ),
109 | ],
110 | ),
111 | );
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/lib/pages/home/views/cell_multi.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: must_be_immutable
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_video_player/models/models.dart';
5 | import 'package:flutter_video_player/routes/route_manager.dart';
6 |
7 | import '../model/home_model.dart';
8 | import 'cell_list.dart';
9 |
10 | class MultiItemCell extends StatefulWidget {
11 | MultiItemCell({
12 | Key? key,
13 | this.model,
14 | }) : super(key: key);
15 |
16 | HomePageModel? model;
17 |
18 | @override
19 | _MultiItemCellState createState() => _MultiItemCellState();
20 | }
21 |
22 | class _MultiItemCellState extends State {
23 | @override
24 | Widget build(BuildContext context) {
25 | return Column(
26 | children: [
27 | SizedBox(
28 | height: (widget.model?.itemArray ?? []).isNotEmpty ? 38 : 0,
29 | child: GestureDetector(
30 | child: Row(
31 | children: [
32 | Padding(
33 | padding: const EdgeInsets.only(left: 12),
34 | child: Text(
35 | widget.model?.title ?? '',
36 | maxLines: 1,
37 | style: const TextStyle(
38 | fontSize: 16,
39 | fontWeight: FontWeight.w500,
40 | ),
41 | ),
42 | ),
43 | const Spacer(),
44 | const Padding(
45 | padding: EdgeInsets.only(right: 4),
46 | child: Text(
47 | '更多',
48 | maxLines: 1,
49 | style: TextStyle(
50 | fontSize: 10,
51 | fontWeight: FontWeight.w500,
52 | color: Color(0xffadb6c2),
53 | ),
54 | ),
55 | ),
56 | const Padding(
57 | padding: EdgeInsets.only(
58 | right: 10,
59 | ),
60 | child: Icon(
61 | Icons.arrow_forward_ios,
62 | size: 10,
63 | ),
64 | ),
65 | ],
66 | ),
67 | onTap: () {
68 | Navigator.pushNamed(
69 | context,
70 | RouteManager.dramaList,
71 | arguments: DramaCoverModel(
72 | coverUrl: widget.model?.title,
73 | dramaId: widget.model?.redirectUrl,
74 | ),
75 | );
76 | },
77 | ),
78 | ),
79 | SizedBox(
80 | child: Wrap(
81 | direction: Axis.horizontal,
82 | spacing: 6,
83 | runSpacing: 6,
84 | children: ((widget.model?.itemArray as List)
85 | .map(
86 | (e) => (GestureDetector(
87 | child: ListCell(
88 | model: e,
89 | ),
90 | onTap: () {
91 | Navigator.pushNamed(
92 | context,
93 | RouteManager.dramaDetail,
94 | arguments: DramaCoverModel(
95 | dramaId: '${e.dramaId}',
96 | coverUrl: e.coverUrl,
97 | ),
98 | );
99 | },
100 | )),
101 | )
102 | .toList()),
103 | ),
104 | ),
105 | const SizedBox(
106 | height: 16,
107 | ),
108 | ],
109 | );
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/lib/pages/home/views/cell_single_image.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: must_be_immutable
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_video_player/models/models.dart';
5 | import 'package:flutter_video_player/routes/route_manager.dart';
6 |
7 | import '../model/home_model.dart';
8 |
9 | class SingleImageViewCell extends StatelessWidget {
10 | SingleImageViewCell({
11 | Key? key,
12 | required this.model,
13 | }) : super(key: key);
14 |
15 | SectionContentModel? model;
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return GestureDetector(
20 | child: Container(
21 | clipBehavior: Clip.hardEdge,
22 | margin: const EdgeInsets.fromLTRB(12, 0, 12, 16),
23 | decoration: BoxDecoration(
24 | color: Colors.red,
25 | borderRadius: BorderRadius.circular(4),
26 | ),
27 | child: Image.network(
28 | model?.icon ?? '',
29 | fit: BoxFit.fitWidth,
30 | ),
31 | ),
32 | onTap: () {
33 | Navigator.pushNamed(
34 | context,
35 | RouteManager.dramaDetail,
36 | arguments: DramaCoverModel(
37 | dramaId: '${model?.dramaId}',
38 | coverUrl: model?.coverUrl,
39 | ),
40 | );
41 | },
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/lib/pages/hot_video/bilibili_model.dart:
--------------------------------------------------------------------------------
1 | class RecommendModel {
2 | RecommendModel({
3 | this.user,
4 | this.item,
5 | });
6 |
7 | final User? user;
8 | final Item? item;
9 |
10 | factory RecommendModel.fromJson(Map json) => RecommendModel(
11 | user: User.fromJson(json["user"]),
12 | item: Item.fromJson(json["item"]),
13 | );
14 | }
15 |
16 | class Item {
17 | Item({
18 | this.docId,
19 | this.posterUid,
20 | this.pictures,
21 | this.title,
22 | this.category,
23 | this.uploadTime,
24 | this.alreadyLiked,
25 | this.alreadyVoted,
26 | });
27 |
28 | final int? docId;
29 | final int? posterUid;
30 | final List? pictures;
31 | final String? title;
32 | final String? category;
33 | final int? uploadTime;
34 | final int? alreadyLiked;
35 | final int? alreadyVoted;
36 |
37 | factory Item.fromJson(Map json) => Item(
38 | docId: json["doc_id"],
39 | posterUid: json["poster_uid"],
40 | pictures: List.from(
41 | json["pictures"].map((x) => Picture.fromJson(x))),
42 | title: json["title"],
43 | category: json["category"],
44 | uploadTime: json["upload_time"],
45 | alreadyLiked: json["already_liked"],
46 | alreadyVoted: json["already_voted"],
47 | );
48 | }
49 |
50 | class Picture {
51 | Picture({
52 | this.imgSrc,
53 | this.imgWidth,
54 | this.imgHeight,
55 | this.imgSize,
56 | });
57 |
58 | final String? imgSrc;
59 | final int? imgWidth;
60 | final int? imgHeight;
61 | final int? imgSize;
62 |
63 | factory Picture.fromJson(Map json) => Picture(
64 | imgSrc: json["img_src"],
65 | imgWidth: json["img_width"],
66 | imgHeight: json["img_height"],
67 | imgSize: json["img_size"],
68 | );
69 | }
70 |
71 | class User {
72 | User({
73 | this.uid,
74 | this.headUrl,
75 | this.name,
76 | });
77 |
78 | final int? uid;
79 | final String? headUrl;
80 | final String? name;
81 |
82 | factory User.fromJson(Map json) => User(
83 | uid: json["uid"],
84 | headUrl: json["head_url"],
85 | name: json["name"],
86 | );
87 | }
88 |
--------------------------------------------------------------------------------
/lib/pages/hot_video/card_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:extended_image/extended_image.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_video_player/util/util.dart';
4 | import 'bilibili_model.dart';
5 |
6 | class CardView extends StatelessWidget {
7 | const CardView({
8 | Key? key,
9 | required this.model,
10 | }) : super(key: key);
11 |
12 | final RecommendModel model;
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | Picture? picture = model.item?.pictures?.first;
17 | int imgWidth = picture?.imgWidth ?? 1;
18 | int imgHeight = picture?.imgHeight ?? 1;
19 | final w = Util.appWidth / 2.0 - 15;
20 | final h = w / imgWidth * imgHeight;
21 |
22 | return Container(
23 | clipBehavior: Clip.hardEdge,
24 | padding: EdgeInsets.zero,
25 | decoration: const BoxDecoration(
26 | color: Colors.white,
27 | borderRadius: BorderRadius.all(Radius.circular(4)),
28 | ),
29 | child: Column(
30 | crossAxisAlignment: CrossAxisAlignment.start,
31 | children: [
32 | ExtendedImage.network(
33 | model.item?.pictures?.first.imgSrc ?? '',
34 | fit: BoxFit.fitWidth,
35 | cacheMaxAge: const Duration(hours: 1),
36 | enableLoadState: false,
37 | width: w,
38 | height: h,
39 | cacheWidth: w.toInt(),
40 | cacheHeight: h.toInt(),
41 | filterQuality: FilterQuality.medium,
42 | ),
43 | Padding(
44 | padding: const EdgeInsets.symmetric(
45 | horizontal: 12,
46 | vertical: 10,
47 | ),
48 | child: Text(
49 | model.item?.title ?? '',
50 | maxLines: 1,
51 | style: const TextStyle(
52 | fontSize: 14,
53 | color: Color(0xff333333),
54 | ),
55 | ),
56 | ),
57 | SizedBox(
58 | width: MediaQuery.of(context).size.width / 2 - 20,
59 | child: Stack(
60 | children: [
61 | Container(
62 | width: 24,
63 | height: 24,
64 | margin: const EdgeInsets.only(
65 | left: 12,
66 | right: 10,
67 | bottom: 5,
68 | ),
69 | decoration: const BoxDecoration(
70 | borderRadius: BorderRadius.all(
71 | Radius.circular(12),
72 | ),
73 | ),
74 | clipBehavior: Clip.hardEdge,
75 | child: Image.network(
76 | model.user?.headUrl ?? '',
77 | fit: BoxFit.cover,
78 | cacheWidth: 24,
79 | cacheHeight: 24,
80 | ),
81 | ),
82 | Positioned(
83 | left: 46,
84 | right: 10,
85 | height: 24,
86 | child: Align(
87 | alignment: Alignment.centerLeft,
88 | child: Text(
89 | model.user?.name ?? '',
90 | maxLines: 1,
91 | style: const TextStyle(
92 | fontSize: 14,
93 | color: Color(0xff999999),
94 | overflow: TextOverflow.ellipsis,
95 | ),
96 | ),
97 | ),
98 | ),
99 | ],
100 | ),
101 | ),
102 | ],
103 | ),
104 | );
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/lib/pages/hot_video/hot_video_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:extended_image/extended_image.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_video_player/util/util.dart';
4 | import 'package:pull_to_refresh/pull_to_refresh.dart';
5 | import 'package:waterfall_flow/waterfall_flow.dart';
6 | import 'card_view.dart';
7 | import 'hot_video_view_model.dart';
8 |
9 | class HotVideoPage extends StatefulWidget {
10 | const HotVideoPage({Key? key}) : super(key: key);
11 |
12 | @override
13 | State createState() => _HotVideoPageState();
14 | }
15 |
16 | class _HotVideoPageState extends State {
17 | late HotVideoViewModel viewModel;
18 |
19 | late final RefreshController _controller;
20 |
21 | @override
22 | void initState() {
23 | super.initState();
24 | viewModel = HotVideoViewModel();
25 | _controller = RefreshController(initialRefresh: true);
26 | }
27 |
28 | @override
29 | void dispose() {
30 | _controller.dispose();
31 | super.dispose();
32 | }
33 |
34 | @override
35 | Widget build(BuildContext context) {
36 | return Scaffold(
37 | appBar: AppBar(
38 | toolbarHeight: Util.navBarHeight,
39 | title: const Text(
40 | 'Bilibili',
41 | style: TextStyle(color: Colors.black),
42 | ),
43 | backgroundColor: Colors.white,
44 | elevation: 0.5,
45 | ),
46 | body: SmartRefresher(
47 | enablePullUp: true,
48 | controller: _controller,
49 | onRefresh: () async {
50 | viewModel.requestData().then((value) {
51 | if (mounted) {
52 | _controller.refreshCompleted();
53 | setState(() {});
54 | }
55 | });
56 | },
57 | onLoading: () async {
58 | viewModel.requestData(pageNum: viewModel.pageNumber).then((value) {
59 | if (mounted) {
60 | _controller.loadComplete();
61 | setState(() {});
62 | }
63 | });
64 | },
65 | child: WaterfallFlow.builder(
66 | padding: const EdgeInsets.all(12.0),
67 | gridDelegate: SliverWaterfallFlowDelegateWithFixedCrossAxisCount(
68 | crossAxisCount: 2,
69 | crossAxisSpacing: 6.0,
70 | mainAxisSpacing: 12.0,
71 | collectGarbage: (List garbages) {
72 | for (var index in garbages) {
73 | final provider = ExtendedNetworkImageProvider(
74 | viewModel.pageModelList[index].item?.pictures?.first.imgSrc ??
75 | '',
76 | );
77 | provider.evict();
78 | }
79 | },
80 | lastChildLayoutTypeBuilder: (index) =>
81 | index == viewModel.pageModelList.length
82 | ? LastChildLayoutType.foot
83 | : LastChildLayoutType.none,
84 | ),
85 | itemCount: viewModel.pageModelList.length,
86 | itemBuilder: (BuildContext context, int index) {
87 | return CardView(
88 | model: viewModel.pageModelList[index],
89 | );
90 | },
91 | ),
92 | ),
93 | );
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/lib/pages/hot_video/hot_video_view_model.dart:
--------------------------------------------------------------------------------
1 | import '../../abstracts/abstract_interface.dart';
2 | import 'bilibili_model.dart';
3 | import '../../http/http_manager.dart';
4 | import '../../http/http_response_model.dart';
5 |
6 | class HotVideoViewModel implements Request {
7 | late List pageModelList = [];
8 |
9 | int pageNumber = 0;
10 |
11 | @override
12 | Future> handleData(ResponseModel data) async {
13 | pageModelList.clear();
14 | List items = data.map['items'];
15 | pageModelList.addAll(items.map((e) => RecommendModel.fromJson(e)));
16 | return pageModelList;
17 | }
18 |
19 | @override
20 | Future requestData({
21 | int pageNum = 0,
22 | Map queryParams = const {},
23 | }) async {
24 | ResponseCallBack data = await HttpManager.request(
25 | req: NetApi.bilibiliHotVideo,
26 | queryParams: {
27 | 'page_size': 20,
28 | 'page_num': pageNum,
29 | 'type': 'recommend',
30 | },
31 | );
32 | pageNumber = pageNum + 1;
33 |
34 | if (pageNum == 0) {
35 | pageModelList.clear();
36 | }
37 |
38 | if (data.model == null) {
39 | return [];
40 | }
41 |
42 | List items = data.model!.map['items'];
43 | pageModelList.addAll(items.map((e) => RecommendModel.fromJson(e)));
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/lib/pages/login/login_input_text_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 |
3 | class LoginInputTextView extends StatefulWidget {
4 | const LoginInputTextView({
5 | Key? key,
6 | this.controller,
7 | this.placeholder,
8 | this.maxLength,
9 | this.onChanged,
10 | }) : super(key: key);
11 |
12 | final TextEditingController? controller;
13 | final String? placeholder;
14 | final int? maxLength;
15 | final ValueChanged? onChanged;
16 |
17 | @override
18 | _LoginInputTextViewState createState() => _LoginInputTextViewState();
19 | }
20 |
21 | class _LoginInputTextViewState extends State {
22 | late bool hasInput = false;
23 |
24 | @override
25 | Widget build(BuildContext context) {
26 | return CupertinoTextField(
27 | controller: widget.controller,
28 | cursorHeight: 18,
29 | cursorColor: const Color(0xfffb6060),
30 | clearButtonMode: OverlayVisibilityMode.editing,
31 | maxLength: widget.maxLength,
32 | padding:
33 | hasInput ? EdgeInsets.zero : const EdgeInsets.symmetric(vertical: 6),
34 | textAlignVertical: TextAlignVertical.top,
35 | textAlign: TextAlign.left,
36 | decoration: null,
37 | style: const TextStyle(
38 | fontSize: 24,
39 | fontWeight: FontWeight.w500,
40 | letterSpacing: 2,
41 | color: Color(0xff32383a),
42 | ),
43 | placeholder: widget.placeholder,
44 | placeholderStyle: const TextStyle(
45 | fontSize: 16,
46 | fontWeight: FontWeight.normal,
47 | color: Color(0xffadb6c2),
48 | height: 1.5,
49 | ),
50 | onChanged: (value) {
51 | setState(() {
52 | hasInput = value.isNotEmpty;
53 | });
54 | widget.onChanged?.call(value);
55 | },
56 | );
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/lib/pages/login/login_view_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_video_player/http/http_config.dart';
2 | import 'package:flutter_video_player/http/http_response_model.dart';
3 | import 'package:flutter_video_player/user/user_info_model.dart';
4 | import 'package:flutter_video_player/user/user_manager.dart';
5 |
6 | class LoginViewModel {
7 | ///发送验证码
8 | Future sendVerifyCode(String mobile) async {
9 | ResponseModel? data = await HttpConfig.request(
10 | api: Api.sendVerifyCode,
11 | queryParams: {
12 | 'mobile': mobile,
13 | 'countryCode': '+86',
14 | },
15 | );
16 | return data?.code == '0000';
17 | }
18 |
19 | /* 验证码登录 */
20 | Future verifyCodeLogin(String mobile, String code) async {
21 | ResponseModel? data = await HttpConfig.request(
22 | api: Api.verifyCodeLogin,
23 | queryParams: {
24 | 'mobile': mobile,
25 | 'captchaSms': code,
26 | 'countryCode': '+86',
27 | },
28 | );
29 | if (data != null) {
30 | UserInfoModel model = UserInfoModel.fromJson(data.map);
31 | bool saved = await UserManger.saveUserInfo(model);
32 | return data.code == '0000' && saved;
33 | }
34 | return false;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lib/pages/mine/mine_view_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_video_player/abstracts/abstract_interface.dart';
2 | import 'package:flutter_video_player/http/http_config.dart';
3 | import 'package:flutter_video_player/http/http_response_model.dart';
4 | import 'package:flutter_video_player/pages/home/model/home_model.dart';
5 | import 'package:flutter_video_player/user/user_manager.dart';
6 | import 'package:flutter_video_player/util/r_sources.dart';
7 |
8 | class MineViewModel implements Request {
9 | String get avatar {
10 | return UserManger.instance.isLogin
11 | ? R.Img.pic_Avatar_h
12 | : R.Img.pic_Avatar_n;
13 | }
14 |
15 | String get nickName {
16 | return UserManger.instance.isLogin
17 | ? (UserManger().userModel?.nickName ?? '')
18 | : '登录/注册';
19 | }
20 |
21 | String get phoneText {
22 | return UserManger.instance.isLogin
23 | ? (UserManger().userModel?.mobile ?? '00000000000')
24 | .replaceRange(3, 7, ' **** ')
25 | : '快来开启剧圈圈煲剧之旅';
26 | }
27 |
28 | late List funcList;
29 |
30 | MineViewModel() {
31 | funcList = [];
32 | }
33 |
34 | @override
35 | Future handleData(ResponseModel data) async {
36 | var model = HomeDataModel.fromJson(data.map);
37 | funcList.clear();
38 | if (model.sections != null) {
39 | funcList = model.sections!.first.sectionContents ?? [];
40 | }
41 | }
42 |
43 | @override
44 | Future requestData({
45 | int pageNum = 1,
46 | Map queryParams = const {},
47 | }) async {
48 | ResponseModel? model = await HttpConfig.request(api: Api.mine);
49 | if (model != null) {
50 | await handleData(model);
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/pages/player/video_player.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: must_be_immutable, camel_case_types
2 |
3 | import 'package:flutter/services.dart';
4 | import 'package:flutter_video_player/pages/drama_detail/model/video_info_model.dart';
5 | import 'package:flutter_video_player/pages/drama_detail/page/drama_detail_page.dart';
6 | import 'package:video_player/video_player.dart';
7 | import 'package:flutter/material.dart';
8 | import 'controller_overlay.dart';
9 |
10 | final overlayKey = GlobalKey(debugLabel: 'overlayKey');
11 |
12 | class VideoPlayerLayer extends StatefulWidget
13 | implements ControllerOverlayCallback {
14 | VideoPlayerLayer({
15 | Key? key,
16 | this.smallScreenCallback,
17 | this.fullScreenCallback,
18 | this.nextSourceCallback,
19 | }) : super(key: key);
20 |
21 | @override
22 | _VideoPlayerLayerState createState() => _VideoPlayerLayerState();
23 |
24 | @override
25 | VoidCallback? smallScreenCallback;
26 |
27 | @override
28 | VoidCallback? fullScreenCallback;
29 |
30 | @override
31 | VoidCallback? nextSourceCallback;
32 | }
33 |
34 | class _VideoPlayerLayerState extends State {
35 | bool isFullScreen = false;
36 |
37 | late PlayerControllerOverlay overlay;
38 | VideoPlayerController? oldController;
39 | late VideoPlayerController newController;
40 |
41 | int? _oldPlayInfoId;
42 |
43 | @override
44 | void initState() {
45 | super.initState();
46 | }
47 |
48 | @override
49 | void didChangeDependencies() {
50 | super.didChangeDependencies();
51 |
52 | PlayInfo? info =
53 | DramaPlayInfoWidget.of(context)?.viewModel.playInfoModel?.playInfo;
54 | int? playId = info?.episodeSid;
55 | if (_oldPlayInfoId == playId) {
56 | return;
57 | }
58 | try {
59 | if (oldController != null) {
60 | oldController?.pause();
61 | oldController?.dispose();
62 | oldController = null;
63 | }
64 | } catch (e) {
65 | //(e);
66 | // print(e);
67 | }
68 |
69 | String playUrl = info?.url ?? '';
70 | if (playUrl.isNotEmpty) {
71 | newController = VideoPlayerController.network(playUrl);
72 | _oldPlayInfoId = info?.episodeSid;
73 | oldController = newController;
74 | }
75 | }
76 |
77 | @override
78 | void dispose() {
79 | try {
80 | if (oldController?.value.isPlaying == true) {
81 | oldController?.pause();
82 | }
83 | // ignore: empty_catches
84 | } catch (e) {}
85 |
86 | if (newController.value.isPlaying) {
87 | newController.pause();
88 | }
89 | newController.dispose();
90 | super.dispose();
91 | }
92 |
93 | Widget get playerView {
94 | return Center(
95 | child: AspectRatio(
96 | aspectRatio: newController.value.aspectRatio,
97 | child: VideoPlayer(newController),
98 | ),
99 | );
100 | }
101 |
102 | PlayerControllerOverlay get controllerOverlay {
103 | return PlayerControllerOverlay(
104 | key: overlayKey,
105 | player: newController,
106 | isFullScreen: isFullScreen,
107 | smallScreenCallback: () {
108 | SystemChrome.setPreferredOrientations([
109 | DeviceOrientation.portraitUp,
110 | ]);
111 | setState(() {
112 | isFullScreen = false;
113 | });
114 | widget.smallScreenCallback?.call();
115 | },
116 | fullScreenCallback: () {
117 | SystemChrome.setPreferredOrientations([
118 | DeviceOrientation.landscapeRight,
119 | DeviceOrientation.landscapeLeft,
120 | ]);
121 | setState(() {
122 | isFullScreen = true;
123 | });
124 | widget.fullScreenCallback?.call();
125 | },
126 | nextSourceCallback: widget.nextSourceCallback,
127 | );
128 | }
129 |
130 | @override
131 | Widget build(BuildContext context) {
132 | DramaPlayInfoWidget.of(context)?.viewModel;
133 | return FutureBuilder(
134 | future: started(),
135 | builder: (BuildContext ctx, AsyncSnapshot snapshot) {
136 | if (snapshot.connectionState == ConnectionState.done &&
137 | newController.value.isInitialized) {
138 | return Container(
139 | color: Colors.black,
140 | child: Stack(
141 | children: [
142 | playerView,
143 | controllerOverlay,
144 | ],
145 | ),
146 | );
147 | }
148 | return Container(
149 | color: const Color(0xff000000),
150 | );
151 | },
152 | );
153 | }
154 |
155 | Future started() async {
156 | try {
157 | if (!newController.value.isInitialized) {
158 | await newController.initialize();
159 | }
160 | await newController.play();
161 | return true;
162 | } catch (e) {
163 | return false;
164 | }
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/lib/pages/search/search_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | import '../../util/util.dart';
5 |
6 | class SearchPage extends StatefulWidget {
7 | const SearchPage({Key? key}) : super(key: key);
8 |
9 | @override
10 | _SearchPageState createState() => _SearchPageState();
11 | }
12 |
13 | class _SearchPageState extends State {
14 | @override
15 | Widget build(BuildContext context) {
16 | return Column(
17 | children: [
18 | const CupertinoNavigationBar(
19 | backgroundColor: Colors.white,
20 | middle: Text('搜索'),
21 | border: Border(
22 | bottom: BorderSide(
23 | color: Color(0xFFB2B8C2),
24 | width: 0.5,
25 | style: BorderStyle.solid,
26 | ),
27 | ),
28 | ),
29 | Container(
30 | constraints: BoxConstraints(
31 | maxHeight: Util.instance.contentHeight(),
32 | maxWidth: MediaQuery.of(context).size.width,
33 | ),
34 | padding: const EdgeInsets.fromLTRB(12, 0, 12, 0),
35 | ),
36 | ],
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/pages/setting/setting_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_video_player/util/util.dart';
3 | import 'package:shared_preferences/shared_preferences.dart';
4 |
5 | class SettiingViewPage extends StatefulWidget {
6 | const SettiingViewPage({
7 | Key? key,
8 | }) : super(key: key);
9 |
10 | @override
11 | _SettingViewPageState createState() => _SettingViewPageState();
12 | }
13 |
14 | class _SettingViewPageState extends State {
15 | late final SharedPreferences prefs;
16 | bool _canCellarPlay = false;
17 | bool _canCellarDownload = false;
18 |
19 | final items = [
20 | '允许移动网络播放',
21 | '允许移动网络下载',
22 | '清理缓存',
23 | 'APP版本',
24 | ];
25 |
26 | @override
27 | void initState() {
28 | super.initState();
29 | }
30 |
31 | Future initData() async {
32 | prefs = await SharedPreferences.getInstance();
33 | _canCellarPlay = prefs.getBool('canCellarPlay') ?? false;
34 | _canCellarDownload = prefs.getBool('canCellarDownload') ?? false;
35 | }
36 |
37 | @override
38 | Widget build(BuildContext context) {
39 | return FutureBuilder(
40 | future: initData(),
41 | builder: (context, snap) {
42 | return Scaffold(
43 | appBar: AppBar(
44 | toolbarHeight: Util.navBarHeight,
45 | backgroundColor: Colors.white,
46 | title: const Text(
47 | '设置',
48 | style: TextStyle(color: Colors.black),
49 | ),
50 | elevation: 0.5,
51 | leading: MaterialButton(
52 | padding: EdgeInsets.zero,
53 | minWidth: 42,
54 | onPressed: () {
55 | Navigator.pop(context);
56 | },
57 | child: const Icon(
58 | Icons.arrow_back_ios_new,
59 | size: 24,
60 | color: Color(0xff32383a),
61 | ),
62 | ),
63 | ),
64 | body: Container(
65 | margin: const EdgeInsets.only(bottom: 100),
66 | child: ListView.separated(
67 | itemBuilder: (context, index) {
68 | if (index == 0) {
69 | return SwitchListTile(
70 | title: Text(items[index]),
71 | value: _canCellarPlay,
72 | onChanged: (onChanged) async {
73 | await prefs.setBool('canCellarPlay', onChanged);
74 | setState(() {
75 | _canCellarPlay = onChanged;
76 | });
77 | },
78 | );
79 | }
80 | if (index == 1) {
81 | return SwitchListTile(
82 | title: Text(items[index]),
83 | value: _canCellarDownload,
84 | onChanged: (onChanged) async {
85 | await prefs.setBool('canCellarDownload', onChanged);
86 | setState(() {
87 | _canCellarDownload = onChanged;
88 | });
89 | },
90 | );
91 | }
92 | return ListTile(
93 | title: Text(items[index]),
94 | contentPadding: const EdgeInsets.only(left: 16, right: 30),
95 | trailing:
96 | index == 2 ? const Text('10M') : const Text('1.0.0'),
97 | );
98 | },
99 | separatorBuilder: (context, index) {
100 | return const Divider(
101 | indent: 18,
102 | );
103 | },
104 | itemCount: items.length,
105 | ),
106 | ),
107 | );
108 | });
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/lib/pages/splash/splash_page.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: prefer_const_constructors
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_video_player/routes/route_manager.dart';
5 | import 'package:flutter_video_player/user/user_manager.dart';
6 | import 'package:shared_preferences/shared_preferences.dart';
7 |
8 | import '../../util/r_sources.dart';
9 |
10 | class SplashScreen extends StatefulWidget {
11 | const SplashScreen({Key? key}) : super(key: key);
12 | @override
13 | _SplashScreenState createState() => _SplashScreenState();
14 | }
15 |
16 | class _SplashScreenState extends State
17 | with SingleTickerProviderStateMixin {
18 | late AnimationController _controller;
19 | late Animation _animation;
20 |
21 | Future get isFirstLaunch async {
22 | SharedPreferences prefs = await SharedPreferences.getInstance();
23 | bool ret = prefs.getBool('FirstLaunch') ?? true;
24 | if (ret) {
25 | prefs.setBool('FirstLaunch', false);
26 | }
27 | return ret;
28 | }
29 |
30 | @override
31 | initState() {
32 | super.initState();
33 | UserManger.instance.getLocalLogin();
34 | _controller = AnimationController(
35 | vsync: this,
36 | duration: Duration(milliseconds: 1500),
37 | );
38 | _animation = Tween(begin: 1.0, end: 1.2).animate(_controller);
39 | _animation.addStatusListener((status) {
40 | if (status == AnimationStatus.completed) {
41 | Navigator.of(context).pushNamedAndRemoveUntil(
42 | RouteManager.root,
43 | (route) => false,
44 | );
45 | }
46 | });
47 | _controller.forward();
48 | }
49 |
50 | @override
51 | void dispose() {
52 | _controller.dispose();
53 | super.dispose();
54 | }
55 |
56 | @override
57 | Widget build(BuildContext context) {
58 | return FutureBuilder(
59 | future: isFirstLaunch,
60 | builder: (context, AsyncSnapshot snap) {
61 | if (snap.data == false) {
62 | return ScaleTransition(
63 | scale: _animation,
64 | child: Image.asset(
65 | R.Img.splash_shake,
66 | scale: 1.5,
67 | fit: BoxFit.cover,
68 | ),
69 | );
70 | }
71 | return Container(
72 | color: Colors.white,
73 | );
74 | });
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/lib/pages/web/web_page.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: must_be_immutable
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_video_player/custom/navigationbar.dart';
5 |
6 | class WebViewPage extends StatefulWidget {
7 | WebViewPage({
8 | Key? key,
9 | this.url,
10 | }) : super(key: key);
11 |
12 | String? url;
13 | @override
14 | _WebViewPageState createState() => _WebViewPageState();
15 | }
16 |
17 | class _WebViewPageState extends State {
18 | @override
19 | void initState() {
20 | super.initState();
21 | }
22 |
23 | @override
24 | Widget build(BuildContext context) {
25 | return Scaffold(
26 | appBar: UINavigationBar(
27 | title: '',
28 | leading: [
29 | MaterialButton(
30 | padding: EdgeInsets.zero,
31 | minWidth: 42,
32 | onPressed: () {
33 | Navigator.pop(context);
34 | },
35 | child: const Icon(
36 | Icons.arrow_back_ios_new,
37 | size: 24,
38 | color: Color(0xff32383a),
39 | ),
40 | ),
41 | ],
42 | ),
43 | body: Container(),
44 | );
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/lib/routes/route_manager.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: unnecessary_overrides
2 |
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_video_player/pages/cache/cache_page.dart';
5 | import 'package:flutter_video_player/pages/mine/mine_page.dart';
6 | import 'package:flutter_video_player/pages/setting/setting_page.dart';
7 | import 'package:flutter_video_player/pages/web/web_page.dart';
8 |
9 | import '../models/models.dart';
10 | import '../pages/drama_detail/page/drama_detail_page.dart';
11 | import '../pages/drama_list/drama_list_page.dart';
12 | import '../pages/haokan_video/short_video/haokan_short_video_page.dart';
13 | import '../pages/login/login_page.dart';
14 | import '../pages/tab_controller/tab_controller.dart';
15 |
16 | class RouteManager {
17 | static const root = '/root';
18 | static const dramaDetail = '/dramaDetail';
19 | static const dramaList = '/dramaList';
20 | static const login = '/login';
21 | static const fullScreenPlayer = '/fullScreenPlayer';
22 | static const webPage = '/webPage';
23 | static const mine = '/mine';
24 | static const setting = '/setting';
25 | static const cache = '/cache';
26 | static const category = '/category';
27 | static const shortVideo = '/shortVideo';
28 |
29 | static Route generateRoute(RouteSettings settings) {
30 | return MaterialPageRoute(builder: (context) {
31 | switch (settings.name) {
32 | case root:
33 | return const RootTabViewController();
34 | case dramaDetail:
35 | return DramaDetailPageView(
36 | model: (settings.arguments is DramaCoverModel)
37 | ? (settings.arguments as DramaCoverModel)
38 | : null,
39 | );
40 | case dramaList:
41 | return DramaListPageView(
42 | model: (settings.arguments is DramaCoverModel)
43 | ? (settings.arguments as DramaCoverModel)
44 | : null,
45 | );
46 | case login:
47 | return const LoginPageView();
48 | case fullScreenPlayer:
49 | // var isVc = settings.arguments.runtimeType is VideoPlayerLayer;
50 | return Container();
51 | case webPage:
52 | return WebViewPage(
53 | url: settings.arguments as String,
54 | );
55 | case mine:
56 | return const MinePageView();
57 | case setting:
58 | return const SettiingViewPage();
59 | case cache:
60 | return const CacheViewPage();
61 | case shortVideo:
62 | return const HaoKanShortVideoPage(active: true,);
63 | default:
64 | return const Scaffold();
65 | }
66 | });
67 | }
68 | }
69 |
70 | class CFNavigatorObservers extends NavigatorObserver {
71 | @override
72 | void didPush(Route route, Route? previousRoute) {
73 | super.didPush(route, previousRoute);
74 | }
75 |
76 | @override
77 | void didPop(Route route, Route? previousRoute) {
78 | super.didPop(route, previousRoute);
79 | }
80 |
81 | @override
82 | void didRemove(Route route, Route? previousRoute) {
83 | super.didRemove(route, previousRoute);
84 | }
85 |
86 | @override
87 | void didReplace({Route? newRoute, Route? oldRoute}) {
88 | super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
89 | }
90 |
91 | @override
92 | void didStartUserGesture(Route route, Route? previousRoute) {
93 | super.didStartUserGesture(route, previousRoute);
94 | }
95 |
96 | @override
97 | void didStopUserGesture() {
98 | super.didStopUserGesture();
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/lib/user/user_info_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:hive/hive.dart';
2 |
3 | @HiveType(typeId: 0)
4 | class UserInfoModel extends HiveObject {
5 | @HiveField(0)
6 | int? userId;
7 |
8 | @HiveField(1)
9 | String? nickName;
10 |
11 | @HiveField(2)
12 | String? mobile;
13 |
14 | @HiveField(3)
15 | String? token;
16 |
17 | // 网络数据,完整数据
18 | UserInfoModel.fromJson(Map map) {
19 | if (map.isEmpty) {
20 | return;
21 | }
22 | Map user = map['user'];
23 | userId = user['userId'];
24 | nickName = user['nickName'];
25 | mobile = user['mobile'];
26 | token = map['token'];
27 | }
28 |
29 | // 本地数据, 简化数据
30 | UserInfoModel(Map user) {
31 | userId = user['userId'];
32 | nickName = user['nickName'];
33 | mobile = user['mobile'];
34 | token = user['token'];
35 | }
36 |
37 | Map toJson() {
38 | return {
39 | 'userId': userId,
40 | 'nickName': nickName,
41 | 'mobile': mobile,
42 | 'token': token,
43 | };
44 | }
45 | }
46 |
47 | class UserInfoAdapter extends TypeAdapter {
48 | @override
49 | final typeId = 0;
50 |
51 | @override
52 | UserInfoModel read(BinaryReader reader) {
53 | dynamic va = reader.read(typeId);
54 | if (va is Map && va.isNotEmpty) {
55 | return UserInfoModel(va);
56 | }
57 |
58 | return UserInfoModel({});
59 | }
60 |
61 | @override
62 | void write(BinaryWriter writer, UserInfoModel obj) {
63 | writer.write(obj.userId);
64 | writer.write(obj.nickName);
65 | writer.write(obj.mobile);
66 | writer.write(obj.token);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/lib/user/user_manager.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_video_player/database/hv_manager.dart';
2 |
3 | import 'user_info_model.dart';
4 |
5 | class UserManger {
6 | UserManger._();
7 |
8 | static final UserManger _instance = UserManger._();
9 | static UserManger get instance => _instance;
10 | factory UserManger() => _instance;
11 |
12 | bool _isLogin = false;
13 | bool get isLogin => _isLogin;
14 | set setIsLogin(bool value) {
15 | _isLogin = value;
16 | if (!value) {
17 | UserManger.instance.userModel = null;
18 | }
19 | }
20 |
21 | UserInfoModel? userModel;
22 |
23 | getLocalLogin() async {
24 | UserInfoModel? model = await getUserInfo();
25 | setIsLogin = model != null;
26 | userModel = model;
27 | }
28 |
29 | ///保存用户信息
30 | static Future saveUserInfo(UserInfoModel model) async {
31 | UserManger.instance.setIsLogin = true;
32 | UserManger.instance.userModel = model;
33 | return HiveManager().saveUserInfo(model);
34 | }
35 |
36 | /// 获取用户信息
37 | static Future getUserInfo() async {
38 | return await HiveManager().getUserInfo();
39 | }
40 |
41 | /// 删除用户信息
42 | static Future deleteUserInfo() async {
43 | UserManger.instance.setIsLogin = false;
44 | return await HiveManager().deleteUserInfo();
45 | }
46 |
47 | /// 退出登录
48 | logout() async {
49 | UserInfoModel? model = await getUserInfo();
50 | if (model == null || model.token == null) {
51 | return;
52 | }
53 | await deleteUserInfo();
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/lib/util/device_info.dart:
--------------------------------------------------------------------------------
1 | import 'package:package_info_plus/package_info_plus.dart';
2 |
3 | class Device {
4 | static late final PackageInfo info;
5 | static final Device _instance = Device._();
6 | static Device get instance => _instance;
7 |
8 | Device._() {
9 | _initPackageInfo();
10 | }
11 |
12 | Future _initPackageInfo() async {
13 | info = await PackageInfo.fromPlatform();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/lib/util/r_sources.dart:
--------------------------------------------------------------------------------
1 | // ignore_for_file: file_names, non_constant_identifier_names, constant_identifier_names
2 |
3 | typedef R = ResourceRef;
4 |
5 | /// 资源名管理器
6 | /// 图片
7 | class Imgs {
8 | Imgs._();
9 | static final instance = Imgs._();
10 |
11 | ///gif
12 | final ic_playing = 'resource/image/ic_playing.gif';
13 |
14 | ///png
15 | final ic_vip = 'resource/image/ic_vip.png';
16 | final pic_banner_shadow = 'resource/image/pic_banner_shadow.png';
17 | final pic_Avatar_n = 'resource/image/pic_Avatar_n.png';
18 | final pic_Avatar_h = 'resource/image/pic_Avatar_h.png';
19 | final splas_logo = 'resource/image/splas_logo.png';
20 |
21 | final cover_img = 'resource/image/cover_img.png';
22 |
23 | final haokan_logo = 'resource/image/haokan_logo.png';
24 |
25 | final splash_shake = 'resource/image/splash_shake.png';
26 | }
27 |
28 | /// 字符串
29 | class Strs {
30 | Strs._();
31 | static final instance = Strs._();
32 |
33 | final firstLaunch = 'FirstLaunch';
34 |
35 | final home = '首页';
36 | final haokanVideo = '好看';
37 | final hotVideo = 'Hot';
38 | final category = '分类';
39 | final hotComment = '漫画';
40 | final mine = '我的';
41 | }
42 |
43 | class JsonPath {
44 | JsonPath._();
45 | static final instance = JsonPath._();
46 |
47 | final homePage = 'resource/json/home_page.json';
48 | final minePage = 'resource/json/mine_page.json';
49 |
50 | final categoryFilter = 'resource/json/category_filters.json';
51 | final categoryPage1 = 'resource/json/category_list_page1.json';
52 | final categoryPage2 = 'resource/json/category_list_page2.json';
53 |
54 | final dramaDetail = 'resource/json/drama_info_detail.json';
55 |
56 | final play1Detail = 'resource/json/play1_info_detail.json';
57 | final play2Detail = 'resource/json/play2_info_detail.json';
58 | final play3Detail = 'resource/json/play3_info_detail.json';
59 | final play4Detail = 'resource/json/play4_info_detail.json';
60 |
61 | final loginSendCode = 'resource/json/login_send_code.json';
62 | final loginUserInfo = 'resource/json/user_info.json';
63 | }
64 |
65 | class ResourceRef {
66 | ResourceRef._();
67 |
68 | static final Img = Imgs.instance;
69 | static final Str = Strs.instance;
70 | static final Jsp = JsonPath.instance;
71 | }
72 |
--------------------------------------------------------------------------------
/lib/util/toast.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class Toast {
4 | static OverlayEntry? _overlayEntry;
5 | static DateTime _startedTime = DateTime.now();
6 |
7 | static show(BuildContext context, String msg) async {
8 | _startedTime = DateTime.now();
9 | OverlayState? state = Overlay.of(context);
10 | if (_overlayEntry == null) {
11 | _overlayEntry = OverlayEntry(
12 | builder: (BuildContext context) {
13 | return Positioned(
14 | top: MediaQuery.of(context).size.height / 2 - 20,
15 | width: MediaQuery.of(context).size.width,
16 | child: Row(
17 | children: [
18 | const Spacer(),
19 | Container(
20 | alignment: Alignment.center,
21 | decoration: BoxDecoration(
22 | color: Colors.black.withOpacity(0.7),
23 | borderRadius: BorderRadius.circular(8),
24 | ),
25 | margin: const EdgeInsets.symmetric(horizontal: 40),
26 | padding: const EdgeInsets.all(18),
27 | child: AnimatedOpacity(
28 | opacity: 1,
29 | duration: const Duration(milliseconds: 400),
30 | child: Text(
31 | msg,
32 | textAlign: TextAlign.center,
33 | style: const TextStyle(
34 | fontSize: 15,
35 | color: Colors.white,
36 | fontWeight: FontWeight.w500,
37 | decoration: TextDecoration.none,
38 | ),
39 | ),
40 | ),
41 | ),
42 | const Spacer(),
43 | ],
44 | ),
45 | );
46 | },
47 | );
48 | } else {}
49 | if (_overlayEntry?.mounted == false) {
50 | state.insert(_overlayEntry!);
51 | } else {
52 | _overlayEntry?.markNeedsBuild();
53 | }
54 |
55 | await Future.delayed(const Duration(seconds: 2));
56 | if (DateTime.now().difference(_startedTime).inSeconds >= 2) {
57 | if (_overlayEntry != null && _overlayEntry?.mounted == true) {
58 | _overlayEntry?.remove();
59 | _overlayEntry = null;
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/lib/util/util.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | export 'r_sources.dart';
3 |
4 | class Util {
5 | Util._();
6 |
7 | static final Util _instance = Util._();
8 | static Util get instance => _instance;
9 | factory Util() => _instance;
10 |
11 | /// 屏幕像素倍数
12 | static final double _devicePixelRatio =
13 | WidgetsBinding.instance.window.devicePixelRatio;
14 |
15 | /// 屏幕物理像素大小
16 | static Size get _size =>
17 | WidgetsBinding.instance.window.physicalSize / _devicePixelRatio;
18 |
19 | static bool get isPhoneX => WidgetsBinding.instance.window.padding.top > 60;
20 |
21 | /// 图片宽高比
22 | static const imgRatio = 0.75;
23 |
24 | /// banner的图片比例
25 | static const bigEyeImgRatio = 351.0 / 468.0;
26 |
27 | /// 状态栏高度,44 : 20, 部分X机型上,高度会判断为47,这里写死
28 | static double get statusBarHeight => isPhoneX ? 44 : 20;
29 |
30 | /// 导航条高度 44
31 | static const double navBarHeight = 44;
32 |
33 | /// 导航栏整体高度
34 | static double get navgationBarHeight => statusBarHeight + 44;
35 |
36 | /// 底部导航栏高度 83 : 49
37 | static double get tabBarHeight => isPhoneX ? 83 : 49;
38 |
39 | /// 屏幕宽度
40 | static double get appWidth => _size.width;
41 |
42 | /// 屏幕高度
43 | static double get appHeight => _size.height;
44 |
45 | /// 内容显示区域的高度
46 | double contentHeight({
47 | bool showNavBar = true,
48 | bool showTabBar = true,
49 | }) {
50 | double height = appHeight;
51 | if (showNavBar) {
52 | height -= navgationBarHeight;
53 | }
54 | if (showTabBar) {
55 | height -= tabBarHeight;
56 | }
57 | // -1 分割线高度
58 | return height - 1;
59 | }
60 |
61 | static bool isMobile(String mobile) {
62 | if (mobile.isEmpty || mobile.length != 11) {
63 | return false;
64 | }
65 | const reg = r'^1[0-9]\d{9}$';
66 | return RegExp(reg).hasMatch(mobile);
67 | }
68 |
69 | static bool isVerifyCode(String code) {
70 | if (code.isEmpty || code.length != 6) {
71 | return false;
72 | }
73 | const reg = r'\d{6}$';
74 | return RegExp(reg).hasMatch(code);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/local.properties:
--------------------------------------------------------------------------------
1 | ## This file must *NOT* be checked into Version Control Systems,
2 | # as it contains information specific to your local configuration.
3 | #
4 | # Location of the SDK. This is only used by Gradle.
5 | # For customization when using a Version Control System, please read the
6 | # header note.
7 | #Fri Feb 25 11:24:59 CST 2022
8 | sdk.dir=/Users/a1111/Library/Android/sdk
9 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_video_player
2 | description: flutter、video、player
3 |
4 | # The following line prevents the package from being accidentally published to
5 | # pub.dev using `flutter pub publish`. This is preferred for private packages.
6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
7 |
8 | # The following defines the version and build number for your application.
9 | # A version number is three numbers separated by dots, like 1.2.43
10 | # followed by an optional build number separated by a +.
11 | # Both the version and the builder number may be overridden in flutter
12 | # build by specifying --build-name and --build-number, respectively.
13 | # In Android, build-name is used as versionName while build-number used as versionCode.
14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
16 | # Read more about iOS versioning at
17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
18 | version: 1.0.0+1
19 |
20 | environment:
21 | sdk: ">=2.17.0"
22 |
23 | # Dependencies specify other packages that your package needs in order to work.
24 | # To automatically upgrade your package dependencies to the latest versions
25 | # consider running `flutter pub upgrade --major-versions`. Alternatively,
26 | # dependencies can be manually updated by changing the version numbers below to
27 | # the latest version available on pub.dev. To see which dependencies have newer
28 | # versions available, run `flutter pub outdated`.
29 | dependencies:
30 | flutter:
31 | sdk: flutter
32 | #刷新控件
33 | pull_to_refresh: ^2.0.0
34 | #请求库
35 | dio: ^4.0.3
36 | #加密库
37 | crypto: ^3.0.1
38 | #视频播放器
39 | video_player: ^2.2.19
40 | #滑动选项
41 | flutter_slidable: ^1.1.0
42 | #网页
43 | # webview_flutter: ^3.0.0
44 | # 机型等信息
45 | package_info_plus: ^1.3.0
46 | # NSUserDefaults on iOS, SharedPreferences on Android
47 | shared_preferences: ^2.0.11
48 | # 滑动组件
49 | flutter_swiper_null_safety: ^1.0.2
50 | # 路径
51 | path_provider: ^2.0.8
52 | # hive数据库
53 | hive: ^2.0.5
54 | hive_flutter: ^1.1.0
55 | hive_generator: ^1.1.2
56 |
57 | provider: ^6.0.2
58 |
59 | #图片处理库
60 | extended_image: ^6.0.3
61 | waterfall_flow: any
62 |
63 | # The following adds the Cupertino Icons font to your application.
64 | # Use with the CupertinoIcons class for iOS style icons.
65 | cupertino_icons: ^1.0.2
66 |
67 | dev_dependencies:
68 | flutter_test:
69 | sdk: flutter
70 |
71 | # The "flutter_lints" package below contains a set of recommended lints to
72 | # encourage good coding practices. The lint set provided by the package is
73 | # activated in the `analysis_options.yaml` file located at the root of your
74 | # package. See that file for information about deactivating specific lint
75 | # rules and activating additional ones.
76 | flutter_lints: ^1.0.0
77 |
78 | # For information on the generic Dart part of this file, see the
79 | # following page: https://dart.dev/tools/pub/pubspec
80 |
81 | # The following section is specific to Flutter.
82 | flutter:
83 |
84 | # The following line ensures that the Material Icons font is
85 | # included with your application, so that you can use the icons in
86 | # the material Icons class.
87 | uses-material-design: true
88 |
89 | # To add assets to your application, add an assets section, like this:
90 | assets:
91 | - resource/json/
92 | - resource/image/pic_banner_shadow.png
93 | - resource/image/ic_vip.png
94 | - resource/image/ic_playing.gif
95 | - resource/image/pic_Avatar_n.png
96 | - resource/image/pic_Avatar_h.png
97 | - resource/image/splash_logo.png
98 | - resource/image/cover_img.png
99 | - resource/image/haokan_logo.png
100 | - resource/image/splash_shake.png
101 |
102 | # An image asset can refer to one or more resolution-specific "variants", see
103 | # https://flutter.dev/assets-and-images/#resolution-aware.
104 |
105 | # For details regarding adding assets from package dependencies, see
106 | # https://flutter.dev/assets-and-images/#from-packages
107 |
108 | # To add custom fonts to your application, add a fonts section here,
109 | # in this "flutter" section. Each entry in this list should have a
110 | # "family" key with the font family name, and a "fonts" key with a
111 | # list giving the asset and other descriptors for the font. For
112 | # example:
113 | # fonts:
114 | # - family: Schyler
115 | # fonts:
116 | # - asset: fonts/Schyler-Regular.ttf
117 | # - asset: fonts/Schyler-Italic.ttf
118 | # style: italic
119 | # - family: Trajan Pro
120 | # fonts:
121 | # - asset: fonts/TrajanPro.ttf
122 | # - asset: fonts/TrajanPro_Bold.ttf
123 | # weight: 700
124 | #
125 | # For details regarding fonts from package dependencies,
126 | # see https://flutter.dev/custom-fonts/#from-packages
127 |
--------------------------------------------------------------------------------
/resource/image/2.0x/ic_vip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/resource/image/2.0x/ic_vip.png
--------------------------------------------------------------------------------
/resource/image/2.0x/pic_Avatar_h.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/resource/image/2.0x/pic_Avatar_h.png
--------------------------------------------------------------------------------
/resource/image/2.0x/pic_Avatar_n.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/resource/image/2.0x/pic_Avatar_n.png
--------------------------------------------------------------------------------
/resource/image/2.0x/pic_banner_shadow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/resource/image/2.0x/pic_banner_shadow.png
--------------------------------------------------------------------------------
/resource/image/3.0x/ic_vip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/resource/image/3.0x/ic_vip.png
--------------------------------------------------------------------------------
/resource/image/3.0x/pic_Avatar_h.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/resource/image/3.0x/pic_Avatar_h.png
--------------------------------------------------------------------------------
/resource/image/3.0x/pic_Avatar_n.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/resource/image/3.0x/pic_Avatar_n.png
--------------------------------------------------------------------------------
/resource/image/3.0x/pic_banner_shadow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/resource/image/3.0x/pic_banner_shadow.png
--------------------------------------------------------------------------------
/resource/image/3.0x/splash_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/resource/image/3.0x/splash_logo.png
--------------------------------------------------------------------------------
/resource/image/cover_img.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/resource/image/cover_img.png
--------------------------------------------------------------------------------
/resource/image/haokan_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/resource/image/haokan_logo.png
--------------------------------------------------------------------------------
/resource/image/ic_playing.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/resource/image/ic_playing.gif
--------------------------------------------------------------------------------
/resource/image/splash_shake.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hadudi/flutter_video_player/96f1df7e4a40c384b8a5af660f2cc9c83acd0b9a/resource/image/splash_shake.png
--------------------------------------------------------------------------------
/resource/json/category_filters.json:
--------------------------------------------------------------------------------
1 | {
2 | "requestId": "",
3 | "code": "0000",
4 | "msg": "Success",
5 | "recordsTotal": null,
6 | "data": [
7 | {
8 | "filterType": "sort",
9 | "dramaFilterItemList": [
10 | {
11 | "displayName": "最热播放",
12 | "value": "hot"
13 | },
14 | {
15 | "displayName": "最近更新",
16 | "value": "new"
17 | },
18 | {
19 | "displayName": "最受好评",
20 | "value": "score"
21 | },
22 | {
23 | "displayName": "本季新剧",
24 | "value": "season_new"
25 | }
26 | ]
27 | },
28 | {
29 | "filterType": "dramaType",
30 | "dramaFilterItemList": [
31 | {
32 | "displayName": "全部",
33 | "value": ""
34 | },
35 | {
36 | "displayName": "电视剧",
37 | "value": "TV"
38 | },
39 | {
40 | "displayName": "电影",
41 | "value": "MOVIE"
42 | },
43 | {
44 | "displayName": "综艺",
45 | "value": "VARIETY"
46 | },
47 | {
48 | "displayName": "脱口秀",
49 | "value": "TALK"
50 | },
51 | {
52 | "displayName": "纪录片",
53 | "value": "DOCUMENTARY"
54 | },
55 | {
56 | "displayName": "动漫",
57 | "value": "COMIC"
58 | },
59 | {
60 | "displayName": "迷你剧",
61 | "value": "MINISERIES"
62 | },
63 | {
64 | "displayName": "网剧",
65 | "value": "SETI"
66 | }
67 | ]
68 | },
69 | {
70 | "filterType": "area",
71 | "dramaFilterItemList": [
72 | {
73 | "displayName": "全部",
74 | "value": ""
75 | },
76 | {
77 | "displayName": "美国",
78 | "value": "美国"
79 | },
80 | {
81 | "displayName": "英国",
82 | "value": "英国"
83 | },
84 | {
85 | "displayName": "韩国",
86 | "value": "韩国"
87 | },
88 | {
89 | "displayName": "日本",
90 | "value": "日本"
91 | },
92 | {
93 | "displayName": "泰国",
94 | "value": "泰国"
95 | },
96 | {
97 | "displayName": "内地",
98 | "value": "中国大陆"
99 | },
100 | {
101 | "displayName": "中国香港",
102 | "value": "中国香港"
103 | },
104 | {
105 | "displayName": "中国台湾",
106 | "value": "中国台湾"
107 | },
108 | {
109 | "displayName": "其他",
110 | "value": "其他"
111 | }
112 | ]
113 | },
114 | {
115 | "filterType": "plotType",
116 | "dramaFilterItemList": [
117 | {
118 | "displayName": "全部",
119 | "value": ""
120 | },
121 | {
122 | "displayName": "剧情",
123 | "value": "剧情"
124 | },
125 | {
126 | "displayName": "喜剧",
127 | "value": "喜剧"
128 | },
129 | {
130 | "displayName": "动作",
131 | "value": "动作"
132 | },
133 | {
134 | "displayName": "冒险",
135 | "value": "冒险"
136 | },
137 | {
138 | "displayName": "爱情",
139 | "value": "爱情"
140 | },
141 | {
142 | "displayName": "歌舞",
143 | "value": "歌舞"
144 | },
145 | {
146 | "displayName": "恐怖",
147 | "value": "恐怖"
148 | },
149 | {
150 | "displayName": "科幻",
151 | "value": "科幻"
152 | },
153 | {
154 | "displayName": "奇幻",
155 | "value": "奇幻"
156 | },
157 | {
158 | "displayName": "动画",
159 | "value": "动画"
160 | },
161 | {
162 | "displayName": "悬疑",
163 | "value": "悬疑"
164 | },
165 | {
166 | "displayName": "惊悚",
167 | "value": "惊悚"
168 | },
169 | {
170 | "displayName": "犯罪",
171 | "value": "犯罪"
172 | },
173 | {
174 | "displayName": "同性",
175 | "value": "同性"
176 | },
177 | {
178 | "displayName": "传记",
179 | "value": "传记"
180 | },
181 | {
182 | "displayName": "历史",
183 | "value": "历史"
184 | },
185 | {
186 | "displayName": "战争",
187 | "value": "战争"
188 | },
189 | {
190 | "displayName": "灾难",
191 | "value": "灾难"
192 | },
193 | {
194 | "displayName": "美食",
195 | "value": "美食"
196 | },
197 | {
198 | "displayName": "真人秀",
199 | "value": "真人秀"
200 | }
201 | ]
202 | },
203 | {
204 | "filterType": "year",
205 | "dramaFilterItemList": [
206 | {
207 | "displayName": "全部",
208 | "value": ""
209 | },
210 | {
211 | "displayName": "2021",
212 | "value": "2021"
213 | },
214 | {
215 | "displayName": "2020",
216 | "value": "2020"
217 | },
218 | {
219 | "displayName": "2019",
220 | "value": "2019"
221 | },
222 | {
223 | "displayName": "2018",
224 | "value": "2018"
225 | },
226 | {
227 | "displayName": "2017",
228 | "value": "2017"
229 | },
230 | {
231 | "displayName": "2016",
232 | "value": "2016"
233 | },
234 | {
235 | "displayName": "2015",
236 | "value": "2015"
237 | },
238 | {
239 | "displayName": "2014-2011",
240 | "value": "2014-2011"
241 | },
242 | {
243 | "displayName": "2010-2000",
244 | "value": "2010-2000"
245 | },
246 | {
247 | "displayName": "90年代",
248 | "value": "90年代"
249 | },
250 | {
251 | "displayName": "80年代",
252 | "value": "80年代"
253 | }
254 | ]
255 | },
256 | {
257 | "filterType": "serializedStatus",
258 | "dramaFilterItemList": [
259 | {
260 | "displayName": "全部",
261 | "value": ""
262 | },
263 | {
264 | "displayName": "连载",
265 | "value": "0"
266 | },
267 | {
268 | "displayName": "完结",
269 | "value": "1"
270 | },
271 | {
272 | "displayName": "未开播",
273 | "value": "2"
274 | }
275 | ]
276 | }
277 | ]
278 | }
--------------------------------------------------------------------------------
/resource/json/drama_info_detail.json:
--------------------------------------------------------------------------------
1 | {
2 | "requestId": "",
3 | "code": "0000",
4 | "msg": "Success",
5 | "data": {
6 | "seriesList": null,
7 | "drama": {
8 | "id": 333333,
9 | "createTime": "2021年8月19日",
10 | "title": "神医喜来乐 来源于好看网 非商业用途 侵权删",
11 | "cover": "https://pic.rmb.bdstatic.com/baidu-rmb-video-cover-1/2021-7/1626194565491/6f3d7a62247e.jpg@s_0,w_800,h_1000,q_80",
12 | "score": 7.4,
13 | "year": "2022",
14 | "area": "中国",
15 | "enName": "播放:54.7万",
16 | "cat": "古装剧",
17 | "dramaType": "电视剧",
18 | "serializedStatus": "已完结 共35集 这里测试只放4集",
19 | "brief":"清末,沧州的“一笑堂”郎中喜来乐治病救人,乐善好施,本与视无争,偏偏的,京城的靖王爷的格格得了重病被太医王天和宣布为绝症,靖王爷亲信鲁正明将喜来乐推荐到京城为格格治病,喜来乐用裸体熏浴法将牙关紧闭、汤水不进的格格救活,赢得靖王爷欢喜,但却引起王太医切齿嫉恨,于是千方百计对喜来乐加以谋害。喜来乐本来就恋着在沧州开饭馆的情人,年轻漂亮的寡妇赛西施,更不愿在京城这是非之地与王太医纠缠,他几番谢绝靖王爷挽留美意,靖王爷和鲁正明终于用计将他留在京城,并把“一笑堂”搬到京城来开业。",
20 | "episodeCount": 4
21 | },
22 | "episodeList": [
23 | {
24 | "episodeNo": 1,
25 | "text": "",
26 | "episodeSid": 10001
27 | },
28 | {
29 | "episodeNo": 2,
30 | "text": "",
31 | "episodeSid": 10002
32 | },
33 | {
34 | "episodeNo": 3,
35 | "text": "",
36 | "episodeSid": 10003
37 | },
38 | {
39 | "episodeNo": 4,
40 | "text": "",
41 | "episodeSid": 10004
42 | }
43 | ],
44 | "playInfo": {
45 | "url": "https://vd4.bdstatic.com/mda-mhhu5st92hmp1an5/default/hksr/1639922751/mda-mhhu5st92hmp1an5.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1645698585-0-0-49364b64cb374e0a3c6751d47930bd38&bcevod_channel=searchbox_feed&cd=0&pd=1&pt=3&logid=3585533028&vid=2015598530162537812&abtest=17451_1-3000216_1",
46 | "currentQuality": "LD",
47 | "mediaId": 11111,
48 | "episodeSid": 10001,
49 | "startingLength": 0
50 | },
51 | "qualityList": [
52 | {
53 | "quality": "FD",
54 | "qualityName": "标清"
55 | },
56 | {
57 | "quality": "LD",
58 | "qualityName": "高清"
59 | },
60 | {
61 | "quality": "AI_720",
62 | "qualityName": "超清"
63 | },
64 | {
65 | "quality": "AI_1080",
66 | "qualityName": "蓝光"
67 | }
68 | ]
69 | }
70 | }
--------------------------------------------------------------------------------
/resource/json/login_send_code.json:
--------------------------------------------------------------------------------
1 | {
2 | "code": "0000",
3 | "msg": "Success",
4 | "data": ""
5 | }
--------------------------------------------------------------------------------
/resource/json/mine_page.json:
--------------------------------------------------------------------------------
1 | {
2 | "requestId": "",
3 | "code": "0000",
4 | "msg": "Success",
5 | "recordsTotal": null,
6 | "data": {
7 | "bannerTop": [],
8 | "sections": [
9 | {
10 | "id": 4358,
11 | "display": "SCROLL",
12 | "moreText": "",
13 | "name": "其他",
14 | "position": 23,
15 | "sectionType": "MAGIC_CUBE",
16 | "sequence": 1,
17 | "targetId": null,
18 | "targetType": null,
19 | "displayTitle": "1",
20 | "sectionContents": [
21 | {
22 | "title": "我的收藏",
23 | "icon": "http://img.juquanquanapp.com/img/img/20211130/o_07a2f3f668464fd398cfc48a65c60754.png",
24 | "targetType": "ASSIGN_PAGE",
25 | "targetId": ""
26 | },
27 | {
28 | "title": "设置",
29 | "icon": "http://img.juquanquanapp.com/img/img/20210915/o_aa7d7ab579174a43843b919634c56f59.png",
30 | "targetType": "ASSIGN_PAGE",
31 | "targetId": ""
32 | },
33 | {
34 | "title": "问题反馈",
35 | "icon": "http://img.juquanquanapp.com/img/img/20210915/o_b627939ccc564971969dc8882de6bac9.png",
36 | "targetType": "H5",
37 | "targetId": "http%3A%2F%2Fmobile.juquanquanapp.com%2FcontactCustomerService"
38 | },
39 | {
40 | "title": "儿童隐私政策",
41 | "icon": "http://img.juquanquanapp.com/img/img/20210915/o_9ae0cb77f0384a3ea64ea97c032a102c.png",
42 | "targetType": "H5",
43 | "targetId": "https%3A%2F%2Fmobile.juquanquanapp.com%2Fagree%2FCHILDREN_PRIVACY_JQQ"
44 | },
45 | {
46 | "title": "隐私政策",
47 | "icon": "http://img.juquanquanapp.com/img/img/20210915/o_56566a1281264d9bbec66ff286f08e5f.png",
48 | "targetType": "H5",
49 | "targetId": "https%3A%2F%2Fmobile.juquanquanapp.com%2Fagree%2FCONCEAL_JQQ_IOS"
50 | },
51 | {
52 | "title": "用户协议",
53 | "icon": "http://img.juquanquanapp.com/img/img/20210915/o_a39d6f2d352d4f3db0159b700a5635de.png",
54 | "targetType": "H5",
55 | "targetId": "https%3A%2F%2Fmobile.juquanquanapp.com%2Fagree%2FUSERAGREE_JQQ_IOS"
56 | }
57 | ],
58 | "startTime": null,
59 | "endTime": null
60 | }
61 | ]
62 | }
63 | }
--------------------------------------------------------------------------------
/resource/json/play1_info_detail.json:
--------------------------------------------------------------------------------
1 | {
2 | "requestId": "",
3 | "code": "0000",
4 | "msg": "Success",
5 | "data": {
6 | "playInfo": {
7 | "title": "神医喜来乐第1集",
8 | "url": "https://vd4.bdstatic.com/mda-mhhu5st92hmp1an5/default/hksr/1639922751/mda-mhhu5st92hmp1an5.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1645698585-0-0-49364b64cb374e0a3c6751d47930bd38&bcevod_channel=searchbox_feed&cd=0&pd=1&pt=3&logid=3585533028&vid=2015598530162537812&abtest=17451_1-3000216_1",
9 | "currentQuality": "LD",
10 | "episodeSid": 10001,
11 | "startingLength": 0
12 | },
13 | "qualityList": [
14 | {
15 | "quality": "FD",
16 | "qualityName": "标清",
17 | "url": "https://vd4.bdstatic.com/mda-mhhu5st92hmp1an5/default/hksr/1639922751/mda-mhhu5st92hmp1an5.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1645698585-0-0-49364b64cb374e0a3c6751d47930bd38&bcevod_channel=searchbox_feed&cd=0&pd=1&pt=3&logid=3585533028&vid=2015598530162537812&abtest=17451_1-3000216_1"
18 | },
19 | {
20 | "quality": "LD",
21 | "qualityName": "高清",
22 | "url": "https://vd4.bdstatic.com/mda-mhhu5st92hmp1an5/hd/hksr/1639922751/mda-mhhu5st92hmp1an5.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1645698585-0-0-a58203b02781cb5a14fbb82860773399&bcevod_channel=searchbox_feed&cd=0&pd=1&pt=3&logid=3585533028&vid=2015598530162537812&abtest=17451_1-3000216_1"
23 | },
24 | {
25 | "quality": "AI_720",
26 | "qualityName": "超清",
27 | "url": "https://vd4.bdstatic.com/mda-mhhu5st92hmp1an5/sc/hksr/1639922751/mda-mhhu5st92hmp1an5.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1645698585-0-0-1533a66c3606a63c4c979853abf0ff95&bcevod_channel=searchbox_feed&cd=0&pd=1&pt=3&logid=3585533028&vid=2015598530162537812&abtest=17451_1-3000216_1"
28 | },
29 | {
30 | "quality": "AI_1080",
31 | "qualityName": "蓝光",
32 | "url": "https://vd4.bdstatic.com/mda-mhhu5st92hmp1an5/1080p/hksr/1639922751/mda-mhhu5st92hmp1an5.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1645698585-0-0-d833821a4a0d79de5c6a0134efd386e3&bcevod_channel=searchbox_feed&cd=0&pd=1&pt=3&logid=3585533028&vid=2015598530162537812&abtest=17451_1-3000216_1"
33 | }
34 | ]
35 | }
36 | }
--------------------------------------------------------------------------------
/resource/json/play2_info_detail.json:
--------------------------------------------------------------------------------
1 | {
2 | "requestId": "",
3 | "code": "0000",
4 | "msg": "Success",
5 | "data": {
6 | "playInfo": {
7 | "title": "神医喜来乐第2集",
8 | "url": "https://vd3.bdstatic.com/mda-mhhu5t08rqr1bc8m/default/hksr/1639923626/mda-mhhu5t08rqr1bc8m.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1645699433-0-0-31d2f1aaf827cc9e6af7e14959285789&bcevod_channel=searchbox_feed&cd=0&pd=1&pt=3&logid=0833412090&vid=5177033977559325844&abtest=17451_1-3000216_1",
9 | "currentQuality": "LD",
10 | "episodeSid": 10002,
11 | "startingLength": 0
12 | },
13 | "qualityList": [
14 | {
15 | "quality": "FD",
16 | "qualityName": "标清",
17 | "url": "https://vd3.bdstatic.com/mda-mhhu5t08rqr1bc8m/default/hksr/1639923626/mda-mhhu5t08rqr1bc8m.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1645699433-0-0-31d2f1aaf827cc9e6af7e14959285789&bcevod_channel=searchbox_feed&cd=0&pd=1&pt=3&logid=0833412090&vid=5177033977559325844&abtest=17451_1-3000216_1"
18 | },
19 | {
20 | "quality": "LD",
21 | "qualityName": "高清",
22 | "url": "https://vd3.bdstatic.com/mda-mhhu5t08rqr1bc8m/hd/hksr/1639923626/mda-mhhu5t08rqr1bc8m.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1645699433-0-0-6cb591562c18deaa9cb22db176501266&bcevod_channel=searchbox_feed&cd=0&pd=1&pt=3&logid=0833412090&vid=5177033977559325844&abtest=17451_1-3000216_1"
23 | },
24 | {
25 | "quality": "AI_720",
26 | "qualityName": "超清",
27 | "url": "https://vd3.bdstatic.com/mda-mhhu5t08rqr1bc8m/sc/hksr/1639923626/mda-mhhu5t08rqr1bc8m.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1645699433-0-0-b8ef6e5b3bad90ce6c440d89cfdad67a&bcevod_channel=searchbox_feed&cd=0&pd=1&pt=3&logid=0833412090&vid=5177033977559325844&abtest=17451_1-3000216_1"
28 | },
29 | {
30 | "quality": "AI_1080",
31 | "qualityName": "蓝光",
32 | "url": "https://vd3.bdstatic.com/mda-mhhu5t08rqr1bc8m/1080p/hksr/1639923626/mda-mhhu5t08rqr1bc8m.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1645699433-0-0-5d6ea26809fb378f7f890d80185cd79a&bcevod_channel=searchbox_feed&cd=0&pd=1&pt=3&logid=0833412090&vid=5177033977559325844&abtest=17451_1-3000216_1"
33 | }
34 | ]
35 | }
36 | }
--------------------------------------------------------------------------------
/resource/json/play3_info_detail.json:
--------------------------------------------------------------------------------
1 | {
2 | "requestId": "",
3 | "code": "0000",
4 | "msg": "Success",
5 | "data": {
6 | "playInfo": {
7 | "title": "神医喜来乐第3集",
8 | "url": "https://vd4.bdstatic.com/mda-mhhu5tawjkb7613d/default/hksr/1639923920/mda-mhhu5tawjkb7613d.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1645699080-0-0-5af792314f2a20ff5728715f7a7a378d&bcevod_channel=searchbox_feed&cd=0&pd=1&pt=3&logid=0480703955&vid=17713599438673069285&abtest=17451_1-3000216_1",
9 | "currentQuality": "LD",
10 | "episodeSid": 10003,
11 | "startingLength": 0
12 | },
13 | "qualityList": [
14 | {
15 | "quality": "FD",
16 | "qualityName": "标清",
17 | "url": "https://vd4.bdstatic.com/mda-mhhu5tawjkb7613d/default/hksr/1639923920/mda-mhhu5tawjkb7613d.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1645699080-0-0-5af792314f2a20ff5728715f7a7a378d&bcevod_channel=searchbox_feed&cd=0&pd=1&pt=3&logid=0480703955&vid=17713599438673069285&abtest=17451_1-3000216_1"
18 | },
19 | {
20 | "quality": "LD",
21 | "qualityName": "高清",
22 | "url": "https://vd4.bdstatic.com/mda-mhhu5tawjkb7613d/hd/hksr/1639923920/mda-mhhu5tawjkb7613d.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1645699080-0-0-55706a687ca93d3df096614f23036e3a&bcevod_channel=searchbox_feed&cd=0&pd=1&pt=3&logid=0480703955&vid=17713599438673069285&abtest=17451_1-3000216_1"
23 | },
24 | {
25 | "quality": "AI_720",
26 | "qualityName": "超清",
27 | "url": "https://vd4.bdstatic.com/mda-mhhu5tawjkb7613d/sc/hksr/1639923920/mda-mhhu5tawjkb7613d.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1645699080-0-0-9b151e019cc22b786da56671296c602a&bcevod_channel=searchbox_feed&cd=0&pd=1&pt=3&logid=0480703955&vid=17713599438673069285&abtest=17451_1-3000216_1"
28 | },
29 | {
30 | "quality": "AI_1080",
31 | "qualityName": "蓝光",
32 | "url": "https://vd4.bdstatic.com/mda-mhhu5tawjkb7613d/1080p/hksr/1639923920/mda-mhhu5tawjkb7613d.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1645699080-0-0-0415fe6e89f515141f722fc29a63ef89&bcevod_channel=searchbox_feed&cd=0&pd=1&pt=3&logid=0480703955&vid=17713599438673069285&abtest=17451_1-3000216_1"
33 | }
34 | ]
35 | }
36 | }
--------------------------------------------------------------------------------
/resource/json/play4_info_detail.json:
--------------------------------------------------------------------------------
1 | {
2 | "requestId": "",
3 | "code": "0000",
4 | "msg": "Success",
5 | "data": {
6 | "playInfo": {
7 | "title": "神医喜来乐第3集",
8 | "url": "https://vd3.bdstatic.com/mda-mhhu5tfh9gk4rjer/default/hksr/1639924225/mda-mhhu5tfh9gk4rjer.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1645699599-0-0-cb7485b9c9000d9f5220f335ad70959f&bcevod_channel=searchbox_feed&cd=0&pd=1&pt=3&logid=0999124611&vid=14538582800946936017&abtest=17451_1-3000216_1",
9 | "currentQuality": "LD",
10 | "episodeSid": 10004,
11 | "startingLength": 0
12 | },
13 | "qualityList": [
14 | {
15 | "quality": "FD",
16 | "qualityName": "标清",
17 | "url": "https://vd3.bdstatic.com/mda-mhhu5tfh9gk4rjer/default/hksr/1639924225/mda-mhhu5tfh9gk4rjer.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1645699599-0-0-cb7485b9c9000d9f5220f335ad70959f&bcevod_channel=searchbox_feed&cd=0&pd=1&pt=3&logid=0999124611&vid=14538582800946936017&abtest=17451_1-3000216_1"
18 | },
19 | {
20 | "quality": "LD",
21 | "qualityName": "高清",
22 | "url": "https://vd3.bdstatic.com/mda-mhhu5tfh9gk4rjer/hd/hksr/1639924225/mda-mhhu5tfh9gk4rjer.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1645699599-0-0-b6b713873eaa3dad0526c9c78018a203&bcevod_channel=searchbox_feed&cd=0&pd=1&pt=3&logid=0999124611&vid=14538582800946936017&abtest=17451_1-3000216_1"
23 | },
24 | {
25 | "quality": "AI_720",
26 | "qualityName": "超清",
27 | "url": "https://vd3.bdstatic.com/mda-mhhu5tfh9gk4rjer/sc/hksr/1639924225/mda-mhhu5tfh9gk4rjer.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1645699599-0-0-a627624cdb9cacb59a01d433ad873d39&bcevod_channel=searchbox_feed&cd=0&pd=1&pt=3&logid=0999124611&vid=14538582800946936017&abtest=17451_1-3000216_1"
28 | },
29 | {
30 | "quality": "AI_1080",
31 | "qualityName": "蓝光",
32 | "url": "https://vd3.bdstatic.com/mda-mhhu5tfh9gk4rjer/1080p/hksr/1639924225/mda-mhhu5tfh9gk4rjer.mp4?v_from_s=hkapp-haokan-nanjing&auth_key=1645699599-0-0-d5c09228b501ced667209d852527859a&bcevod_channel=searchbox_feed&cd=0&pd=1&pt=3&logid=0999124611&vid=14538582800946936017&abtest=17451_1-3000216_1"
33 | }
34 | ]
35 | }
36 | }
--------------------------------------------------------------------------------
/resource/json/user_info.json:
--------------------------------------------------------------------------------
1 | {
2 | "requestId": "",
3 | "code": "0000",
4 | "msg": "Success",
5 | "data": {
6 | "user": {
7 | "userId": 10000000000001,
8 | "pwd": "123456",
9 | "nickName": "与君共勉",
10 | "mobile": "1999999999"
11 | },
12 | "token": "1111111111xxxxxxx0000000000"
13 | }
14 | }
--------------------------------------------------------------------------------
/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:flutter_video_player/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(const 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 |
--------------------------------------------------------------------------------