├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── manifest.json └── index.html ├── android ├── gradle.properties ├── app │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── drawable │ │ │ │ │ ├── free_91.png │ │ │ │ │ ├── bg_background.png │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21 │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── values │ │ │ │ │ └── styles.xml │ │ │ │ └── values-night │ │ │ │ │ └── styles.xml │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── yuchen │ │ │ │ │ └── sex_91porn │ │ │ │ │ └── MainActivity.java │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle └── build.gradle ├── ios ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner │ ├── AppDelegate.h │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── icon-29.png │ │ │ ├── icon-40.png │ │ │ ├── icon-76.png │ │ │ ├── icon-1024.png │ │ │ ├── icon-20@2x.png │ │ │ ├── icon-20@3x.png │ │ │ ├── icon-29@2x.png │ │ │ ├── icon-29@3x.png │ │ │ ├── icon-40@2x.png │ │ │ ├── icon-40@3x.png │ │ │ ├── icon-60@2x.png │ │ │ ├── icon-60@3x.png │ │ │ ├── icon-76@2x.png │ │ │ ├── icon-20-ipad.png │ │ │ ├── icon-29-ipad.png │ │ │ ├── icon-83.5@2x.png │ │ │ ├── icon-20@2x-ipad.png │ │ │ ├── icon-29@2x-ipad.png │ │ │ └── Contents.json │ │ └── LaunchImage.imageset │ │ │ ├── bg_background.png │ │ │ ├── bg_background-1.png │ │ │ ├── bg_background-2.png │ │ │ ├── README.md │ │ │ └── Contents.json │ ├── main.m │ ├── AppDelegate.m │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── .gitignore ├── Podfile └── Podfile.lock ├── .metadata ├── README.md ├── lib ├── util │ ├── cookie_util.dart │ ├── object_util.dart │ ├── application.dart │ ├── http_util.dart │ ├── sp_util.dart │ ├── widget_util.dart │ └── brn_custom_photo_config.dart ├── model │ ├── pair.dart │ ├── config.dart │ ├── category_model.dart │ ├── img_model.dart │ └── video_model.dart ├── router │ ├── routers.dart │ └── router_handler.dart ├── main.dart ├── pages │ ├── sihu │ │ ├── model_entity.dart │ │ ├── config.dart │ │ ├── common_widget.dart │ │ ├── page_detail.dart │ │ ├── search.dart │ │ ├── data_service.dart │ │ └── index.dart │ ├── image_detail_page.dart │ ├── channel_page.dart │ ├── madou │ │ ├── madou_config.dart │ │ ├── video_list.dart │ │ ├── madou_service.dart │ │ ├── common_widget.dart │ │ └── index.dart │ ├── index.dart │ ├── video_91.dart │ ├── video_db.dart │ ├── play_video.dart │ └── video_91_list.dart ├── sql │ ├── sql_helper.dart │ └── video_dao.dart └── service │ └── list_parse.dart ├── .gitignore ├── test ├── service_api_test.dart └── widget_test.dart ├── analysis_options.yaml ├── pubspec.yaml ├── LICENSE └── pubspec.lock /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/free_91.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/android/app/src/main/res/drawable/free_91.png -------------------------------------------------------------------------------- /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/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/bg_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/android/app/src/main/res/drawable/bg_background.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20-ipad.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29-ipad.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x-ipad.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x-ipad.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/bg_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/bg_background.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/bg_background-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/bg_background-1.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/bg_background-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cpyczd/sex91porn/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/bg_background-2.png -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/yuchen/sex_91porn/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.yuchen.sex_91porn; 2 | 3 | import io.flutter.embedding.android.FlutterActivity; 4 | 5 | public class MainActivity extends FlutterActivity { 6 | } 7 | -------------------------------------------------------------------------------- /ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /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-7.2-all.zip 7 | -------------------------------------------------------------------------------- /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.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.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: fba99f6cf9a14512e461e3122c8ddfaa25394e89 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /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/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | #import "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 7 | # sex91porn 8 | 91Porn 视屏爬取在线播放器 9 | 10 | 方便的驾车体验,废话不多自行下载安装体验。 11 | 12 | 13 | 14 | ## 技术规格 15 | 16 | 本项目主要使用**Flutter**框架进行开发 17 | 18 | Platform支持平台 19 | 20 | 1. **Android** 21 | 2. **IOS** 22 | 23 | Android已经打包发布到本仓库可自行下载。 24 | 25 | IOS需要用户自己Clone代码自行构建。 26 | 27 | 当前停止维护,部分或许还能用。仅供学习请勿传播 28 | 29 | ## 声明 30 | 31 | 本软件仅供技术学习,请勿传播 32 | 本项目仅做技术交流使用,任何人或组织无论以何种形式将其用在其他任何地方由此引发的各种问题均与本人无关 33 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "bg_background.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "bg_background-1.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "bg_background-2.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:4.1.0' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | mavenCentral() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | project.evaluationDependsOn(':app') 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/util/cookie_util.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2021-10-03 00:13:05 5 | * @LastEditTime: 2021-10-03 00:16:49 6 | */ 7 | import 'dart:io'; 8 | 9 | import 'package:cookie_jar/cookie_jar.dart'; 10 | import 'package:path_provider/path_provider.dart'; 11 | 12 | class Cook { 13 | //改为使用 PersistCookieJar,在文档中有介绍,PersistCookieJar将 cookie保留在文件中,因此,如果应用程序退出,则cookie始终存在,除 非显式调用delete 14 | static PersistCookieJar? _cookieJar; 15 | 16 | static Future get cookieJar async { 17 | if (_cookieJar == null) { 18 | Directory appDocDir = await getApplicationDocumentsDirectory(); 19 | String appDocPath = appDocDir.path; 20 | print('获取的文件系统目录: ' + appDocPath); 21 | _cookieJar = new PersistCookieJar(storage: FileStorage(appDocPath)); 22 | } 23 | return _cookieJar!; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /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 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/model/pair.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | /* 4 | * @Description: 5 | * @Author: chenzedeng 6 | * @Date: 2022-03-19 19:38:19 7 | * @LastEditTime: 2022-03-19 19:38:19 8 | */ 9 | 10 | class Pair { 11 | final T key; 12 | final K value; 13 | Pair({ 14 | required this.key, 15 | required this.value, 16 | }); 17 | 18 | Pair copyWith({ 19 | T? key, 20 | K? value, 21 | }) { 22 | return Pair( 23 | key: key ?? this.key, 24 | value: value ?? this.value, 25 | ); 26 | } 27 | 28 | @override 29 | String toString() => 'Pair(key: $key, value: $value)'; 30 | 31 | @override 32 | bool operator ==(Object other) { 33 | if (identical(this, other)) return true; 34 | 35 | return other is Pair && other.key == key && other.value == value; 36 | } 37 | 38 | @override 39 | int get hashCode => key.hashCode ^ value.hashCode; 40 | } 41 | -------------------------------------------------------------------------------- /.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 | .vscode 35 | 36 | # Web related 37 | lib/generated_plugin_registrant.dart 38 | 39 | # Symbolication related 40 | app.*.symbols 41 | 42 | # Obfuscation related 43 | app.*.map.json 44 | 45 | # Android Studio will place build artifacts here 46 | /android/app/debug 47 | /android/app/profile 48 | /android/app/release 49 | /ios/build/ -------------------------------------------------------------------------------- /lib/router/routers.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2021-01-25 19:43:40 5 | * @LastEditTime: 2022-03-28 20:52:30 6 | */ 7 | import 'package:fluro/fluro.dart'; 8 | import 'router_handler.dart'; 9 | 10 | class Routers { 11 | static void configRouters(FluroRouter router) { 12 | _defineRouter(router, "/", indexHandler); 13 | _defineRouter(router, "/imgPreview", imageDetailPage); 14 | _defineRouter(router, "/play", videoPlayHandler); 15 | 16 | _defineRouter(router, "/sihu", sihuHandler); 17 | _defineRouter(router, "/sihuDetail", sihuDetailHandler); 18 | _defineRouter(router, "/sihuSearch", sihuSearchHandler); 19 | 20 | _defineRouter(router, "/madou", madouHandler); 21 | _defineRouter(router, "/madouVideoList", madouVideoListHandler); 22 | } 23 | 24 | static void _defineRouter(FluroRouter router, String path, Handler handler) { 25 | router.define(path, 26 | handler: handler, transitionType: TransitionType.inFromRight); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sex_91porn", 3 | "short_name": "sex_91porn", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2021-01-25 19:10:23 5 | * @LastEditTime: 2022-03-29 19:08:10 6 | */ 7 | import 'package:fluro/fluro.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:sex_91porn/router/routers.dart'; 10 | import 'package:sex_91porn/util/application.dart'; 11 | import 'package:sex_91porn/util/http_util.dart'; 12 | 13 | void main() { 14 | runApp(MyApp()); 15 | } 16 | 17 | class MyApp extends StatelessWidget { 18 | @override 19 | Widget build(BuildContext context) { 20 | //配置路由的相关配置 21 | FluroRouter router = new FluroRouter(); 22 | Routers.configRouters(router); 23 | Application.router = router; 24 | HttpUtil.setup(); 25 | 26 | //DEV 27 | // HttpUtil.setProxy(); 28 | 29 | return MaterialApp( 30 | title: 'Free91Porn', 31 | debugShowCheckedModeBanner: false, 32 | onGenerateRoute: Application.router.generator, 33 | initialRoute: "/", 34 | theme: ThemeData( 35 | primarySwatch: Colors.lightBlue, 36 | visualDensity: VisualDensity.adaptivePlatformDensity, 37 | ), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/util/object_util.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2021-05-21 22:59:39 5 | * @LastEditTime: 2022-03-27 22:09:55 6 | */ 7 | import 'package:collection/collection.dart'; 8 | import 'dart:convert'; 9 | import 'package:crypto/crypto.dart'; 10 | 11 | class EnumUtil { 12 | ///枚举类型转string 13 | static String enumToString(o) => o.toString().split('.').last; 14 | 15 | ///string转枚举类型 16 | static T? enumFromString(List values, String value) { 17 | return values 18 | .firstWhereOrNull((type) => type.toString().split('.').last == value); 19 | } 20 | } 21 | 22 | ///format number to local number. 23 | ///example 10001 -> 1万 24 | /// 100 -> 100 25 | /// 11000-> 1.1万 26 | String getFormattedNumber(int number) { 27 | if (number < 10000) { 28 | return number.toString(); 29 | } 30 | number = number ~/ 10000; 31 | return "$number万"; 32 | } 33 | 34 | ///字符串工具类 35 | class StringUtils { 36 | ///判断字符串是否不为空 37 | static bool isNotBlank(String? str) { 38 | return str != null && str.isNotEmpty; 39 | } 40 | 41 | ///判断字符串是否为空 42 | static bool isBlank(String? str) { 43 | return str == null || str.isEmpty; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/pages/sihu/model_entity.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2022-03-19 15:56:16 5 | * @LastEditTime: 2022-03-19 15:58:12 6 | */ 7 | 8 | import 'package:flutter/material.dart'; 9 | 10 | class SihuCategoryModel { 11 | final String title; 12 | final String href; 13 | final Icon icon; 14 | SihuCategoryModel({ 15 | required this.title, 16 | required this.href, 17 | required this.icon, 18 | }); 19 | 20 | SihuCategoryModel copyWith({ 21 | String? title, 22 | String? href, 23 | Icon? icon, 24 | }) { 25 | return SihuCategoryModel( 26 | title: title ?? this.title, 27 | href: href ?? this.href, 28 | icon: icon ?? this.icon, 29 | ); 30 | } 31 | 32 | @override 33 | String toString() => 'CategoryModel(title: $title, href: $href, icon: $icon)'; 34 | 35 | @override 36 | bool operator ==(Object other) { 37 | if (identical(this, other)) return true; 38 | 39 | return other is SihuCategoryModel && 40 | other.title == title && 41 | other.href == href && 42 | other.icon == icon; 43 | } 44 | 45 | @override 46 | int get hashCode => title.hashCode ^ href.hashCode ^ icon.hashCode; 47 | } 48 | -------------------------------------------------------------------------------- /lib/pages/image_detail_page.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2022-03-19 22:50:20 5 | * @LastEditTime: 2022-03-19 23:49:22 6 | */ 7 | import 'package:bruno/bruno.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:sex_91porn/model/img_model.dart'; 10 | 11 | import '../util/brn_custom_photo_config.dart'; 12 | 13 | ///图文详情页面 14 | class ImageDetailPage extends StatefulWidget { 15 | final ImageModel model; 16 | 17 | ImageDetailPage({Key? key, required this.model}) : super(key: key); 18 | 19 | @override 20 | State createState() => _ImageDetailPageState(); 21 | } 22 | 23 | class _ImageDetailPageState extends State { 24 | @override 25 | Widget build(BuildContext context) { 26 | return Scaffold( 27 | appBar: BrnAppBar( 28 | brightness: Brightness.dark, 29 | title: widget.model.title, 30 | automaticallyImplyLeading: true), 31 | body: Container( 32 | child: BrnGallerySummaryPage( 33 | rowCount: 3, 34 | allConfig: [ 35 | BrnCustomPhotoGroupConfig.url( 36 | urls: widget.model.imageUrls, title: "全部") 37 | ], 38 | )), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/service_api_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2021-01-29 11:38:59 5 | * @LastEditTime: 2022-03-27 22:16:05 6 | */ 7 | 8 | import 'dart:convert'; 9 | 10 | import 'package:flutter/foundation.dart'; 11 | import 'package:sex_91porn/pages/madou/madou_service.dart'; 12 | import 'package:sex_91porn/pages/sihu/data_service.dart'; 13 | import 'package:sex_91porn/util/http_util.dart'; 14 | 15 | main() async { 16 | HttpUtil.setProxy(); 17 | // var list = await SihuDataService.search("门事件"); 18 | // print(list); 19 | 20 | // var list = await MadouService.hotVideoList(1); 21 | // print(list); 22 | // var m3u8 = await MadouService.analysisVideoPath(list[0]); 23 | // print(m3u8); 24 | // var categoryList = await MadouService.getCategoryList(); 25 | // print(categoryList); 26 | 27 | var original = 28 | '%3c%73%6f%75%72%63%65%20%73%72%63%3d%27%68%74%74%70%73%3a%2f%2f%63%64%6e%37%37%2e%39%31%70%34%39%2e%63%6f%6d%2f%6d%33%75%38%2f%37%30%30%32%30%37%2f%37%30%30%32%30%37%2e%6d%33%75%38%27%20%74%79%70%65%3d%27%61%70%70%6c%69%63%61%74%69%6f%6e%2f%78%2d%6d%70%65%67%55%52%4c%27%3e'; 29 | original = Uri.decodeFull(original); 30 | // split on 'u' and remove the first empty element 31 | print(original); 32 | } 33 | -------------------------------------------------------------------------------- /lib/util/application.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2020-08-05 15:13:46 5 | * @LastEditTime: 2021-10-02 22:15:54 6 | */ 7 | import 'package:fluro/fluro.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:fluttertoast/fluttertoast.dart'; 10 | 11 | class Application { 12 | //Router全局路由管理对象 13 | static late FluroRouter router; 14 | 15 | static late BuildContext context; 16 | 17 | ///IOS 可侧滑返回的跳转界面 18 | static Future navigateToIos(BuildContext context, String path, 19 | {bool replace = false, bool clearStack = false, Object? params}) { 20 | RouteSettings? settings; 21 | if (params != null) { 22 | settings = RouteSettings(name: "params", arguments: params); 23 | } 24 | return router.navigateTo(context, path, 25 | replace: replace, 26 | clearStack: clearStack, 27 | transition: TransitionType.cupertino, 28 | routeSettings: settings); 29 | } 30 | } 31 | 32 | ///系统配置 33 | class AppConfig {} 34 | 35 | class ToastUtil { 36 | static show({@required dynamic msg, Toast length = Toast.LENGTH_SHORT}) { 37 | if (!(msg is String)) { 38 | msg = msg.toString(); 39 | } 40 | Fluttertoast.showToast( 41 | msg: msg, 42 | textColor: Colors.white, 43 | backgroundColor: Colors.black54, 44 | gravity: ToastGravity.BOTTOM, 45 | toastLength: length); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/model/config.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2021-01-26 11:06:12 5 | * @LastEditTime: 2022-09-17 20:34:05 6 | */ 7 | import 'package:sex_91porn/model/category_model.dart'; 8 | 9 | class Config { 10 | //91Porn首页 11 | static String MAIN_URL = "https://w0831.91p30.com"; 12 | 13 | static List categoryList = List.of([ 14 | CategoryModel( 15 | name: "当前最热", url: "$MAIN_URL/v.php?category=hot&viewtype=basic&page="), 16 | CategoryModel( 17 | name: "本月最热", url: "$MAIN_URL/v.php?category=top&viewtype=basic&page="), 18 | CategoryModel( 19 | name: "91原创", url: "$MAIN_URL/v.php?category=ori&viewtype=basic&page="), 20 | CategoryModel( 21 | name: "十分钟以上", 22 | url: "$MAIN_URL/v.php?category=long&viewtype=basic&page="), 23 | CategoryModel( 24 | name: "本月收藏", url: "$MAIN_URL/v.php?category=tf&viewtype=basic&page="), 25 | CategoryModel( 26 | name: "收藏最多", url: "$MAIN_URL/v.php?category=mf&viewtype=basic&page="), 27 | CategoryModel( 28 | name: "最近加精", url: "$MAIN_URL/v.php?category=rf&viewtype=basic&page="), 29 | CategoryModel( 30 | name: "上月最热", 31 | url: "$MAIN_URL/v.php?category=top&m=-1&viewtype=basic&page="), 32 | CategoryModel( 33 | name: "本月讨论", url: "$MAIN_URL/v.php?category=md&viewtype=basic&page="), 34 | CategoryModel(name: "全部视屏", url: "$MAIN_URL/v.php?next=watch&page="), 35 | ]); 36 | } 37 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 32 | end 33 | 34 | post_install do |installer| 35 | installer.pods_project.targets.each do |target| 36 | flutter_additional_ios_build_settings(target) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2021-01-25 19:10:23 5 | * @LastEditTime: 2021-01-28 20:58:34 6 | */ 7 | // This is a basic Flutter widget test. 8 | // 9 | // To perform an interaction with a widget in your test, use the WidgetTester 10 | // utility that Flutter provides. For example, you can send tap and scroll 11 | // gestures. You can also use WidgetTester to find child widgets in the widget 12 | // tree, read text, and verify that the values of widget properties are correct. 13 | 14 | import 'package:flutter/material.dart'; 15 | import 'package:flutter_test/flutter_test.dart'; 16 | 17 | import 'package:sex_91porn/main.dart'; 18 | import 'package:sex_91porn/model/config.dart'; 19 | import 'package:sex_91porn/service/list_parse.dart'; 20 | 21 | void main() { 22 | // testWidgets('Counter increments smoke test', (WidgetTester tester) async { 23 | // // Build our app and trigger a frame. 24 | // await tester.pumpWidget(MyApp()); 25 | 26 | // // Verify that our counter starts at 0. 27 | // expect(find.text('0'), findsOneWidget); 28 | // expect(find.text('1'), findsNothing); 29 | 30 | // // Tap the '+' icon and trigger a frame. 31 | // await tester.tap(find.byIcon(Icons.add)); 32 | // await tester.pump(); 33 | 34 | // // Verify that our counter has incremented. 35 | // expect(find.text('0'), findsNothing); 36 | // expect(find.text('1'), findsOneWidget); 37 | // }); 38 | } 39 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/pages/channel_page.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2022-03-19 12:56:54 5 | * @LastEditTime: 2022-03-29 19:10:39 6 | */ 7 | import 'package:flutter/material.dart'; 8 | import 'package:sex_91porn/util/application.dart'; 9 | 10 | class ChannelPage extends StatefulWidget { 11 | ChannelPage({Key? key}) : super(key: key); 12 | 13 | @override 14 | State createState() => _ChannelPageState(); 15 | } 16 | 17 | class _ChannelPageState extends State { 18 | @override 19 | Widget build(BuildContext context) { 20 | return Scaffold( 21 | appBar: AppBar( 22 | title: Text( 23 | "其他渠道资源", 24 | style: TextStyle(color: Colors.white), 25 | ), 26 | ), 27 | body: Container( 28 | child: ListView( 29 | physics: BouncingScrollPhysics(), 30 | children: [ 31 | ListTile( 32 | leading: Icon( 33 | Icons.insert_emoticon_outlined, 34 | color: Theme.of(context).primaryColor, 35 | ), 36 | title: Text("综合-1"), 37 | subtitle: Text("综合资源"), 38 | onTap: () => Application.navigateToIos(context, "/sihu"), 39 | ), 40 | ListTile( 41 | leading: Icon(Icons.storage_rounded, 42 | color: Theme.of(context).primaryColor), 43 | title: Text("国产-1"), 44 | subtitle: Text("综合企划国产剧情资源"), 45 | onTap: () => Application.navigateToIos(context, "/madou"), 46 | ) 47 | ], 48 | ), 49 | ), 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/model/category_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | /* 4 | * @Description: 5 | * @Author: chenzedeng 6 | * @Date: 2021-01-26 11:06:12 7 | * @LastEditTime: 2021-10-02 22:08:09 8 | */ 9 | 10 | ///类别选择器模块 11 | class CategoryModel { 12 | final String name; 13 | final String url; 14 | int? endPage; 15 | 16 | CategoryModel({ 17 | required this.name, 18 | required this.url, 19 | this.endPage, 20 | }); 21 | 22 | CategoryModel copyWith({ 23 | String? name, 24 | String? url, 25 | int? endPage, 26 | }) { 27 | return CategoryModel( 28 | name: name ?? this.name, 29 | url: url ?? this.url, 30 | endPage: endPage ?? this.endPage, 31 | ); 32 | } 33 | 34 | Map toMap() { 35 | return { 36 | 'name': name, 37 | 'url': url, 38 | 'endPage': endPage, 39 | }; 40 | } 41 | 42 | factory CategoryModel.fromMap(Map map) { 43 | return CategoryModel( 44 | name: map['name'], 45 | url: map['url'], 46 | endPage: map['endPage'], 47 | ); 48 | } 49 | 50 | String toJson() => json.encode(toMap()); 51 | 52 | factory CategoryModel.fromJson(String source) => 53 | CategoryModel.fromMap(json.decode(source)); 54 | 55 | @override 56 | String toString() => 57 | 'CategoryModel(name: $name, url: $url, endPage: $endPage)'; 58 | 59 | @override 60 | bool operator ==(Object other) { 61 | if (identical(this, other)) return true; 62 | 63 | return other is CategoryModel && 64 | other.name == name && 65 | other.url == url && 66 | other.endPage == endPage; 67 | } 68 | 69 | @override 70 | int get hashCode => name.hashCode ^ url.hashCode ^ endPage.hashCode; 71 | } 72 | -------------------------------------------------------------------------------- /lib/sql/sql_helper.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2021-01-26 15:32:37 5 | * @LastEditTime: 2021-10-02 22:14:07 6 | */ 7 | import 'package:sqflite/sqflite.dart'; 8 | import 'package:path/path.dart'; 9 | 10 | class SqlHelper { 11 | static const _VERSION = 1; 12 | 13 | static const _NAME = "free_91porn.db"; 14 | 15 | static Database? _database; 16 | 17 | static init() async { 18 | var databasesPath = await getDatabasesPath(); 19 | 20 | String path = join(databasesPath, _NAME); 21 | 22 | _database = await openDatabase(path, 23 | version: _VERSION, onCreate: (Database db, int version) async {}); 24 | } 25 | 26 | ///获取当前数据库对象 27 | static Future getCurrentDatabase() async { 28 | if (_database == null) { 29 | await init(); 30 | } 31 | return _database!; 32 | } 33 | 34 | ///判断表是否存在 35 | static isTableExits(String tableName) async { 36 | await getCurrentDatabase(); 37 | var res = await _database!.rawQuery( 38 | "select * from Sqlite_master where type = 'table' and name = '$tableName'"); 39 | return res != null && res.length > 0; 40 | } 41 | 42 | ///关闭 43 | static close() { 44 | _database?.close(); 45 | } 46 | } 47 | 48 | ///Sql 提供者父类对象 49 | abstract class SqlBaseProvider { 50 | String getTableName(); 51 | 52 | String getTableCreateSql(); 53 | 54 | ///获取数据库对象 55 | Future getDataBase() async { 56 | return await prepare(); 57 | } 58 | 59 | prepare() async { 60 | var isTableExist = await SqlHelper.isTableExits(getTableName()); 61 | if (!isTableExist) { 62 | Database database = await SqlHelper.getCurrentDatabase(); 63 | await database.execute(getTableCreateSql()); 64 | } 65 | return await SqlHelper.getCurrentDatabase(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/model/img_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | 5 | /* 6 | * @Description: 7 | * @Author: chenzedeng 8 | * @Date: 2022-03-19 22:44:23 9 | * @LastEditTime: 2022-03-19 22:45:24 10 | */ 11 | ///图文详情模型类 12 | class ImageModel { 13 | final String href; 14 | final String title; 15 | final List imageUrls; 16 | ImageModel({ 17 | required this.href, 18 | required this.title, 19 | required this.imageUrls, 20 | }); 21 | 22 | ImageModel copyWith({ 23 | String? href, 24 | String? title, 25 | List? imageUrls, 26 | }) { 27 | return ImageModel( 28 | href: href ?? this.href, 29 | title: title ?? this.title, 30 | imageUrls: imageUrls ?? this.imageUrls, 31 | ); 32 | } 33 | 34 | Map toMap() { 35 | return { 36 | 'href': href, 37 | 'title': title, 38 | 'imageUrls': imageUrls, 39 | }; 40 | } 41 | 42 | factory ImageModel.fromMap(Map map) { 43 | return ImageModel( 44 | href: map['href'] ?? '', 45 | title: map['title'] ?? '', 46 | imageUrls: List.from(map['imageUrls']), 47 | ); 48 | } 49 | 50 | String toJson() => json.encode(toMap()); 51 | 52 | factory ImageModel.fromJson(String source) => 53 | ImageModel.fromMap(json.decode(source)); 54 | 55 | @override 56 | String toString() => 57 | 'ImageModel(href: $href, title: $title, imageUrls: $imageUrls)'; 58 | 59 | @override 60 | bool operator ==(Object other) { 61 | if (identical(this, other)) return true; 62 | 63 | return other is ImageModel && 64 | other.href == href && 65 | other.title == title && 66 | listEquals(other.imageUrls, imageUrls); 67 | } 68 | 69 | @override 70 | int get hashCode => href.hashCode ^ title.hashCode ^ imageUrls.hashCode; 71 | } 72 | -------------------------------------------------------------------------------- /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 from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 31 29 | 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | targetCompatibility JavaVersion.VERSION_1_8 33 | } 34 | 35 | defaultConfig { 36 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 37 | applicationId "com.yuchen.sex_91porn" 38 | minSdkVersion 19 39 | targetSdkVersion 30 40 | versionCode flutterVersionCode.toInteger() 41 | versionName flutterVersionName 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | -------------------------------------------------------------------------------- /lib/pages/madou/madou_config.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | /* 6 | * @Description: 7 | * @Author: chenzedeng 8 | * @Date: 2022-03-27 21:00:46 9 | * @LastEditTime: 2022-03-28 21:15:58 10 | */ 11 | 12 | const MAIN_URL = "https://madou.club/"; 13 | 14 | class MadouCategory { 15 | final String title; 16 | final String href; 17 | Icon? icon; 18 | MadouCategory({ 19 | required this.title, 20 | required this.href, 21 | this.icon, 22 | }); 23 | 24 | MadouCategory copyWith({ 25 | String? title, 26 | String? href, 27 | Icon? icon, 28 | }) { 29 | return MadouCategory( 30 | title: title ?? this.title, 31 | href: href ?? this.href, 32 | icon: icon ?? this.icon, 33 | ); 34 | } 35 | 36 | Map toMap() { 37 | return { 38 | 'title': title, 39 | 'href': href, 40 | }; 41 | } 42 | 43 | factory MadouCategory.fromMap(Map map) { 44 | return MadouCategory( 45 | title: map['title'] ?? '', 46 | href: map['href'] ?? '', 47 | ); 48 | } 49 | 50 | String toJson() => json.encode(toMap()); 51 | 52 | factory MadouCategory.fromJson(String source) => 53 | MadouCategory.fromMap(json.decode(source)); 54 | 55 | @override 56 | String toString() => 'MadouCategory(title: $title, href: $href, icon: $icon)'; 57 | 58 | @override 59 | bool operator ==(Object other) { 60 | if (identical(this, other)) return true; 61 | 62 | return other is MadouCategory && 63 | other.title == title && 64 | other.href == href && 65 | other.icon == icon; 66 | } 67 | 68 | @override 69 | int get hashCode => title.hashCode ^ href.hashCode ^ icon.hashCode; 70 | 71 | String getPagePath(int page) { 72 | if (page == 1) { 73 | return href; 74 | } 75 | return href + "/page/$page"; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Free91 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | sex_91porn 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | 32 | UILaunchStoryboardName 33 | LaunchScreen 34 | UIMainStoryboardFile 35 | Main 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | UIViewControllerBasedStatusBarAppearance 50 | 51 | io.flutter.embedded_views_preview 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /lib/pages/sihu/config.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2022-03-19 15:54:04 5 | * @LastEditTime: 2022-03-28 21:53:54 6 | */ 7 | import 'dart:math'; 8 | 9 | import 'package:flutter/material.dart'; 10 | import 'package:sex_91porn/pages/sihu/model_entity.dart'; 11 | 12 | const MAIN_URL = "https://www.4hu.tv"; //海外 13 | // const MAIN_URL = "https://www.b2x55.com/"; 14 | 15 | final _PLAY_SOURCES = [ 16 | "40cdn", 17 | "41cdn", 18 | "44cdn", 19 | "46cdn", 20 | "47cdn", 21 | "48cdn", 22 | "49cdn", 23 | "63cdn", 24 | "34cdn", 25 | "38cdn" 26 | ]; 27 | 28 | String getM3u8Url() { 29 | var str = _PLAY_SOURCES[Random().nextInt(_PLAY_SOURCES.length)]; 30 | return "https://m3u8.$str.com"; 31 | } 32 | 33 | final CATEGORY_CONFIG = List.of([ 34 | SihuCategoryModel( 35 | title: "自拍视屏", 36 | href: "$MAIN_URL/video/zipai/", 37 | icon: Icon(Icons.movie_outlined)), 38 | SihuCategoryModel( 39 | title: "开放青年", 40 | href: "$MAIN_URL/video/kaifang/", 41 | icon: Icon(Icons.movie_outlined)), 42 | SihuCategoryModel( 43 | title: "精品分享", 44 | href: "$MAIN_URL/video/jingpin/", 45 | icon: Icon(Icons.movie_outlined)), 46 | SihuCategoryModel( 47 | title: "台湾辣妹", 48 | href: "$MAIN_URL/video/twmn/", 49 | icon: Icon(Icons.movie_outlined)), 50 | SihuCategoryModel( 51 | title: "动漫卡通", 52 | href: "$MAIN_URL/video/dongman/", 53 | icon: Icon(Icons.movie_outlined)), 54 | SihuCategoryModel( 55 | title: "无码中字", 56 | href: "$MAIN_URL/movie/wuma/", 57 | icon: Icon(Icons.movie_outlined)), 58 | SihuCategoryModel( 59 | title: "SM系列", 60 | href: "$MAIN_URL/movie/sm/", 61 | icon: Icon(Icons.movie_outlined)), 62 | SihuCategoryModel( 63 | title: "高清无码", 64 | href: "$MAIN_URL/movie/gaoqing/", 65 | icon: Icon(Icons.movie_outlined)), 66 | SihuCategoryModel( 67 | title: "熟女人妻", 68 | href: "$MAIN_URL/movie/shunv/", 69 | icon: Icon(Icons.movie_outlined)), 70 | SihuCategoryModel( 71 | title: "中文有码", 72 | href: "$MAIN_URL/movie/youma/", 73 | icon: Icon(Icons.movie_outlined)), 74 | ]); 75 | -------------------------------------------------------------------------------- /lib/util/http_util.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2021-01-30 11:00:05 5 | * @LastEditTime: 2022-03-28 21:00:05 6 | */ 7 | 8 | import 'dart:io'; 9 | 10 | import 'package:dio/adapter.dart'; 11 | import 'package:dio/dio.dart'; 12 | import 'package:dio_cookie_manager/dio_cookie_manager.dart'; 13 | 14 | import 'cookie_util.dart'; 15 | 16 | class HttpUtil { 17 | static CancelToken? _cancelToken = CancelToken(); 18 | 19 | static final Dio _HTTP = Dio(BaseOptions( 20 | connectTimeout: 10000, 21 | sendTimeout: 10000, 22 | receiveTimeout: 10000, 23 | headers: { 24 | "accept-language": "zh-CN,zh;q=0.9,ja;q=0.8,en;q=0.7,zh-TW;q=0.6" 25 | })); 26 | 27 | static void setup() async { 28 | var cookie = await Cook.cookieJar; 29 | _HTTP.interceptors.add(CookieManager(cookie)); 30 | // _HTTP.interceptors.add(LogInterceptor(responseBody: false)); //开启请求日志 31 | } 32 | 33 | ///测试 设置代理 34 | static setProxy() { 35 | (_HTTP.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = 36 | (client) { 37 | client.findProxy = (url) { 38 | ///设置代理 电脑ip地址 39 | return "PROXY 10.0.2.2:7890"; 40 | 41 | ///不设置代理 42 | // return 'DIRECT'; 43 | }; 44 | 45 | ///忽略证书 46 | client.badCertificateCallback = 47 | (X509Certificate cert, String host, int port) => true; 48 | }; 49 | } 50 | 51 | ///get请求 52 | static Future> getRequest(String url, 53 | {Map? queryParams}) { 54 | _cancelToken = CancelToken(); 55 | return _HTTP.get(url, 56 | queryParameters: queryParams, 57 | cancelToken: _cancelToken, 58 | options: Options(followRedirects: false)); 59 | } 60 | 61 | ///返回Html 62 | static Future getHtml(String url) async { 63 | try { 64 | var response = await getRequest(url); 65 | if (response.statusCode == 200) { 66 | return response.data; 67 | } 68 | return Future.error("请求失败: ${response.statusMessage}"); 69 | } catch (e) { 70 | print("请求失败: $e"); 71 | return Future.error("请求失败:超时"); 72 | } 73 | } 74 | 75 | static void cancel() { 76 | _cancelToken?.cancel(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/service/list_parse.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2021-01-25 19:46:49 5 | * @LastEditTime: 2021-10-03 12:12:59 6 | */ 7 | 8 | import 'package:html/dom.dart'; 9 | import 'package:html/parser.dart'; 10 | import 'package:sex_91porn/model/video_model.dart'; 11 | 12 | class VideoPageParse { 13 | ///获取列表的视屏数据 14 | static List getVideoList(String html) { 15 | List list = []; 16 | Document document = parse(html); 17 | var rootList = document.querySelectorAll(".videos-text-align"); 18 | if (rootList.isEmpty) { 19 | return list; 20 | } 21 | for (var l in rootList) { 22 | Element a = l.getElementsByTagName("a")[0]; 23 | //获取视屏原始链接地址 24 | String href = a.attributes["href"] ?? ""; 25 | //获取标题 26 | String title = 27 | a.getElementsByClassName("video-title title-truncate m-t-5")[0].text; 28 | //获取Id号码 29 | var id = a.getElementsByClassName("thumb-overlay")[0].id; 30 | id = id.replaceAll(new RegExp("\\D"), ""); 31 | //获取图片 32 | String img = 33 | a.getElementsByClassName("img-responsive")[0].attributes["src"] ?? ""; 34 | //获取时长 35 | String duration = a.getElementsByClassName("duration")[0].text; 36 | 37 | VideoModel videoModel = VideoModel( 38 | href: href, 39 | title: title, 40 | cover: img, 41 | duration: duration, 42 | id: int.parse(id)); 43 | 44 | // print("videoModel===>>> $videoModel"); 45 | list.add(videoModel); 46 | } 47 | return list; 48 | } 49 | 50 | ///返回最大的页码 51 | static int getPageNum(String html) { 52 | Document document = parse(html); 53 | var pageDiv = document.getElementsByClassName("pagingnav")[0]; 54 | var aList = pageDiv.querySelectorAll("a"); 55 | var a = aList.reversed.toList()[1]; 56 | return int.parse(a.text); 57 | } 58 | 59 | ///返回播放连接加密的字符串 60 | static String getSrcCode(String html) { 61 | Document document = parse(html); 62 | Element? script = 63 | document.getElementById("player_one")?.querySelector("script"); 64 | if (script == null) { 65 | return ""; 66 | } 67 | String sciptContent = script.innerHtml; 68 | String code = sciptContent.substring( 69 | sciptContent.indexOf("\"") + 1, sciptContent.lastIndexOf("\"")); 70 | return code; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/pages/index.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2021-01-29 11:42:48 5 | * @LastEditTime: 2021-10-02 22:02:55 6 | */ 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:sex_91porn/pages/channel_page.dart'; 10 | import 'package:sex_91porn/pages/video_91.dart'; 11 | import 'package:sex_91porn/pages/video_db.dart'; 12 | import 'package:sex_91porn/util/application.dart'; 13 | 14 | class IndexPage extends StatefulWidget { 15 | IndexPage({Key? key}) : super(key: key); 16 | 17 | @override 18 | _IndexPageState createState() => _IndexPageState(); 19 | } 20 | 21 | class _IndexPageState extends State 22 | with AutomaticKeepAliveClientMixin { 23 | DateTime? _lastPopTime; 24 | int _currentPageIndex = 0; 25 | 26 | ///返回NavPage页 27 | List _getPageWidget() { 28 | return List.of([Video91Page(), VideoDbPage(), ChannelPage()]); 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return WillPopScope( 34 | child: Scaffold( 35 | bottomNavigationBar: BottomNavigationBar( 36 | onTap: (value) { 37 | setState(() { 38 | _currentPageIndex = value; 39 | }); 40 | }, 41 | currentIndex: _currentPageIndex, 42 | selectedItemColor: Theme.of(context).primaryColor, 43 | unselectedItemColor: Colors.grey, 44 | selectedLabelStyle: TextStyle(fontWeight: FontWeight.w600), 45 | unselectedLabelStyle: TextStyle(fontWeight: FontWeight.w200), 46 | type: BottomNavigationBarType.fixed, 47 | items: [ 48 | BottomNavigationBarItem( 49 | icon: Icon(Icons.video_library), label: "实时"), 50 | BottomNavigationBarItem( 51 | icon: Icon(Icons.local_activity), label: "本地历史"), 52 | BottomNavigationBarItem(icon: Icon(Icons.list), label: "其他渠道") 53 | ], 54 | ), 55 | body: IndexedStack( 56 | children: _getPageWidget(), 57 | index: _currentPageIndex, 58 | ), 59 | ), 60 | onWillPop: () async { 61 | if (_lastPopTime == null || 62 | DateTime.now().difference(_lastPopTime!) > Duration(seconds: 2)) { 63 | _lastPopTime = DateTime.now(); 64 | ToastUtil.show(msg: "再按一次退出程序"); 65 | return false; 66 | } 67 | return true; 68 | }, 69 | ); 70 | } 71 | 72 | @override 73 | bool get wantKeepAlive => true; 74 | } 75 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 20 | 24 | 27 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 42 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /lib/sql/video_dao.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2021-01-26 15:33:29 5 | * @LastEditTime: 2022-03-19 18:44:46 6 | */ 7 | 8 | import 'package:sex_91porn/model/video_model.dart'; 9 | import 'package:sex_91porn/sql/sql_helper.dart'; 10 | import 'package:sqflite/sqlite_api.dart'; 11 | 12 | class VideoDao extends SqlBaseProvider { 13 | @override 14 | String getTableCreateSql() { 15 | return ''' 16 | create table if not exists ${getTableName()} ( 17 | N_ID INTEGER PRIMARY KEY autoincrement,-- 编号ID主键 18 | id INTEGER ,-- 视屏ID 19 | title varchar(50) NOT NULL , -- 标题 20 | cover varchar(100) NOT NULL, -- 封面 21 | duration varchar(50), -- 时长 22 | createTime varchar(50) , -- 上传时间 23 | topCount int(11) default 1, -- 查看次数 24 | href varchar(100) not NULL , -- 原始视屏链接 25 | src varchar(100) not NULL -- 视屏播放地址 m3u8 26 | ); 27 | '''; 28 | } 29 | 30 | @override 31 | String getTableName() { 32 | return "video"; 33 | } 34 | 35 | ///查询 36 | Future> queryList( 37 | {int current = 1, 38 | int size = 10, 39 | String? search, 40 | bool hotSort = false}) async { 41 | Database database = await getDataBase(); 42 | String where = ""; 43 | if (search != null && search.isNotEmpty) { 44 | where += 'title like %$search%'; 45 | } 46 | return (await database.query(getTableName(), 47 | where: where, 48 | orderBy: hotSort ? 'topCount' : 'createTime' + " desc", 49 | limit: size, 50 | offset: size * current)) 51 | .map((e) => VideoModel.fromMap(e)) 52 | .toList(); 53 | } 54 | 55 | Future> queryAll() async { 56 | Database database = await getDataBase(); 57 | return (await database.query(getTableName())) 58 | .map((e) => VideoModel.fromMap(e)) 59 | .toList(); 60 | } 61 | 62 | Future addVideo(VideoModel model) async { 63 | Database database = await getDataBase(); 64 | //判断库中是否存在记录 65 | var resList = await database.rawQuery( 66 | "select count(N_ID) as count from ${getTableName()} where id != 0 and id = ? or href = ? limit 1", 67 | [model.id, model.href]); 68 | if (resList[0]["count"] != 0) { 69 | return -1; 70 | } 71 | Map values = model.toMap(); 72 | return await database.insert(getTableName(), values, 73 | conflictAlgorithm: ConflictAlgorithm.ignore); 74 | } 75 | 76 | ///清空所有的数据 77 | Future clearAll() async { 78 | Database database = await getDataBase(); 79 | return await database.delete(getTableName()); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - fluttertoast (0.0.2): 4 | - Flutter 5 | - Toast 6 | - FMDB (2.7.5): 7 | - FMDB/standard (= 2.7.5) 8 | - FMDB/standard (2.7.5) 9 | - path_provider_ios (0.0.1): 10 | - Flutter 11 | - "permission_handler (5.1.0+2)": 12 | - Flutter 13 | - shared_preferences_ios (0.0.1): 14 | - Flutter 15 | - sqflite (0.0.2): 16 | - Flutter 17 | - FMDB (>= 2.7.5) 18 | - Toast (4.0.0) 19 | - video_player_avfoundation (0.0.1): 20 | - Flutter 21 | - wakelock (0.0.1): 22 | - Flutter 23 | - webview_flutter_wkwebview (0.0.1): 24 | - Flutter 25 | 26 | DEPENDENCIES: 27 | - Flutter (from `Flutter`) 28 | - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) 29 | - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) 30 | - permission_handler (from `.symlinks/plugins/permission_handler/ios`) 31 | - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`) 32 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 33 | - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`) 34 | - wakelock (from `.symlinks/plugins/wakelock/ios`) 35 | - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) 36 | 37 | SPEC REPOS: 38 | trunk: 39 | - FMDB 40 | - Toast 41 | 42 | EXTERNAL SOURCES: 43 | Flutter: 44 | :path: Flutter 45 | fluttertoast: 46 | :path: ".symlinks/plugins/fluttertoast/ios" 47 | path_provider_ios: 48 | :path: ".symlinks/plugins/path_provider_ios/ios" 49 | permission_handler: 50 | :path: ".symlinks/plugins/permission_handler/ios" 51 | shared_preferences_ios: 52 | :path: ".symlinks/plugins/shared_preferences_ios/ios" 53 | sqflite: 54 | :path: ".symlinks/plugins/sqflite/ios" 55 | video_player_avfoundation: 56 | :path: ".symlinks/plugins/video_player_avfoundation/ios" 57 | wakelock: 58 | :path: ".symlinks/plugins/wakelock/ios" 59 | webview_flutter_wkwebview: 60 | :path: ".symlinks/plugins/webview_flutter_wkwebview/ios" 61 | 62 | SPEC CHECKSUMS: 63 | Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a 64 | fluttertoast: 16fbe6039d06a763f3533670197d01fc73459037 65 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 66 | path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 67 | permission_handler: ccb20a9fad0ee9b1314a52b70b76b473c5f8dab0 68 | shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad 69 | sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 70 | Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 71 | video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff 72 | wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f 73 | webview_flutter_wkwebview: 005fbd90c888a42c5690919a1527ecc6649e1162 74 | 75 | PODFILE CHECKSUM: 8e679eca47255a8ca8067c4c67aab20e64cb974d 76 | 77 | COCOAPODS: 1.11.3 78 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /lib/pages/sihu/common_widget.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2022-03-19 16:33:49 5 | * @LastEditTime: 2022-03-19 18:38:32 6 | */ 7 | 8 | import 'package:bruno/bruno.dart'; 9 | import 'package:cached_network_image/cached_network_image.dart'; 10 | import 'package:flutter/material.dart'; 11 | import 'package:sex_91porn/model/video_model.dart'; 12 | import 'package:sex_91porn/sql/video_dao.dart'; 13 | import 'package:sex_91porn/util/application.dart'; 14 | 15 | ///预览Cardview 16 | class PreviewCardPanel extends StatefulWidget { 17 | ///视屏信息 18 | final VideoModel videoModel; 19 | 20 | PreviewCardPanel({Key? key, required this.videoModel}) : super(key: key); 21 | 22 | @override 23 | State createState() => _PreviewCardPenalState(); 24 | } 25 | 26 | class _PreviewCardPenalState extends State { 27 | @override 28 | Widget build(BuildContext context) { 29 | return GestureDetector( 30 | onTap: () { 31 | VideoDao().addVideo(widget.videoModel); 32 | Application.navigateToIos(context, "/play", params: widget.videoModel); 33 | }, 34 | onLongPress: () {}, 35 | child: Container( 36 | child: BrnShadowCard( 37 | child: Column( 38 | children: [ 39 | Expanded( 40 | child: Stack( 41 | children: [ 42 | CachedNetworkImage( 43 | imageUrl: widget.videoModel.cover ?? "", 44 | fit: BoxFit.cover, 45 | height: double.infinity, 46 | width: double.infinity, 47 | errorWidget: (c, u, e) { 48 | return Icon(Icons.broken_image); 49 | }, 50 | placeholder: (context, url) { 51 | return Center( 52 | child: CircularProgressIndicator( 53 | valueColor: AlwaysStoppedAnimation(Colors.blue)), 54 | ); 55 | }), 56 | Align( 57 | alignment: Alignment.bottomLeft, 58 | child: BrnTagCustom( 59 | tagText: widget.videoModel.duration ?? "-", 60 | textPadding: EdgeInsets.all(3.3), 61 | tagBorderRadius: BorderRadius.circular(3), 62 | backgroundColor: Colors.black45, 63 | textColor: Colors.white, 64 | ), 65 | ) 66 | ], 67 | )), 68 | SizedBox.fromSize( 69 | size: Size.fromHeight(10), 70 | ), 71 | Padding( 72 | padding: EdgeInsets.only(left: 5, right: 5, bottom: 5), 73 | child: Text( 74 | widget.videoModel.title, 75 | style: TextStyle(fontSize: 12), 76 | ), 77 | ) 78 | ], 79 | ), 80 | ), 81 | ), 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/pages/sihu/page_detail.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2022-03-19 19:13:40 5 | * @LastEditTime: 2022-03-19 20:04:01 6 | */ 7 | import 'package:bruno/bruno.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 10 | import 'package:sex_91porn/model/pair.dart'; 11 | import 'package:sex_91porn/model/video_model.dart'; 12 | import 'package:sex_91porn/pages/sihu/common_widget.dart'; 13 | import 'package:sex_91porn/pages/sihu/data_service.dart'; 14 | import 'package:sex_91porn/util/application.dart'; 15 | 16 | import 'model_entity.dart'; 17 | 18 | ///分类详情 19 | class SihuPageDetail extends StatefulWidget { 20 | final SihuCategoryModel categoryModel; 21 | 22 | SihuPageDetail({Key? key, required this.categoryModel}) : super(key: key); 23 | 24 | @override 25 | State createState() => _SihuPageDetailState(); 26 | } 27 | 28 | class _SihuPageDetailState extends State { 29 | final EasyRefreshController _controller = EasyRefreshController(); 30 | 31 | final List _modelList = List.empty(growable: true); 32 | 33 | int _currentPageIndex = 0; 34 | 35 | int _maxPageSize = 2; 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | return Scaffold( 40 | appBar: BrnAppBar( 41 | brightness: Brightness.dark, 42 | title: widget.categoryModel.title, 43 | automaticallyImplyLeading: true), 44 | body: Container( 45 | child: EasyRefresh( 46 | enableControlFinishLoad: _maxPageSize <= _currentPageIndex, 47 | firstRefresh: true, 48 | controller: _controller, 49 | onRefresh: () async { 50 | _currentPageIndex = 0; 51 | _modelList.clear(); 52 | return; 53 | }, 54 | onLoad: _onLoadData, 55 | child: GridView.builder( 56 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 57 | crossAxisCount: 2, childAspectRatio: 1), 58 | itemBuilder: (context, index) { 59 | return Padding( 60 | padding: EdgeInsets.all(6), 61 | child: PreviewCardPanel(videoModel: _modelList[index]), 62 | ); 63 | }, 64 | itemCount: _modelList.length, 65 | ), 66 | ), 67 | ), 68 | ); 69 | } 70 | 71 | ///加载数据 72 | Future _onLoadData() async { 73 | _currentPageIndex++; 74 | if (_currentPageIndex >= _maxPageSize) { 75 | ToastUtil.show(msg: "没有更多了"); 76 | return; 77 | } 78 | Pair> pair = await SihuDataService.getCategoryInfo( 79 | widget.categoryModel, _currentPageIndex); 80 | _maxPageSize = pair.key; 81 | setState(() { 82 | _modelList.addAll(pair.value); 83 | }); 84 | print( 85 | "抓取新数据===>>爬取页码:$_currentPageIndex, 页码数量:${pair.key} , 列表长度: ${pair.value.length}"); 86 | return; 87 | } 88 | 89 | @override 90 | void dispose() { 91 | _controller.dispose(); 92 | super.dispose(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/router/router_handler.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2021-01-29 11:44:21 5 | * @LastEditTime: 2022-03-28 15:16:16 6 | */ 7 | 8 | import 'package:fluro/fluro.dart'; 9 | import 'package:flutter/cupertino.dart'; 10 | import 'package:sex_91porn/model/img_model.dart'; 11 | import 'package:sex_91porn/model/video_model.dart'; 12 | import 'package:sex_91porn/pages/image_detail_page.dart'; 13 | import 'package:sex_91porn/pages/index.dart'; 14 | import 'package:sex_91porn/pages/madou/index.dart'; 15 | import 'package:sex_91porn/pages/madou/madou_config.dart'; 16 | import 'package:sex_91porn/pages/madou/video_list.dart'; 17 | import 'package:sex_91porn/pages/play_video.dart'; 18 | import 'package:sex_91porn/pages/sihu/index.dart'; 19 | import 'package:sex_91porn/pages/sihu/model_entity.dart'; 20 | import 'package:sex_91porn/pages/sihu/page_detail.dart'; 21 | import 'package:sex_91porn/pages/sihu/search.dart'; 22 | 23 | ///首页Index 24 | Handler indexHandler = Handler( 25 | handlerFunc: (BuildContext? context, Map> parameters) { 26 | return IndexPage(); 27 | }); 28 | 29 | ///播放页 30 | Handler videoPlayHandler = Handler( 31 | handlerFunc: (BuildContext? context, Map> parameters) { 32 | VideoModel model = context!.settings!.arguments as VideoModel; 33 | return new PlayVideoPage( 34 | videoModel: model, 35 | ); 36 | }); 37 | 38 | //图文详情 39 | Handler imageDetailPage = Handler( 40 | handlerFunc: (BuildContext? context, Map> parameters) { 41 | return ImageDetailPage( 42 | model: context!.settings!.arguments as ImageModel, 43 | ); 44 | }); 45 | 46 | //四虎Index 47 | Handler sihuHandler = Handler( 48 | handlerFunc: (BuildContext? context, Map> parameters) { 49 | return SihuIndexPage(); 50 | }); 51 | 52 | //四虎分类详情 53 | Handler sihuDetailHandler = Handler( 54 | handlerFunc: (BuildContext? context, Map> parameters) { 55 | return SihuPageDetail( 56 | categoryModel: context!.settings!.arguments as SihuCategoryModel, 57 | ); 58 | }); 59 | 60 | //四虎搜索 61 | Handler sihuSearchHandler = Handler( 62 | handlerFunc: (BuildContext? context, Map> parameters) { 63 | return SihuSearchResultPage( 64 | keyword: context!.settings!.arguments as String, 65 | ); 66 | }); 67 | 68 | //Madou Index 69 | Handler madouHandler = Handler( 70 | handlerFunc: (BuildContext? context, Map> parameters) { 71 | return MadouIndexPage(); 72 | }); 73 | 74 | //Madou VideoList 75 | Handler madouVideoListHandler = Handler( 76 | handlerFunc: (BuildContext? context, Map> parameters) { 77 | var val = context!.settings!.arguments; 78 | MadouVideoList page; 79 | if (val is String) { 80 | page = MadouVideoList( 81 | pageTitle: val, 82 | searchText: val, 83 | ); 84 | } else if (val is MadouCategory) { 85 | page = MadouVideoList( 86 | pageTitle: val.title, 87 | category: val, 88 | ); 89 | } else { 90 | throw Exception("madouVideoListHandler 传入类型不支持"); 91 | } 92 | return page; 93 | }); 94 | -------------------------------------------------------------------------------- /lib/model/video_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | /* 4 | * @Description: 5 | * @Author: chenzedeng 6 | * @Date: 2021-01-26 15:18:03 7 | * @LastEditTime: 2022-03-27 21:23:27 8 | */ 9 | 10 | ///视屏模块 11 | class VideoModel { 12 | ///编号ID 13 | final int id; 14 | 15 | ///标题 16 | final String title; 17 | 18 | final String? subTtile; 19 | 20 | ///封面 21 | final String? cover; 22 | 23 | String? dynamicCover; 24 | 25 | ///时长 26 | final String? duration; 27 | 28 | ///原始视屏链接 29 | final String? href; 30 | 31 | ///视屏播放地址 m3u8 32 | String? src; 33 | VideoModel( 34 | {required this.id, 35 | required this.title, 36 | this.cover, 37 | this.dynamicCover, 38 | this.duration, 39 | this.href, 40 | this.src, 41 | this.subTtile}); 42 | 43 | VideoModel copyWith( 44 | {int? id, 45 | String? title, 46 | String? cover, 47 | String? dynamicCover, 48 | String? duration, 49 | String? href, 50 | String? src, 51 | String? subTtile}) { 52 | return VideoModel( 53 | id: id ?? this.id, 54 | title: title ?? this.title, 55 | cover: cover ?? this.cover, 56 | dynamicCover: dynamicCover ?? this.dynamicCover, 57 | duration: duration ?? this.duration, 58 | href: href ?? this.href, 59 | src: src ?? this.src, 60 | subTtile: subTtile ?? this.subTtile); 61 | } 62 | 63 | Map toMap() { 64 | return { 65 | 'id': id, 66 | 'title': title, 67 | 'cover': cover, 68 | 'duration': duration, 69 | 'href': href, 70 | 'src': src, 71 | }; 72 | } 73 | 74 | factory VideoModel.fromMap(Map map) { 75 | return VideoModel( 76 | id: map['id']?.toInt() ?? 0, 77 | title: map['title'] ?? '', 78 | cover: map['cover'], 79 | duration: map['duration'], 80 | href: map['href'], 81 | src: map['src'], 82 | ); 83 | } 84 | 85 | String toJson() => json.encode(toMap()); 86 | 87 | factory VideoModel.fromJson(String source) => 88 | VideoModel.fromMap(json.decode(source)); 89 | 90 | @override 91 | String toString() { 92 | return 'VideoModel(id: $id, title: $title, subTitle: $subTtile cover: $cover, dynamicCover: $dynamicCover, duration: $duration, href: $href, src: $src)'; 93 | } 94 | 95 | @override 96 | bool operator ==(Object other) { 97 | if (identical(this, other)) return true; 98 | 99 | return other is VideoModel && 100 | other.id == id && 101 | other.title == title && 102 | other.cover == cover && 103 | other.dynamicCover == dynamicCover && 104 | other.duration == duration && 105 | other.href == href && 106 | other.src == src; 107 | } 108 | 109 | @override 110 | int get hashCode { 111 | return id.hashCode ^ 112 | title.hashCode ^ 113 | cover.hashCode ^ 114 | dynamicCover.hashCode ^ 115 | duration.hashCode ^ 116 | href.hashCode ^ 117 | src.hashCode; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /lib/pages/madou/video_list.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2022-03-28 20:33:50 5 | * @LastEditTime: 2022-09-17 22:07:38 6 | */ 7 | 8 | import 'package:bruno/bruno.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 11 | import 'package:sex_91porn/model/video_model.dart'; 12 | import 'package:sex_91porn/pages/madou/common_widget.dart'; 13 | import 'package:sex_91porn/pages/madou/madou_config.dart'; 14 | import 'package:sex_91porn/util/object_util.dart'; 15 | 16 | import 'madou_service.dart'; 17 | 18 | ///分类列表详情页 19 | class MadouVideoList extends StatefulWidget { 20 | final MadouCategory? category; 21 | final String pageTitle; 22 | final String? searchText; 23 | 24 | MadouVideoList( 25 | {Key? key, this.category, required this.pageTitle, this.searchText}) 26 | : super(key: key); 27 | 28 | @override 29 | State createState() => _MadouVideoListState(); 30 | } 31 | 32 | class _MadouVideoListState extends State { 33 | int _current = 0; 34 | bool _isEnd = false; 35 | List _videos = List.empty(growable: true); 36 | 37 | Future _onLoad() async { 38 | if (!_isEnd) { 39 | _current++; 40 | List list = List.empty(); 41 | try { 42 | if (widget.category != null) { 43 | list = await MadouService.categoryVideo(widget.category!, _current); 44 | } else if (StringUtils.isNotBlank(widget.searchText)) { 45 | list = await MadouService.search(_current, widget.searchText!); 46 | } else { 47 | throw new Exception("category | searchText 不可全部为空"); 48 | } 49 | } catch (e) { 50 | print("请求失败: $e"); 51 | } 52 | 53 | if (list.isEmpty) { 54 | _isEnd = true; 55 | return; 56 | } 57 | list.removeWhere((le) => 58 | _videos.indexWhere((element) => le.href == element.href) != -1); 59 | if (list.isEmpty) { 60 | _isEnd = true; 61 | print("全部爬完成: pageIndex: $_current, videoSize: ${_videos.length}"); 62 | return; 63 | } 64 | setState(() { 65 | _videos.addAll(list); 66 | }); 67 | } 68 | } 69 | 70 | @override 71 | Widget build(BuildContext context) { 72 | return Scaffold( 73 | appBar: BrnAppBar( 74 | brightness: Brightness.dark, 75 | title: widget.pageTitle, 76 | automaticallyImplyLeading: true), 77 | body: Container( 78 | padding: EdgeInsets.only(left: 2, right: 2), 79 | child: EasyRefresh( 80 | onLoad: _onLoad, 81 | enableControlFinishLoad: _isEnd, 82 | child: GridView.builder( 83 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 84 | crossAxisCount: 2, childAspectRatio: 1), 85 | itemBuilder: (context, index) { 86 | return Padding( 87 | padding: EdgeInsets.all(6), 88 | child: MadouVideoListTial(videoModel: _videos[index]), 89 | ); 90 | }, 91 | itemCount: _videos.length, 92 | ), 93 | ), 94 | ), 95 | ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "size": "20x20", 5 | "idiom": "iphone", 6 | "filename": "icon-20@2x.png", 7 | "scale": "2x" 8 | }, 9 | { 10 | "size": "20x20", 11 | "idiom": "iphone", 12 | "filename": "icon-20@3x.png", 13 | "scale": "3x" 14 | }, 15 | { 16 | "size": "29x29", 17 | "idiom": "iphone", 18 | "filename": "icon-29.png", 19 | "scale": "1x" 20 | }, 21 | { 22 | "size": "29x29", 23 | "idiom": "iphone", 24 | "filename": "icon-29@2x.png", 25 | "scale": "2x" 26 | }, 27 | { 28 | "size": "29x29", 29 | "idiom": "iphone", 30 | "filename": "icon-29@3x.png", 31 | "scale": "3x" 32 | }, 33 | { 34 | "size": "40x40", 35 | "idiom": "iphone", 36 | "filename": "icon-40@2x.png", 37 | "scale": "2x" 38 | }, 39 | { 40 | "size": "40x40", 41 | "idiom": "iphone", 42 | "filename": "icon-40@3x.png", 43 | "scale": "3x" 44 | }, 45 | { 46 | "size": "60x60", 47 | "idiom": "iphone", 48 | "filename": "icon-60@2x.png", 49 | "scale": "2x" 50 | }, 51 | { 52 | "size": "60x60", 53 | "idiom": "iphone", 54 | "filename": "icon-60@3x.png", 55 | "scale": "3x" 56 | }, 57 | { 58 | "size": "20x20", 59 | "idiom": "ipad", 60 | "filename": "icon-20-ipad.png", 61 | "scale": "1x" 62 | }, 63 | { 64 | "size": "20x20", 65 | "idiom": "ipad", 66 | "filename": "icon-20@2x-ipad.png", 67 | "scale": "2x" 68 | }, 69 | { 70 | "size": "29x29", 71 | "idiom": "ipad", 72 | "filename": "icon-29-ipad.png", 73 | "scale": "1x" 74 | }, 75 | { 76 | "size": "29x29", 77 | "idiom": "ipad", 78 | "filename": "icon-29@2x-ipad.png", 79 | "scale": "2x" 80 | }, 81 | { 82 | "size": "40x40", 83 | "idiom": "ipad", 84 | "filename": "icon-40.png", 85 | "scale": "1x" 86 | }, 87 | { 88 | "size": "40x40", 89 | "idiom": "ipad", 90 | "filename": "icon-40@2x.png", 91 | "scale": "2x" 92 | }, 93 | { 94 | "size": "76x76", 95 | "idiom": "ipad", 96 | "filename": "icon-76.png", 97 | "scale": "1x" 98 | }, 99 | { 100 | "size": "76x76", 101 | "idiom": "ipad", 102 | "filename": "icon-76@2x.png", 103 | "scale": "2x" 104 | }, 105 | { 106 | "size": "83.5x83.5", 107 | "idiom": "ipad", 108 | "filename": "icon-83.5@2x.png", 109 | "scale": "2x" 110 | }, 111 | { 112 | "size": "1024x1024", 113 | "idiom": "ios-marketing", 114 | "filename": "icon-1024.png", 115 | "scale": "1x" 116 | } 117 | ], 118 | "info": { 119 | "version": 1, 120 | "author": "icon.wuruihong.com" 121 | } 122 | } -------------------------------------------------------------------------------- /lib/pages/video_91.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2021-01-30 21:47:54 5 | * @LastEditTime: 2021-10-02 22:09:00 6 | */ 7 | import 'package:flutter/material.dart'; 8 | import 'package:sex_91porn/model/config.dart'; 9 | import 'package:sex_91porn/pages/video_91_list.dart'; 10 | import 'package:sex_91porn/util/application.dart'; 11 | import 'package:sex_91porn/util/widget_util.dart'; 12 | import 'package:shared_preferences/shared_preferences.dart'; 13 | 14 | class Video91Page extends StatefulWidget { 15 | Video91Page({Key? key}) : super(key: key); 16 | 17 | @override 18 | _Video91PageState createState() => _Video91PageState(); 19 | } 20 | 21 | class _Video91PageState extends State 22 | with SingleTickerProviderStateMixin { 23 | late TabController _mController; 24 | 25 | static const KEY = "siteUrl"; 26 | 27 | @override 28 | void initState() { 29 | super.initState(); 30 | _mController = 31 | new TabController(length: Config.categoryList.length, vsync: this); 32 | Future.delayed(Duration.zero, () async { 33 | SharedPreferences sp = await SharedPreferences.getInstance(); 34 | if (sp.containsKey(KEY)) { 35 | String? url = sp.getString(KEY); 36 | if (url != null || url!.isNotEmpty) { 37 | Config.MAIN_URL = url; 38 | } 39 | } 40 | }); 41 | } 42 | 43 | @override 44 | void dispose() { 45 | super.dispose(); 46 | _mController.dispose(); 47 | } 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | return Scaffold( 52 | appBar: AppBar( 53 | title: Text( 54 | "实时资源库", 55 | style: TextStyle(color: Colors.white), 56 | ), 57 | leading: IconButton( 58 | icon: Icon( 59 | Icons.settings, 60 | color: Colors.white, 61 | ), 62 | onPressed: () { 63 | DialogUtil.showInputDialog(context, 64 | title: "配置爬取URL", 65 | placeholder: "请输入Url地址 无/结尾", 66 | initVal: Config.MAIN_URL, call: (String val) async { 67 | if (val.isEmpty) { 68 | ToastUtil.show(msg: "请配置完整"); 69 | } else { 70 | SharedPreferences sp = await SharedPreferences.getInstance(); 71 | sp.setString(KEY, val); 72 | setState(() { 73 | Config.MAIN_URL = val; 74 | ToastUtil.show(msg: "请上拉刷新重新加载"); 75 | }); 76 | } 77 | Navigator.pop(context); 78 | }); 79 | }, 80 | ), 81 | ), 82 | body: Column( 83 | children: [ 84 | Padding( 85 | padding: EdgeInsets.only(left: 5, right: 5), 86 | child: TabBar( 87 | tabs: Config.categoryList 88 | .map((e) => Tab( 89 | text: e.name, 90 | )) 91 | .toList(), 92 | isScrollable: true, 93 | controller: _mController, 94 | ), 95 | ), 96 | Padding(padding: EdgeInsets.only(bottom: 10)), 97 | Expanded( 98 | child: TabBarView( 99 | controller: _mController, 100 | children: Config.categoryList 101 | .map((e) => Video91ListPage( 102 | model: e, 103 | )) 104 | .toList(), 105 | ), 106 | ), 107 | ], 108 | ), 109 | ); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /lib/pages/sihu/search.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2022-03-19 22:24:29 5 | * @LastEditTime: 2022-03-19 23:28:03 6 | */ 7 | import 'package:bruno/bruno.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:sex_91porn/model/video_model.dart'; 10 | import 'package:sex_91porn/sql/video_dao.dart'; 11 | import 'package:sex_91porn/util/application.dart'; 12 | 13 | import 'data_service.dart'; 14 | 15 | class SihuSearchResultPage extends StatefulWidget { 16 | final String keyword; 17 | 18 | SihuSearchResultPage({Key? key, required this.keyword}) : super(key: key); 19 | 20 | @override 21 | State createState() => _SihuSearchResultPageState(); 22 | } 23 | 24 | class _SihuSearchResultPageState extends State { 25 | @override 26 | Widget build(BuildContext context) { 27 | return Scaffold( 28 | appBar: BrnAppBar( 29 | brightness: Brightness.dark, 30 | title: widget.keyword, 31 | automaticallyImplyLeading: true), 32 | body: Container( 33 | child: FutureBuilder>( 34 | future: SihuDataService.search(widget.keyword), 35 | builder: (BuildContext context, 36 | AsyncSnapshot> snapshot) { 37 | if (snapshot.hasData) { 38 | var list = snapshot.requireData; 39 | return _initListView(list); 40 | } else { 41 | return Center( 42 | child: CircularProgressIndicator( 43 | backgroundColor: Colors.grey[200], 44 | valueColor: AlwaysStoppedAnimation(Colors.blue), 45 | )); 46 | } 47 | }), 48 | ), 49 | ); 50 | } 51 | 52 | Widget _initListView(List list) { 53 | return ListView.builder( 54 | itemBuilder: ((context, index) { 55 | var item = list[index]; 56 | //是否是视屏还是图 57 | var isVideo = item.href!.indexOf("/view/") == -1; 58 | return ListTile( 59 | onTap: () { 60 | BrnLoadingDialog.show(context); 61 | if (isVideo) { 62 | SihuDataService.searchVideoProcessConversion(item).then((value) { 63 | BrnLoadingDialog.dismiss(context); 64 | VideoDao().addVideo(value); 65 | Application.navigateToIos(context, "/play", params: value); 66 | }).onError((error, stackTrace) { 67 | BrnLoadingDialog.dismiss(context); 68 | print("error:$error, stackTrace:$stackTrace"); 69 | ToastUtil.show(msg: "请求失败"); 70 | }); 71 | } else { 72 | //图文 73 | SihuDataService.searchImageProcessConversion(item).then((value) { 74 | BrnLoadingDialog.dismiss(context); 75 | Application.navigateToIos(context, "/imgPreview", 76 | params: value); 77 | }).onError((error, stackTrace) { 78 | BrnLoadingDialog.dismiss(context); 79 | print("error:$error, stackTrace:$stackTrace"); 80 | ToastUtil.show(msg: "请求失败"); 81 | }); 82 | } 83 | }, 84 | leading: Icon( 85 | isVideo ? Icons.movie : Icons.image, 86 | color: Colors.blueAccent, 87 | ), 88 | title: Text(item.title, 89 | overflow: TextOverflow.ellipsis, 90 | style: TextStyle( 91 | color: Color.fromARGB(255, 0, 134, 207), fontSize: 13)), 92 | subtitle: Text( 93 | isVideo ? "视屏" : "图文", 94 | style: TextStyle(fontSize: 12), 95 | ), 96 | ); 97 | }), 98 | itemCount: list.length, 99 | ); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /lib/pages/madou/madou_service.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2022-03-27 21:00:35 5 | * @LastEditTime: 2022-03-28 21:23:47 6 | */ 7 | import 'dart:convert'; 8 | 9 | import 'package:html/dom.dart'; 10 | import 'package:html/parser.dart'; 11 | import 'package:sex_91porn/model/video_model.dart'; 12 | import 'package:sex_91porn/util/http_util.dart'; 13 | import 'package:sex_91porn/util/sp_util.dart'; 14 | import './madou_config.dart'; 15 | 16 | class MadouService { 17 | ///获取首页热门推荐列表数据 18 | static Future> hotVideoList(int page) async { 19 | return _parseVideoList(MAIN_URL + "page/$page"); 20 | } 21 | 22 | ///解析视屏列表 23 | static Future> _parseVideoList(String url) async { 24 | print("URL=====>>> $url"); 25 | var html = await HttpUtil.getHtml(url); 26 | var root = parse(html); 27 | List videos = root.getElementsByClassName("excerpt excerpt-c5"); 28 | return videos.map((e) { 29 | var h2 = e.querySelector("h2")!; 30 | var href = h2.querySelector("a")!.attributes["href"]; 31 | String title = h2.text; 32 | String viewCount = e.getElementsByClassName("post-view")[0].text; 33 | String? cover = e.querySelector("img")!.attributes["data-src"]; 34 | 35 | String? subTitle = e.querySelector(".hot")?.text; 36 | return VideoModel( 37 | id: 0, 38 | title: title, 39 | href: href, 40 | duration: viewCount, 41 | cover: cover, 42 | subTtile: subTitle); 43 | }).toList(); 44 | } 45 | 46 | ///获取分类下的视屏列表分页 47 | static Future> categoryVideo( 48 | MadouCategory category, int page) { 49 | return _parseVideoList(category.getPagePath(page)); 50 | } 51 | 52 | ///解析m3u8播放链接 53 | static Future analysisVideoPath(VideoModel videoModel) async { 54 | var html = await HttpUtil.getHtml(videoModel.href!); 55 | var root = parse(html); 56 | String path = root 57 | .getElementsByClassName("article-content")[0] 58 | .querySelector("iframe")! 59 | .attributes["src"]!; 60 | html = await HttpUtil.getHtml(path); 61 | String tmp = html.substring(html.indexOf("var token = ") + 12); 62 | String token = tmp.substring(0, tmp.indexOf(";")).replaceAll("\"", ""); 63 | 64 | tmp = html.substring(html.indexOf("var m3u8 = ") + 11); 65 | String m3u8 = tmp.substring(0, tmp.indexOf(";")).replaceAll("'", ""); 66 | 67 | var httpOrigin = Uri.parse(path).origin; 68 | videoModel.src = httpOrigin + m3u8 + "?token=" + token; 69 | return videoModel; 70 | } 71 | 72 | ///返回类别分类 73 | static Future> getCategoryList() async { 74 | String key = "madou-getCategoryList"; 75 | var map = await SpUtil.getVal(key); 76 | List category; 77 | if (map == null) { 78 | var html = await HttpUtil.getHtml(MAIN_URL); 79 | var root = parse(html); 80 | var lis = root.querySelector(".sitenav")!.querySelectorAll("li"); 81 | category = lis 82 | .map((e) { 83 | return MadouCategory( 84 | href: e.querySelector("a")!.attributes["href"]!, title: e.text); 85 | }) 86 | .where((element) => element.href.startsWith("http")) 87 | .toList(); 88 | //保存到缓存,设置7天过期删除 89 | await SpUtil.save(key, {key: category}, 90 | exprie: 7, level: TimeExpireLevel.DAY); 91 | } else { 92 | category = List.from(map[key]) 93 | .map((e) => MadouCategory.fromMap(jsonDecode(e))) 94 | .toList(); 95 | } 96 | return category; 97 | } 98 | 99 | ///搜索视屏 100 | static Future> search(int page, String searchText) { 101 | String url = MAIN_URL + "page/$page?s=$searchText"; 102 | return _parseVideoList(url); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: sex_91porn 2 | description: 91Porn 视屏爬取在线播放器 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `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.12.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | 27 | # 路由管理框架 28 | fluro: ^2.0.3 29 | #Sql数据库 30 | sqflite: ^2.0.0+4 31 | # 全局状态管理组件 32 | #provider: ^4.3.1 33 | #网络请求 34 | dio: ^4.0.0 35 | cookie_jar: 3.0.1 36 | dio_cookie_manager: ^2.0.0 37 | #权限检测器 38 | permission_handler: ^8.1.6 39 | #设备存储目录获取 40 | path_provider: ^2.0.3 41 | #持久化存储 42 | shared_preferences: ^2.0.8 43 | #Toast 44 | fluttertoast: ^8.0.8 45 | #图片放大预览 46 | #photo_view: ^0.10.1 47 | #Svg 48 | #flutter_svg: ^0.18.0 49 | #图片缓存框架 50 | cached_network_image: ^3.1.0 51 | #Html 解析器 52 | html: ^0.15.0 53 | webview_flutter_plus: ^0.2.3 54 | #视屏播放器 55 | chewie: ^1.2.2 56 | video_player: ^2.2.5 57 | #保持屏幕常亮插件 58 | wakelock: ^0.6.1+2 59 | #上拉加载 下拉刷新组件 60 | flutter_easyrefresh: ^2.2.1 61 | # 贝壳UI组件库 62 | bruno: ^2.1.0-nullsafety.1 63 | 64 | 65 | # The following adds the Cupertino Icons font to your application. 66 | # Use with the CupertinoIcons class for iOS style icons. 67 | cupertino_icons: ^1.0.0 68 | 69 | dev_dependencies: 70 | flutter_test: 71 | sdk: flutter 72 | 73 | # For information on the generic Dart part of this file, see the 74 | # following page: https://dart.dev/tools/pub/pubspec 75 | 76 | # The following section is specific to Flutter. 77 | flutter: 78 | 79 | # The following line ensures that the Material Icons font is 80 | # included with your application, so that you can use the icons in 81 | # the material Icons class. 82 | uses-material-design: true 83 | 84 | # To add assets to your application, add an assets section, like this: 85 | assets: 86 | - assets/html/ 87 | # - images/a_dot_ham.jpeg 88 | 89 | # An image asset can refer to one or more resolution-specific "variants", see 90 | # https://flutter.dev/assets-and-images/#resolution-aware. 91 | 92 | # For details regarding adding assets from package dependencies, see 93 | # https://flutter.dev/assets-and-images/#from-packages 94 | 95 | # To add custom fonts to your application, add a fonts section here, 96 | # in this "flutter" section. Each entry in this list should have a 97 | # "family" key with the font family name, and a "fonts" key with a 98 | # list giving the asset and other descriptors for the font. For 99 | # example: 100 | # fonts: 101 | # - family: Schyler 102 | # fonts: 103 | # - asset: fonts/Schyler-Regular.ttf 104 | # - asset: fonts/Schyler-Italic.ttf 105 | # style: italic 106 | # - family: Trajan Pro 107 | # fonts: 108 | # - asset: fonts/TrajanPro.ttf 109 | # - asset: fonts/TrajanPro_Bold.ttf 110 | # weight: 700 111 | # 112 | # For details regarding fonts from package dependencies, 113 | # see https://flutter.dev/custom-fonts/#from-packages 114 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | sex_91porn 30 | 31 | 32 | 33 | 36 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /lib/util/sp_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | 5 | /* 6 | * @Description: 7 | * @Author: chenzedeng 8 | * @Date: 2021-06-16 10:10:02 9 | * @LastEditTime: 2022-03-27 22:09:25 10 | */ 11 | 12 | import 'package:shared_preferences/shared_preferences.dart'; 13 | 14 | import 'object_util.dart'; 15 | 16 | ///时间单位 17 | enum TimeExpireLevel { SECONDS, MINUTES, HOURS, DAY } 18 | 19 | extension TimeExpireLevelExtension on TimeExpireLevel { 20 | String get name => describeEnum(this); 21 | } 22 | 23 | class SpUtil { 24 | ///获取实例对象 25 | static Future getInstance() { 26 | return SharedPreferences.getInstance(); 27 | } 28 | 29 | ///保存 30 | static Future save(String key, Map map, 31 | {int? exprie, TimeExpireLevel level = TimeExpireLevel.SECONDS}) async { 32 | SharedPreferences prefs = await SharedPreferences.getInstance(); 33 | _SpData _spData = _SpData(data: map); 34 | if (exprie != null) { 35 | DateTime expireDate; 36 | switch (level) { 37 | case TimeExpireLevel.SECONDS: 38 | expireDate = DateTime.now().add(Duration(seconds: exprie)); 39 | break; 40 | case TimeExpireLevel.MINUTES: 41 | expireDate = DateTime.now().add(Duration(minutes: exprie)); 42 | break; 43 | case TimeExpireLevel.HOURS: 44 | expireDate = DateTime.now().add(Duration(hours: exprie)); 45 | break; 46 | case TimeExpireLevel.DAY: 47 | expireDate = DateTime.now().add(Duration(days: exprie)); 48 | break; 49 | default: 50 | throw Exception("no time level!"); 51 | } 52 | if (expireDate == null) { 53 | throw Exception("expireDate dont complate"); 54 | } 55 | _spData.time = expireDate.millisecondsSinceEpoch; 56 | _spData.level = level; 57 | } 58 | return await prefs.setString(key, _spData.toJson()); 59 | } 60 | 61 | ///get value 62 | ///[key] is key 63 | ///[returnExpire] Do you need to return key->data if it expires 64 | ///[delExprie] Do you need to delete key if it expire 65 | static Future getVal(String key, 66 | {bool returnExpire = false, bool delExprie = true}) async { 67 | SharedPreferences prefs = await SharedPreferences.getInstance(); 68 | String? val = prefs.getString(key); 69 | if (val == null) { 70 | return null; 71 | } 72 | var spData = _SpData.fromJson(val); 73 | if (spData.time == null) { 74 | return spData.data; 75 | } 76 | int time = spData.time!; 77 | int currTime = DateTime.now().millisecondsSinceEpoch; 78 | if (time < currTime) { 79 | if (delExprie) { 80 | //是否需要删除 81 | await prefs.remove(key); 82 | } 83 | if (!returnExpire) { 84 | //是否需要返回 85 | return null; 86 | } 87 | } 88 | return spData.data; 89 | } 90 | } 91 | 92 | ///封装数据 93 | class _SpData { 94 | TimeExpireLevel? level; 95 | int? time; 96 | final Map data; 97 | _SpData({ 98 | this.level, 99 | this.time, 100 | required this.data, 101 | }); 102 | 103 | _SpData copyWith({ 104 | TimeExpireLevel? level, 105 | int? time, 106 | Map? data, 107 | }) { 108 | return _SpData( 109 | level: level ?? this.level, 110 | time: time ?? this.time, 111 | data: data ?? this.data, 112 | ); 113 | } 114 | 115 | Map toMap() { 116 | return { 117 | 'level': level?.name, 118 | 'time': time, 119 | 'data': data, 120 | }; 121 | } 122 | 123 | factory _SpData.fromMap(Map map) { 124 | return _SpData( 125 | level: map['level'] != null 126 | ? EnumUtil.enumFromString(TimeExpireLevel.values, map['level']) 127 | : null, 128 | time: map['time'], 129 | data: Map.from(map['data']), 130 | ); 131 | } 132 | 133 | String toJson() => json.encode(toMap()); 134 | 135 | factory _SpData.fromJson(String source) => 136 | _SpData.fromMap(json.decode(source)); 137 | 138 | @override 139 | String toString() => '_SpData(level: $level, time: $time, data: $data)'; 140 | 141 | @override 142 | bool operator ==(Object other) { 143 | if (identical(this, other)) return true; 144 | 145 | return other is _SpData && 146 | other.level == level && 147 | other.time == time && 148 | mapEquals(other.data, data); 149 | } 150 | 151 | @override 152 | int get hashCode => level.hashCode ^ time.hashCode ^ data.hashCode; 153 | } 154 | -------------------------------------------------------------------------------- /lib/pages/sihu/data_service.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2022-03-19 17:00:06 5 | * @LastEditTime: 2022-03-20 00:31:08 6 | */ 7 | 8 | import 'package:html/dom.dart'; 9 | import 'package:html/parser.dart'; 10 | import 'package:sex_91porn/model/img_model.dart'; 11 | import 'package:sex_91porn/model/pair.dart'; 12 | import 'package:sex_91porn/model/video_model.dart'; 13 | import 'package:sex_91porn/pages/sihu/config.dart'; 14 | import 'package:sex_91porn/pages/sihu/model_entity.dart'; 15 | import 'package:sex_91porn/util/http_util.dart'; 16 | 17 | class SihuDataService { 18 | ///转换为视屏模块 19 | static VideoModel conversion(Element element) { 20 | Element? h3 = element.querySelector("h3"); 21 | Element? a = element.querySelector("a"); 22 | Element? img = element.querySelector("img"); 23 | Element? i = element.querySelector("i"); 24 | var imgSrc = img?.attributes["data-original"] ?? "-"; 25 | var m3u8 = getM3u8Url() + 26 | "/newhd/" + 27 | imgSrc 28 | .substring(imgSrc.indexOf("images/") + 7) 29 | .replaceAll("/poster2.jpg", "/hls/index.m3u8"); 30 | 31 | return VideoModel( 32 | id: 0, 33 | cover: imgSrc, 34 | src: m3u8, 35 | title: h3?.text ?? "-", 36 | href: MAIN_URL + "${a?.attributes["href"]}", 37 | duration: i?.text, 38 | dynamicCover: imgSrc.replaceAll("poster2.jpg", "preview.mp4")); 39 | } 40 | 41 | ///返回首页推荐视屏信息 42 | static Future> getIndexVideoList() async { 43 | List list = List.empty(growable: true); 44 | var html = await HttpUtil.getHtml(MAIN_URL); 45 | Document root = parse(html); 46 | var wrapList = root.getElementsByClassName("row col5 clearfix"); 47 | for (var i = 0; i < 3; i++) { 48 | list.addAll(wrapList[i] 49 | .querySelectorAll("dl") 50 | .map((e) => conversion(e)) 51 | .toList()); 52 | } 53 | return list; 54 | } 55 | 56 | ///解析分类页面获取视屏详情列表 57 | static Future>> getCategoryInfo( 58 | SihuCategoryModel model, int pageIndex) async { 59 | String url = 60 | pageIndex == 1 ? model.href : model.href + "index_$pageIndex.html"; 61 | var html = await HttpUtil.getHtml(url); 62 | Document root = parse(html); 63 | List list = root 64 | .getElementsByClassName("row col5 clearfix") 65 | .map((e) => e.querySelectorAll("dl")) 66 | .expand((element) => element 67 | .where((element) => element.id != "listwoBox") 68 | .map((e) => conversion(e)) 69 | .toList()) 70 | .toList(); 71 | String maxIndex = root 72 | .getElementsByClassName("moble_pagination")[0] 73 | .querySelectorAll("a") 74 | .last 75 | .attributes["href"] 76 | ?.replaceAll(new RegExp("\\D"), "") ?? 77 | "${pageIndex + 1}"; 78 | return Pair(key: int.parse(maxIndex), value: list); 79 | } 80 | 81 | ///搜索方法处理代码部分 82 | 83 | ///搜索结果 84 | static Future> search(String keyword) async { 85 | var html = await HttpUtil.getHtml( 86 | "$MAIN_URL/searchs/index.php?keyboard=$keyword&classid="); 87 | Document root = parse(html); 88 | return root 89 | .getElementsByClassName("row-book clearfix")[0] 90 | .querySelectorAll("a") 91 | .map((e) { 92 | return VideoModel(id: 0, title: e.text, href: e.attributes["href"]); 93 | }).toList(); 94 | } 95 | 96 | ///搜索的视屏结果进行数据填充处理 97 | static Future searchVideoProcessConversion( 98 | VideoModel videoModel) async { 99 | Document doc = parse(await HttpUtil.getHtml(videoModel.href!)); 100 | var panel = doc.getElementsByClassName("pannel clearfix")[1]; 101 | 102 | var script = doc 103 | .querySelectorAll("script") 104 | .where((element) => element.text.contains("posterImg")) 105 | .first 106 | .text; 107 | 108 | var imgSrc = script 109 | .substring(script.indexOf("posterImg=") + 11) 110 | .replaceAll("\";", ""); 111 | 112 | var m3u8 = getM3u8Url() + 113 | "/newhd/" + 114 | imgSrc 115 | .substring(imgSrc.indexOf("images/") + 7) 116 | .replaceAll("/poster2.jpg", "/hls/index.m3u8"); 117 | return VideoModel( 118 | id: 0, 119 | title: panel.querySelector("h3")?.text ?? "-", 120 | cover: imgSrc, 121 | href: videoModel.href, 122 | duration: panel.getElementsByTagName("p")[1].text.replaceAll("时间:", ""), 123 | src: m3u8); 124 | } 125 | 126 | ///搜索的图文结果进行数据填充处理 127 | static Future searchImageProcessConversion( 128 | VideoModel videoModel) async { 129 | Document doc = parse(await HttpUtil.getHtml(videoModel.href!)); 130 | var images = doc 131 | .getElementsByClassName("pic")[0] 132 | .querySelectorAll("img") 133 | .map((e) => e.attributes["src"] ?? "") 134 | .toList(); 135 | return ImageModel( 136 | href: videoModel.href!, title: videoModel.title, imageUrls: images); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /lib/pages/video_db.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2021-01-30 21:48:03 5 | * @LastEditTime: 2021-10-03 12:51:43 6 | */ 7 | 8 | import 'package:cached_network_image/cached_network_image.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:sex_91porn/model/video_model.dart'; 11 | import 'package:sex_91porn/sql/video_dao.dart'; 12 | import 'package:sex_91porn/util/application.dart'; 13 | import 'package:sex_91porn/util/widget_util.dart'; 14 | 15 | class VideoDbPage extends StatefulWidget { 16 | VideoDbPage({Key? key}) : super(key: key); 17 | 18 | @override 19 | _VideoDbPageState createState() => _VideoDbPageState(); 20 | } 21 | 22 | class _VideoDbPageState extends State { 23 | VideoDao _vidaoDao = VideoDao(); 24 | 25 | List list = []; 26 | 27 | @override 28 | void initState() { 29 | super.initState(); 30 | Future.delayed(Duration.zero, () async { 31 | list.addAll(await _vidaoDao.queryAll()); 32 | }); 33 | } 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | return Scaffold( 38 | appBar: AppBar( 39 | title: Text( 40 | "本地缓存库", 41 | style: TextStyle(color: Colors.white), 42 | ), 43 | leading: IconButton( 44 | icon: Icon( 45 | Icons.clear_all_rounded, 46 | color: Colors.white, 47 | ), 48 | onPressed: () { 49 | DialogUtil.showConfirmDialog(context, "您确定清理全部观看历史吗?", ok: () { 50 | VideoDao().clearAll(); 51 | setState(() { 52 | list.clear(); 53 | }); 54 | }); 55 | }, 56 | )), 57 | body: Container( 58 | child: RefreshIndicator( 59 | onRefresh: () async { 60 | list.clear(); 61 | var res = await _vidaoDao.queryAll(); 62 | setState(() { 63 | list.addAll(res); 64 | }); 65 | return; 66 | }, 67 | child: GridView.builder( 68 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 69 | crossAxisCount: 2, childAspectRatio: 1), 70 | itemCount: list.length, 71 | itemBuilder: (context, index) { 72 | var item = list[index]; 73 | return GestureDetector( 74 | onTap: () { 75 | //点击的事件 76 | Application.router.navigateTo(context, "/play", 77 | routeSettings: RouteSettings(arguments: item)); 78 | }, 79 | child: Container( 80 | padding: EdgeInsets.fromLTRB(8, 0, 8, 3), 81 | child: Card( 82 | margin: EdgeInsets.only(bottom: 5, top: 5), 83 | child: Container( 84 | child: Column( 85 | children: [ 86 | Expanded( 87 | child: Stack( 88 | children: [ 89 | CachedNetworkImage( 90 | imageUrl: item.cover ?? "", 91 | fit: BoxFit.cover, 92 | height: double.infinity, 93 | width: double.infinity, 94 | errorWidget: (c, u, e) { 95 | return Icon(Icons.broken_image); 96 | }, 97 | placeholder: (context, url) { 98 | return Center( 99 | child: CircularProgressIndicator( 100 | valueColor: AlwaysStoppedAnimation( 101 | Colors.blue)), 102 | ); 103 | }, 104 | ), 105 | Align( 106 | alignment: Alignment.topRight, 107 | child: Container( 108 | color: Colors.redAccent, 109 | padding: EdgeInsets.all(3), 110 | child: Text( 111 | item.duration ?? "-", 112 | style: TextStyle( 113 | color: Colors.white, fontSize: 10), 114 | ), 115 | ), 116 | ) 117 | ], 118 | )), 119 | SizedBox.fromSize( 120 | size: Size.fromHeight(10), 121 | ), 122 | Padding( 123 | padding: 124 | EdgeInsets.only(left: 5, right: 5, bottom: 5), 125 | child: Text(item.title), 126 | ) 127 | ], 128 | ), 129 | ), 130 | ), 131 | ), 132 | ); 133 | }), 134 | )), 135 | ); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /lib/pages/sihu/index.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2022-03-19 15:09:52 5 | * @LastEditTime: 2022-03-19 23:03:42 6 | */ 7 | import 'package:bruno/bruno.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:sex_91porn/model/video_model.dart'; 10 | import 'package:sex_91porn/pages/sihu/common_widget.dart'; 11 | import 'package:sex_91porn/pages/sihu/data_service.dart'; 12 | import 'package:sex_91porn/util/application.dart'; 13 | import 'config.dart'; 14 | 15 | class SihuIndexPage extends StatefulWidget { 16 | SihuIndexPage({Key? key}) : super(key: key); 17 | 18 | @override 19 | State createState() => _SihuIndexPageState(); 20 | } 21 | 22 | class _SihuIndexPageState extends State { 23 | @override 24 | void initState() { 25 | SihuDataService.getIndexVideoList().then((value) { 26 | print("valueSize ${value.length}=====>>> ${value[0]}"); 27 | }); 28 | super.initState(); 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return Scaffold( 34 | appBar: BrnSearchAppbar( 35 | leading: IconButton( 36 | alignment: Alignment.centerLeft, 37 | icon: Icon( 38 | Icons.arrow_back_ios, 39 | color: Colors.white, 40 | ), 41 | onPressed: () => {Application.router.pop(context)}, 42 | ), 43 | hint: "点击输入关键字搜索!", 44 | autoFocus: false, 45 | searchBarInputSubmitCallback: (text) { 46 | print("搜索回调内容:$text"); 47 | if (text.isNotEmpty) { 48 | Application.navigateToIos(context, "/sihuSearch", params: text); 49 | } else { 50 | ToastUtil.show(msg: "请输入要查询的内容"); 51 | } 52 | }, 53 | ), 54 | body: Container( 55 | padding: EdgeInsets.only(left: 5, right: 5), 56 | child: CustomScrollView( 57 | physics: BouncingScrollPhysics(), 58 | slivers: [ 59 | SliverToBoxAdapter( 60 | child: Padding( 61 | padding: EdgeInsets.only(top: 10, bottom: 10, left: 5), 62 | child: BrnDashedLine( 63 | contentWidget: Padding( 64 | child: Text("视屏分类"), 65 | padding: EdgeInsets.only(left: 10), 66 | ), 67 | axis: Axis.vertical, 68 | color: Colors.blueAccent, 69 | dashedSpacing: 0, 70 | dashedThickness: 2, 71 | ), 72 | ), 73 | ), 74 | SliverGrid( 75 | delegate: SliverChildBuilderDelegate((context, index) { 76 | var category = CATEGORY_CONFIG[index]; 77 | return BrnIconButton( 78 | direction: Direction.bottom, 79 | name: category.title, 80 | style: TextStyle(color: Colors.black54, fontSize: 12), 81 | iconWidget: category.icon, 82 | onTap: () { 83 | //点击事件 84 | Application.navigateToIos(context, "/sihuDetail", 85 | params: category); 86 | }, 87 | ); 88 | }, childCount: CATEGORY_CONFIG.length), 89 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 90 | crossAxisCount: 4, childAspectRatio: 1.5), 91 | ), 92 | SliverToBoxAdapter( 93 | child: Padding( 94 | padding: EdgeInsets.only(top: 10, bottom: 10, left: 5), 95 | child: BrnDashedLine( 96 | contentWidget: Padding( 97 | child: Text("最新视屏"), 98 | padding: EdgeInsets.only(left: 10), 99 | ), 100 | axis: Axis.vertical, 101 | color: Colors.blueAccent, 102 | dashedSpacing: 0, 103 | dashedThickness: 2, 104 | ), 105 | ), 106 | ), 107 | FutureBuilder>( 108 | future: SihuDataService.getIndexVideoList(), 109 | builder: (context, as) { 110 | if (as.hasData) { 111 | var list = as.requireData; 112 | return SliverGrid( 113 | delegate: SliverChildBuilderDelegate((context, index) { 114 | return Padding( 115 | padding: EdgeInsets.all(5), 116 | child: PreviewCardPanel(videoModel: list[index]), 117 | ); 118 | }, childCount: list.length), 119 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 120 | crossAxisCount: 2, childAspectRatio: 1)); 121 | } else { 122 | return SliverToBoxAdapter( 123 | child: Center( 124 | child: Column( 125 | children: [ 126 | CircularProgressIndicator( 127 | valueColor: 128 | AlwaysStoppedAnimation(Colors.blueAccent)), 129 | SizedBox.fromSize( 130 | size: Size.fromHeight(10), 131 | ), 132 | Text("加载中...") 133 | ], 134 | ), 135 | )); 136 | } 137 | }) 138 | ], 139 | ), 140 | ), 141 | ); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /lib/pages/madou/common_widget.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2022-03-28 14:12:16 5 | * @LastEditTime: 2022-03-28 20:58:07 6 | */ 7 | 8 | import 'package:bruno/bruno.dart'; 9 | import 'package:cached_network_image/cached_network_image.dart'; 10 | import 'package:flutter/material.dart'; 11 | import 'package:flutter/widgets.dart'; 12 | import 'package:sex_91porn/model/video_model.dart'; 13 | import 'package:sex_91porn/pages/madou/madou_service.dart'; 14 | import 'package:sex_91porn/sql/video_dao.dart'; 15 | import 'package:sex_91porn/util/application.dart'; 16 | import 'package:sex_91porn/util/http_util.dart'; 17 | 18 | class MadouVideoListTial extends StatelessWidget { 19 | final VideoModel videoModel; 20 | 21 | const MadouVideoListTial({Key? key, required this.videoModel}) 22 | : super(key: key); 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return GestureDetector( 27 | onTap: () { 28 | bool isCancel = false; 29 | BrnDialogManager.showSingleButtonDialog(context, 30 | label: "取消", 31 | title: "提示", 32 | messageWidget: Container( 33 | padding: EdgeInsets.only(top: 20), 34 | child: Column( 35 | children: [ 36 | CircularProgressIndicator( 37 | valueColor: AlwaysStoppedAnimation(Colors.blue)), 38 | SizedBox.fromSize( 39 | size: Size.fromHeight(20), 40 | ), 41 | Text("正在获取播放资源...") 42 | ], 43 | ), 44 | ), onTap: () { 45 | HttpUtil.cancel(); 46 | isCancel = true; 47 | Navigator.of(context).pop(); 48 | }, barrierDismissible: false); 49 | 50 | MadouService.analysisVideoPath(videoModel).then((v) { 51 | if (!isCancel) { 52 | Navigator.of(context).pop(); 53 | VideoDao().addVideo(v); 54 | Application.navigateToIos(context, "/play", params: videoModel); 55 | } 56 | }).catchError((e, s) { 57 | print("analysisVideoPath error: $e , stack: s"); 58 | if (!isCancel) { 59 | Navigator.of(context).pop(); 60 | ToastUtil.show(msg: "解析失败"); 61 | } 62 | }); 63 | }, 64 | child: BrnShadowCard( 65 | child: Column( 66 | children: [ 67 | Expanded( 68 | child: Stack( 69 | children: [ 70 | CachedNetworkImage( 71 | imageUrl: videoModel.cover ?? "", 72 | fit: BoxFit.cover, 73 | height: double.infinity, 74 | width: double.infinity, 75 | errorWidget: (c, u, e) { 76 | return Icon(Icons.broken_image); 77 | }, 78 | placeholder: (context, url) { 79 | return Center( 80 | child: CircularProgressIndicator( 81 | valueColor: AlwaysStoppedAnimation(Colors.blue)), 82 | ); 83 | }), 84 | Align( 85 | alignment: Alignment.bottomLeft, 86 | child: BrnTagCustom( 87 | tagText: videoModel.duration ?? "-", 88 | textPadding: EdgeInsets.all(3.3), 89 | tagBorderRadius: BorderRadius.circular(3), 90 | backgroundColor: Colors.redAccent.withOpacity(.8), 91 | textColor: Colors.white, 92 | ), 93 | ), 94 | Align( 95 | alignment: Alignment.topRight, 96 | child: BrnTagCustom( 97 | tagText: videoModel.subTtile ?? "-", 98 | textPadding: EdgeInsets.all(3.3), 99 | tagBorderRadius: BorderRadius.circular(3), 100 | backgroundColor: Colors.black45, 101 | textColor: Colors.white, 102 | ), 103 | ) 104 | ], 105 | )), 106 | SizedBox.fromSize( 107 | size: Size.fromHeight(10), 108 | ), 109 | Padding( 110 | padding: EdgeInsets.only(left: 5, right: 5, bottom: 5), 111 | child: Text( 112 | videoModel.title, 113 | style: TextStyle(fontSize: 12), 114 | ), 115 | ) 116 | ], 117 | ), 118 | )); 119 | } 120 | } 121 | 122 | // typedef MadouGetDataList = Future> Function(int page); 123 | 124 | // class MadouSliveList extends StatefulWidget { 125 | // final MadouGetDataList getDataList; 126 | 127 | // MadouSliveList({Key? key, required this.getDataList}) : super(key: key); 128 | 129 | // @override 130 | // State createState() => _MadouSliveListState(); 131 | // } 132 | 133 | // class _MadouSliveListState extends State { 134 | // bool _isLoad = true; 135 | // int _current = 1; 136 | // List _dataList = List.empty(growable: true); 137 | 138 | // @override 139 | // Widget build(BuildContext context) { 140 | // return SliverList( 141 | // delegate: SliverChildBuilderDelegate((context, index) { 142 | // var video = _dataList[index]; 143 | // return Container( 144 | // padding: EdgeInsets.all(8), 145 | // child: MadouVideoListTial( 146 | // videoModel: video, 147 | // ), 148 | // ); 149 | // }, childCount: _dataList.length)); 150 | // } 151 | // } 152 | -------------------------------------------------------------------------------- /lib/util/widget_util.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2020-08-09 10:55:01 5 | * @LastEditTime: 2022-07-19 16:34:30 6 | */ 7 | import 'package:flutter/cupertino.dart'; 8 | import 'package:flutter/material.dart'; 9 | 10 | class DialogUtil { 11 | static bool _isLoading = false; 12 | 13 | ///显示一个Alert 提示框 14 | static void showAlertMessageDialog(BuildContext context, String message, 15 | {Function? call, String title = "提示"}) { 16 | showDialog( 17 | context: context, 18 | builder: (context) { 19 | return CupertinoAlertDialog( 20 | title: Text(title), 21 | content: Center( 22 | child: Text(message), 23 | ), 24 | actions: [ 25 | CupertinoDialogAction( 26 | child: Text("确定"), 27 | onPressed: () { 28 | Navigator.pop(context); 29 | if (call != null) { 30 | call(); 31 | } 32 | }, 33 | ) 34 | ], 35 | ); 36 | }); 37 | } 38 | 39 | ///弹出一个询问框 40 | static void showConfirmDialog(BuildContext context, String message, 41 | {Function? ok, 42 | Function? cancel, 43 | String okText = "确定", 44 | String cancenText = "取消", 45 | String title = "提示"}) { 46 | showDialog( 47 | context: context, 48 | builder: (context) { 49 | return CupertinoAlertDialog( 50 | title: Text(title), 51 | content: Center( 52 | child: Text(message), 53 | ), 54 | actions: [ 55 | CupertinoDialogAction( 56 | child: Text(okText), 57 | onPressed: () { 58 | Navigator.pop(context); 59 | if (ok != null) { 60 | ok(); 61 | } 62 | }, 63 | ), 64 | CupertinoDialogAction( 65 | child: Text(cancenText), 66 | onPressed: () { 67 | Navigator.pop(context); 68 | if (cancel != null) { 69 | cancel(); 70 | } 71 | }, 72 | ) 73 | ], 74 | ); 75 | }); 76 | } 77 | 78 | ///打开加载对话框 79 | static void showLoading(BuildContext context, String msg, 80 | {bool barrierDismissible = true}) { 81 | if (_isLoading) { 82 | Navigator.pop(context); 83 | } 84 | _isLoading = true; 85 | showDialog( 86 | context: context, 87 | barrierDismissible: barrierDismissible, 88 | builder: (context) { 89 | return UnconstrainedBox( 90 | child: SizedBox( 91 | width: MediaQuery.of(context).size.width / 2 + 50, 92 | child: CupertinoAlertDialog( 93 | content: Column( 94 | children: [ 95 | CupertinoActivityIndicator( 96 | animating: true, 97 | radius: 30, 98 | ), 99 | Padding( 100 | padding: EdgeInsets.fromLTRB(0, 10, 0, 10), 101 | child: Text(msg), 102 | ) 103 | ], 104 | ), 105 | ), 106 | ), 107 | ); 108 | }); 109 | } 110 | 111 | static void showSmallLoading(BuildContext context) { 112 | if (_isLoading) { 113 | Navigator.pop(context); 114 | } 115 | _isLoading = true; 116 | double size = MediaQuery.of(context).size.width / 2; 117 | showDialog( 118 | context: context, 119 | builder: (context) { 120 | return UnconstrainedBox( 121 | child: SizedBox( 122 | width: size, 123 | child: AlertDialog( 124 | content: Center( 125 | child: CircularProgressIndicator( 126 | valueColor: AlwaysStoppedAnimation( 127 | Theme.of(context).primaryColor), 128 | ), 129 | ), 130 | ), 131 | ), 132 | ); 133 | }); 134 | } 135 | 136 | ///关闭加载对话框 137 | static void closeLoading(BuildContext context) { 138 | if (_isLoading) { 139 | if (context != null) { 140 | Navigator.pop(context); 141 | } 142 | _isLoading = false; 143 | } 144 | } 145 | 146 | ///只是设置为flag为关闭 147 | static void closeLoadingFlag() { 148 | _isLoading = false; 149 | } 150 | 151 | //显示一个输入框 152 | static void showInputDialog(BuildContext context, 153 | {Function? call, 154 | String title = "提示", 155 | String? placeholder, 156 | String? initVal}) { 157 | showDialog( 158 | context: context, 159 | builder: (context) { 160 | TextEditingController editingController = TextEditingController(); 161 | if (initVal != null && initVal.isNotEmpty) { 162 | editingController.text = initVal; 163 | } 164 | return CupertinoAlertDialog( 165 | title: Text(title), 166 | content: Container( 167 | child: CupertinoTextField( 168 | placeholder: placeholder, 169 | controller: editingController, 170 | autofocus: true, 171 | keyboardType: TextInputType.text, 172 | maxLines: 1, 173 | maxLength: 255, 174 | textInputAction: TextInputAction.done, 175 | ), 176 | ), 177 | actions: [ 178 | CupertinoDialogAction( 179 | child: Text("提交"), 180 | onPressed: () { 181 | String text = editingController.text; 182 | if (call != null) { 183 | call(text); 184 | } 185 | }, 186 | ), 187 | CupertinoDialogAction( 188 | child: Text("取消"), 189 | onPressed: () { 190 | Navigator.pop(context); 191 | }, 192 | ) 193 | ], 194 | ); 195 | }); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /lib/util/brn_custom_photo_config.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2022-03-19 23:33:05 5 | * @LastEditTime: 2022-03-19 23:41:03 6 | */ 7 | import 'package:bruno/src/components/gallery/config/brn_basic_gallery_config.dart'; 8 | import 'package:bruno/src/components/gallery/config/brn_bottom_card.dart'; 9 | import 'package:bruno/src/components/loading/brn_loading.dart'; 10 | import 'package:bruno/src/constants/brn_strings_constants.dart'; 11 | import 'package:bruno/src/theme/brn_theme_configurator.dart'; 12 | import 'package:bruno/src/theme/configs/brn_gallery_detail_config.dart'; 13 | import 'package:cached_network_image/cached_network_image.dart'; 14 | import 'package:flutter/material.dart'; 15 | import 'package:photo_view/photo_view.dart'; 16 | 17 | class BrnCustomPhotoGroupConfig extends BrnBasicGroupConfig { 18 | final List? urls; 19 | final String? title; 20 | final BrnGalleryDetailConfig? themeData; 21 | 22 | /// 通过 [urls] 列表生成配置 23 | BrnCustomPhotoGroupConfig.url( 24 | {this.title, 25 | required this.urls, 26 | this.themeData, 27 | List? configList}) 28 | : super( 29 | title: title, 30 | configList: urls 31 | ?.map((item) => 32 | BrnPhotoItemConfig(url: item, themeData: themeData)) 33 | .toList()); 34 | 35 | /// 自定义配置列表 36 | BrnCustomPhotoGroupConfig( 37 | {this.urls, 38 | this.title, 39 | List? configList, 40 | this.themeData}) 41 | : super(title: title, configList: configList); 42 | } 43 | 44 | /// 图片类的配置 45 | class BrnPhotoItemConfig extends BrnBasicItemConfig { 46 | /// 图片url 47 | final String url; 48 | 49 | /// 图片的展示模式 50 | final BoxFit fit; 51 | 52 | /// 占位图 53 | final String placeHolder; 54 | 55 | /// 图片名称 用于详情页展示 56 | final String? name; 57 | 58 | /// 图片描述公 用于详情页展示 59 | final String? des; 60 | 61 | /// 详情页图片点击回调 62 | final VoidCallback? onTap; 63 | 64 | /// 详情页双击回调 65 | final VoidCallback? onDoubleTap; 66 | 67 | /// 详情页长按回调 68 | final VoidCallback? onLongPress; 69 | 70 | /// 详情页是否展示底部卡片,需要提供name和des信息 71 | final bool showBottom; 72 | 73 | /// [PhotoBottomCardState] 底部展示卡片的模式 74 | final PhotoBottomCardState bottomCardModel; 75 | 76 | /// 指定展开不可收起下 content的高度 77 | final double bottomContentHeight; 78 | 79 | BrnGalleryDetailConfig? themeData; 80 | 81 | BrnPhotoItemConfig({ 82 | required this.url, 83 | this.fit = BoxFit.cover, 84 | this.placeHolder = 85 | "packages/${BrnStrings.flutterPackageName}/assets/icons/grey_place_holder.png", 86 | this.onTap, 87 | this.onDoubleTap, 88 | this.onLongPress, 89 | this.name, 90 | this.des, 91 | this.showBottom = false, 92 | this.bottomCardModel = PhotoBottomCardState.cantFold, 93 | this.bottomContentHeight = 150, 94 | this.themeData, 95 | }) { 96 | this.themeData ??= BrnGalleryDetailConfig(); 97 | this.themeData = BrnThemeConfigurator.instance 98 | .getConfig(configId: this.themeData!.configId) 99 | .galleryDetailConfig 100 | .merge(this.themeData); 101 | } 102 | 103 | @override 104 | Widget buildSummaryWidget(BuildContext context, 105 | List allConfig, int groupId, int index) { 106 | return Container( 107 | decoration: BoxDecoration( 108 | borderRadius: BorderRadius.all(Radius.circular(2.0)), 109 | border: Border.all(color: Color(0xFFF0F0F0), width: 0.5)), 110 | child: ClipRRect( 111 | borderRadius: BorderRadius.circular(2), 112 | // child: FadeInImage.assetNetwork( 113 | // image: url, 114 | // fit: fit, 115 | // placeholder: placeHolder, 116 | // ), 117 | child: CachedNetworkImage( 118 | imageUrl: url, 119 | fit: fit, 120 | errorWidget: (c, u, e) { 121 | return Icon(Icons.broken_image); 122 | }, 123 | placeholder: (context, url) { 124 | return Center( 125 | child: CircularProgressIndicator( 126 | valueColor: AlwaysStoppedAnimation(Colors.blue)), 127 | ); 128 | }, 129 | ), 130 | ), 131 | ); 132 | } 133 | 134 | @override 135 | Widget buildDetailWidget(BuildContext context, 136 | List allConfig, int groupId, int index) { 137 | return Container( 138 | color: Colors.white, 139 | child: Stack( 140 | children: [ 141 | Positioned( 142 | left: 0, 143 | top: 0, 144 | right: 0, 145 | bottom: 0, 146 | child: GestureDetector( 147 | onTap: () { 148 | onTap?.call(); 149 | }, 150 | onDoubleTap: () { 151 | onDoubleTap?.call(); 152 | }, 153 | onLongPress: () { 154 | onLongPress?.call(); 155 | }, 156 | child: Container( 157 | color: Colors.white, 158 | child: PhotoView( 159 | backgroundDecoration: 160 | BoxDecoration(color: themeData!.pageBackgroundColor), 161 | loadingBuilder: (context, event) { 162 | return Container( 163 | child: BrnLoadingDialog(), 164 | color: themeData!.pageBackgroundColor, 165 | ); 166 | }, 167 | imageProvider: CachedNetworkImageProvider(url), 168 | ), 169 | ), 170 | ), 171 | ), 172 | showBottom 173 | ? Positioned( 174 | left: 0, 175 | right: 0, 176 | bottom: 0, 177 | child: SafeArea( 178 | top: false, 179 | child: BrnPhotoBottomCard( 180 | name: name, 181 | des: des, 182 | model: bottomCardModel, 183 | contentHeight: bottomContentHeight, 184 | themeData: themeData, 185 | ), 186 | ), 187 | ) 188 | : Row() 189 | ], 190 | ), 191 | ); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /lib/pages/play_video.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2021-01-31 23:17:08 5 | * @LastEditTime: 2022-09-17 22:50:25 6 | */ 7 | import 'package:bruno/bruno.dart'; 8 | import 'package:cached_network_image/cached_network_image.dart'; 9 | import 'package:chewie/chewie.dart'; 10 | import 'package:flutter/material.dart'; 11 | import 'package:flutter/services.dart'; 12 | import 'package:sex_91porn/model/video_model.dart'; 13 | import 'package:sex_91porn/util/application.dart'; 14 | import 'package:video_player/video_player.dart'; 15 | import 'package:wakelock/wakelock.dart'; 16 | 17 | class PlayVideoPage extends StatefulWidget { 18 | final VideoModel videoModel; 19 | 20 | PlayVideoPage({Key? key, required this.videoModel}) : super(key: key); 21 | 22 | @override 23 | _PlayVideoPageState createState() => _PlayVideoPageState(); 24 | } 25 | 26 | class _PlayVideoPageState extends State { 27 | VideoPlayerController? videoPlayerController; 28 | ChewieController? chewieController; 29 | 30 | bool onLoad = true; 31 | 32 | bool isLoadSrcCode = false; 33 | 34 | @override 35 | void initState() { 36 | super.initState(); 37 | //保持屏幕常亮 38 | Wakelock.enable(); 39 | print("拿到视屏数据: ${widget.videoModel}"); 40 | Future.delayed(Duration.zero, () { 41 | if (widget.videoModel.src != null) { 42 | setState(() { 43 | initVideoWiget(widget.videoModel); 44 | onLoad = false; 45 | }); 46 | } else { 47 | BrnDialogManager.showSingleButtonDialog(context, 48 | label: "确定", 49 | title: '错误', 50 | warning: '无法播放', 51 | message: "错误没有找到对应的播放路径!", onTap: () { 52 | Application.router.pop(context); 53 | }); 54 | } 55 | }); 56 | } 57 | 58 | @override 59 | void dispose() { 60 | videoPlayerController?.dispose(); 61 | chewieController?.dispose(); 62 | Wakelock.disable(); 63 | SystemChrome.setPreferredOrientations([ 64 | DeviceOrientation.portraitUp, 65 | ]); 66 | super.dispose(); 67 | } 68 | 69 | @override 70 | Widget build(BuildContext context) { 71 | return Scaffold( 72 | appBar: BrnAppBar( 73 | brightness: Brightness.dark, 74 | title: "视屏播放", 75 | automaticallyImplyLeading: true), 76 | body: SafeArea( 77 | child: Container( 78 | child: CustomScrollView( 79 | slivers: [ 80 | SliverToBoxAdapter( 81 | child: Container( 82 | height: 250, 83 | decoration: BoxDecoration( 84 | image: DecorationImage( 85 | image: CachedNetworkImageProvider( 86 | widget.videoModel.cover ?? ""), 87 | fit: BoxFit.cover)), 88 | child: Column( 89 | children: [ 90 | Expanded( 91 | flex: 1, 92 | child: onLoad 93 | ? Container( 94 | child: Center( 95 | child: // 模糊进度条(会执行一个旋转动画) 96 | Column( 97 | mainAxisAlignment: 98 | MainAxisAlignment.center, 99 | crossAxisAlignment: 100 | CrossAxisAlignment.center, 101 | children: [ 102 | CircularProgressIndicator( 103 | backgroundColor: Colors.grey[200], 104 | valueColor: AlwaysStoppedAnimation( 105 | Colors.blue), 106 | ), 107 | SizedBox.fromSize( 108 | size: Size.fromHeight(15), 109 | ), 110 | BrnTagCustom( 111 | tagText: "解码视屏资源中请稍后...", 112 | textColor: Colors.white, 113 | fontSize: 18, 114 | tagBorderRadius: 115 | BorderRadius.circular(7), 116 | textPadding: EdgeInsets.all(5), 117 | backgroundColor: Colors.black54, 118 | ), 119 | ], 120 | ), 121 | ), 122 | ) 123 | : chewieController != null 124 | ? Chewie(controller: chewieController!) 125 | : Center( 126 | child: BrnTagCustom( 127 | tagText: "视屏解码完成,正在初始化播放...", 128 | textColor: Colors.white, 129 | fontSize: 18, 130 | tagBorderRadius: 131 | BorderRadius.circular(7), 132 | textPadding: EdgeInsets.all(5), 133 | backgroundColor: Colors.black54, 134 | ), 135 | )) 136 | ], 137 | ), 138 | ), 139 | ), 140 | SliverToBoxAdapter( 141 | child: Padding( 142 | padding: const EdgeInsets.only(top: 10, left: 10), 143 | child: BrnDashedLine( 144 | contentWidget: Padding( 145 | child: Text(widget.videoModel.title, 146 | maxLines: 4, 147 | overflow: TextOverflow.ellipsis, 148 | style: TextStyle( 149 | color: Colors.black, 150 | fontWeight: FontWeight.w500, 151 | fontSize: 14)), 152 | padding: EdgeInsets.only(left: 10), 153 | ), 154 | axis: Axis.vertical, 155 | color: Colors.blueAccent, 156 | dashedSpacing: 0, 157 | dashedThickness: 2, 158 | ), 159 | )), 160 | SliverToBoxAdapter( 161 | child: Padding( 162 | padding: const EdgeInsets.only(top: 10, left: 10), 163 | child: Text( 164 | "时长/标签: ${widget.videoModel.duration}", 165 | style: TextStyle(color: Colors.redAccent, fontSize: 12), 166 | ), 167 | )), 168 | ], 169 | ), 170 | ), 171 | )); 172 | } 173 | 174 | //初始化视屏播放器 175 | initVideoWiget(VideoModel model) async { 176 | if (model.src == null || model.src!.isEmpty) { 177 | ToastUtil.show(msg: "获取播放地址失败无法播放"); 178 | return; 179 | } 180 | videoPlayerController = VideoPlayerController.network(model.src!); 181 | await videoPlayerController!.initialize(); 182 | setState(() { 183 | chewieController = ChewieController( 184 | videoPlayerController: videoPlayerController!, 185 | // aspectRatio: 4 / 3, //宽高比 186 | autoPlay: true, //自动播放 187 | looping: false, //循环播放 188 | isLive: false, 189 | showOptions: false, 190 | placeholder: Container( 191 | color: Colors.black, 192 | )); 193 | }); 194 | ToastUtil.show(msg: "已开始自动播放"); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /lib/pages/madou/index.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2022-03-28 14:05:44 5 | * @LastEditTime: 2022-03-28 20:53:59 6 | */ 7 | import 'package:bruno/bruno.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 10 | import 'package:sex_91porn/model/video_model.dart'; 11 | import 'package:sex_91porn/pages/madou/madou_config.dart'; 12 | import 'package:sex_91porn/pages/madou/madou_service.dart'; 13 | import 'package:sex_91porn/util/application.dart'; 14 | 15 | import 'common_widget.dart'; 16 | 17 | class MadouIndexPage extends StatefulWidget { 18 | MadouIndexPage({Key? key}) : super(key: key); 19 | 20 | @override 21 | State createState() => _MadouIndexPageState(); 22 | } 23 | 24 | class _MadouIndexPageState extends State { 25 | ///分类列表 26 | List _categoryList = List.empty(growable: true); 27 | List _categoryListAll = List.empty(growable: true); 28 | 29 | ///视屏列表 30 | List _videos = List.empty(growable: true); 31 | 32 | int _current = 0; 33 | bool _isEnd = false; 34 | 35 | @override 36 | void initState() { 37 | Future.delayed(Duration.zero).then((value) { 38 | _initCategory(); 39 | }); 40 | super.initState(); 41 | } 42 | 43 | ///加载分类 44 | void _initCategory() { 45 | BrnLoadingDialog.show(context); 46 | MadouService.getCategoryList().then((value) { 47 | _categoryListAll = value; 48 | _categoryList.addAll(value.sublist(0, 7)); 49 | _categoryList.add(MadouCategory( 50 | title: "更多企划", 51 | href: "", 52 | icon: Icon( 53 | Icons.more_horiz, 54 | color: Theme.of(context).primaryColor, 55 | ))); 56 | setState(() {}); 57 | BrnLoadingDialog.dismiss(context); 58 | }).catchError((error, stackTrace) { 59 | BrnLoadingDialog.dismiss(context); 60 | print("error:$error, stackTrace:$stackTrace"); 61 | ToastUtil.show(msg: "加载失败"); 62 | }); 63 | } 64 | 65 | Future _onLoad() async { 66 | if (!_isEnd) { 67 | _current++; 68 | var list = await MadouService.hotVideoList(_current); 69 | if (list.isEmpty) { 70 | _isEnd = true; 71 | return; 72 | } 73 | list.removeWhere((le) => 74 | _videos.where((element) => element.href == le.href).isNotEmpty); 75 | if (list.isEmpty) { 76 | _isEnd = true; 77 | return; 78 | } 79 | setState(() { 80 | _videos.addAll(list); 81 | }); 82 | } 83 | } 84 | 85 | @override 86 | Widget build(BuildContext context) { 87 | return Scaffold( 88 | appBar: BrnSearchAppbar( 89 | leading: IconButton( 90 | alignment: Alignment.centerLeft, 91 | icon: Icon( 92 | Icons.arrow_back_ios, 93 | color: Colors.white, 94 | ), 95 | onPressed: () => {Application.router.pop(context)}, 96 | ), 97 | hint: "点击输入关键字搜索!", 98 | autoFocus: false, 99 | searchBarInputSubmitCallback: (text) { 100 | print("搜索回调内容:$text"); 101 | if (text.isNotEmpty) { 102 | Application.navigateToIos(context, "/madouVideoList", 103 | params: text); 104 | } else { 105 | ToastUtil.show(msg: "请输入要查询的内容"); 106 | } 107 | }, 108 | ), 109 | body: Container( 110 | padding: EdgeInsets.all(8), 111 | child: EasyRefresh( 112 | enableControlFinishLoad: _isEnd, 113 | onLoad: _onLoad, 114 | child: CustomScrollView( 115 | slivers: [ 116 | SliverToBoxAdapter( 117 | child: Padding( 118 | padding: EdgeInsets.only(top: 10, bottom: 10, left: 5), 119 | child: BrnDashedLine( 120 | contentWidget: Padding( 121 | child: Text("精选企划方分类"), 122 | padding: EdgeInsets.only(left: 10), 123 | ), 124 | axis: Axis.vertical, 125 | color: Colors.blueAccent, 126 | dashedSpacing: 0, 127 | dashedThickness: 2, 128 | ), 129 | ), 130 | ), 131 | SliverGrid( 132 | delegate: SliverChildBuilderDelegate((context, index) { 133 | var category = _categoryList[index]; 134 | return BrnIconButton( 135 | direction: Direction.bottom, 136 | name: category.title, 137 | style: TextStyle(color: Colors.black54, fontSize: 12), 138 | iconWidget: category.icon ?? 139 | Icon( 140 | Icons.category_rounded, 141 | color: Theme.of(context).primaryColor, 142 | ), 143 | onTap: () { 144 | //点击事件 145 | if (category.href.isEmpty) { 146 | BrnMultiSelectTagsPicker( 147 | pickerTitleConfig: BrnPickerTitleConfig( 148 | titleContent: '企划方筛选', 149 | confirm: SizedBox(), 150 | cancel: SizedBox()), 151 | tagPickerConfig: BrnTagsPickerConfig( 152 | tagItemSource: _categoryListAll 153 | .map((e) => BrnTagItemBean( 154 | name: e.title, 155 | code: e.href, 156 | ext: e.toMap())) 157 | .toList()), 158 | context: context, 159 | onTagValueGetter: (choice) { 160 | return choice.name; 161 | }, 162 | onConfirm: (value) {}, 163 | onItemClick: 164 | (BrnTagItemBean onTapTag, bool isSelect) { 165 | Navigator.of(context).pop(); 166 | Application.navigateToIos( 167 | context, "/madouVideoList", 168 | params: 169 | MadouCategory.fromMap(onTapTag.ext!)); 170 | }, 171 | crossAxisCount: 4, 172 | layoutStyle: BrnMultiSelectTagsLayoutStyle.auto, 173 | maxSelectItemCount: 1, 174 | ).show(); 175 | } else { 176 | Application.navigateToIos( 177 | context, "/madouVideoList", 178 | params: category); 179 | } 180 | }, 181 | ); 182 | }, childCount: _categoryList.length), 183 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 184 | crossAxisCount: 4, childAspectRatio: 1)), 185 | SliverToBoxAdapter( 186 | child: Padding( 187 | padding: EdgeInsets.only(top: 10, bottom: 10, left: 5), 188 | child: BrnDashedLine( 189 | contentWidget: Padding( 190 | child: Text("热门推荐"), 191 | padding: EdgeInsets.only(left: 10), 192 | ), 193 | axis: Axis.vertical, 194 | color: Colors.blueAccent, 195 | dashedSpacing: 0, 196 | dashedThickness: 2, 197 | ), 198 | ), 199 | ), 200 | SliverGrid( 201 | delegate: SliverChildBuilderDelegate((context, index) { 202 | return Padding( 203 | padding: EdgeInsets.all(5), 204 | child: MadouVideoListTial(videoModel: _videos[index]), 205 | ); 206 | }, childCount: _videos.length), 207 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 208 | crossAxisCount: 2, childAspectRatio: 1)) 209 | ], 210 | ), 211 | ), 212 | )); 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /lib/pages/video_91_list.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 3 | * @Author: chenzedeng 4 | * @Date: 2021-01-31 21:38:18 5 | * @LastEditTime: 2022-09-17 22:43:38 6 | */ 7 | import 'package:bruno/bruno.dart'; 8 | import 'package:cached_network_image/cached_network_image.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:flutter_easyrefresh/easy_refresh.dart'; 11 | import 'package:sex_91porn/model/category_model.dart'; 12 | import 'package:sex_91porn/model/video_model.dart'; 13 | import 'package:sex_91porn/service/list_parse.dart'; 14 | import 'package:sex_91porn/util/application.dart'; 15 | import 'package:sex_91porn/util/http_util.dart'; 16 | import 'package:sex_91porn/util/widget_util.dart'; 17 | 18 | ///91在线爬取页码PageWidget 19 | class Video91ListPage extends StatefulWidget { 20 | final CategoryModel model; 21 | 22 | Video91ListPage({Key? key, required this.model}) : super(key: key); 23 | 24 | @override 25 | _Video91ListPageState createState() => _Video91ListPageState(); 26 | } 27 | 28 | class _Video91ListPageState extends State 29 | with AutomaticKeepAliveClientMixin { 30 | EasyRefreshController _controller = EasyRefreshController(); 31 | 32 | List _list = []; 33 | 34 | int _currentPage = 0; 35 | 36 | String tip = "首次请点击下面按钮进行加载"; 37 | 38 | @override 39 | void initState() { 40 | super.initState(); 41 | print("初始化页面: ${widget.model.name}"); 42 | //爬取数据 43 | // _getNextData(); 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | return Container( 49 | child: EasyRefresh( 50 | controller: _controller, 51 | onRefresh: () async { 52 | _list.clear(); 53 | _currentPage = 0; 54 | await _getNextData(); 55 | return; 56 | }, 57 | onLoad: () async { 58 | await _getNextData(); 59 | return; 60 | }, 61 | child: _list.isEmpty 62 | ? Container( 63 | padding: EdgeInsets.only( 64 | top: MediaQuery.of(context).size.height / 3.5), 65 | child: Center( 66 | child: Column( 67 | mainAxisAlignment: MainAxisAlignment.center, 68 | children: [ 69 | CircularProgressIndicator( 70 | valueColor: AlwaysStoppedAnimation(Colors.blueAccent)), 71 | SizedBox.fromSize( 72 | size: Size.fromHeight(10), 73 | ), 74 | Text(tip), 75 | SizedBox.fromSize( 76 | size: Size.fromHeight(20), 77 | ), 78 | IconButton( 79 | icon: Icon( 80 | Icons.refresh_outlined, 81 | color: Colors.green, 82 | size: 35, 83 | ), 84 | onPressed: () { 85 | //重新请求 86 | _currentPage = 0; 87 | _list.clear(); 88 | _getNextData(); 89 | }) 90 | ], 91 | ), 92 | ), 93 | ) 94 | : GridView.builder( 95 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 96 | crossAxisCount: 2, childAspectRatio: .65), 97 | itemCount: _list.length, 98 | itemBuilder: (context, index) { 99 | var item = _list[index]; 100 | return GestureDetector( 101 | onTap: () { 102 | if (item.href == null || item.href!.isEmpty) { 103 | ToastUtil.show(msg: "该视频获取播放地址失败,无法播放"); 104 | return; 105 | } 106 | //开始解析M3u8资源 107 | BrnDialogManager.showSingleButtonDialog(context, 108 | label: "取消", 109 | title: "提示", 110 | messageWidget: Container( 111 | padding: EdgeInsets.only(top: 20), 112 | child: Column( 113 | children: [ 114 | CircularProgressIndicator( 115 | valueColor: 116 | AlwaysStoppedAnimation(Colors.blue)), 117 | SizedBox.fromSize( 118 | size: Size.fromHeight(20), 119 | ), 120 | Text("正在获取播放资源...") 121 | ], 122 | ), 123 | ), onTap: () { 124 | HttpUtil.cancel(); 125 | }, barrierDismissible: false); 126 | HttpUtil.getHtml(item.href!).then((value) { 127 | var el = Uri.decodeFull(VideoPageParse.getSrcCode(value)); 128 | el = el 129 | .substring(el.indexOf("'") + 1, el.indexOf("type")) 130 | .replaceAll("'", "") 131 | .trim(); 132 | item.src = el; 133 | Navigator.of(context).pop(); 134 | Application.navigateToIos(context, "/play", params: item); 135 | }).catchError((e) { 136 | ToastUtil.show(msg: "获取资源失败"); 137 | Navigator.of(context).pop(); 138 | }); 139 | }, 140 | child: Container( 141 | padding: EdgeInsets.fromLTRB(8, 0, 8, 3), 142 | child: Card( 143 | margin: EdgeInsets.only(bottom: 5, top: 5), 144 | child: Column( 145 | children: [ 146 | Expanded( 147 | child: Stack( 148 | children: [ 149 | CachedNetworkImage( 150 | imageUrl: item.cover ?? "", 151 | fit: BoxFit.fill, 152 | height: double.infinity, 153 | width: double.infinity, 154 | errorWidget: (c, u, e) { 155 | return Icon(Icons.broken_image); 156 | }, 157 | placeholder: (context, url) { 158 | return Center( 159 | child: CircularProgressIndicator( 160 | valueColor: AlwaysStoppedAnimation( 161 | Colors.blue)), 162 | ); 163 | }, 164 | ), 165 | Align( 166 | alignment: Alignment.topRight, 167 | child: Container( 168 | color: Colors.redAccent, 169 | padding: EdgeInsets.all(3), 170 | child: Text( 171 | item.duration ?? "-", 172 | style: TextStyle( 173 | color: Colors.white, fontSize: 10), 174 | ), 175 | ), 176 | ) 177 | ], 178 | )), 179 | Padding( 180 | padding: EdgeInsets.only( 181 | left: 5, right: 5, bottom: 5, top: 10), 182 | child: Text( 183 | item.title, 184 | style: TextStyle(fontSize: 11), 185 | ), 186 | ) 187 | ], 188 | ), 189 | ), 190 | ), 191 | ); 192 | }), 193 | )); 194 | } 195 | 196 | _getNextData() async { 197 | if (widget.model.endPage != null) { 198 | if (widget.model.endPage! <= _currentPage + 1) { 199 | ToastUtil.show(msg: "没有更多的内容了"); 200 | return; 201 | } 202 | } 203 | setState(() { 204 | tip = "加载中..."; 205 | }); 206 | this._currentPage++; 207 | String url = "${widget.model.url}$_currentPage"; 208 | print("爬取第$_currentPage页 链接:$url"); 209 | try { 210 | String html = await HttpUtil.getHtml(url); 211 | if (widget.model.endPage == null) { 212 | widget.model.endPage = VideoPageParse.getPageNum(html); 213 | } 214 | var res = VideoPageParse.getVideoList(html); 215 | setState(() { 216 | _list.addAll(res); 217 | }); 218 | print("爬取成功得到${res.length}条数据"); 219 | //添加数据库里 220 | //todo 221 | return true; 222 | } catch (e) { 223 | print("请求失败:$e"); 224 | ToastUtil.show(msg: "请求失败"); 225 | DialogUtil.showConfirmDialog(context, "请求失败是否重新请求?", okText: "重新尝试", 226 | ok: () { 227 | this._currentPage--; 228 | _getNextData(); 229 | }); 230 | setState(() { 231 | tip = "请求失败"; 232 | }); 233 | return Future.error("请求失败"); 234 | } 235 | } 236 | 237 | @override 238 | bool get wantKeepAlive => true; 239 | } 240 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "2.9.0" 11 | badges: 12 | dependency: transitive 13 | description: 14 | name: badges 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "2.0.2" 18 | boolean_selector: 19 | dependency: transitive 20 | description: 21 | name: boolean_selector 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "2.1.0" 25 | bruno: 26 | dependency: "direct main" 27 | description: 28 | name: bruno 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "2.1.0-nullsafety.1" 32 | cached_network_image: 33 | dependency: "direct main" 34 | description: 35 | name: cached_network_image 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "3.2.0" 39 | cached_network_image_platform_interface: 40 | dependency: transitive 41 | description: 42 | name: cached_network_image_platform_interface 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.0.0" 46 | cached_network_image_web: 47 | dependency: transitive 48 | description: 49 | name: cached_network_image_web 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "1.0.1" 53 | characters: 54 | dependency: transitive 55 | description: 56 | name: characters 57 | url: "https://pub.flutter-io.cn" 58 | source: hosted 59 | version: "1.2.1" 60 | charcode: 61 | dependency: transitive 62 | description: 63 | name: charcode 64 | url: "https://pub.flutter-io.cn" 65 | source: hosted 66 | version: "1.3.1" 67 | chewie: 68 | dependency: "direct main" 69 | description: 70 | name: chewie 71 | url: "https://pub.flutter-io.cn" 72 | source: hosted 73 | version: "1.3.1" 74 | clock: 75 | dependency: transitive 76 | description: 77 | name: clock 78 | url: "https://pub.flutter-io.cn" 79 | source: hosted 80 | version: "1.1.1" 81 | collection: 82 | dependency: transitive 83 | description: 84 | name: collection 85 | url: "https://pub.flutter-io.cn" 86 | source: hosted 87 | version: "1.16.0" 88 | cookie_jar: 89 | dependency: "direct main" 90 | description: 91 | name: cookie_jar 92 | url: "https://pub.flutter-io.cn" 93 | source: hosted 94 | version: "3.0.1" 95 | crypto: 96 | dependency: transitive 97 | description: 98 | name: crypto 99 | url: "https://pub.flutter-io.cn" 100 | source: hosted 101 | version: "3.0.1" 102 | csslib: 103 | dependency: transitive 104 | description: 105 | name: csslib 106 | url: "https://pub.flutter-io.cn" 107 | source: hosted 108 | version: "0.17.1" 109 | cupertino_icons: 110 | dependency: "direct main" 111 | description: 112 | name: cupertino_icons 113 | url: "https://pub.flutter-io.cn" 114 | source: hosted 115 | version: "1.0.4" 116 | dio: 117 | dependency: "direct main" 118 | description: 119 | name: dio 120 | url: "https://pub.flutter-io.cn" 121 | source: hosted 122 | version: "4.0.4" 123 | dio_cookie_manager: 124 | dependency: "direct main" 125 | description: 126 | name: dio_cookie_manager 127 | url: "https://pub.flutter-io.cn" 128 | source: hosted 129 | version: "2.0.0" 130 | fake_async: 131 | dependency: transitive 132 | description: 133 | name: fake_async 134 | url: "https://pub.flutter-io.cn" 135 | source: hosted 136 | version: "1.3.1" 137 | ffi: 138 | dependency: transitive 139 | description: 140 | name: ffi 141 | url: "https://pub.flutter-io.cn" 142 | source: hosted 143 | version: "1.1.2" 144 | file: 145 | dependency: transitive 146 | description: 147 | name: file 148 | url: "https://pub.flutter-io.cn" 149 | source: hosted 150 | version: "6.1.2" 151 | fluro: 152 | dependency: "direct main" 153 | description: 154 | name: fluro 155 | url: "https://pub.flutter-io.cn" 156 | source: hosted 157 | version: "2.0.3" 158 | flutter: 159 | dependency: "direct main" 160 | description: flutter 161 | source: sdk 162 | version: "0.0.0" 163 | flutter_blurhash: 164 | dependency: transitive 165 | description: 166 | name: flutter_blurhash 167 | url: "https://pub.flutter-io.cn" 168 | source: hosted 169 | version: "0.6.4" 170 | flutter_cache_manager: 171 | dependency: transitive 172 | description: 173 | name: flutter_cache_manager 174 | url: "https://pub.flutter-io.cn" 175 | source: hosted 176 | version: "3.3.0" 177 | flutter_easyrefresh: 178 | dependency: "direct main" 179 | description: 180 | name: flutter_easyrefresh 181 | url: "https://pub.flutter-io.cn" 182 | source: hosted 183 | version: "2.2.1" 184 | flutter_test: 185 | dependency: "direct dev" 186 | description: flutter 187 | source: sdk 188 | version: "0.0.0" 189 | flutter_web_plugins: 190 | dependency: transitive 191 | description: flutter 192 | source: sdk 193 | version: "0.0.0" 194 | fluttertoast: 195 | dependency: "direct main" 196 | description: 197 | name: fluttertoast 198 | url: "https://pub.flutter-io.cn" 199 | source: hosted 200 | version: "8.0.9" 201 | html: 202 | dependency: "direct main" 203 | description: 204 | name: html 205 | url: "https://pub.flutter-io.cn" 206 | source: hosted 207 | version: "0.15.0" 208 | http: 209 | dependency: transitive 210 | description: 211 | name: http 212 | url: "https://pub.flutter-io.cn" 213 | source: hosted 214 | version: "0.13.4" 215 | http_parser: 216 | dependency: transitive 217 | description: 218 | name: http_parser 219 | url: "https://pub.flutter-io.cn" 220 | source: hosted 221 | version: "4.0.0" 222 | intl: 223 | dependency: transitive 224 | description: 225 | name: intl 226 | url: "https://pub.flutter-io.cn" 227 | source: hosted 228 | version: "0.17.0" 229 | js: 230 | dependency: transitive 231 | description: 232 | name: js 233 | url: "https://pub.flutter-io.cn" 234 | source: hosted 235 | version: "0.6.4" 236 | lpinyin: 237 | dependency: transitive 238 | description: 239 | name: lpinyin 240 | url: "https://pub.flutter-io.cn" 241 | source: hosted 242 | version: "2.0.3" 243 | matcher: 244 | dependency: transitive 245 | description: 246 | name: matcher 247 | url: "https://pub.flutter-io.cn" 248 | source: hosted 249 | version: "0.12.12" 250 | material_color_utilities: 251 | dependency: transitive 252 | description: 253 | name: material_color_utilities 254 | url: "https://pub.flutter-io.cn" 255 | source: hosted 256 | version: "0.1.5" 257 | meta: 258 | dependency: transitive 259 | description: 260 | name: meta 261 | url: "https://pub.flutter-io.cn" 262 | source: hosted 263 | version: "1.8.0" 264 | mime: 265 | dependency: transitive 266 | description: 267 | name: mime 268 | url: "https://pub.flutter-io.cn" 269 | source: hosted 270 | version: "1.0.1" 271 | nested: 272 | dependency: transitive 273 | description: 274 | name: nested 275 | url: "https://pub.flutter-io.cn" 276 | source: hosted 277 | version: "1.0.0" 278 | octo_image: 279 | dependency: transitive 280 | description: 281 | name: octo_image 282 | url: "https://pub.flutter-io.cn" 283 | source: hosted 284 | version: "1.0.1" 285 | path: 286 | dependency: transitive 287 | description: 288 | name: path 289 | url: "https://pub.flutter-io.cn" 290 | source: hosted 291 | version: "1.8.2" 292 | path_drawing: 293 | dependency: transitive 294 | description: 295 | name: path_drawing 296 | url: "https://pub.flutter-io.cn" 297 | source: hosted 298 | version: "1.0.0" 299 | path_parsing: 300 | dependency: transitive 301 | description: 302 | name: path_parsing 303 | url: "https://pub.flutter-io.cn" 304 | source: hosted 305 | version: "1.0.0" 306 | path_provider: 307 | dependency: "direct main" 308 | description: 309 | name: path_provider 310 | url: "https://pub.flutter-io.cn" 311 | source: hosted 312 | version: "2.0.9" 313 | path_provider_android: 314 | dependency: transitive 315 | description: 316 | name: path_provider_android 317 | url: "https://pub.flutter-io.cn" 318 | source: hosted 319 | version: "2.0.12" 320 | path_provider_ios: 321 | dependency: transitive 322 | description: 323 | name: path_provider_ios 324 | url: "https://pub.flutter-io.cn" 325 | source: hosted 326 | version: "2.0.8" 327 | path_provider_linux: 328 | dependency: transitive 329 | description: 330 | name: path_provider_linux 331 | url: "https://pub.flutter-io.cn" 332 | source: hosted 333 | version: "2.1.5" 334 | path_provider_macos: 335 | dependency: transitive 336 | description: 337 | name: path_provider_macos 338 | url: "https://pub.flutter-io.cn" 339 | source: hosted 340 | version: "2.0.5" 341 | path_provider_platform_interface: 342 | dependency: transitive 343 | description: 344 | name: path_provider_platform_interface 345 | url: "https://pub.flutter-io.cn" 346 | source: hosted 347 | version: "2.0.3" 348 | path_provider_windows: 349 | dependency: transitive 350 | description: 351 | name: path_provider_windows 352 | url: "https://pub.flutter-io.cn" 353 | source: hosted 354 | version: "2.0.5" 355 | pedantic: 356 | dependency: transitive 357 | description: 358 | name: pedantic 359 | url: "https://pub.flutter-io.cn" 360 | source: hosted 361 | version: "1.11.1" 362 | permission_handler: 363 | dependency: "direct main" 364 | description: 365 | name: permission_handler 366 | url: "https://pub.flutter-io.cn" 367 | source: hosted 368 | version: "8.3.0" 369 | permission_handler_platform_interface: 370 | dependency: transitive 371 | description: 372 | name: permission_handler_platform_interface 373 | url: "https://pub.flutter-io.cn" 374 | source: hosted 375 | version: "3.7.0" 376 | petitparser: 377 | dependency: transitive 378 | description: 379 | name: petitparser 380 | url: "https://pub.flutter-io.cn" 381 | source: hosted 382 | version: "4.4.0" 383 | photo_view: 384 | dependency: transitive 385 | description: 386 | name: photo_view 387 | url: "https://pub.flutter-io.cn" 388 | source: hosted 389 | version: "0.13.0" 390 | platform: 391 | dependency: transitive 392 | description: 393 | name: platform 394 | url: "https://pub.flutter-io.cn" 395 | source: hosted 396 | version: "3.1.0" 397 | plugin_platform_interface: 398 | dependency: transitive 399 | description: 400 | name: plugin_platform_interface 401 | url: "https://pub.flutter-io.cn" 402 | source: hosted 403 | version: "2.1.2" 404 | process: 405 | dependency: transitive 406 | description: 407 | name: process 408 | url: "https://pub.flutter-io.cn" 409 | source: hosted 410 | version: "4.2.4" 411 | provider: 412 | dependency: transitive 413 | description: 414 | name: provider 415 | url: "https://pub.flutter-io.cn" 416 | source: hosted 417 | version: "6.0.2" 418 | rxdart: 419 | dependency: transitive 420 | description: 421 | name: rxdart 422 | url: "https://pub.flutter-io.cn" 423 | source: hosted 424 | version: "0.27.3" 425 | shared_preferences: 426 | dependency: "direct main" 427 | description: 428 | name: shared_preferences 429 | url: "https://pub.flutter-io.cn" 430 | source: hosted 431 | version: "2.0.13" 432 | shared_preferences_android: 433 | dependency: transitive 434 | description: 435 | name: shared_preferences_android 436 | url: "https://pub.flutter-io.cn" 437 | source: hosted 438 | version: "2.0.11" 439 | shared_preferences_ios: 440 | dependency: transitive 441 | description: 442 | name: shared_preferences_ios 443 | url: "https://pub.flutter-io.cn" 444 | source: hosted 445 | version: "2.1.0" 446 | shared_preferences_linux: 447 | dependency: transitive 448 | description: 449 | name: shared_preferences_linux 450 | url: "https://pub.flutter-io.cn" 451 | source: hosted 452 | version: "2.1.0" 453 | shared_preferences_macos: 454 | dependency: transitive 455 | description: 456 | name: shared_preferences_macos 457 | url: "https://pub.flutter-io.cn" 458 | source: hosted 459 | version: "2.0.3" 460 | shared_preferences_platform_interface: 461 | dependency: transitive 462 | description: 463 | name: shared_preferences_platform_interface 464 | url: "https://pub.flutter-io.cn" 465 | source: hosted 466 | version: "2.0.0" 467 | shared_preferences_web: 468 | dependency: transitive 469 | description: 470 | name: shared_preferences_web 471 | url: "https://pub.flutter-io.cn" 472 | source: hosted 473 | version: "2.0.3" 474 | shared_preferences_windows: 475 | dependency: transitive 476 | description: 477 | name: shared_preferences_windows 478 | url: "https://pub.flutter-io.cn" 479 | source: hosted 480 | version: "2.1.0" 481 | sky_engine: 482 | dependency: transitive 483 | description: flutter 484 | source: sdk 485 | version: "0.0.99" 486 | source_span: 487 | dependency: transitive 488 | description: 489 | name: source_span 490 | url: "https://pub.flutter-io.cn" 491 | source: hosted 492 | version: "1.9.0" 493 | sqflite: 494 | dependency: "direct main" 495 | description: 496 | name: sqflite 497 | url: "https://pub.flutter-io.cn" 498 | source: hosted 499 | version: "2.0.2" 500 | sqflite_common: 501 | dependency: transitive 502 | description: 503 | name: sqflite_common 504 | url: "https://pub.flutter-io.cn" 505 | source: hosted 506 | version: "2.2.1" 507 | stack_trace: 508 | dependency: transitive 509 | description: 510 | name: stack_trace 511 | url: "https://pub.flutter-io.cn" 512 | source: hosted 513 | version: "1.10.0" 514 | stream_channel: 515 | dependency: transitive 516 | description: 517 | name: stream_channel 518 | url: "https://pub.flutter-io.cn" 519 | source: hosted 520 | version: "2.1.0" 521 | string_scanner: 522 | dependency: transitive 523 | description: 524 | name: string_scanner 525 | url: "https://pub.flutter-io.cn" 526 | source: hosted 527 | version: "1.1.1" 528 | synchronized: 529 | dependency: transitive 530 | description: 531 | name: synchronized 532 | url: "https://pub.flutter-io.cn" 533 | source: hosted 534 | version: "3.0.0" 535 | term_glyph: 536 | dependency: transitive 537 | description: 538 | name: term_glyph 539 | url: "https://pub.flutter-io.cn" 540 | source: hosted 541 | version: "1.2.1" 542 | test_api: 543 | dependency: transitive 544 | description: 545 | name: test_api 546 | url: "https://pub.flutter-io.cn" 547 | source: hosted 548 | version: "0.4.12" 549 | typed_data: 550 | dependency: transitive 551 | description: 552 | name: typed_data 553 | url: "https://pub.flutter-io.cn" 554 | source: hosted 555 | version: "1.3.0" 556 | uuid: 557 | dependency: transitive 558 | description: 559 | name: uuid 560 | url: "https://pub.flutter-io.cn" 561 | source: hosted 562 | version: "3.0.6" 563 | vector_math: 564 | dependency: transitive 565 | description: 566 | name: vector_math 567 | url: "https://pub.flutter-io.cn" 568 | source: hosted 569 | version: "2.1.2" 570 | very_good_analysis: 571 | dependency: transitive 572 | description: 573 | name: very_good_analysis 574 | url: "https://pub.flutter-io.cn" 575 | source: hosted 576 | version: "2.4.0" 577 | video_player: 578 | dependency: "direct main" 579 | description: 580 | name: video_player 581 | url: "https://pub.flutter-io.cn" 582 | source: hosted 583 | version: "2.3.0" 584 | video_player_android: 585 | dependency: transitive 586 | description: 587 | name: video_player_android 588 | url: "https://pub.flutter-io.cn" 589 | source: hosted 590 | version: "2.3.2" 591 | video_player_avfoundation: 592 | dependency: transitive 593 | description: 594 | name: video_player_avfoundation 595 | url: "https://pub.flutter-io.cn" 596 | source: hosted 597 | version: "2.3.1" 598 | video_player_platform_interface: 599 | dependency: transitive 600 | description: 601 | name: video_player_platform_interface 602 | url: "https://pub.flutter-io.cn" 603 | source: hosted 604 | version: "5.1.1" 605 | video_player_web: 606 | dependency: transitive 607 | description: 608 | name: video_player_web 609 | url: "https://pub.flutter-io.cn" 610 | source: hosted 611 | version: "2.0.7" 612 | wakelock: 613 | dependency: "direct main" 614 | description: 615 | name: wakelock 616 | url: "https://pub.flutter-io.cn" 617 | source: hosted 618 | version: "0.6.1+2" 619 | wakelock_macos: 620 | dependency: transitive 621 | description: 622 | name: wakelock_macos 623 | url: "https://pub.flutter-io.cn" 624 | source: hosted 625 | version: "0.4.0" 626 | wakelock_platform_interface: 627 | dependency: transitive 628 | description: 629 | name: wakelock_platform_interface 630 | url: "https://pub.flutter-io.cn" 631 | source: hosted 632 | version: "0.3.0" 633 | wakelock_web: 634 | dependency: transitive 635 | description: 636 | name: wakelock_web 637 | url: "https://pub.flutter-io.cn" 638 | source: hosted 639 | version: "0.4.0" 640 | wakelock_windows: 641 | dependency: transitive 642 | description: 643 | name: wakelock_windows 644 | url: "https://pub.flutter-io.cn" 645 | source: hosted 646 | version: "0.2.0" 647 | webview_flutter: 648 | dependency: transitive 649 | description: 650 | name: webview_flutter 651 | url: "https://pub.flutter-io.cn" 652 | source: hosted 653 | version: "2.8.0" 654 | webview_flutter_android: 655 | dependency: transitive 656 | description: 657 | name: webview_flutter_android 658 | url: "https://pub.flutter-io.cn" 659 | source: hosted 660 | version: "2.8.3" 661 | webview_flutter_platform_interface: 662 | dependency: transitive 663 | description: 664 | name: webview_flutter_platform_interface 665 | url: "https://pub.flutter-io.cn" 666 | source: hosted 667 | version: "1.8.1" 668 | webview_flutter_plus: 669 | dependency: "direct main" 670 | description: 671 | name: webview_flutter_plus 672 | url: "https://pub.flutter-io.cn" 673 | source: hosted 674 | version: "0.2.4" 675 | webview_flutter_wkwebview: 676 | dependency: transitive 677 | description: 678 | name: webview_flutter_wkwebview 679 | url: "https://pub.flutter-io.cn" 680 | source: hosted 681 | version: "2.7.1" 682 | win32: 683 | dependency: transitive 684 | description: 685 | name: win32 686 | url: "https://pub.flutter-io.cn" 687 | source: hosted 688 | version: "2.4.1" 689 | xdg_directories: 690 | dependency: transitive 691 | description: 692 | name: xdg_directories 693 | url: "https://pub.flutter-io.cn" 694 | source: hosted 695 | version: "0.2.0+1" 696 | xml: 697 | dependency: transitive 698 | description: 699 | name: xml 700 | url: "https://pub.flutter-io.cn" 701 | source: hosted 702 | version: "5.3.1" 703 | sdks: 704 | dart: ">=2.17.0-0 <3.0.0" 705 | flutter: ">=2.8.0" 706 | --------------------------------------------------------------------------------