commit 8c74b14e5cca3686d62218b296df07fc6c433475 Author: WinUser01 Date: Thu Dec 9 20:24:36 2021 +0800 hyzp_ybqx-Commit001:代码刚转换好,编译通过 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9d532b1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..9432b08 --- /dev/null +++ b/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: f30b7f4db93ee747cd727df747941a28ead25ff5 + channel: stable + +project_type: app diff --git a/README.md b/README.md new file mode 100644 index 0000000..33e1357 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# hyzp_ybqx + +HeiYanZhuaPai_YiBin_QuXian Flutter application . + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/Scrshot-03-OK/2020-11-11_155145.png b/Scrshot-03-OK/2020-11-11_155145.png new file mode 100644 index 0000000..10f6fe5 Binary files /dev/null and b/Scrshot-03-OK/2020-11-11_155145.png differ diff --git a/Scrshot-03-OK/2020-11-11_155149.png b/Scrshot-03-OK/2020-11-11_155149.png new file mode 100644 index 0000000..5cdfb14 Binary files /dev/null and b/Scrshot-03-OK/2020-11-11_155149.png differ diff --git a/Scrshot-03-OK/2020-11-11_155155.png b/Scrshot-03-OK/2020-11-11_155155.png new file mode 100644 index 0000000..a463438 Binary files /dev/null and b/Scrshot-03-OK/2020-11-11_155155.png differ diff --git a/Scrshot-03-OK/2020-11-11_155243.png b/Scrshot-03-OK/2020-11-11_155243.png new file mode 100644 index 0000000..578a928 Binary files /dev/null and b/Scrshot-03-OK/2020-11-11_155243.png differ diff --git a/Scrshot-03-OK/2020-11-11_155303.png b/Scrshot-03-OK/2020-11-11_155303.png new file mode 100644 index 0000000..392ab46 Binary files /dev/null and b/Scrshot-03-OK/2020-11-11_155303.png differ diff --git a/Scrshot-03-OK/2020-11-11_155809.png b/Scrshot-03-OK/2020-11-11_155809.png new file mode 100644 index 0000000..d072b57 Binary files /dev/null and b/Scrshot-03-OK/2020-11-11_155809.png differ diff --git a/Scrshot-03-OK/2020-11-11_155828.png b/Scrshot-03-OK/2020-11-11_155828.png new file mode 100644 index 0000000..16b0d5b Binary files /dev/null and b/Scrshot-03-OK/2020-11-11_155828.png differ diff --git a/Scrshot-03-OK/hyzp_ybqx项目创建信息.txt b/Scrshot-03-OK/hyzp_ybqx项目创建信息.txt new file mode 100644 index 0000000..7fdd52e Binary files /dev/null and b/Scrshot-03-OK/hyzp_ybqx项目创建信息.txt differ diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..0a741cb --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,11 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 0000000..af31424 --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,71 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 29 + + //解决Android或者Flutter 打包 Execution failed for task ':app:compileFlutterBuildRelease'.的问题 +// lintOptions { +// checkReleaseBuilds false +// abortOnError false +// disable 'InvalidPackage' +// } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.flutter.hyzp_ybqx" + minSdkVersion 21 + targetSdkVersion 29 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + + //关闭代码混淆,解决 Flutter 打包后真机运行闪退。 + //flutter build apk --release --target-platform android-arm64 + //真机运行正常 + minifyEnabled false //删除无用代码,只用该行编译报错 + //Removing unused resources requires unused code shrinking to be turned on. See http://d.android.com/r/tools/shrink-resources.html for more information. + shrinkResources false //删除无用资源,只用该行编译通过,但真机运行闪退 + } + } + +//解决Flutter应用打包release版本apk后,打不开闪退。经测试无效 +// packagingOptions { +// pickFirst 'lib/x86_64/libapp.so' +// pickFirst 'lib/armeabi-v7a/libapp.so' +// pickFirst 'lib/arm64-v8a/libapp.so' +// } +} + +flutter { + source '../..' +} diff --git a/android/app/release/output.json b/android/app/release/output.json new file mode 100644 index 0000000..b909282 --- /dev/null +++ b/android/app/release/output.json @@ -0,0 +1 @@ +[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":20210729,"versionName":"1.3.12","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b290df0 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/java/com/example/hyzp_yibin/MainActivity.java b/android/app/src/main/java/com/example/hyzp_yibin/MainActivity.java new file mode 100644 index 0000000..7e5d674 --- /dev/null +++ b/android/app/src/main/java/com/example/hyzp_yibin/MainActivity.java @@ -0,0 +1,6 @@ +package com.flutter.hyzp_ybqx; + +import io.flutter.embedding.android.FlutterActivity; + +public class MainActivity extends FlutterActivity { +} diff --git a/android/app/src/main/java/com/example/hyzp_yibin/MyApplication.java b/android/app/src/main/java/com/example/hyzp_yibin/MyApplication.java new file mode 100644 index 0000000..5e7fe96 --- /dev/null +++ b/android/app/src/main/java/com/example/hyzp_yibin/MyApplication.java @@ -0,0 +1,10 @@ +package com.flutter.hyzp_ybqx; + +import com.baidu.flutter_bmfbase.BmfMapApplication; + +public class MyApplication extends BmfMapApplication { + @Override + public void onCreate() { + super.onCreate(); + } +} \ No newline at end of file diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..20eb120 --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/hyzp_yibin_launche.png b/android/app/src/main/res/mipmap-hdpi/hyzp_yibin_launche.png new file mode 100644 index 0000000..3df0786 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/hyzp_yibin_launche.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..6e760ec Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/hyzp_yibin_launche.png b/android/app/src/main/res/mipmap-mdpi/hyzp_yibin_launche.png new file mode 100644 index 0000000..c917628 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/hyzp_yibin_launche.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..6e760ec Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/hyzp_yibin_launche.png b/android/app/src/main/res/mipmap-xhdpi/hyzp_yibin_launche.png new file mode 100644 index 0000000..d5b6e74 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/hyzp_yibin_launche.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..6e760ec Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/hyzp_yibin_launche.png b/android/app/src/main/res/mipmap-xxhdpi/hyzp_yibin_launche.png new file mode 100644 index 0000000..859b6fe Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/hyzp_yibin_launche.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..6e760ec Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ybsthbj_launche.png b/android/app/src/main/res/mipmap-xxhdpi/ybsthbj_launche.png new file mode 100644 index 0000000..a8fcd11 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ybsthbj_launche.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/hyzp_yibin_launche.png b/android/app/src/main/res/mipmap-xxxhdpi/hyzp_yibin_launche.png new file mode 100644 index 0000000..3d34764 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/hyzp_yibin_launche.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..6e760ec Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..1f83a33 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/xml/network_security_config.xml b/android/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000..d7b4192 --- /dev/null +++ b/android/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..ee0ed26 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build-0.gradle b/android/build-0.gradle new file mode 100644 index 0000000..e0d7ae2 --- /dev/null +++ b/android/build-0.gradle @@ -0,0 +1,29 @@ +buildscript { + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0' + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..6ccfd4d --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,35 @@ +buildscript { + repositories { + google() + jcenter() +// maven { url 'https://maven.aliyun.com/repository/google' } +// maven { url 'https://maven.aliyun.com/repository/jcenter' } +// maven { url 'http://maven.aliyun.com/nexus/content/groups/public' } + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0' + } +} + +allprojects { + repositories { + google() + jcenter() +// maven { url 'https://maven.aliyun.com/repository/google' } +// maven { url 'https://maven.aliyun.com/repository/jcenter' } +// maven { url 'http://maven.aliyun.com/nexus/content/groups/public' } + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..a673820 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true +android.enableR8=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..296b146 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/android/settings_aar.gradle b/android/settings_aar.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/android/settings_aar.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/assets/audio/yinxiao1064.mp3 b/assets/audio/yinxiao1064.mp3 new file mode 100644 index 0000000..f7a23e9 Binary files /dev/null and b/assets/audio/yinxiao1064.mp3 differ diff --git a/assets/files_icons/2.0x/excel.png b/assets/files_icons/2.0x/excel.png new file mode 100644 index 0000000..6704e2c Binary files /dev/null and b/assets/files_icons/2.0x/excel.png differ diff --git a/assets/files_icons/2.0x/file.png b/assets/files_icons/2.0x/file.png new file mode 100644 index 0000000..79ee613 Binary files /dev/null and b/assets/files_icons/2.0x/file.png differ diff --git a/assets/files_icons/2.0x/folder.png b/assets/files_icons/2.0x/folder.png new file mode 100644 index 0000000..934f9c9 Binary files /dev/null and b/assets/files_icons/2.0x/folder.png differ diff --git a/assets/files_icons/2.0x/image.png b/assets/files_icons/2.0x/image.png new file mode 100644 index 0000000..7d3b451 Binary files /dev/null and b/assets/files_icons/2.0x/image.png differ diff --git a/assets/files_icons/2.0x/mp3.png b/assets/files_icons/2.0x/mp3.png new file mode 100644 index 0000000..a5c0c17 Binary files /dev/null and b/assets/files_icons/2.0x/mp3.png differ diff --git a/assets/files_icons/2.0x/ppt.png b/assets/files_icons/2.0x/ppt.png new file mode 100644 index 0000000..fce55f7 Binary files /dev/null and b/assets/files_icons/2.0x/ppt.png differ diff --git a/assets/files_icons/2.0x/psd.png b/assets/files_icons/2.0x/psd.png new file mode 100644 index 0000000..1dd1c64 Binary files /dev/null and b/assets/files_icons/2.0x/psd.png differ diff --git a/assets/files_icons/2.0x/txt.png b/assets/files_icons/2.0x/txt.png new file mode 100644 index 0000000..eb8f6f0 Binary files /dev/null and b/assets/files_icons/2.0x/txt.png differ diff --git a/assets/files_icons/2.0x/unknown.png b/assets/files_icons/2.0x/unknown.png new file mode 100644 index 0000000..f2063a0 Binary files /dev/null and b/assets/files_icons/2.0x/unknown.png differ diff --git a/assets/files_icons/2.0x/video.png b/assets/files_icons/2.0x/video.png new file mode 100644 index 0000000..5527fb5 Binary files /dev/null and b/assets/files_icons/2.0x/video.png differ diff --git a/assets/files_icons/2.0x/word.png b/assets/files_icons/2.0x/word.png new file mode 100644 index 0000000..9f8d374 Binary files /dev/null and b/assets/files_icons/2.0x/word.png differ diff --git a/assets/files_icons/2.0x/zip.png b/assets/files_icons/2.0x/zip.png new file mode 100644 index 0000000..e925567 Binary files /dev/null and b/assets/files_icons/2.0x/zip.png differ diff --git a/assets/files_icons/3.0x/excel.png b/assets/files_icons/3.0x/excel.png new file mode 100644 index 0000000..7639694 Binary files /dev/null and b/assets/files_icons/3.0x/excel.png differ diff --git a/assets/files_icons/3.0x/file.png b/assets/files_icons/3.0x/file.png new file mode 100644 index 0000000..adcb0ae Binary files /dev/null and b/assets/files_icons/3.0x/file.png differ diff --git a/assets/files_icons/3.0x/folder.png b/assets/files_icons/3.0x/folder.png new file mode 100644 index 0000000..72722d1 Binary files /dev/null and b/assets/files_icons/3.0x/folder.png differ diff --git a/assets/files_icons/3.0x/image.png b/assets/files_icons/3.0x/image.png new file mode 100644 index 0000000..1efec19 Binary files /dev/null and b/assets/files_icons/3.0x/image.png differ diff --git a/assets/files_icons/3.0x/mp3.png b/assets/files_icons/3.0x/mp3.png new file mode 100644 index 0000000..c63583b Binary files /dev/null and b/assets/files_icons/3.0x/mp3.png differ diff --git a/assets/files_icons/3.0x/ppt.png b/assets/files_icons/3.0x/ppt.png new file mode 100644 index 0000000..f4a3f9b Binary files /dev/null and b/assets/files_icons/3.0x/ppt.png differ diff --git a/assets/files_icons/3.0x/psd.png b/assets/files_icons/3.0x/psd.png new file mode 100644 index 0000000..60d3a45 Binary files /dev/null and b/assets/files_icons/3.0x/psd.png differ diff --git a/assets/files_icons/3.0x/txt.png b/assets/files_icons/3.0x/txt.png new file mode 100644 index 0000000..171e504 Binary files /dev/null and b/assets/files_icons/3.0x/txt.png differ diff --git a/assets/files_icons/3.0x/unknown.png b/assets/files_icons/3.0x/unknown.png new file mode 100644 index 0000000..fd746ab Binary files /dev/null and b/assets/files_icons/3.0x/unknown.png differ diff --git a/assets/files_icons/3.0x/video.png b/assets/files_icons/3.0x/video.png new file mode 100644 index 0000000..b3418a6 Binary files /dev/null and b/assets/files_icons/3.0x/video.png differ diff --git a/assets/files_icons/3.0x/word.png b/assets/files_icons/3.0x/word.png new file mode 100644 index 0000000..16feaec Binary files /dev/null and b/assets/files_icons/3.0x/word.png differ diff --git a/assets/files_icons/3.0x/zip.png b/assets/files_icons/3.0x/zip.png new file mode 100644 index 0000000..c2d10de Binary files /dev/null and b/assets/files_icons/3.0x/zip.png differ diff --git a/assets/files_icons/excel.png b/assets/files_icons/excel.png new file mode 100644 index 0000000..af8262e Binary files /dev/null and b/assets/files_icons/excel.png differ diff --git a/assets/files_icons/file.png b/assets/files_icons/file.png new file mode 100644 index 0000000..a8c59c6 Binary files /dev/null and b/assets/files_icons/file.png differ diff --git a/assets/files_icons/folder.png b/assets/files_icons/folder.png new file mode 100644 index 0000000..c90ab30 Binary files /dev/null and b/assets/files_icons/folder.png differ diff --git a/assets/files_icons/image.png b/assets/files_icons/image.png new file mode 100644 index 0000000..204c024 Binary files /dev/null and b/assets/files_icons/image.png differ diff --git a/assets/files_icons/mp3.png b/assets/files_icons/mp3.png new file mode 100644 index 0000000..2799673 Binary files /dev/null and b/assets/files_icons/mp3.png differ diff --git a/assets/files_icons/ppt.png b/assets/files_icons/ppt.png new file mode 100644 index 0000000..91bd844 Binary files /dev/null and b/assets/files_icons/ppt.png differ diff --git a/assets/files_icons/psd.png b/assets/files_icons/psd.png new file mode 100644 index 0000000..2fcb07d Binary files /dev/null and b/assets/files_icons/psd.png differ diff --git a/assets/files_icons/txt.png b/assets/files_icons/txt.png new file mode 100644 index 0000000..569167a Binary files /dev/null and b/assets/files_icons/txt.png differ diff --git a/assets/files_icons/unknown.png b/assets/files_icons/unknown.png new file mode 100644 index 0000000..846c059 Binary files /dev/null and b/assets/files_icons/unknown.png differ diff --git a/assets/files_icons/video.png b/assets/files_icons/video.png new file mode 100644 index 0000000..cce67ae Binary files /dev/null and b/assets/files_icons/video.png differ diff --git a/assets/files_icons/word.png b/assets/files_icons/word.png new file mode 100644 index 0000000..5a120e9 Binary files /dev/null and b/assets/files_icons/word.png differ diff --git a/assets/files_icons/zip.png b/assets/files_icons/zip.png new file mode 100644 index 0000000..ca2aab0 Binary files /dev/null and b/assets/files_icons/zip.png differ diff --git a/assets/fun_icons/fun_icon_1.png b/assets/fun_icons/fun_icon_1.png new file mode 100644 index 0000000..bb5374e Binary files /dev/null and b/assets/fun_icons/fun_icon_1.png differ diff --git a/assets/fun_icons/fun_icon_2.png b/assets/fun_icons/fun_icon_2.png new file mode 100644 index 0000000..ab64f4a Binary files /dev/null and b/assets/fun_icons/fun_icon_2.png differ diff --git a/assets/fun_icons/fun_icon_3.png b/assets/fun_icons/fun_icon_3.png new file mode 100644 index 0000000..a5c9285 Binary files /dev/null and b/assets/fun_icons/fun_icon_3.png differ diff --git a/assets/fun_icons/fun_icon_4.png b/assets/fun_icons/fun_icon_4.png new file mode 100644 index 0000000..43605bf Binary files /dev/null and b/assets/fun_icons/fun_icon_4.png differ diff --git a/assets/fun_icons/fun_icon_5.png b/assets/fun_icons/fun_icon_5.png new file mode 100644 index 0000000..ec148a7 Binary files /dev/null and b/assets/fun_icons/fun_icon_5.png differ diff --git a/assets/fun_icons/fun_icon_6.png b/assets/fun_icons/fun_icon_6.png new file mode 100644 index 0000000..75b287b Binary files /dev/null and b/assets/fun_icons/fun_icon_6.png differ diff --git a/assets/fun_icons/fun_icon_7.png b/assets/fun_icons/fun_icon_7.png new file mode 100644 index 0000000..c614d00 Binary files /dev/null and b/assets/fun_icons/fun_icon_7.png differ diff --git a/assets/fun_icons/fun_icon_8.png b/assets/fun_icons/fun_icon_8.png new file mode 100644 index 0000000..1794a4e Binary files /dev/null and b/assets/fun_icons/fun_icon_8.png differ diff --git a/assets/fun_icons/fun_icon_9.png b/assets/fun_icons/fun_icon_9.png new file mode 100644 index 0000000..7e5e637 Binary files /dev/null and b/assets/fun_icons/fun_icon_9.png differ diff --git a/assets/fun_icons/fun_icon_a.png b/assets/fun_icons/fun_icon_a.png new file mode 100644 index 0000000..6846094 Binary files /dev/null and b/assets/fun_icons/fun_icon_a.png differ diff --git a/assets/fun_icons/fun_icon_b.png b/assets/fun_icons/fun_icon_b.png new file mode 100644 index 0000000..9028546 Binary files /dev/null and b/assets/fun_icons/fun_icon_b.png differ diff --git a/assets/images/1 (104).png b/assets/images/1 (104).png new file mode 100644 index 0000000..7010838 Binary files /dev/null and b/assets/images/1 (104).png differ diff --git a/assets/images/1 (15).png b/assets/images/1 (15).png new file mode 100644 index 0000000..ba0ac92 Binary files /dev/null and b/assets/images/1 (15).png differ diff --git a/assets/images/1 (177).png b/assets/images/1 (177).png new file mode 100644 index 0000000..e7a246f Binary files /dev/null and b/assets/images/1 (177).png differ diff --git a/assets/images/1 (194).png b/assets/images/1 (194).png new file mode 100644 index 0000000..a9157ed Binary files /dev/null and b/assets/images/1 (194).png differ diff --git a/assets/images/1 (219).png b/assets/images/1 (219).png new file mode 100644 index 0000000..6cd902c Binary files /dev/null and b/assets/images/1 (219).png differ diff --git a/assets/images/1 (84).png b/assets/images/1 (84).png new file mode 100644 index 0000000..36bac81 Binary files /dev/null and b/assets/images/1 (84).png differ diff --git a/assets/images/2.0x/1 (104).png b/assets/images/2.0x/1 (104).png new file mode 100644 index 0000000..3952279 Binary files /dev/null and b/assets/images/2.0x/1 (104).png differ diff --git a/assets/images/2.0x/1 (15).png b/assets/images/2.0x/1 (15).png new file mode 100644 index 0000000..d06b093 Binary files /dev/null and b/assets/images/2.0x/1 (15).png differ diff --git a/assets/images/2.0x/1 (177).png b/assets/images/2.0x/1 (177).png new file mode 100644 index 0000000..9321716 Binary files /dev/null and b/assets/images/2.0x/1 (177).png differ diff --git a/assets/images/2.0x/1 (194).png b/assets/images/2.0x/1 (194).png new file mode 100644 index 0000000..454d2a9 Binary files /dev/null and b/assets/images/2.0x/1 (194).png differ diff --git a/assets/images/2.0x/1 (219).png b/assets/images/2.0x/1 (219).png new file mode 100644 index 0000000..627f485 Binary files /dev/null and b/assets/images/2.0x/1 (219).png differ diff --git a/assets/images/2.0x/1 (84).png b/assets/images/2.0x/1 (84).png new file mode 100644 index 0000000..770b70e Binary files /dev/null and b/assets/images/2.0x/1 (84).png differ diff --git a/assets/images/2.0x/LED.png b/assets/images/2.0x/LED.png new file mode 100644 index 0000000..7300757 Binary files /dev/null and b/assets/images/2.0x/LED.png differ diff --git a/assets/images/2.0x/login.png b/assets/images/2.0x/login.png new file mode 100644 index 0000000..64c90d6 Binary files /dev/null and b/assets/images/2.0x/login.png differ diff --git a/assets/images/2.0x/monitor.png b/assets/images/2.0x/monitor.png new file mode 100644 index 0000000..9492092 Binary files /dev/null and b/assets/images/2.0x/monitor.png differ diff --git a/assets/images/2.0x/user.png b/assets/images/2.0x/user.png new file mode 100644 index 0000000..fafd6df Binary files /dev/null and b/assets/images/2.0x/user.png differ diff --git a/assets/images/2.0x/user_bg.jpg b/assets/images/2.0x/user_bg.jpg new file mode 100644 index 0000000..37376ad Binary files /dev/null and b/assets/images/2.0x/user_bg.jpg differ diff --git a/assets/images/2.0x/个人资料.png b/assets/images/2.0x/个人资料.png new file mode 100644 index 0000000..0dab312 Binary files /dev/null and b/assets/images/2.0x/个人资料.png differ diff --git a/assets/images/2.0x/关于.png b/assets/images/2.0x/关于.png new file mode 100644 index 0000000..2c6556d Binary files /dev/null and b/assets/images/2.0x/关于.png differ diff --git a/assets/images/2.0x/刷新.png b/assets/images/2.0x/刷新.png new file mode 100644 index 0000000..8f2ca00 Binary files /dev/null and b/assets/images/2.0x/刷新.png differ diff --git a/assets/images/2.0x/图层 11.png b/assets/images/2.0x/图层 11.png new file mode 100644 index 0000000..87872f6 Binary files /dev/null and b/assets/images/2.0x/图层 11.png differ diff --git a/assets/images/2.0x/图层 2.png b/assets/images/2.0x/图层 2.png new file mode 100644 index 0000000..581a7ba Binary files /dev/null and b/assets/images/2.0x/图层 2.png differ diff --git a/assets/images/2.0x/图层 5.png b/assets/images/2.0x/图层 5.png new file mode 100644 index 0000000..2a4cbd6 Binary files /dev/null and b/assets/images/2.0x/图层 5.png differ diff --git a/assets/images/2.0x/形状 1.png b/assets/images/2.0x/形状 1.png new file mode 100644 index 0000000..45a365a Binary files /dev/null and b/assets/images/2.0x/形状 1.png differ diff --git a/assets/images/2.0x/形状 2.png b/assets/images/2.0x/形状 2.png new file mode 100644 index 0000000..e78037c Binary files /dev/null and b/assets/images/2.0x/形状 2.png differ diff --git a/assets/images/2.0x/形状 809.png b/assets/images/2.0x/形状 809.png new file mode 100644 index 0000000..1caebdf Binary files /dev/null and b/assets/images/2.0x/形状 809.png differ diff --git a/assets/images/2.0x/形状 810.png b/assets/images/2.0x/形状 810.png new file mode 100644 index 0000000..bb0bd8a Binary files /dev/null and b/assets/images/2.0x/形状 810.png differ diff --git a/assets/images/2.0x/形状 811.png b/assets/images/2.0x/形状 811.png new file mode 100644 index 0000000..8b13241 Binary files /dev/null and b/assets/images/2.0x/形状 811.png differ diff --git a/assets/images/2.0x/意见反馈.png b/assets/images/2.0x/意见反馈.png new file mode 100644 index 0000000..971e334 Binary files /dev/null and b/assets/images/2.0x/意见反馈.png differ diff --git a/assets/images/2.0x/我的.png b/assets/images/2.0x/我的.png new file mode 100644 index 0000000..48fb552 Binary files /dev/null and b/assets/images/2.0x/我的.png differ diff --git a/assets/images/2.0x/播放 (1).png b/assets/images/2.0x/播放 (1).png new file mode 100644 index 0000000..2cd708d Binary files /dev/null and b/assets/images/2.0x/播放 (1).png differ diff --git a/assets/images/2.0x/清除缓存.png b/assets/images/2.0x/清除缓存.png new file mode 100644 index 0000000..e016ccc Binary files /dev/null and b/assets/images/2.0x/清除缓存.png differ diff --git a/assets/images/2.0x/版本更新.png b/assets/images/2.0x/版本更新.png new file mode 100644 index 0000000..15825d3 Binary files /dev/null and b/assets/images/2.0x/版本更新.png differ diff --git a/assets/images/2.0x/盾 密码 安全.png b/assets/images/2.0x/盾 密码 安全.png new file mode 100644 index 0000000..2b8b5ce Binary files /dev/null and b/assets/images/2.0x/盾 密码 安全.png differ diff --git a/assets/images/2.0x/矢量智能对象(1).png b/assets/images/2.0x/矢量智能对象(1).png new file mode 100644 index 0000000..9477cce Binary files /dev/null and b/assets/images/2.0x/矢量智能对象(1).png differ diff --git a/assets/images/2.0x/矢量智能对象.png b/assets/images/2.0x/矢量智能对象.png new file mode 100644 index 0000000..811eda7 Binary files /dev/null and b/assets/images/2.0x/矢量智能对象.png differ diff --git a/assets/images/2.0x/矩形 1 拷贝 39.png b/assets/images/2.0x/矩形 1 拷贝 39.png new file mode 100644 index 0000000..30ca5c7 Binary files /dev/null and b/assets/images/2.0x/矩形 1 拷贝 39.png differ diff --git a/assets/images/2.0x/组 1.png b/assets/images/2.0x/组 1.png new file mode 100644 index 0000000..1e790f8 Binary files /dev/null and b/assets/images/2.0x/组 1.png differ diff --git a/assets/images/2.0x/聚焦.png b/assets/images/2.0x/聚焦.png new file mode 100644 index 0000000..ae3a8db Binary files /dev/null and b/assets/images/2.0x/聚焦.png differ diff --git a/assets/images/2.0x/背景图.png b/assets/images/2.0x/背景图.png new file mode 100644 index 0000000..eb7d22a Binary files /dev/null and b/assets/images/2.0x/背景图.png differ diff --git a/assets/images/2.0x/警察.png b/assets/images/2.0x/警察.png new file mode 100644 index 0000000..00a9bdd Binary files /dev/null and b/assets/images/2.0x/警察.png differ diff --git a/assets/images/3.0x/1 (104).png b/assets/images/3.0x/1 (104).png new file mode 100644 index 0000000..6ac20fa Binary files /dev/null and b/assets/images/3.0x/1 (104).png differ diff --git a/assets/images/3.0x/1 (15).png b/assets/images/3.0x/1 (15).png new file mode 100644 index 0000000..670ceb8 Binary files /dev/null and b/assets/images/3.0x/1 (15).png differ diff --git a/assets/images/3.0x/1 (177).png b/assets/images/3.0x/1 (177).png new file mode 100644 index 0000000..d8f469f Binary files /dev/null and b/assets/images/3.0x/1 (177).png differ diff --git a/assets/images/3.0x/1 (194).png b/assets/images/3.0x/1 (194).png new file mode 100644 index 0000000..d8c3c51 Binary files /dev/null and b/assets/images/3.0x/1 (194).png differ diff --git a/assets/images/3.0x/1 (219).png b/assets/images/3.0x/1 (219).png new file mode 100644 index 0000000..26c5bf4 Binary files /dev/null and b/assets/images/3.0x/1 (219).png differ diff --git a/assets/images/3.0x/1 (84).png b/assets/images/3.0x/1 (84).png new file mode 100644 index 0000000..6a67767 Binary files /dev/null and b/assets/images/3.0x/1 (84).png differ diff --git a/assets/images/3.0x/LED.png b/assets/images/3.0x/LED.png new file mode 100644 index 0000000..445970c Binary files /dev/null and b/assets/images/3.0x/LED.png differ diff --git a/assets/images/3.0x/login.png b/assets/images/3.0x/login.png new file mode 100644 index 0000000..64c90d6 Binary files /dev/null and b/assets/images/3.0x/login.png differ diff --git a/assets/images/3.0x/monitor.png b/assets/images/3.0x/monitor.png new file mode 100644 index 0000000..9492092 Binary files /dev/null and b/assets/images/3.0x/monitor.png differ diff --git a/assets/images/3.0x/user.png b/assets/images/3.0x/user.png new file mode 100644 index 0000000..fafd6df Binary files /dev/null and b/assets/images/3.0x/user.png differ diff --git a/assets/images/3.0x/user_bg.jpg b/assets/images/3.0x/user_bg.jpg new file mode 100644 index 0000000..37376ad Binary files /dev/null and b/assets/images/3.0x/user_bg.jpg differ diff --git a/assets/images/3.0x/个人资料.png b/assets/images/3.0x/个人资料.png new file mode 100644 index 0000000..2a18ec1 Binary files /dev/null and b/assets/images/3.0x/个人资料.png differ diff --git a/assets/images/3.0x/关于.png b/assets/images/3.0x/关于.png new file mode 100644 index 0000000..8922992 Binary files /dev/null and b/assets/images/3.0x/关于.png differ diff --git a/assets/images/3.0x/刷新.png b/assets/images/3.0x/刷新.png new file mode 100644 index 0000000..e604c74 Binary files /dev/null and b/assets/images/3.0x/刷新.png differ diff --git a/assets/images/3.0x/图层 11.png b/assets/images/3.0x/图层 11.png new file mode 100644 index 0000000..3c83126 Binary files /dev/null and b/assets/images/3.0x/图层 11.png differ diff --git a/assets/images/3.0x/图层 2.png b/assets/images/3.0x/图层 2.png new file mode 100644 index 0000000..23e4719 Binary files /dev/null and b/assets/images/3.0x/图层 2.png differ diff --git a/assets/images/3.0x/图层 5.png b/assets/images/3.0x/图层 5.png new file mode 100644 index 0000000..23dd126 Binary files /dev/null and b/assets/images/3.0x/图层 5.png differ diff --git a/assets/images/3.0x/形状 1.png b/assets/images/3.0x/形状 1.png new file mode 100644 index 0000000..05b46b8 Binary files /dev/null and b/assets/images/3.0x/形状 1.png differ diff --git a/assets/images/3.0x/形状 2.png b/assets/images/3.0x/形状 2.png new file mode 100644 index 0000000..667f991 Binary files /dev/null and b/assets/images/3.0x/形状 2.png differ diff --git a/assets/images/3.0x/形状 809.png b/assets/images/3.0x/形状 809.png new file mode 100644 index 0000000..5641216 Binary files /dev/null and b/assets/images/3.0x/形状 809.png differ diff --git a/assets/images/3.0x/形状 810.png b/assets/images/3.0x/形状 810.png new file mode 100644 index 0000000..1ae59b4 Binary files /dev/null and b/assets/images/3.0x/形状 810.png differ diff --git a/assets/images/3.0x/形状 811.png b/assets/images/3.0x/形状 811.png new file mode 100644 index 0000000..4ec7adc Binary files /dev/null and b/assets/images/3.0x/形状 811.png differ diff --git a/assets/images/3.0x/意见反馈.png b/assets/images/3.0x/意见反馈.png new file mode 100644 index 0000000..d501f9f Binary files /dev/null and b/assets/images/3.0x/意见反馈.png differ diff --git a/assets/images/3.0x/我的.png b/assets/images/3.0x/我的.png new file mode 100644 index 0000000..48fb552 Binary files /dev/null and b/assets/images/3.0x/我的.png differ diff --git a/assets/images/3.0x/播放 (1).png b/assets/images/3.0x/播放 (1).png new file mode 100644 index 0000000..3d988fc Binary files /dev/null and b/assets/images/3.0x/播放 (1).png differ diff --git a/assets/images/3.0x/清除缓存.png b/assets/images/3.0x/清除缓存.png new file mode 100644 index 0000000..788876d Binary files /dev/null and b/assets/images/3.0x/清除缓存.png differ diff --git a/assets/images/3.0x/版本更新.png b/assets/images/3.0x/版本更新.png new file mode 100644 index 0000000..39824b0 Binary files /dev/null and b/assets/images/3.0x/版本更新.png differ diff --git a/assets/images/3.0x/盾 密码 安全.png b/assets/images/3.0x/盾 密码 安全.png new file mode 100644 index 0000000..e958517 Binary files /dev/null and b/assets/images/3.0x/盾 密码 安全.png differ diff --git a/assets/images/3.0x/矢量智能对象(1).png b/assets/images/3.0x/矢量智能对象(1).png new file mode 100644 index 0000000..787e226 Binary files /dev/null and b/assets/images/3.0x/矢量智能对象(1).png differ diff --git a/assets/images/3.0x/矢量智能对象.png b/assets/images/3.0x/矢量智能对象.png new file mode 100644 index 0000000..eade2e8 Binary files /dev/null and b/assets/images/3.0x/矢量智能对象.png differ diff --git a/assets/images/3.0x/矩形 1 拷贝 39.png b/assets/images/3.0x/矩形 1 拷贝 39.png new file mode 100644 index 0000000..612c81a Binary files /dev/null and b/assets/images/3.0x/矩形 1 拷贝 39.png differ diff --git a/assets/images/3.0x/组 1.png b/assets/images/3.0x/组 1.png new file mode 100644 index 0000000..c28e77d Binary files /dev/null and b/assets/images/3.0x/组 1.png differ diff --git a/assets/images/3.0x/聚焦.png b/assets/images/3.0x/聚焦.png new file mode 100644 index 0000000..23a6e07 Binary files /dev/null and b/assets/images/3.0x/聚焦.png differ diff --git a/assets/images/3.0x/背景图.png b/assets/images/3.0x/背景图.png new file mode 100644 index 0000000..c6557b6 Binary files /dev/null and b/assets/images/3.0x/背景图.png differ diff --git a/assets/images/3.0x/警察.png b/assets/images/3.0x/警察.png new file mode 100644 index 0000000..1aef883 Binary files /dev/null and b/assets/images/3.0x/警察.png differ diff --git a/assets/images/LED.png b/assets/images/LED.png new file mode 100644 index 0000000..0aca091 Binary files /dev/null and b/assets/images/LED.png differ diff --git a/assets/images/ascending.png b/assets/images/ascending.png new file mode 100644 index 0000000..245d015 Binary files /dev/null and b/assets/images/ascending.png differ diff --git a/assets/images/descending.png b/assets/images/descending.png new file mode 100644 index 0000000..b27ff36 Binary files /dev/null and b/assets/images/descending.png differ diff --git a/assets/images/fhyc.png b/assets/images/fhyc.png new file mode 100644 index 0000000..11a44b9 Binary files /dev/null and b/assets/images/fhyc.png differ diff --git a/assets/images/glmhdbz_016.png b/assets/images/glmhdbz_016.png new file mode 100644 index 0000000..ff8b1f1 Binary files /dev/null and b/assets/images/glmhdbz_016.png differ diff --git a/assets/images/header1.png b/assets/images/header1.png new file mode 100644 index 0000000..3c3f805 Binary files /dev/null and b/assets/images/header1.png differ diff --git a/assets/images/hyc.png b/assets/images/hyc.png new file mode 100644 index 0000000..d13fe76 Binary files /dev/null and b/assets/images/hyc.png differ diff --git a/assets/images/hyzp_yibin_launche.png b/assets/images/hyzp_yibin_launche.png new file mode 100644 index 0000000..d5b6e74 Binary files /dev/null and b/assets/images/hyzp_yibin_launche.png differ diff --git a/assets/images/jkzx_stamp.png b/assets/images/jkzx_stamp.png new file mode 100644 index 0000000..0d0c5f0 Binary files /dev/null and b/assets/images/jkzx_stamp.png differ diff --git a/assets/images/left_arrow.png b/assets/images/left_arrow.png new file mode 100644 index 0000000..4e8e32c Binary files /dev/null and b/assets/images/left_arrow.png differ diff --git a/assets/images/location.png b/assets/images/location.png new file mode 100644 index 0000000..ecce400 Binary files /dev/null and b/assets/images/location.png differ diff --git a/assets/images/login.png b/assets/images/login.png new file mode 100644 index 0000000..64c90d6 Binary files /dev/null and b/assets/images/login.png differ diff --git a/assets/images/monitor.png b/assets/images/monitor.png new file mode 100644 index 0000000..9492092 Binary files /dev/null and b/assets/images/monitor.png differ diff --git a/assets/images/monitor2.png b/assets/images/monitor2.png new file mode 100644 index 0000000..f691126 Binary files /dev/null and b/assets/images/monitor2.png differ diff --git a/assets/images/sort.png b/assets/images/sort.png new file mode 100644 index 0000000..ff22768 Binary files /dev/null and b/assets/images/sort.png differ diff --git a/assets/images/statis.png b/assets/images/statis.png new file mode 100644 index 0000000..c62cc59 Binary files /dev/null and b/assets/images/statis.png differ diff --git a/assets/images/statis_blue.png b/assets/images/statis_blue.png new file mode 100644 index 0000000..d1953d7 Binary files /dev/null and b/assets/images/statis_blue.png differ diff --git a/assets/images/statis_green.png b/assets/images/statis_green.png new file mode 100644 index 0000000..8b9f0eb Binary files /dev/null and b/assets/images/statis_green.png differ diff --git a/assets/images/statis_red.png b/assets/images/statis_red.png new file mode 100644 index 0000000..dd1aeed Binary files /dev/null and b/assets/images/statis_red.png differ diff --git a/assets/images/user.png b/assets/images/user.png new file mode 100644 index 0000000..fafd6df Binary files /dev/null and b/assets/images/user.png differ diff --git a/assets/images/user_bg.jpg b/assets/images/user_bg.jpg new file mode 100644 index 0000000..37376ad Binary files /dev/null and b/assets/images/user_bg.jpg differ diff --git a/assets/images/ybsthbj.png b/assets/images/ybsthbj.png new file mode 100644 index 0000000..c1afed6 Binary files /dev/null and b/assets/images/ybsthbj.png differ diff --git a/assets/images/个人资料.png b/assets/images/个人资料.png new file mode 100644 index 0000000..3f8e09b Binary files /dev/null and b/assets/images/个人资料.png differ diff --git a/assets/images/人脸注册.png b/assets/images/人脸注册.png new file mode 100644 index 0000000..bdbb8d6 Binary files /dev/null and b/assets/images/人脸注册.png differ diff --git a/assets/images/修改密码.png b/assets/images/修改密码.png new file mode 100644 index 0000000..2854bd5 Binary files /dev/null and b/assets/images/修改密码.png differ diff --git a/assets/images/关于.png b/assets/images/关于.png new file mode 100644 index 0000000..2dbcbf0 Binary files /dev/null and b/assets/images/关于.png differ diff --git a/assets/images/刷新.png b/assets/images/刷新.png new file mode 100644 index 0000000..af6ba25 Binary files /dev/null and b/assets/images/刷新.png differ diff --git a/assets/images/图层 11.png b/assets/images/图层 11.png new file mode 100644 index 0000000..519bfee Binary files /dev/null and b/assets/images/图层 11.png differ diff --git a/assets/images/图层 2.png b/assets/images/图层 2.png new file mode 100644 index 0000000..f779f30 Binary files /dev/null and b/assets/images/图层 2.png differ diff --git a/assets/images/图层 5.png b/assets/images/图层 5.png new file mode 100644 index 0000000..97b4c04 Binary files /dev/null and b/assets/images/图层 5.png differ diff --git a/assets/images/形状 1.png b/assets/images/形状 1.png new file mode 100644 index 0000000..df240e9 Binary files /dev/null and b/assets/images/形状 1.png differ diff --git a/assets/images/形状 2.png b/assets/images/形状 2.png new file mode 100644 index 0000000..4f2fb64 Binary files /dev/null and b/assets/images/形状 2.png differ diff --git a/assets/images/形状 809.png b/assets/images/形状 809.png new file mode 100644 index 0000000..6b44cb9 Binary files /dev/null and b/assets/images/形状 809.png differ diff --git a/assets/images/形状 810.png b/assets/images/形状 810.png new file mode 100644 index 0000000..6f686ab Binary files /dev/null and b/assets/images/形状 810.png differ diff --git a/assets/images/形状 811.png b/assets/images/形状 811.png new file mode 100644 index 0000000..3b1d12c Binary files /dev/null and b/assets/images/形状 811.png differ diff --git a/assets/images/意见反馈.png b/assets/images/意见反馈.png new file mode 100644 index 0000000..9f86442 Binary files /dev/null and b/assets/images/意见反馈.png differ diff --git a/assets/images/我的.png b/assets/images/我的.png new file mode 100644 index 0000000..48fb552 Binary files /dev/null and b/assets/images/我的.png differ diff --git a/assets/images/播放 (1).png b/assets/images/播放 (1).png new file mode 100644 index 0000000..e92f15f Binary files /dev/null and b/assets/images/播放 (1).png differ diff --git a/assets/images/权限.png b/assets/images/权限.png new file mode 100644 index 0000000..49f7871 Binary files /dev/null and b/assets/images/权限.png differ diff --git a/assets/images/清除缓存.png b/assets/images/清除缓存.png new file mode 100644 index 0000000..0e12290 Binary files /dev/null and b/assets/images/清除缓存.png differ diff --git a/assets/images/版本更新.png b/assets/images/版本更新.png new file mode 100644 index 0000000..8538862 Binary files /dev/null and b/assets/images/版本更新.png differ diff --git a/assets/images/盾 密码 安全.png b/assets/images/盾 密码 安全.png new file mode 100644 index 0000000..8697cb1 Binary files /dev/null and b/assets/images/盾 密码 安全.png differ diff --git a/assets/images/矢量智能对象(1).png b/assets/images/矢量智能对象(1).png new file mode 100644 index 0000000..231ad28 Binary files /dev/null and b/assets/images/矢量智能对象(1).png differ diff --git a/assets/images/矢量智能对象.png b/assets/images/矢量智能对象.png new file mode 100644 index 0000000..21b75eb Binary files /dev/null and b/assets/images/矢量智能对象.png differ diff --git a/assets/images/矩形 1 拷贝 39.png b/assets/images/矩形 1 拷贝 39.png new file mode 100644 index 0000000..3a5e682 Binary files /dev/null and b/assets/images/矩形 1 拷贝 39.png differ diff --git a/assets/images/矩形 17.png b/assets/images/矩形 17.png new file mode 100644 index 0000000..b9c67ac Binary files /dev/null and b/assets/images/矩形 17.png differ diff --git a/assets/images/系统维护.png b/assets/images/系统维护.png new file mode 100644 index 0000000..2fc0d0e Binary files /dev/null and b/assets/images/系统维护.png differ diff --git a/assets/images/组 1.png b/assets/images/组 1.png new file mode 100644 index 0000000..fd05710 Binary files /dev/null and b/assets/images/组 1.png differ diff --git a/assets/images/聚焦.png b/assets/images/聚焦.png new file mode 100644 index 0000000..77ed21a Binary files /dev/null and b/assets/images/聚焦.png differ diff --git a/assets/images/背景图.png b/assets/images/背景图.png new file mode 100644 index 0000000..156c7ae Binary files /dev/null and b/assets/images/背景图.png differ diff --git a/assets/images/装饰图片10.png b/assets/images/装饰图片10.png new file mode 100644 index 0000000..9121ced Binary files /dev/null and b/assets/images/装饰图片10.png differ diff --git a/assets/images/警察.png b/assets/images/警察.png new file mode 100644 index 0000000..c21ef55 Binary files /dev/null and b/assets/images/警察.png differ diff --git a/assets/images/车流量日统计.png b/assets/images/车流量日统计.png new file mode 100644 index 0000000..fbc8327 Binary files /dev/null and b/assets/images/车流量日统计.png differ diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..e96ef60 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,32 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..f2872cf --- /dev/null +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 9.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..a9b68f8 --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,496 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; + 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + CF3B75C9A7D2FA2A4C99F110 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + ); + path = Runner; + sourceTree = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 97C146F21CF9000F007C117D /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, + 97C146F31CF9000F007C117D /* main.m in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.hyzpYibin; + PRODUCT_NAME = "$(TARGET_NAME)"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.hyzpYibin; + PRODUCT_NAME = "$(TARGET_NAME)"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.hyzpYibin; + PRODUCT_NAME = "$(TARGET_NAME)"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..a28140c --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/AppDelegate.h b/ios/Runner/AppDelegate.h new file mode 100644 index 0000000..36e21bb --- /dev/null +++ b/ios/Runner/AppDelegate.h @@ -0,0 +1,6 @@ +#import +#import + +@interface AppDelegate : FlutterAppDelegate + +@end diff --git a/ios/Runner/AppDelegate.m b/ios/Runner/AppDelegate.m new file mode 100644 index 0000000..70e8393 --- /dev/null +++ b/ios/Runner/AppDelegate.m @@ -0,0 +1,13 @@ +#import "AppDelegate.h" +#import "GeneratedPluginRegistrant.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [GeneratedPluginRegistrant registerWithRegistry:self]; + // Override point for customization after application launch. + return [super application:application didFinishLaunchingWithOptions:launchOptions]; +} + +@end diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..28c6bf0 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..f091b6b Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cde121 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..d0ef06e Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..dcdc230 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..c8f9ed8 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..75b2d16 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..c4df70d Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..6a84f41 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..d0e1f58 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +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. \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 0000000..174268a --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + hyzp_ybqx + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/ios/Runner/main.m b/ios/Runner/main.m new file mode 100644 index 0000000..dff6597 --- /dev/null +++ b/ios/Runner/main.m @@ -0,0 +1,9 @@ +#import +#import +#import "AppDelegate.h" + +int main(int argc, char* argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/lib/components/EncryptUtil.dart b/lib/components/EncryptUtil.dart new file mode 100644 index 0000000..c7448d1 --- /dev/null +++ b/lib/components/EncryptUtil.dart @@ -0,0 +1,77 @@ +import 'dart:convert'; +import 'package:encrypt/encrypt.dart'; + +class EncryptUtil { + static const List intList16 = [ + 3, + 8, + 7, + 8, + 3, + 7, + 8, + 9, + 1, + 8, + 6, + 8, + 2, + 8, + 7, + 8 + ]; + + //aes加密 + static String aesEncode0(String content) { + try { + final key = Key.fromBase64(base64Encode(intList16)); + final encrypter = Encrypter(AES(key, mode: AESMode.cbc)); + final encrypted = + encrypter.encrypt(content, iv: IV.fromBase64(base64Encode(intList16))); + return encrypted.base64; + } catch (err) { + print("aes encode error:$err"); + return content; + } + } + + //aes加密 + static String aesEncode(String content) { + try { + final key = Key.fromBase64(base64Encode(intList16)); + final encrypter = Encrypter(AES(key, mode: AESMode.cbc)); + final encrypted = + encrypter.encrypt(content, iv: IV.fromBase64(base64Encode(intList16))); + return encrypted.base64; + } catch (err) { + print("aes encode error:$err"); + return content; + } + } + + //aes解密 + static String aesDecode(String base64) { + try { + final key = Key.fromBase64(base64Encode(intList16)); + final encrypter = Encrypter(AES(key, mode: AESMode.cbc)); + return encrypter.decrypt64(base64, + iv: IV.fromBase64(base64Encode(intList16))); + } catch (err) { + print("aes decode error:$err"); + return base64; + } + } + + //aes解密-dynamic + static dynamic aesDecodeDynamic(dynamic base64) { + try { + final key = Key.fromBase64(base64Encode(intList16)); + final encrypter = Encrypter(AES(key, mode: AESMode.cbc)); + return encrypter.decrypt64(base64, + iv: IV.fromBase64(base64Encode(intList16))); + } catch (err) { + print("aes decode error:$err"); + return base64; + } + } +} diff --git a/lib/components/LogUtil.dart b/lib/components/LogUtil.dart new file mode 100644 index 0000000..ad05210 --- /dev/null +++ b/lib/components/LogUtil.dart @@ -0,0 +1,150 @@ +import 'package:flutter/cupertino.dart'; + +void segmentPrint(String msg, {int len = 600}) { + var outStr = StringBuffer(); + for (var index = 0; index < msg.length; index++) { + outStr.write(msg[index]); + if (index % len == 0 && index != 0) { + print(outStr); + outStr.clear(); + var lastIndex = index + 1; + if (msg.length - lastIndex < len) { + var remainderStr = msg.substring(lastIndex, msg.length); + print(remainderStr); + break; + } + } + } +} + +// ///是否在生产环境 +// ///const bool isDebug = !const bool.fromEnvironment("dart.vm.product"); +// +// //参数可选 isDebug默认true limitLength默认800 +// LogUtil.init(title: "来自LogUtil", isDebug: isDebug,limitLength:800); +// +// var log = "我是日志"; +// //仅在Debug时打印 +// LogUtil.d(log); +// LogUtil.d("我是日志"); +// +// //在所有环境中打印 +// LogUtil.v(log); +// LogUtil.v("我是日志"); +// ———————————————— +// 版权声明:本文为CSDN博主「懒散的阿乐」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 +// 原文链接:https://blog.csdn.net/zylvip/article/details/102608256 + +class LogUtil { + static var _separator = "="; + static var _split = + "$_separator$_separator$_separator$_separator$_separator$_separator$_separator$_separator$_separator"; + static var _title = "Yl-Log"; + static var _isDebug = true; + static int _limitLength = 1200; + static String _startLine = "$_split$_title$_split"; + static String _endLine = "$_split$_separator$_separator$_separator$_split"; + + static void init({String title, @required bool isDebug, int limitLength}) { + _title = title; + _isDebug = isDebug; + _limitLength = limitLength ??= _limitLength; + _startLine = "$_split$_title$_split"; + var endLineStr = StringBuffer(); + var cnCharReg = RegExp("[\u4e00-\u9fa5]"); + for (int i = 0; i < _startLine.length; i++) { + if (cnCharReg.stringMatch(_startLine[i]) != null) { + endLineStr.write(_separator); + } + endLineStr.write(_separator); + } + _endLine = endLineStr.toString(); + } + + //仅Debug模式可见 + static void d(dynamic obj) { + if (_isDebug) { + _log(obj.toString()); + } + } + + static void v(dynamic obj) { + _log(obj.toString()); + } + + static void _log(String msg) { + print("$_startLine"); + _logEmpyLine(); + if (msg.length < _limitLength) { + print(msg); + } else { + segmentationLog(msg); + } + _logEmpyLine(); + print("$_endLine"); + } + + static void segmentationLog(String msg) { + var outStr = StringBuffer(); + for (var index = 0; index < msg.length; index++) { + outStr.write(msg[index]); + if (index % _limitLength == 0 && index != 0) { + print(outStr); + outStr.clear(); + var lastIndex = index + 1; + if (msg.length - lastIndex < _limitLength) { + var remainderStr = msg.substring(lastIndex, msg.length); + print(remainderStr); + break; + } + } + } + } + + static void _logEmpyLine() { + print(""); + } +} + +//common_utils 工具类已经将pring 封装为工具类 +// +// common_utils: ^1.1.1 +// 使用common_utils工具类中的LogUtil +// +// //初始化设置 LogUtil +// LogUtil.init(true); +// //输出日志 +// LogUtil.v("test"); +// 当然 LogUtil 的 init 方法可根据是否是生产环境来配置 true 与 false ,如果是 false ,则不输出日志,这样的一个优化也是应用在发版本后可以节省向控制台输出日志信息的消耗。 +// +// 封装源码如下 + +class LogUtil2 { + static const String _TAG_DEF = "###common_utils###"; + + static bool debuggable = false; //是否是debug模式,true: log v 不输出. + static String TAG = _TAG_DEF; + + static void init({bool isDebug = false, String tag = _TAG_DEF}) { + debuggable = isDebug; + TAG = tag; + } + + static void e(Object object, {String tag}) { + _printLog(tag, ' e ', object); + } + + static void v(Object object, {String tag}) { + if (debuggable) { + _printLog(tag, ' v ', object); + } + } + + static void _printLog(String tag, String stag, Object object) { + StringBuffer sb = new StringBuffer(); + sb.write((tag == null || tag.isEmpty) ? TAG : tag); + sb.write(stag); + sb.write(object); + print(sb.toString()); + } +} diff --git a/lib/components/UserAuthority.dart b/lib/components/UserAuthority.dart new file mode 100644 index 0000000..6bbce50 --- /dev/null +++ b/lib/components/UserAuthority.dart @@ -0,0 +1,549 @@ +import 'package:dio/dio.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hyzp_ybqx/config/service_url.dart'; +import 'package:hyzp_ybqx/services/EventBus.dart'; + +import 'commonFun.dart'; + +///用户权限管理 +// static const String getUserAccessUrl = ServiceUrl + '?s=App.User_User.GetAccess'; //1、根据用户ID获取用户所属角色 +// static const String getUserGroupUrl = ServiceUrl + '?s=App.User_User.GetGroup'; //2、获取后台用户角色分组数据 +// static const String getUserGroupListUrl = ServiceUrl + '?s=App.User_User.GetGroupList'; //3、获取后台用户角色分组分页列表数据 +// static const String getUserAuthUrl = ServiceUrl + '?s=App.User_User.GetAuth'; //4、获取后台功能分类数据 +// static const String getUserAuthListUrl = ServiceUrl + '?s=App.User_User.GetAuthList'; //5、获取后台功能分类分页列表数据 + +//2.2、获取后台用户全部角色分组数据 +Future getUserGroupAll({int user_id = -1}) async { + if (user_id < 0) { + user_id = g_userInfo.mapUserInfo['user_id']; + } + //1、根据用户ID获取用户所属角色(用户组) + getUserAccess(user_id: user_id).then((value) { + // I/flutter (15540): g_userInfo.userGroupIDlist = [31, 27] + int len = g_userInfo.userGroupIDlist.length; + for (int i = 0; i < len; i++) { + getUserGroup(group_id: g_userInfo.userGroupIDlist[i]); + } + print('g_userInfo.userRulesMap = ${g_userInfo.userRulesMap}'); + }); +} + +//2.1、获取后台用户指定 group_id 角色分组数据 +Future getUserGroup({@required int group_id}) async { + var api = ServicePath.getUserGroupUrl; + print(api); + //I/flutter (15540): http://125.64.218.67:9904/?s=App.User_User.GetGroup + + String random = RandomBit(6); + Map map = { + 'random': random, + 'sign': GenerateMd5(APPkey + random), + 'id': group_id, + }; + + print('开始处理登录请求...'); + Response response; + Dio dio = Dio(); + + //返回结果 + // 返回字段 类型 说明 + // id 整型 角色分组ID + // type 整型 角色分组类型:1普通角色 + // level 整型 角色分组级次(1-顶级,2-次级) + // pid 整型 上级角色分组ID(为0则表示为顶级) + // rules 字符串 授权功能ID(如:5,7,112,331),表示此角色拥有相应ID的功能权限 + //{ + // "ret": 200, + // "data": { + // "id": 31, + // "jgid": 2, + // "type": 0, + // "title": "监控室", + // "level": 0, + // "pid": 0, + // "sort": 2, + // "status": 1, + // "rules": "" + // }, + // "msg": "" + // } + + try { + print('response = ${response}'); + // I/flutter (15540): response = null + response = await dio.post(api, data: map); + print('response = ${response}'); + //I/flutter (15540): response = {"ret":200,"data":{"id":31,"jgid":2,"type":0,"title":"监控室","level":0,"pid":0,"sort":2,"status":1,"rules":""},"msg":""} + if (response.statusCode == 200) { + Map _mapRet = await getMapFromJson(response.data); + print('_mapRet = ${_mapRet}'); + // I/flutter (15540): _mapRet = {ret: 200, data: {id: 31, jgid: 2, type: 0, title: 监控室, level: 0, pid: 0, sort: 2, status: 1, rules: }, msg: } + //print('_mapRet[\'data\']["rules"] is a = ${_mapRet['data']["rules"] is String}'); + //_mapRet['data']["rules"] is a = true + + //I/flutter (15540): _mapRet = {ret: 200, data: false, msg: } + //I/flutter (15540): 网络请求过程异常e = NoSuchMethodError: Class 'bool' has no instance method '[]'. + if (_mapRet['data'] is Map) { + String _rules = _mapRet['data']["rules"].trim(); + //print('_rules = ${_rules}'); + print('_rules.length = ${_rules.length}'); + //_rules.length = 0 + List _list = []; + if (_rules.isNotEmpty) { + List _list2 = _rules.split(','); + print('_list2 = $_list2'); + //I/flutter (15540): _list2 = [1968, 1972, 1973, 1969, 1976, 1977, 2008, 2009, 2011, 2014, + // 2015, 2018, 2029, 2030, 2031, 2054, 2055, 2035, 2036, 2037, 2041, 2042, 2043, 2047, 2048, + // 2049, 2053, 1970, 1980, 1981, 1971, 1984, 1985, 1992, 1993, 2000, 2001, 2020, 2022] + int len = _list2.length; + List _list3 = []; + for (int i = 0; i < len; i++) { + _list3.add(int.parse(_list2[i].trim())); + } + _list = _list3; + } + print('_list = $_list'); + //I/flutter (15540): _list = [{uid: 135, group_id: 31}, {uid: 135, group_id: 27}] + g_userInfo.userRulesMap[_mapRet['data']['id']] = _list; + print('g_userInfo.userRulesMap = ${g_userInfo.userRulesMap}'); + // I/flutter (15540): g_userInfo.userGroupIDlist = [31, 27] + } else { + print('获取数据失败!'); + } + print('网络请求过程正常完成'); + } else { + throw Exception('后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('网络请求过程异常e = ${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } +} + +//1、根据用户ID获取用户所属角色(用户组) +Future getUserAccess({int user_id = -1}) async { + if (user_id < 0) { + user_id = g_userInfo.mapUserInfo['user_id']; + } + + var api = ServicePath.getUserAccessUrl; + print(api); + //I/flutter (15540): http://125.64.218.67:9904/?s=App.User_User.GetAccess + + String random = RandomBit(6); + Map map = { + 'random': random, + 'sign': GenerateMd5(APPkey + random), + 'uid': user_id, + }; + + print('开始处理登录请求...'); + Response response; + Dio dio = Dio(); + + //{ + // "ret": 200, + // "data": [ + // { + // "uid": 136, + // "group_id": 32 + // }, + // { + // "uid": 136, + // "group_id": 33 + // } + // ], + // "msg": "" + // } + + try { + print('response = ${response}'); + // I/flutter (15540): response = null + response = await dio.post(api, data: map); + print('response = ${response}'); + // I/flutter (15540): response = {"ret":200,"data":[{"uid":135,"group_id":31},{"uid":135,"group_id":27}],"msg":""} + if (response.statusCode == 200) { + Map _mapRet = await getMapFromJson(response.data); + print('_mapRet = ${_mapRet}'); + // I/flutter (15540): _mapRet = {ret: 200, data: [{uid: 135, group_id: 31}, {uid: 135, group_id: 27}], msg: } + List _list = _mapRet['data']; + print('_list = $_list'); + //I/flutter (15540): _list = [] + + if (_list.isNotEmpty) { + g_userInfo.userGroupIDlist.clear(); + int len = _list.length; + for (int i = 0; i < len; i++) { + g_userInfo.userGroupIDlist.add(_list[i]["group_id"]); + eventBus.fire(GroupIdUpdateEvent('g_userInfo.userGroupIDlist 数据已更新')); //这样刷新有效 + } + } + print('g_userInfo.userGroupIDlist = ${g_userInfo.userGroupIDlist}'); + //I/flutter (15540): g_userInfo.userRulesMap = {31: []} + + print('网络请求过程正常完成'); + } else { + throw Exception('后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('网络请求过程异常e = ${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } +} + +//3、获取后台用户角色分组分页列表数据 ServicePath.getUserGroupListUrl +//5、获取后台功能分类分页列表数据 ServicePath.getUserAuthListUrl +//x、获取后台 x 分类分页列表数据 +// int pages 为获取的页数, -1 为 All;int perpage 为每页记录数 +// 返回: +// mapRecordList = { +// 'mapRecordListRet': {}, +// 'listRecordList': [], +// }; +Future getRecordList({@required String api, int pages = 3, int perpage = 20}) async { + print(api); + int _total = 0; //第一页时保存数据库中记录总数 + int _counter = 0; //已读取的记录数计数器 + + int _page = 0; + String random = RandomBit(6); + Map map = { + 'random': random, + 'sign': GenerateMd5(APPkey + random), + 'page': _page, + 'perpage': perpage, + }; + + print('开始处理登录请求...'); + Response response; + Dio dio = Dio(); + + Map mapRecordList = { + 'mapRecordListRet': {}, + 'listRecordList': [], + }; + try { + while (pages < 0 ? true : _page < pages) { + map['page']++; + + response = await dio.post(api, data: map); + print('response = ${response.toString()}'); + if (response.statusCode == 200) { + mapRecordList['mapRecordListRet'] = await getMapFromJson(response.data); + print('mapRecordList[\'mapRecordListRet\'] = ${mapRecordList['mapRecordListRet']}'); + //第一页时保存数据库中记录总数 + if (1 == map['page']) { + _total = mapRecordList['mapRecordListRet']['data']['total']; + } + mapRecordList['listRecordList'].addAll(mapRecordList['mapRecordListRet']['data']['items']); + //print('mapRecordList[\'listRecordList\'] = ${mapRecordList['listRecordList']}'); + + print('map[\'page\'] = ${map['page']}'); + _counter = mapRecordList['listRecordList'].length; //已读取的记录数计数器 + print('_counter = $_counter'); + //I/flutter (23648): _counter = 8 + print('_total = $_total'); + // I/flutter (23648): _total = 8 + //已读取的记录数计数器,超过或等于数据库中记录总数时,则终止循环 + if (_counter >= _total) { + return mapRecordList; + } + + print('第 ${map['page']} 次网络请求过程正常完成'); + } else { + throw Exception('后端接口出现异常,请检测代码和服务器情况.........'); + } + } + } catch (e) { + print('网络请求过程异常e = ${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + return mapRecordList; +} + +Map mapUserGroupList = { + 'mapRecordListRet': { + "ret": 200, + "data": { + "items": [ + { + "id": 35, + "jgid": 2, + "type": 0, + "title": "局领导", + "level": 0, + "pid": 0, + "sort": 1, + "status": 1, + "rules": "" + }, + { + "id": 34, + "jgid": 2, + "type": 0, + "title": "系统管理", + "level": 0, + "pid": 0, + "sort": 1, + "status": 1, + "rules": "" + }, + { + "id": 33, + "jgid": 2, + "type": 1, + "title": "参观者", + "level": 0, + "pid": 0, + "sort": 4, + "status": 1, + "rules": + "1968,1972,1973,1969,1976,1977,2008,2009,2011,2014,2015,2018,2029,2030,2031,2054,2055,2035,2036,2037,2041,2042,2043,2047,2048,2049,2053,1970,1980,1981,1971,1984,1985,1992,1993,2000,2001,2019,2020,2022" + }, + { + "id": 32, + "jgid": 2, + "type": 0, + "title": "演示账户", + "level": 0, + "pid": 0, + "sort": 3, + "status": 1, + "rules": + "1968,1972,1973,1969,1976,1977,2008,2009,2011,2014,2015,2018,2029,2030,2031,2054,2055,2035,2036,2037,2041,2042,2043,2047,2048,2049,2053,1970,1980,1981,1971,1984,1985,1992,1993,2000,2001,2020,2022" + }, + { + "id": 31, + "jgid": 2, + "type": 0, + "title": "监控室", + "level": 0, + "pid": 0, + "sort": 2, + "status": 1, + "rules": "" + }, + { + "id": 30, + "jgid": 2, + "type": 0, + "title": "中心领导", + "level": 0, + "pid": 0, + "sort": 1, + "status": 1, + "rules": + "1968,1972,1973,1974,1975,1969,1976,1977,1978,1979,1970,1980,1981,1982,1983,1971,1984,1985,1986,1987,1988,1989,1990,1991,1992,1993,1994,1995,1996,1997,1998,1999,2000,2001,2002,2003,2004,2005,2006,2007" + }, + { + "id": 28, + "jgid": 2, + "type": 1, + "title": "审核操作员", + "level": 0, + "pid": 0, + "sort": 3, + "status": 1, + "rules": + "1968,1972,1973,1974,1975,1969,1976,1977,1978,1979,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2029,2030,2031,2054,2055,2032,2033,2034,2035,2036,2037,2038,2039,2040,2041,2042,2043,2044,2045,2046,2047,2048,2049,2050,2051,2052,2053,1970,1980,1981,1982,1983" + }, + { + "id": 26, + "jgid": 2, + "type": 1, + "title": "管理员", + "level": 0, + "pid": 0, + "sort": 2, + "status": 1, + "rules": + "1968,1972,1973,1974,1975,1969,1976,1977,1978,1979,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2029,2030,2031,2054,2055,2032,2033,2034,2035,2036,2037,2038,2039,2040,2041,2042,2043,2044,2045,2046,2047,2048,2049,2050,2051,2052,2053,1970,1980,1981,1982,1983,1971,1984,1985,1986,1987,1988,1989,1990,1991,2019,2020,2021,2022,2023,2024,2025,2026,2027,2028" + } + ], + "total": 8, + "page": 1, + "perpage": 20 + }, + "msg": "" + }, + 'listRecordList': [], +}; + +Map mapUserAuthList = { + 'mapRecordListRet': {}, + 'listRecordList': [], +}; + +Map mapRecordList = { + 'mapRecordListRet': {}, + 'listRecordList': [], +}; + +//用户功能权限索引map,便于直观理解和处理。map_UserAuth.length = 78;1968 - 2069,中间有许多ID没有 +Map map_UserAuth = { + 1968: '黑烟车初审', + 1969: '黑烟车复审', + 1970: '推送交警', + 1971: '设备管理', + 1972: '信息审核', + 1973: '查看', + 1975: '审核', + 1976: '信息审核', + 1977: '查看', + 1979: '审核', + 1980: '推送交警', + 1981: '查看', + 1983: '审核', + 1984: 'LED显示设置', + 1985: '查看', + 1986: '新增', + 1987: '编辑', + 1988: '锁定', + 1989: '删除', + 1990: '导出', + 1991: '导入', + 1992: '设备管理', + 1993: '查看', + 1994: '新增', + 1995: '编辑', + 1996: '锁定', + 1997: '删除', + 1998: '导出', + 1999: '导入', + 2000: '点位管理', + 2001: '查看', + 2002: '新增', + 2003: '编辑', + 2004: '锁定', + 2005: '删除', + 2006: '导出', + 2007: '导入', + 2008: '历史数据', + 2009: '查看', + 2010: '新增', + 2014: '审核', + 2015: '打印', + 2016: '导出', + 2019: '报警信息管理', + 2020: '查看', + 2023: '锁定', + 2025: '审核', + 2027: '导出', + 2028: '分析', + 2029: '查询与统计', + 2030: '历史数据查询', + 2031: '查看', + 2034: '导出', + 2035: '分析', + 2036: '车辆点位频率分析', + 2037: '查看', + 2041: '分析', + 2042: '车辆轨迹查询', + 2043: '查看', + 2047: '分析', + 2048: '年度数据统计', + 2049: '查看', + 2053: '分析', + 2054: '实时统计', + 2055: '实时统计今日抓拍数量', + 2056: '历史数据', + 2057: '查看', + 2059: '审核', + 2060: '导出', + 2061: '监测点位状态', + 2062: '查看', + 2063: '分析', + 2064: '监测点位状态详情', + 2065: '查看', + 2066: '分析', + 2067: '车流量统计', + 2068: '查看', + 2069: '分析', +}; + +Future getUserAuthMap({@required String value, String key = 'id'}) { + int len = mapUserAuthList['listRecordList'].length; + map_UserAuth.clear(); + for (int i = 0; i < len; i++) { + map_UserAuth[mapUserAuthList['listRecordList'][i][key]] = + mapUserAuthList['listRecordList'][i][value]; + } + + map_UserAuth = mapSort(map_UserAuth); + print('map_UserAuth.length = ${map_UserAuth.length}'); + //print('map_UserAuth = $map_UserAuth'); //输出不全 + my_segmentPrint(json_print(map_UserAuth, 1)); +} + +Future getUserAuth() { + int len = mapUserAuthList['listRecordList'].length; + map_UserAuth.clear(); + for (int i = 0; i < len; i++) { + map_UserAuth[mapUserAuthList['listRecordList'][i]["id"]] = + mapUserAuthList['listRecordList'][i]["title"]; + } + + //按抓拍次数排序,升序 + // listHycsGetList2.sort((a, b) => + // (a[mapWzxxDataText[_selectedValue]].split(',').length.toString()) + // .compareTo(b[mapWzxxDataText[_selectedValue]].split(',').length.toString())); + + //print('map_UserAuth = $map_UserAuth'); //输出不全 + print('map_UserAuth.length = ${map_UserAuth.length}'); + my_segmentPrint('map_UserAuth = ${map_UserAuth}'); + + map_UserAuth = mapSort(map_UserAuth); + my_segmentPrint('map_UserAuth = ${map_UserAuth}'); + //I/flutter ( 5140): map_UserAuth = {1968: 黑烟车初审, 1969: 黑烟车复审, 1970: 推送交警, 1971: 设备管理, 1972: 信息审核, 1973: 查看, 1975: 审核, 1976: 信息审核, 1977: 查看, 197 + // 9: 审核, 1980: 推送交警, 1981: 查看, 1983: 审核, 1984: LED显示设置, 1985: 查看, 1986: 新增, 1987: 编辑, 1988: 锁定, 1989: 删除, 1990: 导出, 1991: 导入, 1992: 设备管理, 1993: + // 查看, 1994: 新增, 1995: 编辑, 1996: 锁定, 1997: 删除, 1998: 导出, 1999: 导入, 2000: 点位管理, 2001: 查看, 2002: 新增, 2003: 编辑, 2004: 锁定, 2005: 删除, 2006: 导出, 2007: 导 + // 入, 2008: 历史数据, 2009: 查看, 2010: 新增, 2014: 审核, 2015: 打印, 2016: 导出, 2019: 报警信息管理, 2020: 查看, 2023: 锁定, 2025: 审核, 2027: 导出, 2028: 分析, 2029: 查询与统 + // 计, 2030: 历史数据查询, 2031: 查看, 2034: 导出, 2035: 分析, 2036: 车 + // I/flutter ( 5140): 辆点位频率分析, 2037: 查看, 2041: 分析, 2042: 车辆轨迹查询, 2043: 查看, 2047: 分析, 2048: 年度数据统计, 2049: 查看, 2053: 分析, 2054: 实时统计, 2055: 实时 + // 统计今日抓拍数量, 2056: 历史数据, 2057: 查看, 2059: 审核, 2060: 导出, 2061: 监测点位状态, 2062: 查看, 2063: 分析, 2064: 监测点位状态详情, 2065: 查看, 2066: 分析, 2067: 车流量 + // 统计, 2068: 查看, 2069: 分析} +} + +mapSort(Map map) { + // List keys = map.keys.toList(); + // // key排序 + // keys.sort((a, b) { + // List al = a.codeUnits; + // List bl = b.codeUnits; + // for (int i = 0; i < al.length; i++) { + // if (bl.length <= i) return 1; + // if (al[i] > bl[i]) { + // return 1; + // } else if (al[i] < bl[i]) return -1; + // } + // return 0; + // }); + + var sortedKeys = map_UserAuth.keys.toList()..sort(); + //print('sortedKeys = $sortedKeys'); //输出不全 + //segmentPrint('sortedKeys = ${sortedKeys}'); + //I/flutter ( 5140): sortedKeys = [1968, 1969, 1970, 1971, 1972, 1973, 1975, 1976, 1977, 1979, 1980, 1981, 1983, 1984, 1985, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 199 + // 4, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2014, 2015, 2016, 2019, 2020, 2023, 2025, 2027, 2028, 2029, 2030, 2031, 203 + // 4, 2035, 2036, 2037, 2041, 2042, 2043, 2047, 2048, 2049, 2053, 2054, 2055, 2056, 2057, 2059, 2060, 2061, 2062, 2063, 2064, 2065, 2066, 2067, 2068, 2069] + + //new一个map按照keys的顺序将原先的map数据取出来就可以了。 + Map sortedMap = {}; + sortedKeys.forEach((element) { + sortedMap[element] = map[element]; + }); + + return sortedMap; +} diff --git a/lib/components/UserInfo.dart b/lib/components/UserInfo.dart new file mode 100644 index 0000000..b47d6db --- /dev/null +++ b/lib/components/UserInfo.dart @@ -0,0 +1,72 @@ +import 'EncryptUtil.dart'; +import 'dart:convert'; + +class UserInfo { + UserInfo({this.mapUserInfoRet}) { + setUserInfo(theMapUserInfoRet: mapUserInfoRet); + } + + setUserInfo({Map theMapUserInfoRet}) { + if (200 == theMapUserInfoRet["ret"]) { + mapUserInfoRet = theMapUserInfoRet; + mapUserInfo = theMapUserInfoRet["data"]; + print('mapUserInfo = ${mapUserInfo.toString()}'); + } + } + + //_mapGetData = {is_login: true, user_id: 135, token: 4C5B3F93FEACAEF4B6CAA7296F22CC67825D7E48B867614383D19E3F23DFE510} + setUserInfoFaceLogin(Map _mapGetData) { + mapUserInfo['user_id'] = _mapGetData['user_id']; + mapUserInfo['token'] = _mapGetData['token']; + } + + Map mapUserInfoRet = { + "ret": 200, + "data": { + "is_login": true, + "user_id": 1, + "token": "B93EC91FA2FE293B7077162D4527FC4BB228CD6C0A4F24A882B9A8BBE6C3FB47" + }, + "msg": "" + }; + + Map mapUserInfo = { + "is_login": false, + "user_id": -1, + "token": "" + }; + + //若list[i]为'',解密时会报错:aes decode error:RangeError: Value not in range: -16 + String thisAndroidId = ''; //每个手机唯一的设备号 + String username = ''; + String password = ''; + String userLoginInfo = ''; + List userGroupIDlist = []; //用户所属组列表 + Map userRulesMap = {}; //用户所属组的权限列表 + + String getUserinfoEncrypted2() { + String userinfoEncrypted1 = EncryptUtil.aesEncode(thisAndroidId) + + '\n' + + EncryptUtil.aesEncode(username) + + '\n' + + EncryptUtil.aesEncode(password) + + '\n' + + EncryptUtil.aesEncode(userLoginInfo); + String userinfoEncrypted2 = EncryptUtil.aesEncode(userinfoEncrypted1); + return userinfoEncrypted2; + } + + String getUserinfoDencrypted2(String userinfoEncrypted2) { + String userinfoDencrypted1 = EncryptUtil.aesDecode(userinfoEncrypted2); + + List list = userinfoDencrypted1.split('\n'); + int len = list.length; + String userinfoDencrypted2 = ''; + print('len = $len'); + for (int i = 0; i < len; i++) { + list[i] = EncryptUtil.aesDecode(list[i]); + userinfoDencrypted2 += ('' == userinfoDencrypted2 ? '' : '\n') + list[i]; + } + return userinfoDencrypted2; + } +} diff --git a/lib/components/commonFun.dart b/lib/components/commonFun.dart new file mode 100644 index 0000000..9826a8b --- /dev/null +++ b/lib/components/commonFun.dart @@ -0,0 +1,678 @@ +import 'dart:convert'; +import 'dart:developer' as developer; +import 'dart:io'; +import 'dart:math'; + +import 'package:camera/camera.dart'; +import 'package:convert/convert.dart'; +import 'package:crypto/crypto.dart' as crypto; +import 'package:device_info/device_info.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +//import '../my_wechat_assets_picker_fix/my_asset_picker_1.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hyzp_ybqx/provider/player_region.dart'; + +import 'UserInfo.dart'; + +//LED字幕信息 +//String g_ledMessage = '绿水青山就是金山银山 宜宾市生态环境局宣。'; + +// 是否已经调用 FlutterDownloader.initialize(debug: true) +bool bFlutterDownloader_initialize = false; +bool bNewVer = false; //是否发现新版本 + +//处理延时登录,判断从网络获取三种统计数据是否完成 +bool bMayLogin = false; +//处理延时登录,判断是否已经点击登录按钮 +bool bPreLoading = false; +//处理延时登录,判断用户名登录是否验证通过 +bool bLoginVerify = false; + +bool bHasMore = true; + +//part library +//dart中,通过使用part、part of、library来实现拆分库,这样,就可以将一个庞大的库拆分成各种小库,只要引用主库即可 + +//点位总数 +int dwSum = -1; + +Size sizeWindowPhysicalSize; + +//String dateAppCompile = '2020.12.30'; //1.0.1+1 +//String dateAppCompile = '2021.02.20'; //1.2.5+1 +//String dateAppCompile = '2021.03.18'; //1.2.6+1 +//String dateAppCompile = '2021.05.18'; //1.2.7 +List g_list = []; + +//正在获取点位视频标志,禁止重入 +bool getingDwVideo = false; +int getCount = 1; //获取点位视频地址尝试次数 +int getSumTime = 0; //获取点位视频地址耗时(秒) +int getingIndex = -1; //正在获取视频的点位的索引号 +String getingDwmc = ''; //正在获取视频的点位名称 +String urlnew = + "http://www.yibinu.edu.cn/__local/5/35/DF/264049B7E978EEE2F5849688986_05D4A6FE_152CDB8C.mp4?e=.mp4"; + +bool isVideoUrl(String url, {bool showToast = false}) { + print('url = $url'); + String prefix = url.substring(0, 4); + List list = ['http', 'rtmp', 'rstp']; + for (String item in list) { + if (prefix == item) { + return true; + } + } + + if (showToast) { + Fluttertoast.showToast( + msg: '获取视频地址失败', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + + return false; +} + +final TextEditingController myController = TextEditingController(); + +bool Playing = false; //禁止同时启动两次播放器 + +//final FijkPlayer player = FijkPlayer(); +int g_iIndex = 0; +PlayerRegionProvide playerRegionProvide; + +Future sysPop() async { + // currentPos = player.currentPos.inMilliseconds; //seekto方法的参数是毫秒 + // await writeCurrentPosFile(); + // await player.stop(); + await SystemChannels.platform.invokeMethod('SystemNavigator.pop'); +} + +//人脸注册和人脸识别登录成功标志 +int faceReg = -1; //1 成功,0 失败,-1 处理中 +int faceLogin = -1; //1 成功,0 失败,-1 处理中 + +//人脸注册时所需用户ID +int faceRegUserID = -1; //人脸注册时所需用户ID,-1 非法 + +List cameras; +UserInfo g_userInfo = UserInfo(mapUserInfoRet: { + "ret": 200, + "data": { + "is_login": true, + "user_id": 1, + "token": "B93EC91FA2FE293B7077162D4527FC4BB228CD6C0A4F24A882B9A8BBE6C3FB47" + }, + "msg": "" +}); + +Future getMapFromJson(var response) async { + String _str = json.encode(response); + Map _map = json.decode(_str); + return _map; +} + +// Future getVideoList(BuildContext context) async { +// List assets = []; +// return await MyAssetPicker.pickAssets( +// context, +// maxAssets: 1, +// selectedAssets: assets, +// requestType: RequestType.video, +// ); +// } + +Future getAndroidId() async { + DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); + AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; + //print('每个手机唯一的设备号:${androidInfo.androidId}'); // e.g. "Moto G (4)" + g_userInfo.thisAndroidId = androidInfo.androidId; + return g_userInfo.thisAndroidId; +} + +// void playOrPause() { +// playerRegionProvide.changePlayerState(bPlaying); +// +// if (bPlaying) { +// player.start(); +// } else { +// player.pause(); +// } +// +// Storage.setString('bFirstPlay', bFirstPlay ? 'true' : 'false'); +// Storage.setString('bPlaying', bPlaying ? 'true' : 'false'); +// +// //updateFile(); +// } +// +// void playAndPause() { +// if (player.state == FijkState.started) { +// bPlaying = false; +// player.pause(); +// } else if (player.state == FijkState.paused) { +// bPlaying = true; +// player.start(); +// } +// //setState(() {}); +// playerRegionProvide.changePlayerState(bPlaying); +// Storage.setString('bPlaying', bPlaying ? 'true' : 'false'); +// } + +Alignment getAlignment(Offset offset, Size size) { + // final double centerX = offset.dx / 2.0; + // final double centerY = offset.dy / 2.0; + + //offset.dx = centerX + alignment.x * centerX; + //double alignmentX = (0.0 == centerX) ? 0.0 : ((offset.dx - centerX) / centerX); + double alignmentX = (offset.dx / size.width).clamp(-1.0, 1.0); + + //offset.dy = centerY + alignment.y * centerY; + //double alignmentY = (0.0 == centerY) ? 0.0 : ((offset.dy - centerY) / centerY); + double alignmentY = (offset.dy / size.height).clamp(-1.0, 1.0); + + // print('offset.dx = ${offset.dx}, offset.dy = ${offset.dy}'); + print('Alignment.X = $alignmentX, Alignment.Y = $alignmentY'); + return Alignment(alignmentX, alignmentY); +} + +//flutter (dart)生成N位随机数 +//https://blog.csdn.net/qq_36071410/article/details/101268640 +// 庄童 2019-09-24 10:24:58 10271 收藏 +String RandomBit(int len) { + String scopeF = '123456789'; //首位 + String scopeC = '0123456789'; //中间 + String result = ''; + for (int i = 0; i < len; i++) { + if (i == 0) { + result = scopeF[Random().nextInt(scopeF.length)]; + } else { + result = result + scopeC[Random().nextInt(scopeC.length)]; + } + } + return result; +} + +// sign=md5(ijddvzgEGaxbzsbmCtpdohxHyrAArwJB1003) +// =3967eaebec0eed0642a1d395ac9293dd +String APPkey = 'ijddvzgEGaxbzsbmCtpdohxHyrAArwJB'; +//Flutter对字符串进行MD5运算 发表于 2019-03-26 更新于 2020-12-04 分类于 Flutter 阅读次 +String GenerateMd5(String str) { + var content = new Utf8Encoder().convert(str); + var md5 = crypto.md5; + var digest = md5.convert(content); + return hex.encode(digest.bytes); +} + +//加载中的圈圈 +Widget getMoreWidget2({ + Color color = Colors.white, + String text = '加载中...', + double size = 30.0, + double strokeWidth = 3.0, + FontWeight fontWeight, + double edge = 10.0, + double height = 34, +}) { + return Center( + child: Padding( + padding: EdgeInsets.all(edge), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + height: size, + width: size, + child: CircularProgressIndicator( + strokeWidth: strokeWidth, + valueColor: AlwaysStoppedAnimation(color), + ), + ), + SizedBox( + height: ScreenUtil().setHeight(height), + ), + Text( + text, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: size, // 文字大小 + color: color, + fontWeight: fontWeight, // 文字颜色 + ), + ), + ], + ), + ), + ); +} + +//加载中的圈圈 +Widget getMoreWidget({ + String text = '加载中...', + Color color = Colors.white, + double size = 30.0, + double strokeWidth = 3.0, + TextAlign textAlign = TextAlign.left, +}) { + return Center( + child: Padding( + padding: EdgeInsets.all(10.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + height: size, + width: size, + child: CircularProgressIndicator( + strokeWidth: strokeWidth, + valueColor: AlwaysStoppedAnimation(color), + ), + ), + SizedBox( + height: 10, + ), + Text( + text, + textAlign: textAlign, + style: TextStyle( + fontSize: size, // 文字大小 + color: color, + ), + ), + ], + ), + ), + ); +} + +//自定义带说明图标按钮函数。点击说明文字有反应 +Widget getIconAndTextButton( + {IconData iconData, Color iconColor = Colors.black, var onPress = null}) { + return Container( + width: 50, + height: 50, + alignment: const Alignment(0, 0), + child: FlatButton( + padding: EdgeInsets.all(0), + onPressed: onPress, + //color: Colors.blue, + color: Colors.transparent, + //解决报错问题:FittedBox ← Expanded ← ConstrainedBox ← Container ← Center ← Padding ← + // Container ← IconTheme ← Builder ← _PointerListener + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: 36, //最大高度 + maxWidth: 36, //最大宽度 + ), + child: Padding( + padding: EdgeInsets.only(top: 1), + child: Icon(iconData, color: iconColor, size: 32), + // child: Image.asset( + // 'assets/images/left_arrow.png', + // fit: BoxFit.fitWidth, + // color: iconColor, + // //fit: BoxFit.cover, + // ), + ), + + // child: Row( + // mainAxisAlignment: MainAxisAlignment.center, + // crossAxisAlignment: CrossAxisAlignment.end, + // children: [ + // Image.asset('assets/images/left_arrow.png', fit: BoxFit.cover), + // // Expanded( + // // //child: Icon(iconData, color: iconColor, size: 24), + // // child: Image.asset('assets/images/left_arrow.png', fit: BoxFit.cover), + // // ), + // ], + // ), + ), + + //The offending Expanded is currently placed inside a ConstrainedBox widget. + //The ownership chain for the RenderObject that received the incompatible parent data was: + // FittedBox ← Expanded ← ConstrainedBox ← Container ← Center ← Padding ← Container ← IconTheme ← + //Builder ← _PointerListener + + //The ParentDataWidget Expanded(flex: 1) wants to apply ParentData of type FlexParentData to a + //RenderObject, which has been set up to accept ParentData of incompatible type ParentData. + //Usually, this means that the Expanded widget has the wrong ancestor RenderObjectWidget. Typically, + //Expanded widgets are placed directly inside Flex widgets. + //The offending Expanded is currently placed inside a FittedBox widget. + //The ownership chain for the RenderObject that received the incompatible parent data was: + // ConstrainedBox ← Container ← Expanded ← FittedBox ← Center ← Padding ← Container ← IconTheme ← + //Builder ← _PointerListener ← ⋯ + // child: ConstrainedBox( + // constraints: BoxConstraints( + // maxHeight: 20, //最大高度 + // maxWidth: 28, //最大宽度 + // ), + // child: Container( + // child: Expanded( + // child: new Icon(iconData, color: iconColor), + // ), + // ), + // ), + + // child: ConstrainedBox( + // constraints: BoxConstraints( + // maxHeight: 30, //最大高度 + // maxWidth: 38, //最大宽度 + // minWidth: 38, //最大宽度 + // ), + // child: Row( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Expanded( + // child: Icon(iconData, color: iconColor, size: 24), + // ), + // ], + // ), + // ), + + // child: Container( + // width: 28, + // height: 20, + // child: Expanded( + // child: new FittedBox( + // fit: BoxFit.fill, + // child: new Icon(iconData, color: iconColor), + // ), + // ), + // ), + + // child: FittedBox( + // fit: BoxFit.fitWidth, + // alignment: Alignment.topLeft, + // child: Container( + // width: 28, + // height: 20, + // child: Expanded( + // child: new FittedBox( + // fit: BoxFit.fill, + // child: new Icon(iconData, color: iconColor), + // ), + // ), + // ), + // ), + ), + ); +} + +///获取缩进空白符 +String getDeepSpace(int deep) { + var tab = StringBuffer(); + for (int i = 0; i < deep; i++) { + tab.write("\t"); + } + return tab.toString(); +} + +// List map2list(Map _map) { +// List _list = []; +// _list = List.generate(listContacts2[widget.contactIndex].length, (index) { +// String key = listContacts2[widget.contactIndex].keys.elementAt(index); +// //return TextEditingController(text: listContacts2[widget.contactIndex][key]); +// return TextEditingController(text: getUserText3(widget.contactIndex, key)); +// }); +// +// } + +/// [object] 解析的对象 +/// [deep] 递归的深度,用来获取缩进的空白长度 +/// [isObject] 用来区分当前map或list是不是来自某个字段,则不用显示缩进。单纯的map或list需要添加缩进 +String json_print(dynamic object, int deep, {bool isObject = false}) { + var buffer = StringBuffer(); + var nextDeep = deep + 1; + if (object is Map) { + var list = object.keys.toList(); + if (!isObject) { + //如果map来自某个字段,则不需要显示缩进 + buffer.write("${getDeepSpace(deep)}"); + } + buffer.write("{"); + if (list.isEmpty) { + //当map为空,直接返回‘}’ + buffer.write("}"); + } else { + buffer.write("\n"); + for (int i = 0; i < list.length; i++) { + buffer.write("${getDeepSpace(nextDeep)}\"${list[i]}\":"); + buffer.write(json_print(object[list[i]], nextDeep, isObject: true)); + if (i < list.length - 1) { + buffer.write(","); + buffer.write("\n"); + } + } + buffer.write("\n"); + buffer.write("${getDeepSpace(deep)}}"); + } + } else if (object is List) { + if (!isObject) { + //如果list来自某个字段,则不需要显示缩进 + buffer.write("${getDeepSpace(deep)}"); + } + buffer.write("["); + if (object.isEmpty) { + //当list为空,直接返回‘]’ + buffer.write("]"); + } else { + buffer.write("\n"); + for (int i = 0; i < object.length; i++) { + buffer.write(json_print(object[i], nextDeep)); + if (i < object.length - 1) { + buffer.write(","); + buffer.write("\n"); + } + } + buffer.write("\n"); + buffer.write("${getDeepSpace(deep)}]"); + } + } else if (object is String) { + //为字符串时,需要添加双引号并返回当前内容 + buffer.write("\"$object\""); + } else if (object is num || object is bool) { + //为数字或者布尔值时,返回当前内容 + buffer.write(object); + } else { + //如果对象为空,则返回null字符串 + buffer.write("null"); + } + return buffer.toString(); +} + +//人脸识别和上传的图片,都需要转换为 Base64 格式的字符串提交 +//通过图片路径将图片转换成 Base64 字符串 +Future image2Base64(String imagePath) async { + File file = File(imagePath); + List imageBytes = await file.readAsBytes(); + String bs64 = base64Encode(imageBytes); + String ext = getFileExtension(imagePath); + print('imagePath = $imagePath, ext = $ext'); + //String bs64Image = "data:image/png;base64," + bs64; + String bs64Image = "data:image/${ext};base64," + bs64; + return bs64Image; +} + +//从字符路径 path 获取扩展名,不含点号 +getFileExtension(String path) { + //return path.substring(path.lastIndexOf('.')); + //imagePath = /data/user/0/com.flutter.hyzp_ybqx/app_flutter/Pictures/flutter_test/1614662209478.jpg, ext = .jpg + return path.substring(path.lastIndexOf('.') + 1); //不含点号 +} + +//从字符路径 path 获取文件名(含扩展名) +getFileName(String path) { + //return path.substring(path.lastIndexOf('.')); + //imagePath = /data/user/0/com.flutter.hyzp_ybqx/app_flutter/Pictures/flutter_test/1614662209478.jpg, ext = .jpg + return path.substring(path.lastIndexOf('/') + 1); //不含点号 +} + +Widget getBtnSizeX({@required text, double width = 70.0, double height = 40.0, onPressedFun}) { + return Container( + color: Colors.white12, //onPressedFun为null时无效 + width: width, + height: height, + child: RaisedButton( + padding: EdgeInsets.all(0), + textColor: Colors.black, + child: Text(text), + onPressed: onPressedFun, + ), + ); +} + +//返回主页ashamp +// 博客园首页新随笔联系管理订阅订阅随笔 - 33 文章 - 0 评论 - 51 阅读 - 55018 +// 在debug时使Flutter中的print打印json数据时更美观易读 +// 为了避免deubg信息在生产环境打印,只在测试时打印,在main函数中,改变debugPrint的指向 +// +// 复制代码 +// main(){ +// if (Api.isDebug) { +// debugPrint = (String message, {int wrapWidth}) { +// try { +// var object = json.decode(message); +// message = JsonEncoder.withIndent(' ').convert(object); +// } catch (e) {} +// printWrapped(message); +// }; +// } else { +// debugPrint = (String message, {int wrapWidth}) {}; +// } +// } +// 复制代码 +// 将printWrapped方法放入工具类或你需要的地方 +// +// void printWrapped(String text) { +// final pattern = new RegExp('.{1,800}'); // 800 is the size of each chunk +// pattern.allMatches(text).forEach((match) => developer.log(match.group(0))); +// } +// log方法需要引入 +// +// import 'dart:developer' as developer; +// + +//在debug时使 Flutter 中的 print 打印 json 数据时更美观易读 +void jsonPrint(String message, {int len = 800}) { + //是否在生产环境 + const bool isDebug = !const bool.fromEnvironment("dart.vm.product"); + // if (!isDebug) { + // return; + // } + + try { + var object = json.decode(message); + message = JsonEncoder.withIndent(' ').convert(object); + } catch (e) { + print('e = $e'); + } + printWrapped(message); +} + +// 打印长字符串 +void printWrapped(String text, {int len = 800}) { + final pattern = RegExp('.{1,$len}'); // 800 is the size of each chunk + pattern.allMatches(text).forEach((match) => developer.log(match.group(0))); +} + +//OK +void my_segmentPrint(String str, {int len = 800}) { + //是否在生产环境 + const bool isDebug = !const bool.fromEnvironment("dart.vm.product"); + if (!isDebug) { + return; + } + List list = strToList(str); + for (int i = 0; i < list.length; i++) { + print('${list[i]}'); + } +} + +//OK +List strToList(String str, {int len = 800}) { + List strList = []; + if (str.length <= len) { + strList.add(str); + } else { + int splitCount = str.length ~/ len; //应该切割的次数 + for (int i = 0; i < splitCount; i++) { + strList.add(str.substring(len * i, len * (i + 1))); + } + + //处理最后一段 + if (str.length % len != 0) { + strList.add(str.substring(len * splitCount)); + } + } + print('strList:${strList.toString}'); + return strList; +} + +Widget getImageWidget() { + return Container( + alignment: Alignment(0, 0), + height: ScreenUtil().setHeight(346), + width: ScreenUtil().setWidth(942), + decoration: BoxDecoration( + image: DecorationImage(image: AssetImage('assets/images/装饰图片10.png'), fit: BoxFit.cover), + ), + child: Column( + children: [ + Container( + alignment: Alignment.centerLeft, + padding: EdgeInsets.only(top: ScreenUtil().setWidth(30), left: ScreenUtil().setWidth(55)), + child: Text('改善城市空气质量', style: TextStyle(fontSize: 17, color: Colors.white)), + ), + Container( + alignment: Alignment.centerLeft, + padding: EdgeInsets.only(left: ScreenUtil().setWidth(55)), + child: Text('建设长江生态第一城', + style: TextStyle( + fontSize: 19, + color: Color.fromRGBO(49, 216, 123, 1), + fontWeight: FontWeight.bold)), + ), + ], + ), + // child: Image.asset( + // 'assets/images/装饰图片10.png', + // fit: BoxFit.cover, + // ), + ); +} + +Widget getIconBtnSizeX( + {@required text, + width = 233, + height = 116, + onTop, + Color color = Colors.black, + double circular = 10, + double textSize = 18}) { + return InkWell( + onTap: onTop, + child: Container( + alignment: Alignment(0, 0), + margin: EdgeInsets.all(0), + padding: EdgeInsets.all(0), + width: ScreenUtil().setWidth(width), + height: ScreenUtil().setHeight(height), + decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(circular)), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: ScreenUtil().setWidth(12)), + Icon(Icons.play_arrow, color: Colors.white, size: ScreenUtil().setWidth(56)), + Text( + text, + style: TextStyle(color: Colors.white, fontSize: textSize), + ) + ], + ), + ), + ); +} diff --git a/lib/components/customDialogA.dart b/lib/components/customDialogA.dart new file mode 100644 index 0000000..25e7e21 --- /dev/null +++ b/lib/components/customDialogA.dart @@ -0,0 +1,166 @@ +import 'package:flutter/material.dart'; +import 'customDialogC.dart'; +import 'commonFun.dart'; + +//视频列表對話框 +class customDialogA extends Dialog { + String title; + String content; + String url; + + customDialogA({this.title = "", this.content = "", this.url = ''}); + + @override + Widget build(BuildContext context) { + Size mediaSize = MediaQuery.of(context).size; + // TODO: implement build + return WillPopScope( + child: Material( + type: MaterialType.transparency, + child: Container( + alignment: Alignment(0, 0), + child: Container( + // height: 260, + // width: 300, + height: mediaSize.height * 0.85, + width: mediaSize.width * 0.95, + //color: Colors.white, //Cannot provide both a color and a decoration + decoration: BoxDecoration( + color: Colors.white, + //border: Border.all(color: Colors.blue, width: 1.0), + borderRadius: BorderRadius.all( + Radius.circular(2), + ), + ), + child: Column( + children: [ + Padding( + padding: EdgeInsets.fromLTRB(10, 10, 10, 0), + child: Stack( + children: [ + Align( + alignment: Alignment.center, + child: Text("${this.title}", + style: TextStyle( + fontSize: 18.0, + )), + ), + Align( + alignment: Alignment.centerRight, + child: InkWell( + child: Icon(Icons.close), + onTap: () { + Navigator.pop(context, url); + }, + ), + ) + ], + ), + ), + Divider(), + //addDialoadContainer4(context, g_list, url), + //MyDialogContent(countries: ['china', 'England']), + MyDialogContent(list: g_list, url: url), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + RaisedButton( + onPressed: () { + Navigator.pop(context, url); + }, + child: Text("取消"), + ), + RaisedButton( + child: Text("确认"), + onPressed: () async { + Navigator.pop(context, url); //关闭弹框,播放输入视频地址 + }, + ) + ], + ), + ], + ), + ), + ), + ), + onWillPop: () { + // 屏蔽点击返回键的操作 + //player.pause(); + Navigator.pop(context, url); + }, + ); + } +} + +class MyDialogContent extends StatefulWidget { + List list; + String url; + MyDialogContent({this.list, this.url}); + + @override + _MyDialogContentState createState() => new _MyDialogContentState(); +} + +class _MyDialogContentState extends State { + @override + Widget build(BuildContext context) { + return addDialoadContainer4(context, widget.list, widget.url); + } + + Widget addDialoadContainer4( + BuildContext context, List list, String url) { + Size mediaSize = MediaQuery.of(context).size; + + int i = 0; + print('\ng_list start =========================================\n'); + for (var s in list) { + print('$i - ' + s + '\n'); + i++; + } + print('g_list start =========================================\n\n'); + + return Container( + height: mediaSize.height * 0.65, //0.75越界,0.7不越界。需要0.2 = 0.9 - 0.7 + width: mediaSize.width * 0.9, + child: ListView.builder( + shrinkWrap: true, + itemCount: list.length, + itemBuilder: (BuildContext context, int index) { + int index2 = index + 1; + return Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.fromLTRB(20, 0, 10, 0), + child: InkWell( + child: Text('$index2 : ' + list[index], + style: TextStyle(fontSize: 15.0)), + onTap: () { + print('index : $index'); + Navigator.pop(context, list[index]); + }, + onLongPress: () async { + await showDialog( + context: context, + builder: (context) { + myController.text = ''; + return CustomDialogC( + title: "选择操作", + url: g_list[index], + index: index, + ); + }); + setState(() {}); + }, + ), + ), + Divider(), + ], + ); + }, + ), + ); + } +} diff --git a/lib/components/customDialogB.dart b/lib/components/customDialogB.dart new file mode 100644 index 0000000..fc1f90e --- /dev/null +++ b/lib/components/customDialogB.dart @@ -0,0 +1,188 @@ +import 'package:flutter/material.dart'; +import 'commonFun.dart'; +import 'package:flutter/services.dart'; + +//输入视频地址对话框 +class customDialogB extends Dialog { + String title; + String content; + String url; + + customDialogB({this.title = "", this.content = "", this.url = ''}); + + // Widget getButton({double width = 60.0, double height = 30.0, RaisedButton raisedButton}) { + // return ButtonTheme( + // minWidth: width, //设置最小宽度 + // height: height, + // //colorScheme: , + // buttonColor: Colors.white60, + // child: raisedButton, + // ); + // } + + Widget getBtnSizeX({@required text, width = 60.0, height = 30.0, onPressedFun}) { + return Container( + color: Colors.white12, //onPressedFun为null时无效 + width: width, + height: height, + child: RaisedButton( + padding: EdgeInsets.all(0), + textColor: Colors.black, + child: Text(text), + onPressed: onPressedFun, + ), + ); + } + + @override + Widget build(BuildContext context) { + Size mediaSize = MediaQuery.of(context).size; + // TODO: implement build + return WillPopScope( + child: Material( + type: MaterialType.transparency, + child: Container( + alignment: Alignment(0, -0.7), + child: Container( + // height: 260, + // width: 300, + height: mediaSize.height * 0.4, + width: mediaSize.width * 0.95, + //color: Colors.white, //Cannot provide both a color and a decoration + decoration: BoxDecoration( + color: Colors.white, + //border: Border.all(color: Colors.blue, width: 1.0), + borderRadius: BorderRadius.all( + Radius.circular(2), + ), + ), + child: Column( + children: [ + Padding( + padding: EdgeInsets.fromLTRB(10, 10, 10, 0), + child: Stack( + children: [ + Align( + alignment: Alignment.center, + child: Text( + "${this.title}", + style: TextStyle( + fontSize: 15.0, + ), + ), + ), + Align( + alignment: Alignment.centerRight, + child: InkWell( + child: Icon(Icons.close), + onTap: () { + Navigator.pop(context, url); + }, + ), + ) + ], + ), + ), + Divider(), + Container( + padding: EdgeInsets.fromLTRB(20, 10, 20, 10), + width: double.infinity, + child: TextField( + maxLines: 4, + controller: myController, + autofocus: false, //不会自动打开输入键盘 + decoration: InputDecoration( + fillColor: Theme.of(context).hoverColor, + filled: true, + hintText: 'Media Url', + border: OutlineInputBorder() + //labelText: 'Media Url', + ), + ), + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + getBtnSizeX( + text: "粘贴", + onPressedFun: () async { + ClipboardData data = await Clipboard.getData(Clipboard.kTextPlain); + myController.text = data.text; + }, + ), + // getButton( + // raisedButton: RaisedButton( + // onPressed: () async { + // ClipboardData data = + // await Clipboard.getData(Clipboard.kTextPlain); + // myController.text = data.text; + // }, + // child: Text("粘贴"), + // ), + // ), + getBtnSizeX( + text: "获取", + onPressedFun: () async { + myController.text = url; + }, + ), + // getButton( + // raisedButton: RaisedButton( + // onPressed: () async { + // myController.text = url; + // }, + // child: Text("获取"), + // ), + // ), + getBtnSizeX( + text: "清除", + onPressedFun: () { + myController.clear(); + }, + ), + // getButton( + // raisedButton: RaisedButton( + // onPressed: () { + // myController.clear(); + // }, + // child: Text("清除"), + // ), + // ), + getBtnSizeX( + text: "播放", + onPressedFun: () { + //Navigator.of(context).pop() //关闭弹框,这样关闭会导致视频播放停止,无法开始 + if ("" != myController.text) { + urlnew = url = myController.text; + } + Navigator.pop(context, url); //关闭弹框,播放输入视频地址 + }, + ), + // getButton( + // raisedButton: RaisedButton( + // onPressed: () { + // //Navigator.of(context).pop() //关闭弹框,这样关闭会导致视频播放停止,无法开始 + // if ("" != myController.text) { + // urlnew = url = myController.text; + // } + // Navigator.pop(context, url); //关闭弹框,播放输入视频地址 + // }, + // child: Text("播放"), + // ), + // ), + ], + ), + ], + ), + ), + ), + ), + onWillPop: () { + // 屏蔽点击返回键的操作 + //player.pause(); + Navigator.pop(context, url); + }, + ); + } +} diff --git a/lib/components/customDialogC.dart b/lib/components/customDialogC.dart new file mode 100644 index 0000000..4c793db --- /dev/null +++ b/lib/components/customDialogC.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.dart'; +import 'commonFun.dart'; + +//删除确认对话框 +class CustomDialogC extends Dialog { + String title; + String url; + int index; + + CustomDialogC({this.title = "", this.url = "", this.index}); + + @override + Widget build(BuildContext context) { + Size mediaSize = MediaQuery.of(context).size; + return WillPopScope( + child: Material( + type: MaterialType.transparency, + child: Container( + alignment: Alignment(0, -0.7), + child: Container( + // height: 260, + // width: 300, + height: mediaSize.height * 0.35, + width: mediaSize.width * 0.85, + //color: Colors.white, //Cannot provide both a color and a decoration + decoration: BoxDecoration( + color: Colors.white, + //border: Border.all(color: Colors.blue, width: 1.0), + borderRadius: BorderRadius.all( + Radius.circular(2), + ), + ), + child: Column( + children: [ + Padding( + padding: EdgeInsets.fromLTRB(10, 10, 10, 0), + child: Stack( + children: [ + Align( + alignment: Alignment.center, + child: Text( + "${this.title}", + style: TextStyle( + fontSize: 15.0, + ), + ), + ), + Align( + alignment: Alignment.centerRight, + child: InkWell( + child: Icon(Icons.close), + onTap: () { + Navigator.pop(context, url); + }, + ), + ) + ], + ), + ), + Divider(), + Container( + padding: EdgeInsets.fromLTRB(20, 10, 20, 10), + width: double.infinity, + height: mediaSize.height * 0.15, + child: SingleChildScrollView( + child: Text(index.toString() + ' : ' + url), + ), + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + // RaisedButton( + // onPressed: () async { + // await showDialog( + // context: context, + // builder: (context) { + // myController.text = g_list[index].trim(); + // return CustomDialogD( + // title: "修改视频地址", + // content: g_list[index].trim(), + // url: urlnew); + // }); + // String url0 = g_list[index].trim().toLowerCase(); + // String url2 = myController.text.trim().toLowerCase(); + // if (url2.isNotEmpty && 0 != url0.compareTo(url2)) { + // g_list.removeAt(index); + // g_list.add(url2); + // await updateFile(); + // } + // Navigator.pop(context, url); //关闭弹框,返回sRet + // }, + // child: Text("修改"), + // ), + RaisedButton( + onPressed: () async { + g_list.removeAt(index); + //await updateFile(); + //writeUrlFile2(); + Navigator.pop(context, url); //关闭弹框,返回sRet + }, + child: Text("删除"), + ), + RaisedButton( + child: Text("取消"), + onPressed: () async { + Navigator.pop(context, url); //关闭弹框,返回sRet + }, + ) + ], + ), + ], + ), + ), + ), + ), + onWillPop: () { + // 屏蔽点击返回键的操作 + //player.pause(); + Navigator.pop(context, url); + }, + ); + } +} diff --git a/lib/components/customDialogD.dart b/lib/components/customDialogD.dart new file mode 100644 index 0000000..13bbba2 --- /dev/null +++ b/lib/components/customDialogD.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; +import 'commonFun.dart'; + +//修改视频地址对话框 +class CustomDialogD extends Dialog { + String title; + String content; + String url; + + CustomDialogD({this.title = "", this.content = "", this.url = ''}); + + @override + Widget build(BuildContext context) { + Size mediaSize = MediaQuery.of(context).size; + myController.text = content; //为TextField赋初始值 + // TODO: implement build + return WillPopScope( + child: Material( + type: MaterialType.transparency, + child: Container( + alignment: Alignment(0, -0.7), + child: Container( + // height: 260, + // width: 300, + height: mediaSize.height * 0.4, + width: mediaSize.width * 0.85, + //color: Colors.white, //Cannot provide both a color and a decoration + decoration: BoxDecoration( + color: Colors.white, + //border: Border.all(color: Colors.blue, width: 1.0), + borderRadius: BorderRadius.all( + Radius.circular(2), + ), + ), + child: Column( + children: [ + Padding( + padding: EdgeInsets.fromLTRB(10, 10, 10, 0), + child: Stack( + children: [ + Align( + alignment: Alignment.center, + child: Text( + "${this.title}", + style: TextStyle( + fontSize: 15.0, + ), + ), + ), + Align( + alignment: Alignment.centerRight, + child: InkWell( + child: Icon(Icons.close), + onTap: () { + Navigator.pop(context, url); + }, + ), + ) + ], + ), + ), + Divider(), + Container( + padding: EdgeInsets.fromLTRB(20, 10, 20, 10), + width: double.infinity, + child: TextField( + maxLines: 4, + controller: myController, + autofocus: false, //不会自动打开输入键盘 + decoration: InputDecoration( + fillColor: Theme.of(context).hoverColor, + filled: true, + hintText: 'Media Url', + border: OutlineInputBorder() + //labelText: 'Media Url', + ), + ), + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + RaisedButton( + onPressed: () { + myController.text = url; + }, + child: Text("获取"), + ), + RaisedButton( + onPressed: () { + myController.clear(); + }, + child: Text("清除"), + ), + RaisedButton( + child: Text("确认"), + onPressed: () async { + Navigator.pop(context, url); //关闭弹框,播放输入视频地址 + }, + ) + ], + ), + ], + ), + ), + ), + ), + onWillPop: () { + // 屏蔽点击返回键的操作 + //player.pause(); + Navigator.pop(context, url); + }, + ); + } +} diff --git a/lib/components/customDialogE.dart b/lib/components/customDialogE.dart new file mode 100644 index 0000000..d54d1b9 --- /dev/null +++ b/lib/components/customDialogE.dart @@ -0,0 +1,177 @@ +import 'package:flutter/material.dart'; +import 'commonFun.dart'; +import 'package:flutter/services.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'doJSON.dart'; +import 'hyxx_data_handle.dart'; +import 'dart:convert'; +import '../pages/MyMsics/03_personal/ContactModify.dart'; +import '../pages/MyMsics/03_personal/ContactAdd.dart'; + +//添加、修改、删除联系人对话框 +class customDialogE extends Dialog { + String title; + String content; + int index; + + customDialogE({this.title = "", this.content = "", this.index = -1}); + + // String getTitle(int index) { + // return listContacts2[index]["姓名"] + + // ', ' + + // listContacts2[index]["部门"] + + // ', ' + + // listContacts2[index]["职务"] + + // '\n' + + // listContacts2[index]["手机"] + + // ', ' + + // listContacts2[index]["邮箱"]; + // } + + Widget getBtnSizeX({@required text, width = 60.0, height = 30.0, onPressedFun}) { + return Container( + color: Colors.white12, //onPressedFun为null时无效 + width: width, + height: height, + child: RaisedButton( + padding: EdgeInsets.all(0), + textColor: Colors.black, + child: Text(text), + onPressed: onPressedFun, + ), + ); + } + + @override + Widget build(BuildContext context) { + Size mediaSize = MediaQuery.of(context).size; + //myController.text = getTitle(this.index); + myController.text = content; + // TODO: implement build + return WillPopScope( + child: Material( + type: MaterialType.transparency, + child: Container( + alignment: Alignment(0, -0.7), + child: Container( + height: mediaSize.height * 0.4, + width: mediaSize.width * 0.95, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(2), + ), + ), + child: Column( + children: [ + Padding( + padding: EdgeInsets.fromLTRB(10, 10, 10, 0), + child: Stack( + children: [ + Align( + alignment: Alignment.center, + child: Text( + "${this.title}", + style: TextStyle( + fontSize: 15.0, + ), + ), + ), + Align( + alignment: Alignment.centerRight, + child: InkWell( + child: Icon(Icons.close), + onTap: () { + //Navigator.pop(context, index); + Navigator.pop(context); //关闭弹框,播放输入视频地址 + }, + ), + ) + ], + ), + ), + Divider(), + Container( + padding: EdgeInsets.fromLTRB(20, 10, 20, 10), + width: double.infinity, + child: TextField( + maxLines: 4, + controller: myController, + autofocus: false, + //不会自动打开输入键盘 + decoration: InputDecoration( + fillColor: Theme.of(context).hoverColor, + filled: true, + hintText: 'Media Url', + border: OutlineInputBorder(), + ), + enableInteractiveSelection: false, + onTap: () { + FocusScope.of(context).requestFocus(new FocusNode()); + }, + ), + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + getBtnSizeX( + text: "添加", + onPressedFun: () async { + //跳转到修改对话框 + Navigator.of(context) + .push(MaterialPageRoute( + builder: (context) => ContactAdd(contactIndex: index))) + .then((value) => Navigator.pop(context)); + }, + ), + getBtnSizeX( + text: "修改", + onPressedFun: () async { + //跳转到修改对话框 + await Navigator.of(context).push(MaterialPageRoute( + builder: (context) => ContactModify(contactIndex: index))); + await Navigator.pop(context); //先关闭“选择操作”对话框 + //跳转并关闭当前页面 + // Navigator.pushAndRemoveUntil( + // context, + // MaterialPageRoute( + // builder: (context) => ContactModify(contactIndex: index)), + // (route) => route == null, + // ); + }, + ), + getBtnSizeX( + text: "删除", + onPressedFun: () { + listContacts2.removeAt(index); + bFlash = true; + print("ContactDel bFlash = $bFlash"); + writeJSON(json.encode(listContacts2), 'listContacts2.json'); + Navigator.pop(context); //关闭弹框,播放输入视频地址 + }, + ), + getBtnSizeX( + text: "复制", + onPressedFun: () { + // Flutter 复制文本到剪贴板 + Clipboard.setData(ClipboardData(text: myController.text)); + //showToast('帮助信息已复制到剪贴板', textAlign: TextAlign.left); + Fluttertoast.showToast(msg: '联系人信息已复制到剪贴板', gravity: ToastGravity.BOTTOM); + }, + ), + ], + ), + ], + ), + ), + ), + ), + onWillPop: () { + // 屏蔽点击返回键的操作 + //player.pause(); + //Navigator.pop(context, index); + }, + ); + } +} diff --git a/lib/components/customDialogF.dart b/lib/components/customDialogF.dart new file mode 100644 index 0000000..043248e --- /dev/null +++ b/lib/components/customDialogF.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; +import 'commonFun.dart'; + +//确认对话框 +class CustomDialogF extends Dialog { + String title; + String content; + + bool ret = false; + + CustomDialogF({this.title = "", this.content}); + + @override + Widget build(BuildContext context) { + Size mediaSize = MediaQuery.of(context).size; + return WillPopScope( + child: Material( + type: MaterialType.transparency, + child: Container( + padding: EdgeInsets.only(top: 126), + alignment: Alignment(0, -1), + color: Colors.black12, + child: Container( + // height: 260, + // width: 300, + height: mediaSize.height * 0.315, + width: mediaSize.width * 0.9, + //color: Colors.white, //Cannot provide both a color and a decoration + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Colors.blue, width: 2.0), + borderRadius: BorderRadius.all( + Radius.circular(5), + ), + ), + child: Column( + children: [ + Padding( + padding: EdgeInsets.fromLTRB(10, 10, 10, 0), + child: Stack( + children: [ + Align( + alignment: Alignment.center, + child: Text( + "${this.title}", + style: TextStyle( + fontSize: 20.0, + ), + ), + ), + Align( + alignment: Alignment.centerRight, + child: InkWell( + child: Icon(Icons.close), + onTap: () { + Navigator.pop(context, ret); + }, + ), + ) + ], + ), + ), + Divider(), + Container( + padding: EdgeInsets.fromLTRB(20, 10, 20, 10), + width: double.infinity, + height: mediaSize.height * 0.1, + child: SingleChildScrollView( + child: Text(content, style: TextStyle(fontSize: 18.0, color: Colors.blue)), + ), + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + RaisedButton( + onPressed: () async { + ret = true; + Navigator.pop(context, ret); //关闭弹框,返回sRet + }, + child: Text("确认"), + ), + RaisedButton( + child: Text("取消"), + onPressed: () async { + Navigator.pop(context, ret); //关闭弹框,返回sRet + }, + ) + ], + ), + ], + ), + ), + ), + ), + onWillPop: () { + // 屏蔽点击返回键的操作 + Navigator.pop(context, ret); + }, + ); + } +} diff --git a/lib/components/customDialogFaceReg.dart b/lib/components/customDialogFaceReg.dart new file mode 100644 index 0000000..1c93527 --- /dev/null +++ b/lib/components/customDialogFaceReg.dart @@ -0,0 +1,143 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_drag_scale/core/drag_scale_widget.dart'; +import 'dart:io'; + +//确认对话框 +class CustomDialogFaceReg extends Dialog { + String title; + String username; + String imagePath; + Size imageSize; + + CustomDialogFaceReg( + {@required this.username, + @required this.imagePath, + @required this.imageSize, + this.title = ""}); + + @override + Widget build(BuildContext context) { + Size mediaSize = MediaQuery.of(context).size; + return WillPopScope( + child: Material( + type: MaterialType.transparency, + child: Container( + padding: EdgeInsets.only(top: 45), + alignment: Alignment(0, -1), + color: Colors.black12, + child: Container( + // height: 260, + // width: 300, + height: mediaSize.height * 0.9, + width: mediaSize.width * 0.98, + //color: Colors.white, //Cannot provide both a color and a decoration + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Colors.blue, width: 2.0), + borderRadius: BorderRadius.all( + Radius.circular(5), + ), + ), + child: Column( + children: [ + Padding( + padding: EdgeInsets.fromLTRB(10, 10, 10, 0), + child: Stack( + children: [ + Align( + alignment: Alignment.center, + child: Text( + title, + style: TextStyle( + fontSize: 20.0, + ), + ), + ), + Align( + alignment: Alignment.centerRight, + child: InkWell( + child: Icon(Icons.close), + onTap: () { + Navigator.pop(context, false); + }, + ), + ) + ], + ), + ), + Divider(), + Container( + padding: EdgeInsets.fromLTRB(20, 5, 20, 10), + width: double.infinity, + height: mediaSize.height * 0.1, + child: SingleChildScrollView( + child: RichText( + text: TextSpan(children: [ + TextSpan( + text: '是否确定为用户 ', style: TextStyle(fontSize: 18.0, color: Colors.blue)), + TextSpan( + text: username, + style: TextStyle( + fontSize: 18.0, fontWeight: FontWeight.bold, color: Colors.red)), + TextSpan( + text: ' 注册或更新以下人脸图片?', + style: TextStyle(fontSize: 18.0, color: Colors.blue)), + ]), + ), + ), + ), + //SizedBox(height: 10), + //480*720 + Text('宽高:${imageSize.width}x${imageSize.height}', style: TextStyle(fontSize: 18)), + SizedBox(height: 5), + SingleChildScrollView( + //滑动的方向 Axis.vertical为垂直方向滑动,Axis.horizontal 为水平方向 + scrollDirection: Axis.vertical, + reverse: false, + padding: EdgeInsets.all(0.0), + //滑动到底部回弹效果 + physics: BouncingScrollPhysics(), + child: Container( + alignment: Alignment(0, 0), + height: mediaSize.height * 0.52, + width: mediaSize.width * 0.95, + decoration: BoxDecoration( + border: Border.all(color: Colors.orange, width: 1.0), + borderRadius: BorderRadius.circular(5), + ), + //DragScaleContainer 插件只能放大,不能缩小到比原始尺寸小 + child: DragScaleContainer( + doubleTapStillScale: true, + child: Image.file(File(imagePath), fit: BoxFit.fitHeight)), + ), + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + RaisedButton( + onPressed: () async { + Navigator.pop(context, true); //关闭弹框,返回 true + }, + child: Text("确认"), + ), + RaisedButton( + child: Text("取消"), + onPressed: () async { + Navigator.pop(context, false); //关闭弹框,返回 false + }, + ) + ], + ), + ], + ), + ), + ), + ), + onWillPop: () { + // 屏蔽点击返回键的操作 + Navigator.pop(context, false); + }, + ); + } +} diff --git a/lib/components/customDialogG.dart b/lib/components/customDialogG.dart new file mode 100644 index 0000000..dd03ce7 --- /dev/null +++ b/lib/components/customDialogG.dart @@ -0,0 +1,323 @@ +import 'package:flutter/material.dart'; +import 'commonFun.dart'; +import '../res/listContacts.dart'; +import 'package:flutter/services.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'doJSON.dart'; +import 'hyxx_data_handle.dart'; +import 'dart:convert'; +import '../pages/MyMsics/03_personal/ContactModify.dart'; +import '../pages/MyMsics/03_personal/ContactAdd.dart'; + +//删除消息对话框。自定义透明背景窗口,类似对话框 +class customDialogG extends StatefulWidget { + customDialogG({Key key, this.title = "", this.index = -1}) : super(key: key); + String title; + int index; + + _CheckBoxDemoState createState() => _CheckBoxDemoState(); +} + +class _CheckBoxDemoState extends State { + String getTitle(int index) { + // return listContacts2[index]["姓名"] + + // ', ' + + // listContacts2[index]["部门"] + + // ', ' + + // listContacts2[index]["职务"] + + // '\n' + + // listContacts2[index]["手机"] + + // ', ' + + // listContacts2[index]["邮箱"]; + } + + @override + void initState() { + // TODO: implement initState + super.initState(); + bFlash = false; + getContent(); + } + + String strContent = ''; + + getContent() async { + if (0 == widget.title.compareTo('收到的消息')) { + strContent = "第 ${widget.index + 1} 条(共 ${listMessagesInbox2.length} 条)" + + "\n" + + "时间:" + + listMessagesInbox2[widget.index]['date'] + + ", " + + listMessagesInbox2[widget.index]['time'] + + "\n\n" + + "内容:" + + listMessagesInbox2[widget.index]['content']; + } else if (0 == widget.title.compareTo('发送的消息')) { + strContent = "第 ${widget.index + 1} 条(共 ${listMessagesOutbox2.length} 条)" + + "\n" + + "时间:" + + listMessagesOutbox2[widget.index]['date'] + + ", " + + listMessagesOutbox2[widget.index]['time'] + + "\n\n" + + "内容:" + + listMessagesOutbox2[widget.index]['content']; + } + setState(() {}); + } + + bool flagSelect = false; + bool flagInbox = false; + bool flagOutbox = false; + + Widget getCheckBoxs() { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row(children: [ + SizedBox(width: 10), + Checkbox( + value: flagInbox || flagOutbox ? false : flagSelect, + onChanged: flagInbox || flagOutbox + ? null + : (v) { + setState(() { + flagSelect = v; + }); + }, + ), + Text('删除当前选择的消息'), + ]), + Row(children: [ + SizedBox(width: 10), + Checkbox( + value: flagInbox, + onChanged: (v) { + setState(() { + flagInbox = v; + }); + }, + ), + Text('删除所有收到的消息'), + ]), + Row(children: [ + SizedBox(width: 10), + Checkbox( + value: flagOutbox, + onChanged: (v) { + setState(() { + flagOutbox = v; + }); + }, + ), + Text('删除所有发送的消息'), + ]), + ], + ); + } + + Widget getBtnSizeX({@required text, width = 60.0, height = 30.0, onPressedFun}) { + return Container( + color: Colors.white12, //onPressedFun为null时无效 + width: width, + height: height, + child: RaisedButton( + padding: EdgeInsets.all(0), + textColor: Colors.black, + child: Text(text), + onPressed: onPressedFun, + ), + ); + } + + Future alertDialog(String title, String content, var onPresse) async { + return await showDialog( + barrierDismissible: false, //表示点击灰色背景的时候是否消失弹出框 + context: context, + builder: (context) { + return AlertDialog( + title: Text(title), + content: Text(content), + actions: [ + FlatButton( + child: Text("确定"), + onPressed: onPresse, + ), + FlatButton( + child: Text("取消"), + onPressed: () { + Navigator.pop(context, false); + }, + ), + ], + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + SystemUiOverlayStyle(statusBarColor: Colors.transparent); + Size mediaSize = MediaQuery.of(context).size; + myController.text = getTitle(widget.index); + // TODO: implement build + return WillPopScope( + child: Scaffold( + //type: MaterialType.transparency, + //color: Colors.transparent, + //backgroundColor: Colors.transparent, + //color: Colors.blue, + backgroundColor: Color.fromRGBO(212, 212, 212, 0.6), + body: SafeArea( + child: Stack( + children: [ + Container( + alignment: Alignment(0, -0.4), + child: Container( + height: mediaSize.height * 0.7, + width: mediaSize.width * 0.95, + decoration: BoxDecoration( + color: Colors.white, + //border: Border.all(width: 1.0, color: Colors.black), + borderRadius: BorderRadius.all( + Radius.circular(3), + ), + ), + child: Column( + children: [ + Padding( + padding: EdgeInsets.fromLTRB(10, 10, 10, 0), + child: Stack( + children: [ + Align( + alignment: Alignment.center, + child: Text( + "${widget.title}", + style: TextStyle( + fontSize: 15.0, + ), + ), + ), + Align( + alignment: Alignment.centerRight, + child: InkWell( + child: Icon(Icons.close), + onTap: () { + //Navigator.pop(context, index); + Navigator.pop(context); //关闭弹框,播放输入视频地址 + }, + ), + ) + ], + ), + ), + Divider(), + // Container( + // padding: EdgeInsets.fromLTRB(20, 10, 20, 10), + // width: double.infinity, + // child: SingleChildScrollView( + // child: Container( + // child: Text(strContent), + // ), + // ), + // ), + Container( + padding: EdgeInsets.fromLTRB(20, 10, 20, 10), + width: double.infinity, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(width: 1.0, color: Colors.blue), + borderRadius: BorderRadius.all( + Radius.circular(3), + ), + ), + padding: EdgeInsets.fromLTRB(20, 10, 20, 10), + width: double.infinity, + height: mediaSize.height * 0.2, + child: SingleChildScrollView( + child: Container( + child: Text(strContent), + ), + ), + ), + ), + SizedBox(height: 10), + getCheckBoxs(), + SizedBox(height: 10), + Expanded( + child: Container(), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + getBtnSizeX( + text: "删除", + onPressedFun: () async { + // listContacts2.removeAt(widget.index); + // bFlash = true; + // print("ContactDel bFlash = $bFlash"); + // writeJSON(json.encode(listContacts2), 'listContacts2.json'); + bool ret = await alertDialog('删除确认', '是否确定要删除选中的项目?', () { + print("flagInbox = $flagInbox"); + print("flagOutbox = $flagOutbox"); + print("flagSelect = $flagSelect"); + + if (flagInbox || flagOutbox) { + bFlash = true; + if (flagInbox) { + listMessagesInbox2.clear(); + writeJSON(json.encode(listMessagesInbox2), + 'listMessagesInbox02.json'); + } + if (flagOutbox) { + listMessagesOutbox2.clear(); + writeJSON(json.encode(listMessagesOutbox2), + 'listMessagesOutbox02.json'); + } + } else if (flagSelect) { + bFlash = true; + if (0 == widget.title.compareTo('收到的消息')) { + listMessagesInbox2.removeAt(widget.index); + writeJSON(json.encode(listMessagesInbox2), + 'listMessagesInbox02.json'); + } else if (0 == widget.title.compareTo('发送的消息')) { + listMessagesOutbox2.removeAt(widget.index); + writeJSON(json.encode(listMessagesOutbox2), + 'listMessagesOutbox02.json'); + } + } + + print("bFlash = $bFlash"); + Navigator.pop(context, true); + }); + if (ret) { + Navigator.pop(context); //关闭弹框,播放输入视频地址 + } + }, + ), + getBtnSizeX( + text: "取消", + onPressedFun: () { + Navigator.pop(context); //关闭弹框,播放输入视频地址 + }, + ), + ], + ), + SizedBox(height: 50), + ], + ), + ), + ), + ], + ), + ), + ), + onWillPop: () { + // 屏蔽点击返回键的操作 + //player.pause(); + Navigator.pop(context); + }, + ); + } +} diff --git a/lib/components/customDialogH.dart b/lib/components/customDialogH.dart new file mode 100644 index 0000000..2f70917 --- /dev/null +++ b/lib/components/customDialogH.dart @@ -0,0 +1,160 @@ +import 'package:flutter/material.dart'; +import 'dart:io'; +import 'package:image_picker/image_picker.dart'; +import 'package:dio/dio.dart'; +import '../config/service_url.dart'; + +//拍照、选择相册图片对话框 +class customDialogH extends Dialog { + String title; + String content; + int index; + + customDialogH({this.title = "", this.content = "", this.index = -1}); + + Widget getBtnSizeX({@required text, width = 60.0, height = 30.0, onPressedFun}) { + return Container( + color: Colors.white12, //onPressedFun为null时无效 + width: width, + height: height, + child: RaisedButton( + padding: EdgeInsets.all(0), + textColor: Colors.black, + child: Text(text), + onPressed: onPressedFun, + ), + ); + } + + @override + Widget build(BuildContext context) { + Size mediaSize = MediaQuery.of(context).size; + // TODO: implement build + return WillPopScope( + child: Material( + type: MaterialType.transparency, + child: Container( + alignment: Alignment(0, -0.7), + child: Container( + height: mediaSize.height * 0.25, + width: mediaSize.width * 0.95, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(2), + ), + ), + child: Column( + children: [ + Padding( + padding: EdgeInsets.fromLTRB(10, 10, 10, 0), + child: Stack( + children: [ + Align( + alignment: Alignment.center, + child: Text( + "${this.title}", + style: TextStyle( + fontSize: 15.0, + ), + ), + ), + Align( + alignment: Alignment.centerRight, + child: InkWell( + child: Icon(Icons.close), + onTap: () { + Navigator.pop(context); //关闭弹框,播放输入视频地址 + }, + ), + ) + ], + ), + ), + Divider(), + SizedBox(height: 35), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + getBtnSizeX( + text: "拍照", + onPressedFun: () async { + await _takePhoto(); + await Navigator.pop(context, _imagePath); //先关闭“选择操作”对话框 + }, + ), + getBtnSizeX( + text: "从相册选择", + width: 110.0, + onPressedFun: () async { + await _openGallery(); + await Navigator.pop(context, _imagePath); //先关闭“选择操作”对话框 + }, + ), + getBtnSizeX( + text: "取消", + onPressedFun: () { + Navigator.pop(context); //关闭弹框,播放输入视频地址 + }, + ), + ], + ), + ], + ), + ), + ), + ), + onWillPop: () { + // 屏蔽点击返回键的操作 + // player.pause(); + Navigator.pop(context); + }, + ); + } + + File _image; + String _imagePath = ''; + final picker = ImagePicker(); + + /*拍照*/ + _takePhoto() async { + final pickedFile = await picker.getImage(source: ImageSource.camera); + + if (pickedFile != null) { + _image = File(pickedFile.path); + _imagePath = pickedFile.path; + this._uploadImage(_image); + } else { + print('No image selected.'); + } + } + + /*相册*/ + _openGallery() async { + final pickedFile = await picker.getImage(source: ImageSource.gallery); + + if (pickedFile != null) { + _image = File(pickedFile.path); + _imagePath = pickedFile.path; + this._uploadImage(_image); + } else { + print('No image selected.'); + } + } + + //上传图片 + _uploadImage(File _imageDir) async { + //注意:dio3.x版本为了兼容web做了一些修改,上传图片的时候需要把File类型转换成String类型,具体代码如下 + var fileDir = _imageDir.path; + + FormData formData = FormData.fromMap({ + "name": "zhangsna 6666666666", + "age": 20, + "sex": "男", + "file": await MultipartFile.fromFile(fileDir, filename: "xxx.jpg") + }); + //var response = await Dio().post(ServicePath.uploadImageUrl, data: fileDir); + var response = await Dio().post(ServiceUrlJd, data: fileDir); + print(response); + } +} diff --git a/lib/components/customDialogHysh.dart b/lib/components/customDialogHysh.dart new file mode 100644 index 0000000..a56286b --- /dev/null +++ b/lib/components/customDialogHysh.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:hyzp_ybqx/components/dioFun.dart'; + +import 'commonFun.dart'; + +//确认对话框 +class CustomDialogHysh extends Dialog { + String title; + String content; + String shjg; //审核结果 + bool ret = false; + + CustomDialogHysh({@required this.shjg, this.title = "", this.content}); + + @override + Widget build(BuildContext context) { + Size mediaSize = MediaQuery.of(context).size; + return WillPopScope( + child: Material( + type: MaterialType.transparency, + child: Container( + padding: EdgeInsets.only(top: 122), + alignment: Alignment(0, -1), + color: Colors.black12, + child: Container( + // height: 260, + // width: 300, + height: mediaSize.height * 0.35, + width: mediaSize.width * 0.98, + //color: Colors.white, //Cannot provide both a color and a decoration + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Colors.blue, width: 2.0), + borderRadius: BorderRadius.all( + Radius.circular(5), + ), + ), + child: Column( + children: [ + Padding( + padding: EdgeInsets.fromLTRB(10, 10, 10, 0), + child: Stack( + children: [ + Align( + alignment: Alignment.center, + child: Text( + "${title}确认", + style: TextStyle( + fontSize: 20.0, + ), + ), + ), + Align( + alignment: Alignment.centerRight, + child: InkWell( + child: Icon(Icons.close), + onTap: () { + Navigator.pop(context, ret); + }, + ), + ) + ], + ), + ), + Divider(), + Container( + padding: EdgeInsets.fromLTRB(20, 5, 20, 10), + width: double.infinity, + height: mediaSize.height * 0.15, + child: SingleChildScrollView( + child: RichText( + text: TextSpan(children: [ + TextSpan( + text: '${title}为 ', + style: TextStyle(fontSize: 18.0, color: Colors.blue)), + TextSpan( + text: shjg, + style: TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.bold, + color: shjg == hyc_text ? Colors.red : Colors.green)), + TextSpan( + text: ',' + content, + style: TextStyle(fontSize: 18.0, color: Colors.blue)), + ]), + ), + ), + ), + SizedBox(height: 0), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + RaisedButton( + onPressed: () async { + ret = true; + Navigator.pop(context, ret); //关闭弹框,返回sRet + }, + child: Text("确认"), + ), + RaisedButton( + child: Text("取消"), + onPressed: () async { + Navigator.pop(context, ret); //关闭弹框,返回sRet + }, + ) + ], + ), + ], + ), + ), + ), + ), + onWillPop: () { + // 屏蔽点击返回键的操作 + Navigator.pop(context, ret); + }, + ); + } +} diff --git a/lib/components/customDialogJ.dart b/lib/components/customDialogJ.dart new file mode 100644 index 0000000..76a78e2 --- /dev/null +++ b/lib/components/customDialogJ.dart @@ -0,0 +1,340 @@ +import 'package:flutter/material.dart'; +import 'commonFun.dart'; +import 'package:flutter/services.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import '../pages/Works/SBBJ/sbbj_content.dart'; +import 'doJSON.dart'; + +//删除消息对话框。自定义透明背景窗口,类似对话框 +class CustomDialogJ extends StatefulWidget { + CustomDialogJ({Key key, this.title = "", this.theKey = "", this.index = -1}) : super(key: key); + String title; + String theKey; + int index; + + _CheckBoxDemoState createState() => _CheckBoxDemoState(); +} + +//需要监听软键盘的弹出和隐藏 主要用 WidgetsBindingObserver 这个继承类 +//https://www.jianshu.com/p/872e23124470 +class _CheckBoxDemoState extends State with WidgetsBindingObserver { + String getTitle(int index) {} + int mapLen = 0; + + @override + void dispose() { + // TODO: implement dispose + //销毁 + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void initState() { + // TODO: implement initState + super.initState(); + //mapLen = mapGetSbbjGetData.length; + print("mapLen = $mapLen"); + getContent(); + //初始化 + WidgetsBinding.instance.addObserver(this); + } + + @override + void didChangeMetrics() { + super.didChangeMetrics(); + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + if (MediaQuery.of(context).viewInsets.bottom == 0) { + //关闭键盘 + bViewInsets = false; + } else { + //显示键盘 + bViewInsets = true; + } + }); + }); + } + + String strContent = ''; + bool bModifiable = false; + bool bViewInsets = false; + + getContent() async { + // strContent = ("" == mapGetSbbjGetData[widget.theKey].toString()) + // ? "(无数据)" + // : mapGetSbbjGetData[widget.theKey].toString(); + // widget.title = + // "第 ${widget.index + 1} 项(共 ${mapLen} 项) : " + mapGetSbbjGetDataText[widget.theKey]; + getPreBtn_NextBtn(); + + //时间戳转换 + if(strContent.isNotEmpty && ('addtime' == widget.theKey || 'createtime' == widget.theKey)) { + strContent = getDate(int.parse(strContent)); + } + + myController.text = strContent; + bModifiable = false;//mapGetSbbjGetDataModifiable[widget.thekey]; + setState(() {}); + } + + Widget getBtnSizeX({@required text, width = 60.0, height = 30.0, onPressedFun}) { + return Container( + color: Colors.white12, //onPressedFun为null时无效 + width: width, + height: height, + child: RaisedButton( + padding: EdgeInsets.all(0), + textColor: Colors.black, + child: Text(text), + onPressed: onPressedFun, + ), + ); + } + + Future alertDialog(String title, String content, var onPresse) async { + return await showDialog( + barrierDismissible: false, //表示点击灰色背景的时候是否消失弹出框 + context: context, + builder: (context) { + return AlertDialog( + title: Text(title), + content: Text(content), + actions: [ + FlatButton( + child: Text("确定"), + onPressed: onPresse, + ), + FlatButton( + child: Text("取消"), + onPressed: () { + Navigator.pop(context, false); + }, + ), + ], + ); + }, + ); + } + + //解决第一次进入报错问题。因为getPreBtn_NextBtn()还未执行,preBtn和nextBtn为空 + Widget preBtn = Container( + color: Colors.white12, //onPressedFun为null时无效 + width: 60.0, + height: 30.0, + child: RaisedButton( + padding: EdgeInsets.all(0), + textColor: Colors.black, + child: Text('上一项'), + onPressed: null, + ), + ); + + Widget nextBtn = Container( + color: Colors.white12, //onPressedFun为null时无效 + width: 60.0, + height: 30.0, + child: RaisedButton( + padding: EdgeInsets.all(0), + textColor: Colors.black, + child: Text('下一项'), + onPressed: null, + ), + ); + + getPreBtn_NextBtn() { + preBtn = getBtnSizeX( + text: "上一项", + onPressedFun: null, + ); + nextBtn = getBtnSizeX( + text: "下一项", + onPressedFun: null, + ); + + if (widget.index > 0 && mapLen > 0) { + preBtn = getBtnSizeX( + text: "上一项", + onPressedFun: () async { + if (widget.index > 0) { + widget.index--; + //widget.theKey = mapGetSbbjGetData.keys.elementAt(widget.index); + getContent(); + } + }, + ); + } + + if (widget.index < (mapLen - 1) && mapLen > 0) { + nextBtn = getBtnSizeX( + text: "下一项", + onPressedFun: () async { + if (widget.index < mapLen - 1) { + widget.index++; + //widget.theKey = mapGetSbbjGetData.keys.elementAt(widget.index); + getContent(); + } + }, + ); + } + } + + @override + Widget build(BuildContext context) { + SystemUiOverlayStyle(statusBarColor: Colors.transparent); + Size mediaSize = MediaQuery.of(context).size; + // TODO: implement build + return WillPopScope( + child: Scaffold( + //type: MaterialType.transparency, + //color: Colors.transparent, + //backgroundColor: Colors.transparent, + //color: Colors.blue, + backgroundColor: Color.fromRGBO(212, 212, 212, 0.6), + body: SafeArea( + child: Stack( + children: [ + Container( + alignment: Alignment(0, 0.2), + child: Container( + height: bViewInsets ? mediaSize.height * 0.4 : mediaSize.height * 0.85, + width: mediaSize.width * 0.95, + decoration: BoxDecoration( + color: Colors.white, + //border: Border.all(width: 1.0, color: Colors.black), + borderRadius: BorderRadius.all( + Radius.circular(3), + ), + ), + child: Column( + children: [ + Padding( + padding: EdgeInsets.fromLTRB(10, 10, 10, 0), + child: Stack( + children: [ + Align( + alignment: Alignment.centerLeft, + child: Text( + " ${widget.title}", + style: TextStyle( + fontSize: 15.0, + ), + ), + ), + Align( + alignment: Alignment.centerRight, + child: InkWell( + child: Icon(Icons.close), + onTap: () { + Navigator.pop(context); //关闭弹框,播放输入视频地址 + }, + ), + ) + ], + ), + ), + Divider(), + Container( + padding: EdgeInsets.fromLTRB(20, 10, 20, 10), + width: double.infinity, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(width: 1.0, color: Colors.blue), + borderRadius: BorderRadius.all( + Radius.circular(3), + ), + ), + padding: EdgeInsets.fromLTRB(10, 10, 10, 10), + width: double.infinity, + height: mediaSize.height * 0.15, + child: SingleChildScrollView( + child: Container( + child: TextField( + //textAlign: TextAlign.right, + keyboardType: TextInputType.multiline, + maxLines: 5, + minLines: 1, + style: TextStyle(fontSize: 14), + decoration: InputDecoration( + hintText: '請輸入字段信息', + border: InputBorder.none, //TextField去掉下划线 + contentPadding: EdgeInsets.only(right: 0), + ), + controller: myController, + enabled: bModifiable, + //利用控制器初始化文本 + onChanged: (value) {}, + ), + ), + ), + ), + ), + SizedBox(height: 5), + bViewInsets + ? SizedBox(height: 0) + : Container( + padding: EdgeInsets.fromLTRB(20, 10, 20, 10), + width: double.infinity, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(width: 1.0, color: Colors.blue), + borderRadius: BorderRadius.all( + Radius.circular(3), + ), + ), + padding: EdgeInsets.fromLTRB(10, 10, 10, 10), + width: double.infinity, + height: mediaSize.height * 0.45, + child: SingleChildScrollView( + child: Container( + //child: Text(strContent), + child: Text(strContent), + ), + ), + ), + ), + Expanded( + child: Container(), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + getBtnSizeX( + text: "复制", + onPressedFun: () { + // Flutter 复制文本到剪贴板 + Clipboard.setData(ClipboardData(text: strContent)); + //showToast('帮助信息已复制到剪贴板', textAlign: TextAlign.left); + Fluttertoast.showToast( + msg: '联系人信息已复制到剪贴板', gravity: ToastGravity.BOTTOM); + }, + ), + getBtnSizeX( + text: "返回", + onPressedFun: () { + Navigator.pop(context); //关闭弹框,播放输入视频地址 + }, + ), + preBtn, + nextBtn, + ], + ), + SizedBox(height: 35), + ], + ), + ), + ), + ], + ), + ), + ), + onWillPop: () { + // 屏蔽点击返回键的操作 + //player.pause(); + Navigator.pop(context); + }, + ); + } +} diff --git a/lib/components/customDialogWait.dart b/lib/components/customDialogWait.dart new file mode 100644 index 0000000..aa4abdd --- /dev/null +++ b/lib/components/customDialogWait.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'commonFun.dart'; + +//等待对话框 +class CustomDialogWait extends Dialog { + String title; + bool ret = false; + + CustomDialogWait({this.title = ""}); + + @override + Widget build(BuildContext context) { + Size mediaSize = MediaQuery.of(context).size; + return WillPopScope( + child: Material( + type: MaterialType.transparency, + child: Container( + child: getMoreWidget(text: title), + ), + ), + onWillPop: () { + // 屏蔽点击返回键的操作 + Navigator.pop(context, ret); + }, + ); + } +} diff --git a/lib/components/customIcontButton.dart b/lib/components/customIcontButton.dart new file mode 100644 index 0000000..d91fd39 --- /dev/null +++ b/lib/components/customIcontButton.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; + +//自定义图标按钮组件,顶部工具栏用 +class MyIcontButton extends StatelessWidget { + var pressed; + double width; + double height; + double iconSize; + Color color; + Color iconColor; + double radius; + IconData icon; + MyIcontButton({ + this.width = 30, + this.height = 30, + this.iconSize = 20, + this.color = Colors.blue, + this.iconColor = Colors.white, + this.radius = 0, + this.icon = Icons.message, + this.pressed = null, + }); + + @override + Widget build(BuildContext context) { + return Container( + height: this.height, + width: this.width, + decoration: BoxDecoration( + color: this.color, + border: Border.all(color: Colors.blue, width: 1.0), + borderRadius: BorderRadius.all( + Radius.circular(radius), + )), + child: IconButton( + color: Colors.white, + padding: EdgeInsets.all(0), + iconSize: this.iconSize, + icon: Icon(this.icon), + onPressed: this.pressed, + ), + ); + } +} diff --git a/lib/components/dioFun.dart b/lib/components/dioFun.dart new file mode 100644 index 0000000..8830074 --- /dev/null +++ b/lib/components/dioFun.dart @@ -0,0 +1,2543 @@ +import 'dart:convert'; + +import 'package:dio/dio.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/screen_util.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:http/http.dart' as my_http; +import 'package:hyzp_ybqx/components/doJSON.dart'; +import 'package:hyzp_ybqx/services/EventBus.dart'; +import 'package:hyzp_ybqx/widget/my_superplayer.dart'; +import 'package:sprintf/sprintf.dart'; + +import '../components/hyxx_data_handle.dart'; +import '../config/service_url.dart'; +import '../res/listContacts.dart'; +import '../widget/my_delay_toast.dart'; +// import 'package:hyzp_ybqx/widget/player_pro.dart'; +// import 'package:hyzp_ybqx/widget/player_pro_new.dart'; +import 'UserAuthority.dart'; +import 'commonFun.dart'; + +///球机方向控制 +// 接口地址:http://125.64.218.67:9906/api/ptz/{通道ID}/{球机ID} +// static const String setSphericalCameraUrl = 'http://125.64.218.67:9906/api/ptz'; //球机方向控制接口 +// 接口提交方式:post +// 接口文本:JSON +// 接口提交参数4个: +// cmdCode 说明:位移方向 +// horizonSpeed 说明:横向位移速度 +// verticalSpeed说明:垂直位移速度 +// zoomSpeed说明:变焦速度 +Future setSphericalCameraDio( + {@required int id, + @required int cmdCode, + int horizonSpeed = 120, + int verticalSpeed = 120, + int zoomSpeed = 120}) async { + Map map = { + "cmdCode": cmdCode.toString(), + "horizonSpeed": horizonSpeed.toString(), + "verticalSpeed": verticalSpeed.toString(), + "zoomSpeed": zoomSpeed.toString(), + }; + var api = getSphericalCameraApi(id, map); + print('api = ${api}'); + //I/flutter (28351): api = http://125.64.218.67:9906/api/ptz/34020000001320001016/34020000001320001016?cmdCode=8&horizonSpeed=60&verticalSpeed=60&zoomSpeed=60 + + try { + print('开始处理网络请求...'); + + Dio dio = Dio(); + print('map = ${map}'); + Response response = await dio.post(api); + print('response = ${response.toString()}'); + + if (response.statusCode == 200) { + // Map _mapDataRet = await getMapFromJson(response.data); + // _mapData = _mapDataRet['data']; + print('${api} 球机移动网络请求过程正常完成'); + + ///处理球机停止移动 + map["cmdCode"] = '0'; + api = getSphericalCameraApi(id, map); + print('api = ${api}'); + //I/flutter (28351): api = http://125.64.218.67:9906/api/ptz/34020000001320001016/34020000001320001016?cmdCode=0&horizonSpeed=60&verticalSpeed=60&zoomSpeed=60 + + response = await dio.post(api); + print('response = ${response.toString()}'); + if (response.statusCode == 200) { + print('${api} 球机停止移动网络请求过程正常完成'); + } + } else { + throw Exception('${api} 后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('${api} 网络请求过程异常 e:${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } +} + +// cmdCode:8 +// horizonSpeed:60 +// verticalSpeed:60 +// zoomSpeed:60 +// 上述调用可以一次性书写为: +// http://125.64.218.67:9906/api/ptz/34020000001320002016/34020000001320002016?cmdCode=8&horizonSpeed=60&verticalSpeed=60&zoomSpeed=60 +String getSphericalCameraApi(int id, Map map) { + var api = ServicePath.setSphericalCameraUrl + + getSphericalCameraID(id) + + '/' + + getSphericalCameraID(id) + + '?'; + + map.forEach((key, value) { + //print("$key : $value"); + api += (key + '=' + value + '&'); + }); + api = api.substring(0, api.length - 1); // 删除末尾字符 + + return api; +} + +///四:球机ID说明: +// 1号:34020000001320001016 +//sId = 34020000001320001016 +// 2号:34020000001320002016 +// 3号:34020000001320003016 +// 4号:34020000001320004016 +// 5号:34020000001320005016 +// 6号:34020000001320006016 +// 7号:34020000001320007016 +// 8号:34020000001320008016 +// 9号:34020000001320009016 +// 10号:34020000001320010016 +// 11号:34020000001320011016 +// 12号:34020000001320012016 +// 13号:34020000001320013016 +String getSphericalCameraID(int id) { + String sId = sprintf("%02i", [id]); + //print('sId =${sId}'); + sId = '340200000013200' + sId + '016'; + //print('sId =${sId}'); + return sId; +} + +///从指定 api 接口 获取数据 +Future getSomeDioData({@required String api}) async { + Map _mapGetData = {}; + try { + print('开始处理网络请求...'); + String random = RandomBit(6); //flutter (dart)生成N位随机数 + Map map = { + "sign": GenerateMd5(APPkey + random), + "random": random, + }; + + Dio dio = Dio(); + //static const String getNtimeUrl = ServiceUrl + '?s=App.Car_Hyc.GetNtime'; //获取违章间隔时间数据 + var api = ServicePath.getNtimeUrl; + print('api = ${api}'); + print('map = ${map}'); + //Response response = await dio.post(api, data: map); + Response response = await dio.post(api, data: map); + + print('response = ${response.toString()}'); + + if (response.statusCode == 200) { + Map _mapDataRet = await getMapFromJson(response.data); + _mapGetData = _mapDataRet['data']; + print('${api} 网络请求过程正常完成'); + } else { + throw Exception('${api} 后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('${api} 网络请求过程异常 e:${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + + return _mapGetData; +} + +//设置 sfyc (是否延迟)和 tsjj (推送交警) +Future set_sfyc_tsjj(int _zpsj) async { + String str = mapHyshlx[hyshlx]['apiNtiem']; + print('str = ${str}'); + //sfyc(是否延迟) + getSomeDioData(api: mapHyshlx[hyshlx]['apiNtiem']).then((value) async { + int nowStamp = DateTime.now().millisecondsSinceEpoch ~/ 1000; + int _Ntime = getSecondsOfNtime(value['value']); + //int _zpsj = int.parse(listGetZpjl[index]['zpsj']); + // B、若超出规定时间,即当前时间 > 抓拍时间 + 间隔时间,则sfyc=1,不推送交警。 + print('nowStamp = ${nowStamp}, (_zpsj + _Ntime) = ${(_zpsj + _Ntime)}'); + print('nowStamp > (_zpsj + _Ntime) = ${nowStamp > (_zpsj + _Ntime)}'); + + sfyc = 0; + //tsjj = true; //注释该行,避免 567-未选择推送交警也推送了 + if (nowStamp > (_zpsj + _Ntime)) { + sfyc = 1; + tsjj = false; + } + print('sfyc = ${sfyc}, tsjj = ${tsjj}'); + //黑烟审核 sfyc 改变广播 + eventBus.fire(HycsDataAuditSfyc('黑烟审核 sfyc 已改变')); + }); +} + +//App.Car_Led.Get接口获取的记录数据结构(比App.Car_Led.GetList接口获取的丰富) +// Map _mapGetLedXsxxGetData = { +// "id": 2, +// "dwip": "172.16.3.2", +// "xsnr": "绿水青山就是金山银山 宜宾市生态环境局宣。", +// "xsts": 0, //显示抓拍到的多少条违章记录 +// "stime": "07:00", +// "etime": "23:00", +// "addtime": "2021-01-20 10:16:07", +// "updatetime": "2021-02-13 11:48:51" +// }; + +///从接口 mapHyshlx[theSbgllx]['apiItem'] 获取指定id的单条记录返回 Map +Future getLedXsxxGetData({@required int id, @required String theSbgllx}) async { + Map _mapGetSbglGetData = {}; + + try { + print('开始处理网络请求...'); + String random = RandomBit(6); //flutter (dart)生成N位随机数 + Map map = { + "id": id.toString(), + "sign": GenerateMd5(APPkey + random), + "random": random, + }; + + Dio dio = Dio(); + print('mapHyshlx[theSbgllx][apiItem] = ${mapHyshlx[theSbgllx]['apiItem']}'); + print('map = ${map}'); + Response response = await dio.post(mapHyshlx[theSbgllx]['apiItem'], data: map); + printWrapped('response = ${response.toString()}'); + + if (response.statusCode == 200) { + Map _mapGetSbglGetDataRet = await getMapFromJson(response.data); + _mapGetSbglGetData = _mapGetSbglGetDataRet['data']; + + printWrapped('_mapGetSbglGetData[\'xsts\'] = ${_mapGetSbglGetData['xsts']}'); + print('theSbgllx网络请求过程正常完成'); + } else { + throw Exception('theSbgllx后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('theSbgllx网络请求过程异常e:${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + + return _mapGetSbglGetData; +} + +// App.Car_Led.Update +// 更新数据 +// 接口地址:http://125.64.218.67:9904/?s=App.Car_Led.Update +// 接口文档 +// 根据ID更新数据库中的一条纪录数据 +// +// 接口参数 +// 参数名字 类型 是否必须 默认值 其他 说明 +// sign 字符串 必须 +// random 字符串 必须 +// id 字符串 必须 最小:1 ID +// xsnr 字符串 必须 字幕内容 +// xsts 整型 必须 0 最小:0 同步显示违章记录条数 +// stime 字符串 必须 08:00 启用时间段-开始时间 +// etime 字符串 必须 21:00 启用时间段-结束时间 +// 返回结果 +// 返回字段 类型 说明 +// code 整型 更新的结果,1表示成功,0表示无更新,false表示失败 +// 异常情况 +// 错误码 错误描述信息 +// 400 表示客户端参数错误 +// 404 表示接口服务不存在 +// 500 表示服务端内部错误 + +///从接口 mapHyshlx[theSbgllx]['apiItem'] 更新指定id的LED数据 +Future updateLedData({@required int id, @required String theSbgllx, @required Map map}) async { + Map _mapGetSbglGetData = {}; + + try { + print('开始处理网络请求...'); + String random = RandomBit(6); //flutter (dart)生成N位随机数 + map.addAll({ + "id": id.toString(), + "sign": GenerateMd5(APPkey + random), + "random": random, + }); + + if (!map.containsKey('xsnr')) { + map['xsnr'] = ''; + } + + if (!map.containsKey('xsts')) { + map['xsts'] = 0; + } + + if (!map.containsKey('stime')) { + map['stime'] = '08:00'; + } + + if (!map.containsKey('xsnr')) { + map['etime'] = '21:00'; + } + + Dio dio = Dio(); + print('mapHyshlx[theSbgllx][apiItem] = ${mapHyshlx[theSbgllx]['apiItem']}'); + printWrapped('map = ${map}'); + Response response = await dio.post(mapHyshlx[theSbgllx]['apiItem'], data: map); + print('response = ${response.toString()}'); + + if (response.statusCode == 200) { + Map _mapGetSbglGetDataRet = await getMapFromJson(response.data); + _mapGetSbglGetData = _mapGetSbglGetDataRet['data']; + //code 整型 更新的结果,1表示成功,0表示无更新,false表示失败 + print('_mapGetSbglGetData = ${_mapGetSbglGetData.toString()}'); + //_mapGetSbglGetData = {code: 1} + Map codeMap = { + 0: '没有更新。', + 1: '更新成功。', + false: '更新失败!', + '批量更新完成': '批量更新完成', + }; + + Fluttertoast.showToast( + msg: codeMap[_mapGetSbglGetData['code']], + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + + print('theSbgllx网络请求过程正常完成'); + } else { + throw Exception('theSbgllx后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('theSbgllx网络请求过程异常e:${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + + return _mapGetSbglGetData; +} + +/* +App.Car_Statis.GetStaAll +获取抓拍集成统计数据 +接口地址:http://125.64.218.67:9904/?s=App.Car_Statis.GetStaAll +接口文档 +根据dwip获取抓拍记录统计数据 + +接口参数 +参数名字 类型 是否必须 默认值 其他 说明 +sign 字符串 必须 +random 字符串 必须 +dwip 字符串 可选 点位IP +time 字符串 可选 统计日期,格式如:2021-03-10 +返回结果 +返回字段 类型 说明 +dwip 字符串 点位IP +dwmc 字符串 点位名称 +zp_all 整型 累计抓拍数 +hyc_all 整型 累计已审核数 +cll_today 整型 今日车流量 +cll_all 整型 累计车流量 +zp_today 整型 今日抓拍数 +sends 整型 已推送交警数 +send_all 整型 累计推送交警数 +csnum 整型 已初审数 +fsnum 整型 已复核数 +异常情况 +错误码 错误描述信息 +400 表示客户端参数错误 +404 表示接口服务不存在 +500 表示服务端内部错误 +*/ + +///从接口 App.Car_Statis.GetStaAll 获取今日集成统计数据,返回 Map +///子非鱼: 可指定统计日期和点位参数。若指定,则依次显示当日所有点位。统计日期格式如:2021-03-10 +Future getAllStatisData({String ip = '', String date = ''}) async { + //Map _mapAllStatisData = {}; + List _listAllStatisData = []; + + try { + print('开始处理网络请求...'); + String random = RandomBit(6); //flutter (dart)生成N位随机数 + Map map = { + "sign": GenerateMd5(APPkey + random), + "random": random, + }; + + if (ip.isNotEmpty) { + map['dwip'] = ip; + } + + if (date.isNotEmpty) { + map['time'] = date; + } + + Dio dio = Dio(); + String api = ServicePath.getStaAllUrl; + print('api = ${api}'); + print('map = ${map}'); + Response response = await dio.post(api, data: map); + print('response = ${response.toString()}'); + + if (response.statusCode == 200) { + Map _mapStatisDataRet = await getMapFromJson(response.data); + _listAllStatisData = _mapStatisDataRet['data']; + print('getAllStatisData 网络请求过程正常完成'); + } else { + throw Exception('getAllStatisData 后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('getAllStatisData 网络请求过程异常e:${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + + return _listAllStatisData; +} + +/* +_mapAllStatisData + +200 OK +cache-control: no-store, no-cache, must-revalidate +connection: keep-alive +content-type: application/json;charset=utf-8 +date: Tue, 25 May 2021 01:27:11 GMT +expires: Thu, 19 Nov 1981 08:52:00 GMT +pragma: no-cache +server: openresty/1.15.8.3 +transfer-encoding: chunked +x-powered-by: PHP/7.3.4 + +{ + "ret": 200, + "data": [ + { + "id": 1, + "dwip": "172.16.3.1", + "dwmc": "锦绣花园", + "zp_all": 54, + "hyc_all": 52, + "zp_today": 1, + "sends": 0, + "send_all": 33, + "csnum": 1, + "fsnum": 0, + "cll_today": 6420, + "cll_all": 3225733 + }, + { + "id": 2, + "dwip": "172.16.3.2", + "dwmc": "石马溪桥", + "zp_all": 377, + "hyc_all": 370, + "zp_today": 1, + "sends": 0, + "send_all": 204, + "csnum": 1, + "fsnum": 0, + "cll_today": 3430, + "cll_all": 2127839 + }, + { + "id": 3, + "dwip": "172.16.3.3", + "dwmc": "森林小区", + "zp_all": 380, + "hyc_all": 375, + "zp_today": 0, + "sends": 0, + "send_all": 253, + "csnum": 0, + "fsnum": 0, + "cll_today": 6230, + "cll_all": 3051847 + }, + { + "id": 4, + "dwip": "172.16.3.4", + "dwmc": "南山星城", + "zp_all": 232, + "hyc_all": 229, + "zp_today": 1, + "sends": 0, + "send_all": 164, + "csnum": 1, + "fsnum": 0, + "cll_today": 7900, + "cll_all": 2989374 + }, + { + "id": 5, + "dwip": "172.16.3.5", + "dwmc": "育才学校", + "zp_all": 91, + "hyc_all": 91, + "zp_today": 0, + "sends": 0, + "send_all": 68, + "csnum": 0, + "fsnum": 0, + "cll_today": 0, + "cll_all": 1664910 + }, + { + "id": 6, + "dwip": "172.16.3.6", + "dwmc": "七星花园", + "zp_all": 57, + "hyc_all": 56, + "zp_today": 0, + "sends": 0, + "send_all": 22, + "csnum": 0, + "fsnum": 0, + "cll_today": 4110, + "cll_all": 2559674 + }, + { + "id": 7, + "dwip": "172.16.3.7", + "dwmc": "市财政局", + "zp_all": 0, + "hyc_all": 0, + "zp_today": 0, + "sends": 0, + "send_all": 0, + "csnum": 0, + "fsnum": 0, + "cll_today": 0, + "cll_all": 0 + }, + { + "id": 8, + "dwip": "172.16.3.8", + "dwmc": "宜威路", + "zp_all": 100, + "hyc_all": 97, + "zp_today": 0, + "sends": 0, + "send_all": 66, + "csnum": 0, + "fsnum": 0, + "cll_today": 2835, + "cll_all": 1891302 + }, + { + "id": 9, + "dwip": "172.16.3.9", + "dwmc": "宜长路", + "zp_all": 83, + "hyc_all": 82, + "zp_today": 0, + "sends": 0, + "send_all": 73, + "csnum": 1, + "fsnum": 0, + "cll_today": 4010, + "cll_all": 2211060 + }, + { + "id": 10, + "dwip": "172.16.3.10", + "dwmc": "临港马家湾", + "zp_all": 57, + "hyc_all": 57, + "zp_today": 0, + "sends": 0, + "send_all": 32, + "csnum": 0, + "fsnum": 0, + "cll_today": 0, + "cll_all": 2235153 + }, + { + "id": 11, + "dwip": "172.16.3.11", + "dwmc": "环城路", + "zp_all": 111, + "hyc_all": 110, + "zp_today": 0, + "sends": 0, + "send_all": 75, + "csnum": 0, + "fsnum": 0, + "cll_today": 0, + "cll_all": 1136968 + }, + { + "id": 12, + "dwip": "172.16.3.12", + "dwmc": "陶瓷厂", + "zp_all": 47, + "hyc_all": 46, + "zp_today": 0, + "sends": 0, + "send_all": 12, + "csnum": 0, + "fsnum": 0, + "cll_today": 3315, + "cll_all": 1574973 + }, + { + "id": 13, + "dwip": "172.16.3.13", + "dwmc": "鑫菁英", + "zp_all": 170, + "hyc_all": 170, + "zp_today": 0, + "sends": 0, + "send_all": 87, + "csnum": 0, + "fsnum": 0, + "cll_today": 4090, + "cll_all": 2613670 + } + ], + "msg": "" +} +*/ + +///从接口 mapStatisType[statisType]['api'] 获取指定 ip 的 statisType 类型的统计数据,返回 Map +Future getStatisData({@required String statisType, String ip = '', String date = ''}) async { + Map _mapStatisData = {}; + + try { + print('开始处理网络请求...'); + String random = RandomBit(6); //flutter (dart)生成N位随机数 + Map map = { + "sign": GenerateMd5(APPkey + random), + "random": random, + }; + + if (ip.isNotEmpty) { + map['dwip'] = ip; + } + + if ('sh_hyc_tj' == statisType && date.isNotEmpty) { + map['time'] = date; + } + + if ('cllrtj' == statisType) { + map['stime'] = mapStatisType[statisType]['startDate']; + map['etime'] = mapStatisType[statisType]['endDate']; + // print('map[\'stime\'] = ${map['stime']}'); + // print('map[\'etime\'] = ${map['etime']}'); + } + + Dio dio = Dio(); + String api = mapStatisType[statisType]['api']; + print('api = ${api}'); + print('map = ${map}'); + Response response = await dio.post(api, data: map); + print('response = ${response.toString()}'); + + if (response.statusCode == 200) { + Map _mapStatisDataRet = await getMapFromJson(response.data); + _mapStatisData = _mapStatisDataRet['data']; + print('$statisType 网络请求过程正常完成'); + } else { + throw Exception('$statisType 后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('$statisType 网络请求过程异常e:${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + + return _mapStatisData; +} + +//添加LED字幕 +Future insertLedXsxx(String api, Map map, String text) async { + //var api = ServicePath.insertLedXsxxUrl; + print(api); + + try { + print('开始处理网络请求...'); + Response response; + Dio dio = Dio(); + + map['random'] = RandomBit(6); + map['sign'] = GenerateMd5(APPkey + map['random']); + print('map = ${map}'); + + response = await dio.post(api, data: map); + print('response = ${response.toString()}'); + if (response.statusCode == 200) { + print('$text网络请求过程正常完成'); + return true; + } else { + throw Exception('后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('网络请求过程异常e:${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + + return false; +} + +//违章信息审核 +Future auditWzxxData(String api) async { + //var api = ServicePath.auditWzxxUrl; + print(api); + + try { + print('开始处理网络请求...'); + Response response; + Dio dio = Dio(); + + mapAuditWzxxData['random'] = RandomBit(6); + mapAuditWzxxData['sign'] = GenerateMd5(APPkey + mapAuditWzxxData['random']); + response = await dio.post(api, data: mapAuditWzxxData); + print('response = ${response.toString()}'); + //I/flutter ( 1563): {"ret":200,"data":{"code":1},"msg":""} + //I/flutter ( 5157): {"ret":200,"data":{"code":1},"msg":""} + if (response.statusCode == 200) { + print('违章信息${mapHyshlx[hyshlx]['text']}网络请求过程正常完成'); + return true; + } else { + throw Exception('后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('网络请求过程异常e:${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + + return false; +} + +//1、获取指定id的推送交警记录列表存入listGetTsjj +Future getTsjjData(int id) async { + Map _mapGetTsjjGetData = await getWzxxItemData(id); //获取指定id的违章信息返回_mapGetHycsGetData + + print('_mapGetTsjjGetData = ${_mapGetTsjjGetData}'); + print('_mapGetTsjjGetData["yjxx_id"] = ${_mapGetTsjjGetData["yjxx_id"]}'); + + return listGetTsjj; +} + +//1、获取指定id的抓拍记录列表存入listGetZpjl +//2、如果是黑烟复审,还需获取指定id的违章记录审核信息存入mapGetShenheData +Future getItemData(int id) async { + Map _mapGetHycsGetData = await getWzxxItemData(id); //获取指定id的违章信息返回_mapGetHycsGetData + //Unhandled Exception: NoSuchMethodError: The method 'split' was called on null. + print('_mapGetHycsGetData = ${_mapGetHycsGetData}'); + print('_mapGetHycsGetData["yjxx_id"] = ${_mapGetHycsGetData["yjxx_id"]}'); + + //1、获取指定id的抓拍记录列表存入listGetZpjl + List listID = _mapGetHycsGetData["yjxx_id"].split(","); + listGetZpjl.clear(); + print('listID = ${listID}'); + print('listGetZpjl = ${listGetZpjl}'); + + // listID.forEach((element) async { + // await getZpjlData(int.parse(element)); //Unhandled Exception: type 'String' is not a subtype of type 'int' + // }); + + for (var _id in listID) { + //Unhandled Exception: type 'String' is not a subtype of type 'int' + await getZpjlData(int.parse(_id)); //获取指定id的抓拍记录数据存入listGetZpjl + } + print('listGetZpjl = ${listGetZpjl}'); + + //2、如果是黑烟复审、非黑烟查询,还需获取指定id的违章记录审核信息存入 listGetShenheData + listGetShenheData.clear(); + print('listGetShenheData = ${listGetShenheData}'); + if (hyshlx == 'hyfh' || hyshlx == 'fhycx') { + await getShenheData(id); //获取指定id的审核信息存入 listGetShenheData + await getMapGetShenheData(); //从listGetShenheData中取出数据分别存入mapGetHycsShenheData、mapGetHyfhShenheData + } + print('listGetShenheData = ${listGetShenheData}'); + print('mapGetHycsShenheData = ${mapGetHycsShenheData}'); + print('mapGetHyfhShenheData = ${mapGetHyfhShenheData}'); + + return listGetZpjl; +} + +//从listGetShenheData中取出数据分别存入mapGetHycsShenheData、mapGetHyfhShenheData +getMapGetShenheData() async { + mapGetHycsShenheData.clear(); //黑烟初审信息mapGetHycsShenheData + mapGetHyfhShenheData.clear(); //黑烟复审信息mapGetHyfhShenheData + for (var _mapShenheItem in listGetShenheData) { + //注意:只取符合条件的第一条记录的数据 + //workflow 整型 审核记录所属流程:1-初审,2-复审 + if (_mapShenheItem['workflow'] == 1 && mapGetHycsShenheData.isEmpty) { + mapGetHycsShenheData = _mapShenheItem; + } else if (_mapShenheItem['workflow'] == 2 && mapGetHyfhShenheData.isEmpty) { + mapGetHyfhShenheData = _mapShenheItem; + } + } +} + +//获取指定id的抓拍记录数据存入listGetZpjl +Future getZpjlData(int id) async { + var api = ServicePath.getZpjlGetUrl; + print(api); + //http://125.64.218.67:9904/docs.php?service=App.Car_Yjxx.Get&detail=1&type=fold + + //listGetZpjl.clear(); + Map _mapZpjlGetData = {}; + + print('id = ${id}'); + if (id < 1) { + print('获取${mapHyshlx[hyshlx]['text']}数据的 id 号不正确!'); + Fluttertoast.showToast( + msg: '获取${mapHyshlx[hyshlx]['text']}数据的 id 号不正确!', gravity: ToastGravity.CENTER); + } else { + try { + print('开始处理网络请求...'); + Response response; + Dio dio = Dio(); + + String random = RandomBit(6); //flutter (dart)生成N位随机数 + response = await dio.post(api, data: { + "id": id, + "sign": GenerateMd5(APPkey + random), + "random": random, + }); + print('response = ${response.toString()}'); + //I/flutter ( 5232): {"ret":200,"data":{"items":[],"total":0,"page":1,"perpage":10},"msg":""} + if (response.statusCode == 200) { + Map mapZpjlGetDataRet = await getMapFromJson(response.data); + _mapZpjlGetData = mapZpjlGetDataRet['data']; + listGetZpjl.add(_mapZpjlGetData); + print('获取${mapHyshlx[hyshlx]['text']}数据的网络请求过程正常完成'); + } else { + throw Exception('后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('网络请求过程异常e:${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + } + + return _mapZpjlGetData; +} + +///////////////////////////////////////////////////////////////////// +//处理审核信息数据 + +//App.Car_Hyc.GetShenhe +// 获取审核信息 +// 接口地址:http://125.64.218.67:9904/?s=App.Car_Hyc.GetShenhe +// 接口文档 +// 根据ID获取违章记录的审核记录数据 +// +// 接口参数 +// 参数名字 类型 是否必须 默认值 其他 说明 +// sign 字符串 必须 +// random 字符串 必须 +// id 字符串 必须 最小:1 违章记录ID +// 返回结果 +// 返回字段 类型 说明 +// id 整型 审核记录ID +// xid 整型 违章记录ID +// title 字符串 审核结果 +// shuoming 字符串 审核意见 +// uname 字符串 审核人 +// addtime 字符串 审核日期 +// workflow 整型 审核记录所属流程:1-初审,2-复审 +// 异常情况 +// 错误码 错误描述信息 +// 400 表示客户端参数错误 +// 404 表示接口服务不存在 +// 500 表示服务端内部错误 + +//{ +// "ret": 200, +// "data": [ +// { +// "id": 362, +// "xid": 980, +// "title": "黑烟车", +// "shuoming": "黑烟超标,交由交警处罚", +// "uname": "admin", +// "addtime": "2021-01-31 16:23:26", +// "workflow": 2 +// }, +// { +// "id": 354, +// "xid": 980, +// "title": "非黑烟车", +// "shuoming": "误报,或肉眼无法识别", +// "uname": "admin", +// "addtime": "2021-01-31 14:07:42", +// "workflow": 1 +// } +// ], +// "msg": "" +//} + +// 同时推送交警 +bool fh_hyc = true; //复审为黑烟车 +int sfyc = 0; //复审接口增加是否延迟字段 sfyc 整型 必须 是否延误,0-正常 1-延误。延误状态的不推送 +bool tsjj = true; //同时推送交警 + +//hyc为黑烟车拼音首字母缩写,fhyc为非黑烟车拼音首字母缩写 +String hyc_shyj = '黑烟超标,交由交警处罚。'; +String hyc_text = '黑烟车'; +String fhyc_shyj = '误报,或肉眼无法识别。'; +String fhyc_text = '非黑烟车'; + +Map mapGetShenheDataret = { + "ret": 200, + "data": [ + { + "id": 420, + "xid": 1151, + "title": "黑烟车", + "shuoming": "黑烟超标,交由交警处罚", + "uname": "admin", + "addtime": "2021-02-05 15:45:32", + "workflow": 2 + }, + { + "id": 419, + "xid": 1151, + "title": "黑烟车", + "shuoming": "黑烟超标,交由交警处罚", + "uname": "admin", + "addtime": "2021-02-05 15:44:09", + "workflow": 1 + } + ], + "msg": "" +}; + +//返回字段 类型 说明 +// id 整型 审核记录ID +// xid 整型 违章记录ID +// title 字符串 审核结果 +// shuoming 字符串 审核意见 +// uname 字符串 审核人 +// addtime 字符串 审核日期 +// workflow 整型 审核记录所属流程:1-初审,2-复审 + +//黑烟审核信息: +List listGetShenheData = [ + { + "id": 420, + "xid": 1151, + "title": "黑烟车", + "shuoming": "黑烟超标,交由交警处罚", + "uname": "admin", + "addtime": "2021-02-05 15:45:32", + "workflow": 2 + }, + { + "id": 419, + "xid": 1151, + "title": "黑烟车", + "shuoming": "黑烟超标,交由交警处罚", + "uname": "admin", + "addtime": "2021-02-05 15:44:09", + "workflow": 1 + } +]; + +//黑烟初审信息 +//workflow 整型 审核记录所属流程:1-初审,2-复审 +Map mapGetHycsShenheData = { + "id": 419, + "xid": 1151, + "title": "黑烟车", + "shuoming": "黑烟超标,交由交警处罚", + "uname": "admin", + "addtime": "2021-02-05 15:44:09", + "workflow": 1 +}; + +//黑烟复审信息 +//workflow 整型 审核记录所属流程:1-初审,2-复审 +Map mapGetHyfhShenheData = { + "id": 420, + "xid": 1151, + "title": "黑烟车", + "shuoming": "黑烟超标,交由交警处罚", + "uname": "admin", + "addtime": "2021-02-05 15:45:32", + "workflow": 2 +}; + +//获取指定id的违章记录审核信息存入listGetShenheData +Future getShenheData(int id) async { + var api = ServicePath.getShenheUrl; + print(api); + //http://125.64.218.67:9904/?s=App.Car_Hyc.GetShenhe + + listGetShenheData.clear(); + if (id < 1) { + print('获取${mapHyshlx[hyshlx]['text']}数据的 id 号不正确!'); + Fluttertoast.showToast( + msg: '获取${mapHyshlx[hyshlx]['text']}数据的 id 号不正确!', gravity: ToastGravity.CENTER); + } else { + try { + print('开始处理网络请求...'); + Response response; + Dio dio = Dio(); + + print('id = ${id}'); + String random = RandomBit(6); //flutter (dart)生成N位随机数 + response = await dio.post(api, data: { + "id": id, + "sign": GenerateMd5(APPkey + random), + "random": random, + }); + print('response = ${response.toString()}'); + //response = {"ret":200,"data":[{"id":427,"xid":1171,"title":"非黑烟车","shuoming":"误报,或肉眼无法识别", + // "uname":"admin","addtime":"2021-02-07 12:43:47","workflow":1}],"msg":""} + if (response.statusCode == 200) { + //网络请求过程异常e:type 'List' is not a subtype of type 'Map' + //报错原因:_mapGetShenheDataRet['data']是List类型,而非Map类型 + Map _mapGetShenheDataRet = await getMapFromJson(response.data); + listGetShenheData = _mapGetShenheDataRet['data']; + print('获取${mapHyshlx[hyshlx]['text']}数据的网络请求过程正常完成'); + // print('listGetZpjl = ${listGetZpjl}'); + // print('listGetZpjl = ${listGetZpjl[0]}'); + } else { + throw Exception('后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('网络请求过程异常e:${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + } + + return listGetShenheData; +} + +//违章信息审核 +//App.Car_Hyc.Workflow +// 审核数据 +// 接口地址:http://125.64.218.67:9904/?s=App.Car_Hyc.Workflow +// 接口文档 +// 根据ID审核数据库中的一条纪录数据 +// +// 接口参数 +// 参数名字 类型 是否必须 默认值 其他 说明 +// sign 字符串 必须 +// random 字符串 必须 +// id 整型 必须 最小:1 ID +// workflow 整型 必须 2 最小:1 审核标记: 2=>初审通过 | 999=>复审通过 +// shuoming 字符串 必须 审核意见:如 黑烟超标,交由交警处罚 +// title 字符串 必须 审核结果:黑烟车 | 非黑烟车 +// uid 整型 必须 最小:1 审核用户ID +// uname 字符串 必须 审核用户账号 +// checkid 整型 必须 最小:1 用户选中的作为执法依据的抓拍记录ID +// plate_id 字符串 必须 车牌号 +// plate_color 字符串 必须 车牌颜色 +// 返回结果 +// 返回字段 类型 说明 +// code 整型 更新的结果,1表示成功,0表示无更新,false表示失败 + +Map mapAuditWzxxData = { + 'sign': '', + 'random': '', + 'id': '', + 'workflow': 2, + 'shuoming': '', + 'title': '', + 'uid': -1, + 'uname': '', + 'checkid': -1, + 'plate_id': '', + 'plate_color': '', + 'sfyc': -1, +}; + +Map getMapAuditWzxxData( + int _id, int _indexZpjl, int _workflow, String _shuoming, String _title, int sfyc) { + String random = RandomBit(6); //flutter (dart)生成N位随机数 + + mapAuditWzxxData['sign'] = GenerateMd5(APPkey + random); + mapAuditWzxxData['random'] = random; + mapAuditWzxxData['id'] = _id.toString(); + mapAuditWzxxData['workflow'] = _workflow; + mapAuditWzxxData['shuoming'] = _shuoming; + mapAuditWzxxData['title'] = _title; + mapAuditWzxxData['uid'] = g_userInfo.mapUserInfoRet['data']['user_id']; + mapAuditWzxxData['uname'] = g_userInfo.username; + mapAuditWzxxData['checkid'] = listGetZpjl[_indexZpjl]['id']; + mapAuditWzxxData['plate_id'] = listGetZpjl[_indexZpjl]['car_number']; + mapAuditWzxxData['plate_color'] = listGetZpjl[_indexZpjl]['cpys']; + mapAuditWzxxData['sfyc'] = sfyc; + + return mapAuditWzxxData; +} + +//App.Car_Bjxx.Workflow +// 核查处理报警数据 +// 接口地址:http://125.64.218.67:9904/?s=App.Car_Bjxx.Workflow +// 接口文档 +// 根据ID处理报警数据库中的一条纪录数据 +// +// 接口参数 +// 参数名字 类型 是否必须 默认值 其他 说明 +// sign 字符串 必须 +// random 字符串 必须 +// id 整型 必须 最小:1 ID +// workflow 整型 必须 2 最小:1 处理标记: 999=>已处理 +// shuoming 字符串 必须 处理意见:如 已处理,设备恢复正常 +// title 字符串 必须 处理结果:已处理 | 误报 +// uid 整型 必须 最小:1 审核用户ID +// uname 字符串 必须 审核用户账号 +// 返回结果 +// 返回字段 类型 说明 +// code 整型 更新的结果,1表示成功,0表示无更新,false表示失败 + +// 核查处理报警数据函数 +// int _sbglID, //设备管理信息ID +// int _workflow //workflow 整型 必须 2 最小:1 处理标记: 999=>已处理 +// String _shuoming, //处理意见:如 已处理,设备恢复正常 +// String _title //处理结果:已处理 | 误报 +// int _uid //审核用户ID +// String _uname //审核用户账号 +// sbglContentFirstAudit 整型返回值:更新的结果,1表示成功,0表示无更新,false表示失败 +Future sbglContentAudit( + {@required String sbgllx, + @required int sbglID, + @required String shuoming, + @required String title}) async { + var ret = -1; + //var api = ServicePath.auditSbbjUrl; + var api = mapHyshlx[sbgllx]['apiAudit']; + print('api = $api'); + // api = http://125.64.218.67:9904/?s=App.Car_Bjxx.Workflow + + try { + print('开始处理网络请求...'); + Response response; + Dio dio = Dio(); + + //准备dio数据结构 + String random = RandomBit(6); //flutter (dart)生成N位随机数 + Map map = {}; //必须初始化,否则报错:Tried calling: []=("sign", "a50d481afb6480e2872529cf8092963d") + map['sign'] = GenerateMd5(APPkey + random); + map['random'] = random; + map['id'] = sbglID; + map['workflow'] = mapHyshlx[sbgllx]['audit_workflow']; + map['shuoming'] = shuoming; + map['title'] = title; + map['uid'] = g_userInfo.mapUserInfoRet['data']['user_id']; + map['uname'] = g_userInfo.username; + print('map =\n${map}'); + //map = {sign: 1eec43fe66b350cb599d915d50806b5d, random: 110567, workflow: 999, + // shuoming: 已做简单处理。低级故障,不影响系统运行, title: 已处理, uid: 135, uname: admin} + + response = await dio.post(api, data: map); + //return; + print('response = ${response.toString()}'); + //response = {"ret":200,"data":{"code":1},"msg":""} + //response = {"ret":400,"data":{},"msg":"非法请求:缺少必要参数id"} + //网络请求过程异常e: + // DioError [DioErrorType.DEFAULT]: NoSuchMethodError: The method 'startsWith' was called on null. Receiver: null + + //code 整型返回值:更新的结果,1表示成功,0表示无更新,false表示失败 + //I/flutter ( 5152): {"ret":200,"data":{"code":1},"msg":""} + if (response.statusCode == 200) { + print('${mapHyshlx[hyshlx]['text']}核查处理网络请求过程正常完成'); + Map _mapSbglContentFirstAudit = await getMapFromJson(response.data); + ret = _mapSbglContentFirstAudit["data"]["code"]; + print('_mapSbglContentFirstAudit["data"]["code"] = ${ret}'); + } else { + throw Exception('后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('网络请求过程异常e:${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + + return ret; +} + +//Web 处理提交成功: +//service 必须 App.Car_Bjxx.Workflow +// sign 必须 3967eaebec0eed0642a1d395ac9293dd +// random 必须 1003 +// id 必须 1209 +// workflow 必须 2 +// shuoming 必须 黑烟超标,交由交警处罚 +// title 必须 黑烟车 +// uid 必须 135 +// uname 必须 admin +// http://125.64.218.67:9904/ +// 200 OK +// cache-control: no-store, no-cache, must-revalidate +// connection: keep-alive +// content-type: application/json;charset=utf-8 +// date: Thu, 18 Feb 2021 14:11:37 GMT +// expires: Thu, 19 Nov 1981 08:52:00 GMT +// pragma: no-cache +// server: openresty/1.15.8.3 +// transfer-encoding: chunked +// x-powered-by: PHP/7.3.4 +// +// { +// "ret": 200, +// "data": { +// "code": 1 +// }, +// "msg": "" +// } + +// 黑烟审核提交函数 +// int _wzxxID, //违章信息ID +// int _indexZpjl, //Zpjl 0基索引 +// int _workflow //workflow 整型 必须 2 最小:1 审核标记: 2=>初审通过 | 999=>复审通过 +// String _shuoming, //审核意见 +// String _title //审核结果:黑烟车 | 非黑烟车 +// {bool tsjj = true} //是否推送交警:可选参数,默认为true +// {int sfyc} //复审接口增加是否延迟字段 +// hyshContentFirstAudit整型返回值:-1 表示出现异常,其余为更新结果,1表示成功,0表示无更新,false表示失败; +Future hyshContentFirstAudit( + int _wzxxID, int _indexZpjl, int _workflow, String _shuoming, String _title, + {@required int sfyc, bool tsjj = true}) async { + var ret = -1; + var api = ServicePath.auditWzxxUrl; + print(api); + //I/flutter (19102): http://125.64.218.67:9904/?s=App.Car_Hyc.Workflow + + try { + print('开始处理网络请求...'); + Response response; + Dio dio = Dio(); + + getMapAuditWzxxData(_wzxxID, _indexZpjl, _workflow, _shuoming, _title, sfyc); //准备dio数据结构 + + print('mapAuditWzxxData =\n${mapAuditWzxxData}'); + //mapAuditWzxxData = {sign: 29bc5d7562022c53b7be5771e271a53d, random: 295149, id: 1171, + // workflow: 999, shuoming: 初审为非黑烟车。复审黑烟超标,交由交警处罚。, title: 黑烟车, ui + // d: 135, uname: admin, checkid: 1347, plate_id: 川QDS607, plate_color: 蓝色} + + //黑烟初审 + //mapAuditWzxxData = { + // sign: 1c3c8842e987dff27f7d856d6d47ba18, random: 256171, + // id: 1189, workflow: 2, shuoming: 黑烟超标,交由交警处罚。, title: 黑烟车, + // uid: 135, uname: admin, checkid: 1365, plate_id: 云CX2728, plate_color: 蓝色} + + //黑烟复审 + //mapAuditWzxxData = { + // sign: 5659087c5871e8221b1591eca1f7abd1, random: 795268, + // id: 1170, workflow: 999, shuoming: 初审为黑烟车。复审黑烟超标,交由交警处罚。, title: 黑烟车, + // uid: 135, uname: admin, checkid: 1345, plate_id: 川15C0895, plate_color: 蓝色} + + //return false; + + response = await dio.post(api, data: mapAuditWzxxData); + print('response = ${response.toString()}'); + //response = {"ret":200,"data":{"code":1},"msg":""} + //response = {"ret":200,"data":{"code":1},"msg":""} + + //code 整型 更新的结果,1表示成功,0表示无更新,false表示失败 + //I/flutter ( 5152): {"ret":200,"data":{"code":1},"msg":""} + if (response.statusCode == 200) { + print('违章信息${mapHyshlx[hyshlx]['text']}网络请求过程正常完成'); + Map _maphyshContentFirstAuditRet = await getMapFromJson(response.data); + ret = _maphyshContentFirstAuditRet["data"]["code"]; + print('App.Car_Hyc.Workflow返回值中的code 整型 更新的结果,1表示成功,0表示无更新,false表示失败'); + print('_maphyshContentFirstAuditRet["data"]["code"] = ${ret}'); + } else { + throw Exception('后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('网络请求过程异常e:${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + + return ret; +} + +//处理审核信息数据 +///////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////////// +//向交警服务器推送数据-模块数据 + +//推送交警操作函数 +Future tsjjFun(int _wzxxID, String _plateAndID) async { + String ret = ''; + //var ret = -1;//这样会报错:type '_InternalLinkedHashMap' is not a subtype of type 'int' + //推送交警操作函数步骤,注意: + // 推送交警1、等待5秒,重新读取指定ID的违章信息记录返回_mapGetHycsGetData。因为在复审提交后,可能用户修改了审核结果,所以必须重新延时读取 + // 在 tsjjFun(int _wzxxID) 函数中,调用 _mapGetHycsGetData = getWzxxItemData(_wzxxID); 函数实现 + print('before getWzxxItemData(_wzxxID)'); + getWzxxItemData(_wzxxID).then((_mapGetHycsGetData) async { + //OK + //return -1; + + // 推送交警2、第一方面的条件:需要审核状态workflow=999、sfhy(是否黑烟)=黑烟车、且tsjj为真时才允许推送 + // 在 tsjjFun(int _wzxxID) 函数中,用 if (_mapGetHycsGetData['workflow'] == 999 && _mapGetHycsGetData['sfhy'] == hyc_text) 实现 + print('_mapGetHycsGetData = ${_mapGetHycsGetData}'); + print('_mapGetHycsGetData[\'workflow\'] == 999 : ${_mapGetHycsGetData['workflow'] == 999}'); + print('_mapGetHycsGetData[\'sfhy\'] == hyc_text : ${_mapGetHycsGetData['sfhy'] == hyc_text}'); + //I/flutter (16661): _mapGetHycsGetData = {id: 1220, plate_id: 川Q565H4, plate_color: 蓝色, zpsj: 1612852562, yjxx_id: 1396, workflow: 999, video_url: video/5_6063_20210209_143 + // 602_川Q565H4.mp4, pic_url: /wwwroot/admin/Api/wwwroot/public/uploads/110012f894b695f4586e23497ff4dc69.jpg, clfl: 集装箱卡车, dwip: 172.16.3.5, dwms: 叙州区柏溪收费站出城方向, + // lgmzs: 3, jczxd: 854, sfhy: 黑烟车} + // I/flutter (16661): _mapGetHycsGetData['workflow'] == 999 : true + // I/flutter (16661): _mapGetHycsGetData['sfhy'] == hyc_text : true + + if (_mapGetHycsGetData['workflow'] == 999 && _mapGetHycsGetData['sfhy'] == hyc_text) { + print('开始执行:tsjjGetTsStatus(_wzxxID)'); + //I/flutter (16661): 开始执行:tsjjGetTsStatus(_wzxxID) + + // 获取推送交警状态信息 + // tsjjGetTsStatus返回字段 类型 说明 + // id 整型 违章记录ID + // checkid 整型 推送的抓拍记录ID + // tszt 整型 推送状态:0-未推送 | 1-推送失败 | 3-推送成功 + // ts_time 字符串 推送时间 + //Future tsjjGetTsStatus(int _wzxxID) + Map mapTsjjGetTsStatus = await tsjjGetTsStatus(_wzxxID); + + // 推送交警3、第二方面的条件:推送前还需查询违章记录推送状态,未推送、或推送失败的才进行推送 + // 在 tsjjFun(int _wzxxID) 函数中,调用 mapTsjjGetTsStatus = await tsjjGetTsStatus(_wzxxID); 函数实现 + //_mapTsjjUpftpRet["data"] = {id: 1220, checkid: 1396, tszt: 0, ts_time: null} + if (0 == mapTsjjGetTsStatus['tszt'] || 1 == mapTsjjGetTsStatus['tszt']) { + // 推送交警4、符合上面两个方面的条件后,再开始向交警服务器推送数据操作 + // 在 tsjjFun(int _wzxxID) 函数中,调用 _mapTsjjUpftp = await tsjjUpftp(_wzxxID, mapTsjjGetTsStatus['checkid']); 函数实现 + //向交警服务器推送数据 + //返回值为Map,mapRet['msg']为字符串:推送成功 | 推送失败 | 服务器连接失败,mapRet['ts_time']为推送完成时间戳秒数字符串 + String _ftpdir = getFtpdir_YYYYMMDD(_mapGetHycsGetData['zpsj']); + print('_ftpdir = $_ftpdir'); + + //tsjjUpftp执行很慢,需要几十秒钟,需要显示等待中的圈圈 + Map _mapTsjjUpftp = await tsjjUpftp(_wzxxID, _ftpdir); + print('_mapTsjjUpftp = $_mapTsjjUpftp'); + //I/flutter (24008): _mapTsjjUpftp = {msg: 推送成功, ts_time: 1614600887} + //I/flutter (30430): _mapTsjjUpftp = {msg: 服务器连接失败, ts_time: 1613049849} + //return -1; + + //_mapTsjjUpftp = {msg: 推送成功, ts_time: 1612858030} + + // 推送交警5、推送完成后,不论成功失败,都需要调用回写接口,把推送状态回写入数据库。 + // 推送本身的上传图片和文件FTP是由接口完成,完成后会返回一个msg,说明是成功还是失败。 + // 调用 var ret = await tsjjFtpUptszt(_wzxxID, _mapTsjjUpftp['msg'], _mapTsjjUpftp['ts_time']); 函数实现 + //回写推送交警状态。整型返回值表示更新的结果,1表示成功,0表示无更新,false表示失败 + //_mapTsjjUpftp = {msg: 推送成功, ts_time: 1612858030} + + ret = _mapTsjjUpftp['msg']; + var ret2 = await tsjjFtpUptszt(_wzxxID, _mapTsjjUpftp['msg'], _mapTsjjUpftp['ts_time']); + print('tsjjFtpUptszt_ret2 = $ret2'); + //I/flutter (30430): tsjjFtpUptszt_ret = 1 + //return -1; + + //返回字符串msg:推送成功 | 推送失败 | 服务器连接失败 + String retTsjjFun = ''; + if (null != ret && ret.indexOf('成功') > -1) { + retTsjjFun = _plateAndID + ' 推送交警成功!'; + } else { + retTsjjFun = _plateAndID + ' 推送交警失败。'; + } + + eventBus.fire(HycsDataUpdateEvent('${mapHyshlx[hyshlx]['text']}数据已更新')); + + print('after tsjjFun(widget.id),retTsjjFun = $retTsjjFun'); + Fluttertoast.showToast(msg: retTsjjFun, gravity: ToastGravity.CENTER); + } + } + }); + print('after getWzxxItemData(_wzxxID)'); + + //推送交警操作函数 tsjjFun 整型返回值。-1 表示推送失败;其余表示 tsjjFtpUptszt 函数的更新结果,1表示成功,0表示无更新,false表示失败 + return ret; +} + +//App.Car_Hyc.GetTs +// 获取推送状态信息 +// 接口地址:http://125.64.218.67:9904/?s=App.Car_Hyc.GetTs +// 接口文档 +// 根据ID获取违章记录的推送交警服务器状态 +// +// 接口参数 +// 参数名字 类型 是否必须 默认值 其他 说明 +// sign 字符串 必须 +// random 字符串 必须 +// id 字符串 必须 最小:1 违章记录ID +// 返回结果 +// 返回字段 类型 说明 +// id 整型 违章记录ID +// checkid 整型 推送的抓拍记录ID +// tszt 整型 推送状态:0-未推送 | 1-推送失败 | 3-推送成功 +// ts_time 字符串 推送时间 +// 异常情况 +// 错误码 错误描述信息 +// 400 表示客户端参数错误 +// 404 表示接口服务不存在 +// 500 表示服务端内部错误 + +// 推送交警2、第二方面的条件:推送前还需查询违章记录推送状态,未推送、或推送失败的才进行推送 +// 在 hyshContentFirstAudit() 函数中调用 tsjjGetTsStatus(_wzxxID) 函数实现 +// 获取推送交警状态信息 +// tsjjGetTsStatus返回字段 类型 说明 +// id 整型 违章记录ID +// checkid 整型 推送的抓拍记录ID +// tszt 整型 推送状态:0-未推送 | 1-推送失败 | 3-推送成功 +// ts_time 字符串 推送时间 +Future tsjjGetTsStatus(int _wzxxID) async { + var ret; + Map _mapTsjjGetTsStatusRet = {'msg': ''}; + var api = ServicePath.tsjjGetTsStatus; //获取推送交警状态信息; + print(api); + + print('id = ${_wzxxID}'); + if (_wzxxID < 1) { + ret = '获取${mapHyshlx[hyshlx]['text']}数据的 id 号不正确!'; + print(ret); + Fluttertoast.showToast(msg: ret, gravity: ToastGravity.CENTER); + return; + } else { + try { + print('开始处理网络请求...'); + Response response; + Dio dio = Dio(); + + String random = RandomBit(6); //flutter (dart)生成N位随机数 + print('random = ${random}'); + print('response = ${response}'); //I/flutter (16661): response = null + + response = await dio.post(api, data: { + "id": _wzxxID, + "sign": GenerateMd5(APPkey + random), + "random": random, + }); + print('_wzxxID = ${_wzxxID}'); + print('response = ${response.toString()}'); + //response = {"ret":200,"data":{"id":1220,"checkid":1396,"tszt":0,"ts_time":null},"msg":""} + + if (response.statusCode == 200) { + _mapTsjjGetTsStatusRet = await getMapFromJson(response.data); + + //msg 字符串 msg的值为:推送成功 | 推送失败 | 服务器连接失败 + print('App.Car_Hyc.GetTs返回值中的tszt字段 整型 推送状态:0-未推送 | 1-推送失败 | 3-推送成功'); + ret = _mapTsjjGetTsStatusRet["data"]; + print('_mapTsjjUpftpRet["data"] = ${ret}'); + //_mapTsjjUpftpRet["data"] = {id: 1220, checkid: 1396, tszt: 0, ts_time: null} + + print('获取${mapHyshlx[hyshlx]['text']}数据的网络请求过程正常完成'); + } else { + throw Exception('后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('网络请求过程异常e:${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + } + + print('tsjjGetTsStatus($_wzxxID).ret = ${ret}'); + + return ret; +} + +//App.Car_Ftp.Upftp +// 上传FTP +// 接口地址:http://125.64.218.67:9904/?s=App.Car_Ftp.Upftp +// 接口文档 +// 向交警服务器推送数据 +// +// 接口参数 +// 参数名字 类型 是否必须 默认值 其他 说明 +// sign 字符串 必须 +// random 字符串 必须 +// id 字符串 必须 最小:1 抓拍记录ID,注意这里是提交违章记录中checkid字段 +// ftpdir 字符串 必须 上传目录,为checkid记录的抓拍时间,格式为4位年+2位月+2位日,如:20210208 +// 返回结果 +// 返回字段 类型 说明 +// msg 字符串 msg的值为:推送成功 | 推送失败 | 服务器连接失败 +// 异常情况 +// 错误码 错误描述信息 +// 400 表示客户端参数错误 +// 404 表示接口服务不存在 +// 500 表示服务端内部错误 +// + +// 推送交警3、符合上面两个方面的条件后,再进行向交警服务器推送数据操作 +// 调用 tsjjUpftp(_wzxxID, _indexZpjl) 函数实现 +// 向交警服务器推送数据 +//返回值为Map,mapRet['msg']为字符串:推送成功 | 推送失败 | 服务器连接失败,mapRet['ts_time']为推送完成时间戳秒数字符串 +Future tsjjUpftp(int _wzxxID, String _ftpdir) async { + Map mapRet = {'msg': 'test'}; + var api = ServicePath.tsjjFtpUpftpUrl; + print(api); // http://125.64.218.67:9904/?s=App.Car_Ftp.Upftp + + print('_wzxxID = ${_wzxxID}'); //_wzxxID = 1220 + if (_wzxxID < 1) { + mapRet['msg'] = '获取${mapHyshlx[hyshlx]['text']}数据的 id 号不正确!'; + print(mapRet['msg']); + Fluttertoast.showToast(msg: mapRet['msg'], gravity: ToastGravity.CENTER); + } else { + try { + print('开始处理网络请求...'); + Response response; + Dio dio = Dio(); + + String random = RandomBit(6); //flutter (dart)生成N位随机数 + print('random = ${random}'); + print('response = ${response}'); //I/flutter (16661): response = null + print('ftpdir = $_ftpdir'); //I/flutter (22207): ftpdir = 20210209 + + // ftpdir 字符串 必须 上传目录,为checkid记录的抓拍时间,格式为4位年+2位月+2位日,如:20210208 + response = await dio.post(api, data: { + "id": _wzxxID, + 'ftpdir': _ftpdir, + "sign": GenerateMd5(APPkey + random), + "random": random, + }); + print('_wzxxID = ${_wzxxID}'); + print( + 'response = ${response.toString()}'); //response = {"ret":200,"data":{"msg":"推送成功"},"msg":""} + //{ + // "ret": 200, + // "data": { + // "msg": "推送成功" + // }, + // "msg": "" + // } + if (response.statusCode == 200) { + Map _mapTsjjUpftpRet = await getMapFromJson(response.data); + + //msg 字符串 msg的值为:推送成功 | 推送失败 | 服务器连接失败 + print('App.Car_Ftp.Upftp返回值中的msg 字符串 msg的值为:推送成功 | 推送失败 | 服务器连接失败'); + print('_mapTsjjUpftpRet = ${_mapTsjjUpftpRet}'); + mapRet['msg'] = _mapTsjjUpftpRet["data"]["msg"]; + + print('获取${mapHyshlx[hyshlx]['text']}数据的网络请求过程正常完成'); + } else { + throw Exception('后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('网络请求过程异常e:${e}'); + //I/flutter (16661): 网络请求过程异常e:RangeError (index): Invalid value: Only valid value is 0: 1396 + mapRet['msg'] = e.toString(); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + } + //print('DateTimeNowSecnods = ${_dateTime_now.millisecondsSinceEpoch}'); + //print('DateTimeNowSecnods = ${_dateTime_now.millisecondsSinceEpoch / 1000}'); + ////I/flutter ( 4010): DateTimeNowSecnods = 1612857524906 + ////I/flutter ( 4010): DateTimeNowSecnods = 1612857524.906 + //~ 取整操作符 + mapRet['ts_time'] = + (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(); //获取App.Car_Ftp.Upftp推送完成时间 + print('mapRet = ${mapRet}'); + //mapRet = {msg: 推送成功, ts_time: 1612858030} + //mapRet = {msg: 推送成功, ts_time: 1612857648.202} + //mapRet = {msg: 推送成功, ts_time: 7} + //I/flutter (16661): mapRet = {msg: RangeError (index): Invalid value: Only valid value is 0: 1396, ts_time: 38} + + //I/flutter (30430): mapRet = {msg: 服务器连接失败, ts_time: 1613049849} + + return mapRet; +} + +//App.Car_Ftp.Uptszt +// 更新推送状态 +// 接口地址:http://125.64.218.67:9904/?s=App.Car_Ftp.Uptszt +// 接口文档 +// 根据ID更新违章记录数据库中的推送状态 +// +// 接口参数 +// 参数名字 类型 是否必须 默认值 其他 说明 +// sign 字符串 必须 +// random 字符串 必须 +// id 整型 必须 最小:1 违章记录ID +// tszt 整型 必须 2 最小:0 推送状态: 0=>未推送 | 1=>推送失败 | 2=>推送成功 +// ts_time 字符串 必须 推送时间,时间戳格式 +// 返回结果 +// 返回字段 类型 说明 +// code 整型 更新的结果,1表示成功,0表示无更新,false表示失败 +// 异常情况 +// 错误码 错误描述信息 +// 400 表示客户端参数错误 +// 404 表示接口服务不存在 +// 500 表示服务端内部错误 + +//必须是tsjjGetTsStatus(1221).ret = {id: 1221, checkid: 1398, tszt: 0, ts_time: null}中, +// ts_time: null才能使用tsjjFtpUptszt成功回写,否则回写无更新 +Map mapTsjjFtpUptsztRet = { + "ret": 200, + "data": {"code": 1}, + "msg": "" +}; + +//{ +// "ret": 200, +// "data": { +// "code": 0 +// }, +// "msg": "" +// } + +// 推送交警4、推送完成后,不论成功失败,都需要调用回写接口,把推送状态回写入数据库。 +// 推送本身的上传图片和文件FTP是由接口完成,完成后会返回一个msg,说明是成功还是失败。 +// 调用 函数实现 +//回写推送交警状态。整型返回值表示更新的结果,1表示成功,0表示无更新,false表示失败 +//mapTsjjUpftp = {msg: 推送成功, ts_time: 1612858030} +Future tsjjFtpUptszt(int _wzxxID, String _tszt, String _ts_time) async { + var ret; + Map _mapTsjjUptsztRet = {'msg': ''}; + var api = ServicePath.tsjjFtpUptsztUrl; //回写推送交警状态 + print(api); //I/flutter (22207): http://125.64.218.67:9904/?s=App.Car_Ftp.Uptszt + + //tszt 整型 必须 2 最小:0 推送状态: 0=>未推送 | 1=>推送失败、或服务器连接失败 | 2=>推送成功 + Map mapTszt = { + '未推送': 0, + '服务器连接失败': 1, + '推送失败': 1, + '推送成功': 2, + }; + + int _intTszt = 1; + if (mapTszt.containsKey(_tszt)) { + _intTszt = mapTszt[_tszt]; + } + + print('id = ${_wzxxID}'); //_wzxxID = 1210 + if (_wzxxID < 1) { + ret = '获取${mapHyshlx[hyshlx]['text']}数据的 id 号不正确!'; + print(ret); + Fluttertoast.showToast(msg: ret, gravity: ToastGravity.CENTER); + } else { + try { + print('开始处理网络请求...'); + Response response; + Dio dio = Dio(); + + String random = RandomBit(6); //flutter (dart)生成N位随机数 + print('random = ${random}'); + print('response = ${response}'); + + Map _mapDio = { + "id": _wzxxID, + 'tszt': _intTszt, + 'ts_time': _ts_time, + "sign": GenerateMd5(APPkey + random), + "random": random, + }; + print('_mapDio = ${_mapDio}'); + //_mapDio = {id: 1210, tszt: 2, ts_time: 1612858409, sign: 2212446404dcb8cb07f2476dcd8f9480, random: 455071} + //_mapDio = {id: 1222, tszt: 0, ts_time: 1613052700, sign: eaeec0973a86d6522fcecd175e07f106, random: 228812} + + response = await dio.post(api, data: _mapDio); + print('_wzxxID = ${_wzxxID}'); + print('response = ${response.toString()}'); + //I/flutter (25617): response = {"ret":200,"data":{"code":1},"msg":""} + // response = {"ret":500,"data":{},"msg":"服务器运行错误: Api::$shuoming undefined"} + + //I/flutter ( 5232): {"ret":200,"data":{"items":[],"total":0,"page":1,"perpage":10},"msg":""} + if (response.statusCode == 200) { + _mapTsjjUptsztRet = await getMapFromJson(response.data); + + //code 整型 更新的结果,1表示成功,0表示无更新,false表示失败 + print('App.Car_Ftp.Uptszt返回值中的code 整型 更新的结果,1表示成功,0表示无更新,false表示失败'); + print('_mapTsjjUptsztRet = ${_mapTsjjUptsztRet}'); + ret = _mapTsjjUptsztRet["data"]["code"]; + + print('获取${mapHyshlx[hyshlx]['text']}数据的网络请求过程正常完成'); + } else { + throw Exception('后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('网络请求过程异常e:${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + } + + return ret; +} + +// 向交警服务器推送数据-模块数据 +///////////////////////////////////////////////////////////////////// + +// Map _mapGetHycsGetData = {}; +// Map _mapGetHycsGetDataRet = { +// "ret": 200, +// "data": { +// "id": 1221, +// "plate_id": "云CY4502", +// "plate_color": "蓝色", +// "zpsj": 1612856589, +// "yjxx_id": "1398", +// "workflow": 999, +// "video_url": "video/4_6063_20210209_154309_云CY4502.mp4", +// "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/2ec53c6879c3143bbd8471451be734f6.jpg", +// "clfl": "渣土车", +// "dwip": "172.16.3.4", +// "dwms": "叙州区一曼路出城方向", +// "lgmzs": 3, +// "jczxd": "983", +// "sfhy": "黑烟车" +// }, +// "msg": "" +// }; + +//获取指定id的违章信息返回_mapGetHycsGetData +Future getWzxxItemData(int id) async { + var api = ServicePath.getWzxxGetDataUrl; + print(api); + //I/flutter (16661): http://125.64.218.67:9904/?s=App.Car_Hyc.Get + + Map _mapGetHycsGetData = {}; + print('id = ${id}'); + if (id < 1) { + print('获取${mapHyshlx[hyshlx]['text']}数据的 id 号不正确!'); + Fluttertoast.showToast( + msg: '获取${mapHyshlx[hyshlx]['text']}数据的 id 号不正确!', gravity: ToastGravity.CENTER); + } else { + try { + print('开始处理网络请求...'); + Response response; + Dio dio = Dio(); + + String random = RandomBit(6); //flutter (dart)生成N位随机数 + print('random = ${random}'); + print('response = ${response}'); + print('sign = ${GenerateMd5(APPkey + random)}'); + //I/flutter (16661): response = null + + response = await dio.post(api, data: { + "id": id, + "sign": GenerateMd5(APPkey + random), + "random": random, + }); + print('id = ${id}'); + print('response = ${response.toString()}'); + //I/flutter (16661): response = {"ret":200,"data":{ + // "id":1211,"plate_id":"川Q023Y2","plate_color":"蓝色","zpsj":1612840361,"yjxx_id":"1387","workflow":999, + // "video_url":"video/3_6063_20210209_111241_川Q023Y2.mp4", + // "pic_url":"/wwwroot/admin/Api/wwwroot/public/uploads/c43286bf381af0a393b44272829d1f07.jpg","clfl":"集装箱卡车", + // "dwip":"172.16.3.3","dwms":"宜宾南收费站出城方向","lgmzs":3,"jczxd":"876","sfhy":"黑烟车" + //},"msg":""} + + if (response.statusCode == 200) { + Map _mapGetHycsGetDataRet = await getMapFromJson(response.data); + _mapGetHycsGetData = _mapGetHycsGetDataRet['data']; + print('获取${mapHyshlx[hyshlx]['text']}数据的网络请求过程正常完成'); + print('_mapGetHycsGetData = ${_mapGetHycsGetData}'); + //I/flutter (16661): _mapGetHycsGetData = { + // id: 1217, plate_id: 川QFX088, plate_color: 蓝色, zpsj: 1612847361, yjxx_id: 1393, workflow: 999, + // video_url: video/3_6063_20210209_130921_川QFX088.mp4, + // pic_url: /wwwroot/admin/Api/wwwroot/public/uploads/c227c65bcaadcdf53094c5448c2460ce.jpg, + // clfl: 面包车, dwip: 172.16.3.3, dwms: 宜宾南收费站出城方向, lgmzs: 3, jczxd: 992, sfhy: 黑烟车 + //} + } else { + throw Exception('后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('网络请求过程异常e:${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + } + + return _mapGetHycsGetData; +} + +// App.Car_Hyc.GetList +// 根据审核状态获取分页列表数据 +// 接口地址:http://125.64.218.67:9904/?s=App.Car_Hyc.GetList +// 接口文档 +// 根据审核状态筛选列表数据,支持分页 +// +// 接口参数 +// 参数名字 类型 是否必须 默认值 其他 说明 +// sign 字符串 必须 +// random 字符串 必须 +// workflow 整型 可选 1 最小:1 审核状态:1=>待审 | 2=>已初审 | 999=>已复审 +// sfhy 字符串 可选 黑烟车 是否黑烟车:黑烟车|非黑烟车 +// page 整型 可选 1 最小:1 第几页 +// perpage 整型 可选 10 最小:1;最大:20 分页数量 +// 返回结果 +// 返回字段 类型 说明 +// items 字符串 列表数据,列表字段请参考get接口 +// total 整型 总数量 +// page 整型 当前第几页 +// perpage 整型 每页数量 + +//dart是值传递 +int iPage = 0; +//perpage应该小于等于100 +///从接口 mapHyshlx[hyshlx]['api'] 获取第 iPage 页的列表数据,返回 list +Future getPageList({int perpage = 20, String theHyshlx = '', bool bShowToast = true}) async { + //从网络获取所有违章数据 + iPage++; + + List list = await getThePageList( + theHyshlx: theHyshlx, page: iPage, perpage: perpage, bShowToast: bShowToast); + + return list; +} + +///从接口 mapHyshlx[theHyshlx]['api'] 获取指定类型第 page 页的列表数据,返回 list +Future getThePageList( + {String theHyshlx = '', int page = 1, int perpage = 20, bool bShowToast = true}) async { + List list = []; + if (theHyshlx.isEmpty) { + theHyshlx = hyshlx; + } + + try { + print('开始处理网络请求...'); + Response response; + Dio dio = Dio(); + + String random = RandomBit(6); //flutter (dart)生成N位随机数 + Map map = { + "page": page, + "perpage": perpage, + "sign": GenerateMd5(APPkey + random), + "random": random, + }; + + int i = 1; + print('theHyshlx${i++} = $theHyshlx'); + //增加了一个查询字段 是否黑烟:sfhy 字符串 可选 黑烟车 是否黑烟车:黑烟车|非黑烟车,为空则全部 + if (mapHyshlx[theHyshlx].containsKey('sfhy')) { + map['sfhy'] = mapHyshlx[theHyshlx]['sfhy']; + } + + print('theHyshlx${i++} = $theHyshlx'); + if (mapHyshlx[theHyshlx]['get_workflow'] > 0) { + map['workflow'] = mapHyshlx[theHyshlx]['get_workflow']; + } + + print('theHyshlx${i++} = $theHyshlx'); + //设备类型 + if (mapHyshlx[theHyshlx].containsKey('sblx')) { + map['sblx'] = mapHyshlx[theHyshlx]['sblx']; + } + + print('mapHyshlx[theHyshlx][api] = ${mapHyshlx[theHyshlx]['api']}'); + // mapHyshlx[theHyshlx][api] = http://125.64.218.67:9904/?s=App.Car_Hyc.GetList + print('map = ${map}'); + response = await dio.post(mapHyshlx[theHyshlx]['api'], data: map); + print('response = ${response.toString()}'); + //I/flutter ( 5232): {"ret":200,"data":{"items":[],"total":0,"page":1,"perpage":10},"msg":""} + if (response.statusCode == 200) { + //mapGetWzxxGetListRet = await getMapFromJson(response.data); + Map mapGetData = await getMapFromJson(response.data); + print(mapGetData.toString()); + //第一次必须先进行listWzxxGetList2.clear(),否则addAll报错终止 + print('mapGetData[' 'data' '][' 'items' '].length = ${mapGetData['data']['items'].length}'); + if (bShowToast && 0 == mapGetData['data']['items'].length) { + Fluttertoast.showToast( + msg: '没有更多了!', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } else { + //listWzxxGetList2.addAll(mapGetData['data']['items']); + list = mapGetData['data']['items']; + //eventBus.fire(new WzxxDataUpdateEvent('违章信息数据已更新')); + //eventBus.fire(dataUpdateEvent); + } + //网络请求过程异常e:${e}type '_InternalLinkedHashMap' is not a subtype of type 'Iterable' + print('网络请求过程正常完成'); + } else { + throw Exception('后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('网络请求过程异常e:${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + + return list; +} + +///点位视频播放相关函数 +Future playUrl({@required int index, String url, BuildContext context}) { + urlnew = url; + // var ret = Navigator.of(context).push(MaterialPageRoute( + // builder: (context) => PlayerProNew( + // id: index + 1, + // url: urlnew, + // title: '点位视频\n${(index + 1)}、${listDwinfoGetList2[index]['dwmc']}', + // ))); + var ret = Navigator.of(context).push(MaterialPageRoute( + builder: (context) => SuperPlayerPage( + id: index + 1, + url: urlnew, + title: '点位视频\n${(index + 1)}、${listDwinfoGetList2[index]['dwmc']}', + ))); + + print('ret = $ret'); + //正在获取点位视频标志,禁止重入 + getingDwVideo = false; + + //标注正在获取点位视频的记录 + getingIndex = -1; + print('_getingIndex = $getingIndex'); + eventBus.fire(DwspUpdateEvent('点位视频信息数据更新广播')); + + return ret; +} + +int getCurrentTiem() { + return DateTime.now().millisecondsSinceEpoch ~/ 1000; +} + +///直接获取点位视频(Dwsp)播放地址,并开始播放 +Future getDwspUrlNew( + {@required int indexRecord, String theSbgllx = 'dwsp', BuildContext context}) async { + print('\n --- getingDwVideo = $getingDwVideo --- \n'); + //正在获取点位视频标志,禁止重入 + if (getingDwVideo) { + Fluttertoast.showToast( + msg: '正在后台获取 $getingDwmc 点位视频地址,请稍后重试', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + return ''; + } + + //标注正在获取点位视频的记录 + eventBus.fire(DwspUpdateEvent('点位视频信息数据更新广播')); + + getingDwVideo = true; + getingDwmc = '${(indexRecord + 1)}、${listDwinfoGetList2[indexRecord]['dwmc']}'; //正在获取视频的点位名称 + print('\n --- 正在后台获取 $getingDwmc 点位视频地址 --- \n'); + print('\n --- getingDwVideo = $getingDwVideo --- \n'); + + //MediaQueryData.fromWindow(window).padding.top //这个就是状态栏的高度 + //ScreenUtil().statusBarHeight //状态栏高度 刘海屏会更高 单位dp + // print('ScreenUtil().statusBarHeight = ${ScreenUtil().statusBarHeight}'); + // getSumTime = getCurrentTiem(); + // MyDelayToast( + // context: context, + // showTime: 30, + // top: ScreenUtil().statusBarHeight + 2.5, + // left: ScreenUtil().screenWidth - 80 - 2.5); + + print('listDwspGetList2 = ${listDwspGetList2}'); + print('indexRecord = ${indexRecord}'); + String _dwspUrl = + listDwspGetList2[indexRecord]['play_urlhead'] + listDwspGetList2[indexRecord]['video16']; + print('开始播放:第$getCount次成功获取的视频地址'); + + if (!isVideoUrl(_dwspUrl)) { + Fluttertoast.showToast( + msg: '获取 $getingDwmc 点位视频地址失败,请稍后重试。', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } else { + print('开始播放视频地址'); + playUrl(index: indexRecord, url: _dwspUrl, context: context); + } + + //不能放到此处,此处不会等待,将导致禁止重入失败 + //正在获取点位视频标志,禁止重入 + //getingDwVideo = false; + + //print('失败:$getCount次获取的视频地址都失败!'); + return _dwspUrl; +} + +///得到点位视频(Dwsp)播放地址,用插件 http: ^0.12.2 +Future getDwspUrl({@required int index, String theSbgllx = 'dwsp', BuildContext context}) async { + print('\n --- getingDwVideo = $getingDwVideo --- \n'); + //正在获取点位视频标志,禁止重入 + if (getingDwVideo) { + Fluttertoast.showToast( + msg: '正在后台获取 $getingDwmc 点位视频地址,请稍后重试', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + return ''; + } + + //标注正在获取点位视频的记录 + getingIndex = index; + print('_getingIndex = $getingIndex'); + eventBus.fire(DwspUpdateEvent('点位视频信息数据更新广播')); + + getingDwVideo = true; + getingDwmc = '${(index + 1)}、${listDwinfoGetList2[index]['dwmc']}'; //正在获取视频的点位名称 + print('\n --- 正在后台获取 $getingDwmc 点位视频地址 --- \n'); + print('\n --- getingDwVideo = $getingDwVideo --- \n'); + + String _dwspUrl; + getCount = 1; + + //MediaQueryData.fromWindow(window).padding.top //这个就是状态栏的高度 + //ScreenUtil().statusBarHeight //状态栏高度 刘海屏会更高 单位dp + print('ScreenUtil().statusBarHeight = ${ScreenUtil().statusBarHeight}'); + getSumTime = getCurrentTiem(); + MyDelayToast( + context: context, + showTime: 30, + top: ScreenUtil().statusBarHeight + 2.5, + left: ScreenUtil().screenWidth - 80 - 2.5); + + print('开始第$getCount次获取视频地址'); + getDwspUrl2(index: index, theSbgllx: theSbgllx).then((url) { + _dwspUrl = url; + if (!isVideoUrl(_dwspUrl)) { + print('第$getCount次获取视频地址失败,等待1秒后重试'); + getCount++; + Future.delayed(Duration(seconds: 1), () async { + print('开始第$getCount次获取视频地址'); + getDwspUrl2(index: index, theSbgllx: theSbgllx).then((url) { + _dwspUrl = url; + if (!isVideoUrl(_dwspUrl)) { + print('第$getCount次获取视频地址失败,等待1秒后重试'); + getCount++; + Future.delayed(Duration(seconds: 1), () async { + print('开始第$getCount次获取视频地址'); + getDwspUrl2(index: index, theSbgllx: theSbgllx).then((url) { + _dwspUrl = url; + if (!isVideoUrl(_dwspUrl)) { + print('第$getCount次获取视频地址失败,等待1秒后重试'); + getCount++; + Future.delayed(Duration(seconds: 1), () async { + print('开始第$getCount次获取视频地址'); + getDwspUrl2(index: index, theSbgllx: theSbgllx).then((url) { + _dwspUrl = url; + if (!isVideoUrl(_dwspUrl)) { + print('第$getCount次获取视频地址失败,等待1秒后重试'); + getCount++; + Future.delayed(Duration(seconds: 1), () async { + print('开始第$getCount次获取视频地址'); + getDwspUrl2(index: index, theSbgllx: theSbgllx).then((url) { + _dwspUrl = url; + if (!isVideoUrl(_dwspUrl)) { + getSumTime = getCurrentTiem() - getSumTime; + print('第$getCount次获取 $getingDwmc 点位视频地址失败,直接返回。共用时 $getSumTime 秒'); + Fluttertoast.showToast( + msg: '第$getCount次获取 $getingDwmc 点位视频地址失败,请稍后重试。共用时 $getSumTime 秒', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + //正在获取点位视频标志,禁止重入 + getingDwVideo = false; + + //标注正在获取点位视频的记录 + getingIndex = -1; + print('_getingIndex = $getingIndex'); + eventBus.fire(DwspUpdateEvent('点位视频信息数据更新广播')); + } else { + print('开始播放:第$getCount次成功获取的视频地址'); + playUrl(index: index, url: _dwspUrl, context: context); + } + }); + }); + } else { + print('开始播放:第$getCount次成功获取的视频地址'); + playUrl(index: index, url: _dwspUrl, context: context); + } + }); + }); + } else { + print('开始播放:第$getCount次成功获取的视频地址'); + playUrl(index: index, url: _dwspUrl, context: context); + } + }); + }); + } else { + print('开始播放:第$getCount次成功获取的视频地址'); + playUrl(index: index, url: _dwspUrl, context: context); + } + }); + }); + } else { + print('开始播放:第$getCount次成功获取的视频地址'); + playUrl(index: index, url: _dwspUrl, context: context); + } + }); + + //不能放到此处,此处不会等待,将导致禁止重入失败 + //正在获取点位视频标志,禁止重入 + //getingDwVideo = false; + + //print('失败:$getCount次获取的视频地址都失败!'); + return _dwspUrl; +} + +///得到点位视频(Dwsp)播放地址,用插件 http: ^0.12.2 +Future getDwspUrl2({@required int index, String theSbgllx = 'dwsp'}) async { + String _dwspUrl = ''; + + print('开始处理 ${mapHyshlx[theSbgllx]['text']} 网络请求...'); + String api = ServicePath.getDwspUrl + '${index.toString()}.php'; + print('api = ${api}'); + //api = http://125.64.218.67:9908/rtmp/1.php + + try { + my_http.Response response = await my_http.get(api); + print('response.statusCode = ${response.statusCode}'); + //I/flutter (18114): Response status: 200 + + //var ret = json.decode(response.body); //报错 + //print('Utf8Codec().decode(response.bodyBytes) = ${Utf8Codec().decode(response.bodyBytes)}'); + // I/flutter (18114): rtmp://125.64.218.67:9903/rtp/26CCDAA7 + + print('response = ${response.toString()}'); + //response = Instance of 'Response' + + if (response.statusCode == 200) { + //final Map responseData = json.decode(response.body); + //报错:e:Error on line 1, column 18: Invalid media type: expected application/json; + _dwspUrl = Utf8Codec().decode(response.bodyBytes).replaceAll('\n', ''); //删除所有换行符 + print('_dwspUrl = $_dwspUrl'); + //_dwspUrl = rtmp://125.64.218.67:9903/rtp/30C91573 + } else { + throw Exception('${mapHyshlx[theSbgllx]['text']} 后端接口出现异常,请检测代码和服务器情况...'); + getingDwVideo = false; //出现异常必须提前释放禁止重入标志 + } + } catch (e) { + print('${mapHyshlx[theSbgllx]['text']} 网络请求过程异常 e:${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + getingDwVideo = false; //出现异常必须提前释放禁止重入标志 + } + + return _dwspUrl; +} + +/// 获取最新版本号 +Future getNewverUrl() async { + String _text = '获取最新版本号'; + Map _mapGetData = {}; + + String api = ServicePath.getVerUrl; + print('api = ${api}'); + //I/flutter (12498): api = http://125.64.218.67:9904/?s=App.Car_Ver.Getver + + String random = RandomBit(6); //flutter (dart)生成N位随机数 + Map map = { + "sign": GenerateMd5(APPkey + random), + "random": random, + }; + + try { + print('开始处理 $_text 网络请求...'); + + Dio dio = Dio(); + print('map = ${map}'); + Response response = await dio.post(api, data: map); + print('response = ${response.toString()}'); + //I/flutter (12498): response = {"ret":200,"data":{"id":1,"ver":"1.0.0","miaos":"版本说明", + // "downurl":"http://www.sctastech.com/download/hyzp_20210425.apk","updatetime":1620632231},"msg":""} + + if (response.statusCode == 200) { + print('${api} 网络请求过程正常完成'); + Map _mapDataRet = await getMapFromJson(response.data); + _mapGetData = _mapDataRet['data']; + } else { + throw Exception('${api} 后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('${api} 网络请求过程异常 e:${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + + return _mapGetData; +} + +///人脸注册上传图片 +//App.User_User.Facereg 人脸注册接口 +//返回结果 +// 返回字段 类型 说明 +// user_id 整型 用户ID +// is_reg 布尔型 是否注册成功 +//人脸注册,filePath 人脸图片路径,username 用户名,user_id 用户ID,上级 context +Future faceReg_uploadImage( + {@required String filePath, + @required String username, + @required int user_id, + @required BuildContext context}) async { + print('开始处理网络请求...'); + + //1、将图片转换为 Base64 格式的字符串 + image2Base64(filePath).then((imageBase64) async { + //2、准备 dio 的 api + //static const String uploadFaceregUrl = ServiceUrl + '?s=App.User_User.Facereg'; //人脸注册接口 + var api = ServicePath.uploadFaceregUrl; + print(api); + + //3、准备 dio 参数 map + String random = RandomBit(6); //flutter (dart)生成N位随机数 + Map map = { + 'sign': GenerateMd5(APPkey + random), + 'random': random, + 'user_id': user_id, + 'face': imageBase64, + }; + print('map = ${map}'); + + //4、准备 dio 相关变量 + faceReg = -1; //复位人脸注册成功标志。1 成功,0 失败,-1 处理中 + Map _mapGetData = {}; + Response response; + Dio dio = Dio(); + + //5、开始调用 dio + try { + print('response = ${response}'); + //I/flutter (16661): response = null + response = await dio.post(api, data: map); + print('response = ${response}'); + //I/flutter (11748): response = {"ret":200,"data":{"user_id":135,"is_reg":"true","pic_url":"/wwwroot/admin/Api/wwwroot/public/uploads/b5fb2a19db9cabd4d0b1c3fe496981ec.jpg"},"ms + // g":""} + + if (response.statusCode == 200) { + Map _mapDataRet = await getMapFromJson(response.data); + _mapGetData = _mapDataRet['data']; + print('上传图片 网络请求过程正常完成'); + print('_mapGetData = ${_mapGetData}'); + //I/flutter (11748): _mapGetData = {user_id: 135, is_reg: true, pic_url: /wwwroot/admin/Api/wwwroot/public/uploads/b5fb2a19db9cabd4d0b1c3fe496981ec.jpg} + //I/flutter (28045): _mapGetData = {code: 1, msg: 未检测到人脸信息, count: 0, data: []} + + //设置人脸注册结果标志 + if ('true' == _mapGetData['is_reg']) { + //I/flutter (11748): 网络请求过程异常e:type 'String' is not a subtype of type 'bool' + faceReg = 1; //设置人脸注册成功标志。1 成功,0 失败,-1 处理中 + print('faceReg = $faceReg,人脸注册成功,返回 tabs 页面'); + //Navigator.pushNamed(null, '/tabs'); //返回 tabs 页面 + //I/flutter (11748): 网络请求过程异常e:NoSuchMethodError: The method 'findAncestorStateOfType' was called on null. + //Navigator.pushNamed(context, '/tabs'); //返回 tabs 页面,红屏报错 + getMyUserinfo().then((value) { + Fluttertoast.showToast( + msg: '$username 用户(ID: $user_id)人脸注册成功!', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + }); + Navigator.pop(context); //返回 tabs 页面,成功返回 + } else { + faceReg = 0; //设置人脸注册成功标志。1 成功,0 失败,-1 处理中 + Fluttertoast.showToast( + msg: '$username 用户(ID: $user_id)人脸注册失败。可能原因:${_mapGetData['msg']}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + } else { + throw Exception('后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('网络请求过程异常e:${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + }); +} + +//人脸注册,username 用户名,filePath 人脸图片路径,上级 context +Future faceRegFun( + {@required String username, @required String filePath, @required BuildContext context}) async { + //必须先以用户名查找到 user_id,再进行人脸注册 + username = username.trim(); + faceRegUserID = -1; //人脸注册时所需用户ID,-1 非法 + //获取用户分页列表数据 + var api = ServicePath.getUserListUrl; + print(api); + int _totalUser = 0; //第一页时保存数据库中用户总数 + int _counter = 0; //已读取的用户数计数器 + + int _page = 0; + String random = RandomBit(6); + Map map = { + 'random': random, + 'sign': GenerateMd5(APPkey + random), + 'page': _page, + 'perpage': 20, + }; + + print('开始处理登录请求...'); + Response response; + Dio dio = Dio(); + + try { + while (true) { + map['page']++; + + response = await dio.post(api, data: map); + print('response = ${response.toString()}'); + if (response.statusCode == 200) { + Map _mapUserListInfoRet = await getMapFromJson(response.data); + //print('_mapUserListInfoRet = $_mapUserListInfoRet'); + if (1 == map['page']) { + //第一页时保存数据库中用户总数 + _totalUser = _mapUserListInfoRet['data']['total']; + } + + List _listUserInfo = _mapUserListInfoRet['data']['items']; + print('_listUserInfo = $_listUserInfo'); + //I/flutter (11748): _listUserInfo = null + faceRegUserID = findUserID(_listUserInfo, username); + //查找 faceRegUserID 成功,开始进行人脸注册 + if (faceRegUserID > 0) { + print('查找 faceRegUserID = $faceRegUserID 成功,开始进行人脸注册'); + faceReg_uploadImage( + filePath: filePath, username: username, user_id: faceRegUserID, context: context); + return; + } + + _counter += _listUserInfo.length; //已读取的用户数计数器 + //已读取的用户数计数器,超过或等于数据库中用户总数时,则终止循环 + if (_counter >= _totalUser) { + return; + } + + print('第 ${map['page']} 次网络请求过程正常完成'); + } else { + throw Exception('后端接口出现异常,请检测代码和服务器情况.........'); + } + } + } catch (e) { + print('网络请求过程异常e = ${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } +} + +// List _listUserInfo = [ +// { +// "id": 144, +// "username": "dudakui", +// "truename": "杜大奎", +// "depname": "监控室", +// "posname": "管理员", +// "sex": "男", +// "face": null +// }, +// { +// "id": 143, +// "username": "pengguoping", +// "truename": "彭国平", +// "depname": "局领导", +// "posname": "领导", +// "sex": "女", +// "face": null +// }, +// ]; + +findUserID(List _listUserInfo, String username) { + for (var user in _listUserInfo) { + if (username == user["username"].trim()) { + return user["id"]; + } + } + return -1; +} + +Future getMyUserinfo() async { + print('开始处理网络请求...'); + var api = ServicePath.getUserInfoUrl; + print(api); + //I/flutter (30518): http://125.64.218.67:9904/?s=App.User_User.Profile + + Response response; + Dio dio = Dio(); + String random = RandomBit(6); + try { + response = await dio.post(api, data: { + "user_id": g_userInfo.mapUserInfo['user_id'], + "token": g_userInfo.mapUserInfo['token'], + 'random': random, + 'sign': GenerateMd5(APPkey + random), + }); + print('response = ${response.toString()}'); + //I/flutter ( 5242): {"ret":200,"data":{"is_login":true,"user_id":3,"token":"32EE57A0109A3D1D6590CFD3DEBA71820F77AB654093C1DE750347C88D1A41CF"},"msg":""} + if (response.statusCode == 200) { + mapUserInfoRet = await getMapFromJson(response.data); + mapUserInfo = mapUserInfoRet['data']['profile']; + g_userInfo.username = mapUserInfo['username']; //用于刷脸登录获取用户名 + print('mapUserInfo = $mapUserInfo'); + print('g_userInfo.username = ${g_userInfo.username}'); + + print('网络请求过程正常完成'); + } else { + throw Exception('后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('登录过程异常...'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + + return; +} + +//App.User_User.Facelogin 人脸识别登录接口 +//返回结果 +// 返回字段 类型 说明 +// is_login 布尔型 是否登录成功 +// user_id 整型 用户ID +// token 字符串 登录成功后的token,会话token +//人脸登录,filePath 人脸图片路径,上级 context +Future faceLoginFun({@required String filePath, @required BuildContext context}) async { + print('开始处理网络请求...'); + + //1、将图片转换为 Base64 格式的字符串 + image2Base64(filePath).then((imageBase64) async { + //2、准备 dio 的 api + //static const String uploadFaceloginUrl = ServiceUrl + '?s=App.User_User.Facelogin'; //人脸识别登录接口 + var api = ServicePath.uploadFaceloginUrl; + print(api); + //I/flutter (30518): http://125.64.218.67:9904/?s=App.User_User.Facelogin + + //3、准备 dio 参数 map + String random = RandomBit(6); //flutter (dart)生成N位随机数 + Map map = { + 'sign': GenerateMd5(APPkey + random), + 'random': random, + 'face': imageBase64, + }; + print('map = ${map}'); + + //4、准备 dio 相关变量 + faceLogin = -1; //复位人脸识别登录成功标志。1 成功,0 失败,-1 处理中 + Map _mapGetData = {}; + Response response; + Dio dio = Dio(); + + //5、开始调用 dio + try { + print('response = ${response}'); + //I/flutter (16661): response = null + response = await dio.post(api, data: map); + print('response = ${response}'); + //I/flutter (14273): response = {"ret":200,"data":{"is_login":true,"user_id":135,"token":"4C5B3F93FEACAEF4B6CAA7296F22CC67825D7E48B867614383D19E3F23DFE510"},"msg":""} + //I/flutter ( 9884): response = {"ret":200,"data":{"code":1,"msg":"未检测到人脸信息","count":0,"data":[]},"msg":""} + + if (response.statusCode == 200) { + Map _mapDataRet = await getMapFromJson(response.data); + _mapGetData = _mapDataRet['data']; + print('上传图片 网络请求过程正常完成'); + print('_mapGetData = ${_mapGetData}'); + + //设置人脸验证结果标志 + // I/flutter (14273): _mapGetData = {is_login: true, user_id: 135, token: 4C5B3F93FEACAEF4B6CAA7296F22CC67825D7E48B867614383D19E3F23DFE510} + // I/flutter ( 9884): _mapGetData = {code: 1, msg: 未检测到人脸信息, count: 0, data: []} + if (_mapGetData.containsKey('is_login') && _mapGetData['is_login']) { + print('人脸认证成功!'); + faceLogin = 1; //设置人脸验证成功标志。1 成功,0 失败,-1 处理中 + //保存人脸认证成功的用户信息 + g_userInfo.setUserInfoFaceLogin(_mapGetData); + //获取其他用户信息 + getMyUserinfo().then((value) { + //获取用户所属分组和相应权限 + getUserGroupAll().then((value) { + Navigator.pushNamed(context, '/tabs', arguments: g_iIndex); + print('欢迎 ${g_userInfo.username} 用户,人脸验证成功!'); + Fluttertoast.showToast( + msg: '欢迎 ${g_userInfo.username} 用户,人脸验证成功!', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.TOP, + ); + }); + }); + } else { + faceLogin = 0; //设置人脸验证成功标志。1 成功,0 失败,-1 处理中 + print('人脸验证失败。'); + Navigator.pushNamed(context, '/'); + Fluttertoast.showToast( + msg: '人脸验证失败。', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.BOTTOM, + ); + } + } else { + throw Exception('后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('网络请求过程异常e:${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + }); +} diff --git a/lib/components/doJSON.dart b/lib/components/doJSON.dart new file mode 100644 index 0000000..1f03c4c --- /dev/null +++ b/lib/components/doJSON.dart @@ -0,0 +1,178 @@ +//JSON数据写入文件、从文件中读取 +import 'dart:convert'; +import 'dart:io'; + +import 'package:intl/intl.dart'; +import 'package:path/path.dart' as path; +import 'package:path_provider/path_provider.dart'; + +//控制字段处理数据类 +class ItemData { + ItemData({ + this.fieldName, + this.fieldText, + this.fieldVisible, + this.fieldModifiable, + this.fieldUrlVisible, + }) {} + String fieldName = ''; + String fieldText = ''; + bool fieldVisible = true; + bool fieldUrlVisible = true; + bool fieldModifiable = true; +} + +//ftpdir 字符串 必须 上传目录,为checkid记录的抓拍时间,格式为4位年+2位月+2位日,如:20210208 +//timeStamp可以是int类型或String类型的时间戳(秒) +String getFtpdir_YYYYMMDD(var timeStamp, {String sep = ''}) { + int timeStampSecond; + if (timeStamp is String) { + timeStampSecond = int.parse(timeStamp); + } else { + timeStampSecond = timeStamp; + } + + //将拿到的时间戳转化为日期 + DateTime _dateTime = DateTime.fromMillisecondsSinceEpoch(timeStampSecond * 1000, isUtc: true); + print('_dateTime = $_dateTime'); + //_dateTime_now = 2021-02-08 20:31:31.235300 + print('formattedDateYYYY_MM_DD_KK_MM = ${DateFormat('yyyy-MM-dd – kk:mm').format(_dateTime)}'); + //formattedDateYYYY_MM_DD_KK_MM = 2021-01-31 – 06:49 + String formattedDateYYYYMMDD = DateFormat('yyyy${sep}MM${sep}dd').format(_dateTime); + print('formattedDateYYYYMMDD = ${formattedDateYYYYMMDD}'); + //formattedDateYYYYMMDD = 20210131 + + return formattedDateYYYYMMDD; +} + +// 获得前 31 天的日期起点和终点:['2021-03-18', '2021-04-17'] +List getLast31DateString() { + var _nowTime = DateTime.now(); + String _nowDate_YYYY_MM_DD = DateFormat('yyyy-MM-dd').format(_nowTime); + //print('_nowDate_YYYY_MM_DD = ${_nowDate_YYYY_MM_DD}'); + + var _startTime = _nowTime.add(new Duration(days: -31)); + String _startDate_YYYY_MM_DD = DateFormat('yyyy-MM-dd').format(_startTime); + print('_startDate_YYYY_MM_DD = ${_startDate_YYYY_MM_DD}'); + + var _endTime = _nowTime.add(new Duration(days: -1)); + String _endDate_YYYY_MM_DD = DateFormat('yyyy-MM-dd').format(_endTime); + print('_endDate_YYYY_MM_DD = ${_endDate_YYYY_MM_DD}'); + + return [_startDate_YYYY_MM_DD, _endDate_YYYY_MM_DD]; +} + +///获取昨天的结束时间的秒时间戳 +int getEndDayOfYesterdayStamp() { + var nowTime = DateTime.now(); + var yesterday = nowTime.add(new Duration(days: -1)); + var day = new DateTime(yesterday.year, yesterday.month, yesterday.day, 23, 59, 59); + //return day.microsecondsSinceEpoch; //微妙时间戳 + //return day.millisecondsSinceEpoch; //毫秒时间戳 + return day.millisecondsSinceEpoch ~/ 1000; //秒时间戳,加 ~ 为取整 +} + +///判断字符串时间是否是今日时间 +bool isToday(String strTime) { + //5.字符串转DateTime,DateTime.parse('2019-11-08') 或者 DateTime.parse('2019-11-08 12:30:05') + DateTime _dateTime = DateTime.parse(strTime); + //return day.microsecondsSinceEpoch; //微妙时间戳 + //return day.millisecondsSinceEpoch; //毫秒时间戳 + int _secondTiem = _dateTime.millisecondsSinceEpoch ~/ 1000; //秒时间戳,加 ~ 为取整 + + return _secondTiem > getEndDayOfYesterdayStamp(); +} + +///从字符串时间获取秒时间戳 +int getStampFromString(String strTime) { + //5.字符串转DateTime,DateTime.parse('2019-11-08') 或者 DateTime.parse('2019-11-08 12:30:05') + var _dateTime = DateTime.parse(strTime); + //return day.microsecondsSinceEpoch; //微妙时间戳 + //return day.millisecondsSinceEpoch; //毫秒时间戳 + return _dateTime.millisecondsSinceEpoch ~/ 1000; //秒时间戳,加 ~ 为取整 +} + +///获取字符串时间 Ntime (HH:MM)中时间的秒数 +int getSecondsOfNtime(String Ntime) { + List list = Ntime.split(':'); + int seconds = (int.parse(list[0]) * 60 + int.parse(list[1])) * 60; + + return seconds; //秒时间戳,加 ~ 为取整 +} + +//mantissa尾数 +String getDate(var timeStamp, {bool mantissa = false}) { +//String getDate(int timeStamp, {bool mantissa = false}) { + if (timeStamp == null) { + return "null"; + } + + if (timeStamp is String) { + return timeStamp; + } + //接口获得的时间戳是以秒为单位的 + //flutter 时间戳转换 差8小时。可能是因为默认时区的问题,导致并不是北京时间。(我猜的。。。) + // 【解决办法】 // 在utc上直接加上8小时(28800秒)。 + //var strtime = DateTime.fromMillisecondsSinceEpoch((timeStamp + 28800) * 1000); //将拿到的时间戳转化为日期 + //Android Studio ADV默认是Use network-provided time zone,时区是GTM+00:00 + //需要指定时区为China-Shanghai,China Time(GMT+08:00) + var strtime = DateTime.fromMillisecondsSinceEpoch(timeStamp * 1000, isUtc: true); //将拿到的时间戳转化为日期 + String date = strtime.toLocal().toString(); + //print('date = $date'); + if (!mantissa) { + //去除末尾小数 + date = date.substring(0, date.lastIndexOf('.')); + } + //print('date = $date'); //date = 2021-05-29 13:52:40 + return date; +} + +writeJSON(String jsonContent, String fileName) async { + File file = await getTheFile(fileName); + await file.writeAsString(jsonEncode(jsonContent)); +} + +Future readJSON(String fileName) async { + return jsonDecode(await (await getTheFile(fileName)).readAsString()); +} + +Future getTheFile(String fileName) async { + String strExt = path.extension(fileName).toLowerCase().trim(); + if (0 != strExt.compareTo('.json')) { + fileName += '.json'; + } + + //在指定位置创建"json"目录 + //final dirPath = await getExternalStorageDirectory(); + final dirPath = await getApplicationDocumentsDirectory(); + var dirJson = Directory(dirPath.path + "/" + "json"); + try { + bool exists = await dirJson.exists(); + if (!exists) { + await dirJson.create(); + } + } catch (e) { + print(e); + } + + // /data/data/com.flutter.hyzp_ybqx/app_flutter/json/listMessagesInbox02.json + // /data/data/com.flutter.hyzp_ybqx/app_flutter/json/listContacts02.json + return File('${(await getApplicationDocumentsDirectory()).path}/json/$fileName'); +} + +//以下为测试学习之用 +// Future get getFile async { +// File('${(await getApplicationDocumentsDirectory()).path}/json/listContacts.json'); +// } +// writeJSON(String jsonContent) async { +// //You can write data like this: +// //await (await getFile).writeAsString(jsonEncode(jsonContent)); +// //该句等同于 +// File file = await getFile; +// await file.writeAsString(jsonEncode(jsonContent)); +// } +// +// readJSON() async { +// //从文件中读取,下次您可能想读取更改的数据。你可以这样做: +// String decodedContent = jsonDecode(await (await getFile).readAsString()); +// } diff --git a/lib/components/hyxx_data_handle.dart b/lib/components/hyxx_data_handle.dart new file mode 100644 index 0000000..e852da3 --- /dev/null +++ b/lib/components/hyxx_data_handle.dart @@ -0,0 +1,1503 @@ +import 'dart:ui'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_easyrefresh/easy_refresh.dart'; + +import '../config/service_url.dart'; + +double my_fontSize = 16; +double my_listTileHeight = 30; +double my_listTileHeight2 = 100; +double my_iconSize = 18; +Color my_iconColor = Colors.blue; +double my_textFieldHeight = 8; +double my_marginLeft = 10; +double my_marginLeft2 = 33; +BuildContext my_context; + +//黑烟信息处理数据变量 +List listContacts2 = []; +List listMessagesInbox2 = []; +List listMessagesOutbox2 = []; +List listMachineGetList2 = []; + +bool bFlash; + +int g_tabs = 8; + +//所有字段中,仅车牌号、车牌颜色、审核意见可供用户修改 +Map topTabs_map = { + 'tabs_list': [], //顶部Tab标题 + 'hycs_info_list': [], //黑烟初审信息 + 'car_number_List': [], //可供用户修改的车牌号码 + 'cpysText_List': [], //可供用户修改的车牌颜色 + 'auditShuoming_Controller_List': [], //可供用户修改的审核意见 + 'auditTitle': [], //可供用户修改的审核结果 + 'carNumberAndCpys_List': [], //Tab页面中的车牌号码、车牌颜色组件 + 'listView_List': [], //Tab页面 +}; + +//控制字段处理数据类 +class ItemData { + ItemData({ + this.fieldName = '', + this.fieldText = '', + this.fieldVisible = false, + this.fieldModifiable = false, + this.fieldUrlVisible = true, + }) {} + String fieldName; + String fieldText; + bool fieldVisible; + bool fieldUrlVisible; + bool fieldModifiable; +} + +//4、非黑烟车查询数据变量 +Map mapGetFhycxGetData = {}; +Map mapGetFhycxGetDataRet = {}; +List listFhycxGetList2 = []; + +//3、推送交警数据变量 +List listTsjjGetList2 = []; +Map mapGetTsjjGetData = {}; +Map mapGetTsjjGetDataRet = {}; +Map mapGetTsjjGetListRet = {}; + +// tszt 整型 推送状态:0-未推送 | 1-推送失败 | 3-推送成功 +//_mapTsjjGetTsStatus['tszt'] +// 20210529更新: +// tszt 整型 推送状态:0-未推送 | 1-推送失败 | 2-推送成功 | 3-规定时间内已有违章记录,本次不推送 | 4-现场登记,不推送 +Map mapTsztText = { + 0: '未推送', + 1: '推送失败', + 2: '推送成功', + 3: '规定时间内已有违章记录,本次不推送', + 4: '现场登记,不推送', +}; + +// //workflow 整型 审核状态: 1=>待审 | 2=>已初审 | 999=>已复审 | 1000=>非黑烟车 +// //mapTsjjlx为推送交警类型数据结构,用于在同一套代码中,处理'tsjj'推送交警、'fhcx'复审查询 +// Map mapTsjjlx = { +// 'tsjj': {'get_workflow': 999, 'text': '推送交警', 'audit_workflow': 999}, +// 'fhcx': {'get_workflow': 999, 'text': '复审查询', 'audit_workflow': 999}, +// }; +// //tsjjlx为推送交警类型,用于在同一套代码中,处理'tsjj'推送交警、'fhcx'复审查询 +// String tsjjlx = 'fhcx'; + +// //workflow 整型 审核状态: 1=>待审 | 2=>已初审 | 999=>已复审 | 1000=>非黑烟车 +// //mapWzxxlx为违章信息类型数据结构,用于在同一套代码中,处理'wzxx'违章信息查询、'fhycx'非黑烟查询 +// Map mapWzxxlx = { +// 'wzxx': {'get_workflow': -1, 'text': '违章信息', 'audit_workflow': -1, 'api': ServicePath.getWzxxGetAllUrl}, +// 'fhycx': {'get_workflow': 1000, 'text': '非黑烟查询', 'audit_workflow': -1, 'api': ServicePath.getWzxxGetListUrl}, +// }; +// //wzxxlx为违章信息类型,用于在同一套代码中,处理'wzxx'违章信息查询、'fhycx'非黑烟查询 +// String wzxxlx = 'wzxx'; + +//3、推送交警数据变量 + +// App.Car_Hyc.GetList +// 根据审核状态获取分页列表数据 +// 接口地址:http://125.64.218.67:9904/?s=App.Car_Hyc.GetList +// 接口文档 +// 根据审核状态筛选列表数据,支持分页 +// +// 接口参数 +// 参数名字 类型 是否必须 默认值 其他 说明 +// sign 字符串 必须 +// random 字符串 必须 +// workflow 整型 可选 1 最小:1 审核状态:1=>待审 | 2=>已初审 | 999=>已复审 +// page 整型 可选 1 最小:1 第几页 +// perpage 整型 可选 10 最小:1;最大:20 分页数量 +// 返回结果 +// 返回字段 类型 说明 +// items 字符串 列表数据,列表字段请参考get接口 +// total 整型 总数量 +// page 整型 当前第几页 +// perpage 整型 每页数量 +// 异常情况 +// 错误码 错误描述信息 +// 400 表示客户端参数错误 +// 404 表示接口服务不存在 +// 500 表示服务端内部错误 + +//1、黑烟初审和复审数据变量 + +//参数名字 类型 是否必须 默认值 其他 说明 +//workflow 整型 可选 1 最小:1 审核状态:1=>待审 | 2=>已初审 | 999=>已复审 + +//workflow 整型 审核状态: 1=>待审 | 2=>已初审 | 999=>已复审 +//workflow 整型 审核状态: 1=>待审 | 2=>已初审 | 999=>已复审 | 1000=>非黑烟车 +//mapHyshlx为黑烟审核类型数据结构。用于在同一套代码中,处理'hycs'黑烟初审、'hyfh'黑烟复审 +Map mapHyshlx = { + 'dwdt': { + 'get_workflow': -1, + 'text': '点位地图', + 'nick_text': '地图', + 'audit_workflow': -1, + 'api': ServicePath.getDwinfoGetListUrl + }, + 'dwsp': { + 'get_workflow': -1, + 'text': '点位视频', + 'nick_text': '视频', + 'audit_workflow': -1, + 'api': ServicePath.getDwinfoGetListUrl + }, + 'dwxx': { + 'get_workflow': -1, + 'text': '点位信息', + 'nick_text': '点位', + 'audit_workflow': -1, + 'api': ServicePath.getDwinfoGetListUrl + }, + 'hycs': { + 'get_workflow': 1, + 'text': '黑烟初审', + 'nick_text': '初审', + 'audit_workflow': 2, + 'api': ServicePath.getWzxxGetListUrl + }, + 'hyfh': { + 'get_workflow': 2, + 'text': '黑烟复审', + 'nick_text': '复审', + 'audit_workflow': 999, + //'apiNtiem': ServicePath.getNtimeUrl, //获取违章间隔时间数据 + 'api': ServicePath.getWzxxGetListUrl + }, + 'tsjj': { + 'get_workflow': 999, + 'text': '推送交警', + 'audit_workflow': -1, + 'api': ServicePath.getWzxxGetListUrl, + 'apiNtiem': ServicePath.getNtimeUrl, //获取违章间隔时间数据 + 'sfhy': '黑烟车', //增加了一个查询字段 是否黑烟:sfhy 字符串 可选 黑烟车 是否黑烟车:黑烟车|非黑烟车,为空则全部 + }, + 'wzxx': { + 'get_workflow': -1, + 'text': '违章信息', + 'audit_workflow': -1, + 'api': ServicePath.getWzxxGetAllUrl + }, + 'fhycx': { + 'get_workflow': -1, + 'text': '非黑烟查询', + 'audit_workflow': -1, + 'api': ServicePath.getWzxxGetListUrl, + 'sfhy': '非黑烟车', //增加了一个查询字段 是否黑烟:sfhy 字符串 可选 黑烟车 是否黑烟车:黑烟车|非黑烟车,为空则全部 + }, + 'led_xsxx': { + 'get_workflow': -1, + 'text': '获取LED字幕数据', + 'audit_workflow': -1, + 'api': ServicePath.getLedXsxxGetListUrl, + 'apiItem': ServicePath.getLedXsxxGetUrl, + }, + 'led_update': { + 'get_workflow': -1, + 'text': '更新LED字幕数据', + 'audit_workflow': -1, + 'api': ServicePath.updateLedXsxxGetUrl, + 'apiItem': ServicePath.updateLedXsxxGetUrl, + }, + 'sbbj': { + 'get_workflow': -1, + 'text': '报警信息', + 'nick_text': '报警', + 'audit_workflow': 999, + 'api': ServicePath.getSbbjGetListUrl, + 'apiItem': ServicePath.getSbbjGetUrl, + 'apiAudit': ServicePath.auditSbbjUrl, + }, + 'sbgl': { + 'get_workflow': -1, + 'text': '设备管理信息', + 'audit_workflow': -1, + 'api': ServicePath.getMachineGetListUrl, + 'sblx': '球机1' + }, +}; +//hyshlx为黑烟审核类型,用于在同一套代码中,处理'hycs'黑烟初审、'hyfh'黑烟复审 +String hyshlx = 'hycs'; +//1、黑烟初审和复审数据变量 + +//mapStatisType 为统计数据类型数据结构。用于在同一套代码中,处理相似类型的多种统计数据 +//zptj 是本项目中“抓拍统计”的统一缩写 +//sh_hyc_tj 是本项目中“审核黑烟车统计”的统一缩写 +//clltj 是本项目中“车流量统计”的统一缩写 +Map mapStatisType = { + 'zptj': { + 'text': '抓拍统计', + 'nick_text': '抓拍', + 'api': ServicePath.getStaYjxxUrl, + }, + 'sh_hyc_tj': { + 'text': '审核统计', + 'nick_text': '审核', + 'api': ServicePath.getStaHycUrl, + }, + 'clltj': { + 'text': '车流量统计', + 'nick_text': '车流量', + 'api': ServicePath.getStaCllUrl, + }, + 'cllrtj': { + 'text': '车流量日统计', + 'textTerm': '早高峰 7:30~9:30 晚高峰 17:30~19:30', + 'nick_text': '车流量', + 'startDate': '', // 获得前 31 天的日期:['2021-03-18', '2021-04-17'] + 'endDate': '', + 'api': ServicePath.getRStaCllUrl, + }, +}; + +class cpysItem { + String cpysText; + Color cpysFont; + Color cpysBackground; + Color cpysBorder; + + cpysItem( + {@required this.cpysText, + @required this.cpysBackground, + @required this.cpysFont, + @required this.cpysBorder}); +} + +//车牌颜色Map +Map cpysMap = { + '蓝色': cpysItem( + cpysText: '蓝色', cpysBackground: Colors.blue, cpysFont: Colors.white, cpysBorder: Colors.grey), + '黄色': cpysItem( + cpysText: '黄色', + cpysBackground: Colors.yellow, + cpysFont: Colors.black, + cpysBorder: Colors.purple), + '绿色': cpysItem( + cpysText: '绿色', + cpysBackground: Colors.green, + cpysFont: Colors.white, + cpysBorder: Colors.pink), + '黑色': cpysItem( + cpysText: '黑色', + cpysBackground: Colors.black, + cpysFont: Colors.white, + cpysBorder: Colors.blue), + '白色': cpysItem( + cpysText: '白色', + cpysBackground: Colors.white, + cpysFont: Colors.black, + cpysBorder: Colors.black), + '其他': cpysItem( + cpysText: '其他', + cpysBackground: Colors.red, + cpysFont: Colors.white, + cpysBorder: Colors.black45), +}; + +//车牌颜色选择 +List cpysList = [ + cpysItem( + cpysText: '蓝色', + cpysBackground: Colors.blue, + cpysFont: Colors.white, + cpysBorder: Colors.orange), + cpysItem( + cpysText: '黄色', + cpysBackground: Colors.yellow, + cpysFont: Colors.black, + cpysBorder: Colors.purple), + cpysItem( + cpysText: '绿色', + cpysBackground: Colors.green, + cpysFont: Colors.white, + cpysBorder: Colors.pink), + cpysItem( + cpysText: '黑色', + cpysBackground: Colors.black, + cpysFont: Colors.white, + cpysBorder: Colors.blue), + cpysItem( + cpysText: '白色', + cpysBackground: Colors.white, + cpysFont: Colors.black, + cpysBorder: Colors.black), + cpysItem( + cpysText: '其他', + cpysBackground: Colors.red, + cpysFont: Colors.white, + cpysBorder: Colors.black45), +]; + +int getIndexOfCpysList({@required String colorText}) { + int len = cpysList.length; + colorText = colorText.trim(); + for (int i = 0; i < len; i++) { + if (cpysList[i].cpysText.trim() == colorText) { + return i; + } + } + return -1; +} + +List listHycsGetList2 = []; +List listHycsFliter = []; +Map mapGetHycsGetData = {}; +Map mapGetHycsGetDataRet = { + "ret": 200, + "data": { + "id": 9530, + "plate_id": "贵A5J69X", + "plate_color": "蓝色", + "car_color": null, + "zpsj": 1635061299, + "yjxx_id": "433425", + "zp_num": null, + "workflow": 999, + "uuid": null, + "uuname": null, + "status": 1, + "sfhy": "黑烟车", + "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/6fd82e935e361db183a991a6a078514e.jpg", + "checkid": 433425, + "tszt": 2, + "ts_time": 1635069760, + "dwms": "一曼路南山星城南区附近", + "dwip": "172.16.3.4", + "clfl": "面包车", + "cplx": "其它", + "lgmzs": 3, + "jczxd": "953", + "video_url": "new/4_6063_20211024_154139_贵A5J69X.mp4", + "addtime": "2021-10-24 15:43:09", + "iffiles": 0, + "plate": "贵", + "plate_sd": "贵州", + "pic_thumb": null, + "new_zpsj": 1635061299, + "sfyc": 0, + "plate_sd_s": "贵阳", + "cp_url": "/wwwroot/admin/Api/wwwroot/public/uploads/4f045b43d38c45993ad990f68f981ee2.jpg", + "hpzl_dm": "255", + "cllx_dm": "4", + "csys_dm": "1", + "hpys_dm": "0", + "waittime": 1635069721, + "fid": 0, + "cftype": "系统抓拍", + "uid": null, + "uname": null, + "updatetime": null, + "shtime": "2021-10-24 17:02:01", + "gbcs": 0, + "h5_url": "new/4_6063_20211024_154139_贵A5J69X.mp4", + "source": "PC", + "video_check": 1, + "ts_time_new": null, + "dwbm": "0", + "ycxbm": "0" + }, + "msg": "" +}; +// Map mapGetHycsGetDataRet = { +// "ret": 200, +// "data": { +// "id": 1, +// "uid": 132, +// "uname": "", +// "addtime": "2020-05-20 10:10:00", +// "uuid": 132, +// "uuname": "", +// "updatetime": "2020-09-09 20:19:46", +// "status": 1, +// "sbbh": "S202001", +// "sbmc": "摄像头", +// "pic_url": "hyc/192.168.4.102/违法数据/20200822/12/6063_2_20200822_125252292_川Q09716_P1.jpg", +// "video_url": "hyc/192.168.4.102/违法数据/20200822/12/6063_2_20200822_125252292_川Q09716.mp4", +// "car_number": "川Q09716", +// "car_type": "大型车", +// "car_color": "白色", +// "is_yellow": 0, +// "lgmzs": 3, +// "workflow": 1, +// "clfl": "大货车", +// "xsfx": "上行", +// "cpys": "黄色", +// "cplx": "民用双行尾牌", +// "zpsj": "20200822125252292", +// "all_url": "hyc\\192.168.4.102\\违法数据\\20200822\\12\\6063_2_20200822_125252292_川Q09716", +// "cdh": 1, +// "sjlx": "", +// "wzlx": "黑烟车", +// "jczxd": 0, +// "tp1": "", +// "tp2": "", +// "tp3": "", +// "tp4": "" +// }, +// "msg": "" +// }; + +//获取指定id的推送交警记录列表存入listGetTsjj +List listGetTsjj = []; + +//获取指定id的抓拍记录列表存入listGetZpjl +List listGetZpjl = []; + +//workflow 整型 审核状态: 1=>待审 | 2=>已初审 | 999=>已复审 | 1000=>非黑烟车 +Map mapAuditStatus = { + 1: "待审", + 2: "已初审", + 999: "已复审", + 1000: "非黑烟车", +}; + +//Begin 处理修改字段的变量 +List listFieldModify = []; + +Map mapGetHycsGetListRet = { + "ret": 200, + "data": {"items": [], "total": 0, "page": 1, "perpage": 10}, + "msg": "" +}; + +//7、点位视频数据变量 +List listDwspGetData = []; +List listDwspGetList2 = []; + +//7、设备管理信息查询数据变量 +List listSbglGetData = []; +List listSbglGetList2 = []; + +//6、报警信息查询数据变量 +List listSbbjGetData = []; +List listSbbjGetList2 = [ + { + "id": 1196, + "bjlx": "风扇", + "content": "串口COM1获取风扇异常数据", + "dwip": "172.16.3.12", + "sbip": "192.168.1.10", + "addtime": "2021-02-16 09:50:30", + "workflow": 999 + }, +]; + +//End 处理修改字段的变量 + +// 返回字段 类型 说明 +// id 整型 主键ID +// car_number 字符串 车牌号 +// car_type 字符串 车型 +// cpys 字符串 车牌颜色 +// zpsj 字符串 抓拍时间 +// workflow 整型 审核状态: 1=>待审 | 2=>已初审 | 1000=>已复审 +// video_url 字符串 视频地址 +// lgmzs 整型 林格曼指数 +// wzlx 字符串 违章类型 +// jczxd 整型 检测置信度 +// tp1 字符串 第一张图片地址 +// tp2 字符串 第二张图片地址 +// tp3 字符串 第三张图片地址 +// tp4 字符串 第四张图片地址 + +//控制字段处理数据类 +//class ItemData { +// ItemData({ +// this.fieldName, +// this.fieldText, +// this.fieldVisible, +// this.fieldModifiable, +// this.fieldUrlVisible, +// }) {} +// String fieldName = ''; +// String fieldText = ''; +// bool fieldVisible = true; +// bool fieldUrlVisible = true; +// bool fieldModifiable = true; +// } + +//一、违章信息不可修改字段: +// 1、抓拍时间 zpsj +// 2、视频地址 video_url,只显示视频,不显示地址 +// 3、审核状态 workflow,此字段也不显示出来(审核状态有调整:1=>待审 | 2=>已初审 | 999=>已复审 | 1000=>非黑烟车) +// 4、林格曼指数 lgmzs +// 5、违章类型 wzlx,此字段不显示出来 +// 6、检测置信度 jczxd,此字段不显示 +// 7、四张图片地址 tp1,tp2,tp3,tp3,只显示图片本身,不显示地址 +// 8、Id 不可修改,不显示 +// 其余字段可修改 + +//App.Car_Yjxx.Update更新时的整型字段只有两项 +// lgmzs 整型 必须 最小:1 林格曼指数 +// jczxd 整型 必须 最小:1 检测置信度 + +//Map mapGetHycsGetDataRet = { +// "ret": 200, +// "data": { +// "id": 512, +// "plate_id": "川Q60387", +// "plate_color": "黄色", +// "zpsj": 1611205595, +// "yjxx_id": "654", +// "workflow": 1, +// "video_url": "video/8_6063_20210121_130635_川Q60387.mp4", +// "pic_url": "http://10.194.142.91:9001/uploads/29f82a29ee17e7d668e1ed2b6239f2b7.jpg", +// "clfl": "货车", +// "dwip": "172.16.3.8", +// "dwms": "宜威路南广镇附近入城方向", +// "lgmzs": 5, +// "jczxd": "2" +// }, +// "msg": "" +// }; + +//返回字段 类型 说明 +// id 整型 主键ID +// plate_id 字符串 车牌号 +// plate_color 字符串 车牌颜色 +// zpsj 字符串 抓拍时间 +// yjxx_id 字符串 抓拍记录ID,可能为多条,用逗号分割。与抓拍记录关联 +// workflow 整型 审核状态: 1=>待审 | 2=>已初审 | 999=>已复审 +// video_url 字符串 视频地址 +// pic_url 字符串 图片地址 +// clfl 字符串 车辆分类 +// dwip 字符串 抓拍地点位IP +// dwms 字符串 抓拍地点 +// lgmzs 整型 林格曼黑度值 +// jczxd 整型 检测置信度 + +//不显示的是4项:0-"id"、17-"workflow"、26-"wzlx"、27-"jczxd"、 +//所有字段中,仅仅提供车牌颜色、车牌号、审核意见供修改 +Map mapGetWzxxGetDataSpecial = { + "id": ItemData(fieldText: "主键ID", fieldVisible: false), + "plate_id": ItemData(fieldText: "车牌号码"), + "plate_color": ItemData(fieldText: "车牌颜色"), + "zpsj": ItemData(fieldText: "抓拍时间"), + "yjxx_id": ItemData(fieldText: "抓拍记录ID"), //抓拍记录ID,可能为多条,用逗号分割。与抓拍记录关联(移交信息) + "workflow": ItemData(fieldText: "审核状态", fieldVisible: false), //审核状态: 1=>待审 | 2=>已初审 | 999=>已复审 + "video_url": ItemData(fieldText: "视频地址", fieldUrlVisible: false), + "pic_url": ItemData(fieldText: "图片地址", fieldUrlVisible: false), + "clfl": ItemData(fieldText: "车辆分类"), + "dwip": ItemData(fieldText: "抓拍地点位IP"), + "dwms": ItemData(fieldText: "抓拍地点"), + "lgmzs": ItemData(fieldText: "林格曼黑度值"), + "jczxd": ItemData(fieldText: "检测置信度", fieldVisible: false), +}; + +Map mapWzxxDataText = { + "主键ID": "id", + "车牌号码": "plate_id", + "车牌颜色": "plate_color", + "抓拍时间": "zpsj", + "抓拍记录ID": "yjxx_id", + "抓拍次数": "yjxx_id", //为排序增加的记录 + "审核状态": "workflow", + "视频地址": "video_url", + "图片地址": "pic_url", + "车辆分类": "clfl", + "车辆类型": "clfl", //为排序增加的记录 + "抓拍地点位IP": "dwip", + "抓拍地点": "dwms", + "林格曼黑度值": "lgmzs", + "林格曼黑度": "lgmzs", //为排序增加的记录 + "检测置信度": "jczxd", + "推送状态": "tszt", //为tsjj页面排序增加的记录 + "推送时间": "ts_time", //为tsjj页面排序增加的记录 + //为LED字幕排序增加的记录 + "显示信息": "xsnr", + "添加时间": "addtime", + "更新时间": "updatetime", + //为设备报警信息排序增加的记录 + '设备类型': "bjlx", + '报警内容': "content", + '点位IP': "dwip", + '设备IP': "sbip", + '处理状态': "workflow", + //为点位信息排序增加的记录 + '点位名称': "dwmc", + '点位编号': "dwbh", + '点位信息': "dwinfo", + '点位坐标': "dwzb", + '点位描述': "dwms", + '点位状态': "dwzt", +}; + +//点位信息数据 +//{ +// "id": 1, +// "dwip": "172.16.3.1", +// "dwmc": "江北振兴大道", +// "dwbh": 1, +// "dwinfo": "江北振兴大道入城方向", +// "dwzb": "104.607091|28.807061", +// "dwms": "江北振兴大道入城方向,识别孜岩、红坝路入城排放黑烟车辆", +// "dwzt": "正常" +//}, + +// Map mapZpljTemp = +// { +// "ret": 200, +// "data": { +// "id": 660, +// "uid": 0, +// "uname": "", +// "addtime": "2021-01-21 14:53:48", +// "uuid": 0, +// "uuname": "", +// "updatetime": "0000-00-00 00:00:00", +// "status": 1, +// "dwip": "172.16.3.11", +// "sbmc": "", +// "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/fa656d26ced72b628858eb53aeea2e54.jpg", +// "video_url": "video/11_6063_20210121_145225_川Q61308.mp4", +// "car_number": "川Q61308", +// "car_type": "其它", +// "car_color": "未识别", +// "is_yellow": 1, +// "lgmzs": 5, +// "workflow": 1, +// "clfl": "货车", +// "xsfx": "", +// "cpys": "黄色", +// "cplx": "标准民用车与军车", +// "zpsj": "1611211945", +// "all_url": null, +// "cdh": 1, +// "sjlx": "", +// "wzlx": "黑烟车", +// "jczxd": 2, +// "tp1": null, +// "tp2": null, +// "tp3": null, +// "tp4": null, +// "dwms": "观斗山隧道入城方向出口", +// "cp_url": "http://10.194.142.91:9001/uploads/4268007146bf08472bdb92a7e064306d.jpg", +// "cscd": "0", +// "clsd": "761", +// "kkbh": "0", +// "cdbh": "2", +// "jcdbh": "12345", +// "truetime": "2147483647", +// "dwbh": 11, +// "plate": "川", +// "plate_sd": "四川" +// }, +// "msg": "" +// }; + +//只显示8个字段 +//车牌号 +// 车牌颜色 +// 车辆类型 +// 林格曼黑度 +// 抓拍次数 +// 首次抓拍时间 +// 首次抓拍地点 +// 图片地址 +// 视频地址 +//所有字段中,仅仅提供车牌颜色、车牌号、审核意见供修改 +//已经与mapZpljTemp字段进行对应检查 +Map mapGetZpjlGetDataSpecial = { + "id": ItemData(fieldText: "主键ID"), + "uid": ItemData(fieldText: "用户ID"), + "uname": ItemData(fieldText: "用户名"), + "addtime": ItemData(fieldText: "添加时间"), + "uuid": ItemData(fieldText: "更新用户ID"), + "uuname": ItemData(fieldText: "更新用户名"), + "updatetime": ItemData(fieldText: "更新时间"), + "status": ItemData(fieldText: "状态"), + "dwip": ItemData(fieldText: "抓拍地点位IP"), + "sbmc": ItemData(fieldText: "设备名称"), + "pic_url": ItemData(fieldText: "图片地址", fieldUrlVisible: false, fieldVisible: true), + "video_url": ItemData(fieldText: "视频地址", fieldUrlVisible: false, fieldVisible: true), + "car_number": ItemData(fieldText: "车牌号码", fieldModifiable: true, fieldVisible: true), + "car_type": ItemData(fieldText: "车型"), + "car_color": ItemData(fieldText: "车辆颜色"), + "is_yellow": ItemData(fieldText: "是否为黄色"), + "lgmzs": ItemData(fieldText: "林格曼黑度", fieldVisible: true), + "workflow": ItemData(fieldText: "审核状态"), + "clfl": ItemData(fieldText: "车辆类型", fieldVisible: true), + "xsfx": ItemData(fieldText: "行驶方向"), + "cpys": ItemData(fieldText: "车牌颜色", fieldModifiable: true, fieldVisible: true), + "cplx": ItemData(fieldText: "民用双行尾牌"), + "zpsj": ItemData(fieldText: "抓拍时间", fieldVisible: true), + "all_url": ItemData(fieldText: "资料地址"), + "cdh": ItemData(fieldText: "出单号"), + "sjlx": ItemData(fieldText: "审结类型"), + "wzlx": ItemData(fieldText: "违章类型"), + "jczxd": ItemData(fieldText: "检测置信度"), + "tp1": ItemData(fieldText: "第一张图片地址", fieldUrlVisible: false), + "tp2": ItemData(fieldText: "第二张图片地址", fieldUrlVisible: false), + "tp3": ItemData(fieldText: "第三张图片地址", fieldUrlVisible: false), + "tp4": ItemData(fieldText: "第四张图片地址", fieldUrlVisible: false), + "dwms": ItemData(fieldText: "抓拍地点", fieldVisible: true), + "cp_url": ItemData(fieldText: "抓拍图片地址", fieldUrlVisible: false), + "cscd": ItemData(fieldText: "初审出单"), + "clsd": ItemData(fieldText: "车辆速度"), + "kkbh": ItemData(fieldText: "扣款编号"), + "cdbh": ItemData(fieldText: "出单编号"), + "jcdbh": ItemData(fieldText: "监测点编号"), + "truetime": ItemData(fieldText: "抓拍时间"), + "dwbh": ItemData(fieldText: "抓拍地点编号"), + "plate": ItemData(fieldText: "车牌省份简称"), + "plate_sd": ItemData(fieldText: "车牌省份属地"), +}; + +//Map mapGetWzxxGetDataSpecial = { +// "zpsj": ItemData(fieldText: "抓拍时间"), +// "video_url": ItemData(fieldText: "视频地址", fieldUrlVisible: false), +// "workflow": ItemData(fieldText: "审核状态", fieldVisible: false), +// "lgmzs": ItemData(fieldText: "林格曼指数"), +// "wzlx": ItemData(fieldText: "违章类型", fieldVisible: false), +// "jczxd": ItemData(fieldText: "检测置信度", fieldVisible: false), +// "pic_url": ItemData(fieldText: "图片地址", fieldUrlVisible: false), +// "tp1": ItemData(fieldText: "第一张图片地址", fieldUrlVisible: false), +// "tp2": ItemData(fieldText: "第二张图片地址", fieldUrlVisible: false), +// "tp3": ItemData(fieldText: "第三张图片地址", fieldUrlVisible: false), +// "tp4": ItemData(fieldText: "第四张图片地址", fieldUrlVisible: false), +// "id": ItemData(fieldText: "主键ID", fieldVisible: false), +// "uid": ItemData(fieldText: "用户ID"), +// "uname": ItemData(fieldText: "用户名"), +// "addtime": ItemData(fieldText: "添加时间"), +// "uuid": ItemData(fieldText: "更新用户ID"), +// "uuname": ItemData(fieldText: "更新用户名"), +// "updatetime": ItemData(fieldText: "更新时间"), +// "status": ItemData(fieldText: "状态"), +// "cdh": ItemData(fieldText: "出单号"), +// "sjlx": ItemData(fieldText: "审结类型"), +// //"sbbh": ItemData(fieldText: "设备编号"), +// "dwip": ItemData(fieldText: "抓拍地点IP"), +// "all_url": ItemData(fieldText: "资料地址"), +// "sbmc": ItemData(fieldText: "设备名称"), +// "car_color": ItemData(fieldText: "车辆颜色"), +// "is_yellow": ItemData(fieldText: "黄色"), +// "clfl": ItemData(fieldText: "车辆类型"), +// "xsfx": ItemData(fieldText: "行驶方向"), +// "cplx": ItemData(fieldText: "民用双行尾牌"), +// "car_number": ItemData(fieldText: "车牌号", fieldModifiable: true), +// "car_type": ItemData(fieldText: "车型"), +// "cpys": ItemData(fieldText: "车牌颜色", fieldModifiable: true), +// "dwms": ItemData(fieldText: "抓拍地点名称"), +// "cp_url": ItemData(fieldText: "抓拍图片地址", fieldUrlVisible: false), +// "cscd": ItemData(fieldText: "初审cd"), +// "clsd": ItemData(fieldText: "处理sd"), +// "kkbh": ItemData(fieldText: "kk编号"), +// "cdbh": ItemData(fieldText: "cd编号"), +// "jcdbh": ItemData(fieldText: "监测点编号"), +// "truetime": ItemData(fieldText: "抓拍时间"), +// "dwbh": ItemData(fieldText: "抓拍地点编号"), +// "plate": ItemData(fieldText: "车牌省份简称"), +// "plate_sd": ItemData(fieldText: "车牌省份属地"), +// }; + +cloneMap({Map mapTarget, Map mapSource}) async { + if (0 == mapSource.length) { + return; + } + mapTarget.clear(); + mapSource.forEach((key, value) { + mapTarget[key] = mapSource[key]; + }); +} + +//usrMap.forEach((k,v) => print('${k}: ${v}')); +copyMapUpdateWzxxData(Map map) async { + mapUpdateWzxxData.forEach((key, value) { + //这两个字段为整型,必须单独处理。其他字段都是字符型 + // if("lgmzs" == key || "jczxd" == key) { + // mapUpdateWzxxData[key] = map[key]; + // } else if('' == map[key]) { + // mapUpdateWzxxData[key] = '(空)'; + // } else { + // mapUpdateWzxxData[key] = map[key]; + // } + + if (map[key] is int) { + mapUpdateWzxxData[key] = map[key] > 0 ? map[key] : 1; + } else { + mapUpdateWzxxData[key] = (map[key] == '') ? '(空)' : map[key]; + } + }); +} + +Map mapUpdateWzxxData = { + "id": '', + "car_number": '', + "car_type": '', + "cpys": '', + "zpsj": '', + "video_url": '', + "lgmzs": 1, + "jczxd": 1, + "tp1": '', + "tp2": '', + "tp3": '', + "tp4": '', +}; + +// Map mapGetHycxGetDataText = { +// "id": "主键ID", +// "uid": "用户ID", +// "uname": "用户名", +// "addtime": "添加时间", +// "uuid": "更新用户ID", +// "uuname": "更新用户名", +// "updatetime": "更新时间", +// "status": "状态", +// "sbbh": "设备编号", +// "sbmc": "设备名称", +// "pic_url": "图片地址", +// "video_url": "视频地址", +// "car_number": "车牌号", +// "car_type": "车型", +// "car_color": "车辆颜色", +// "is_yellow": "黄色", +// "lgmzs": "林格曼指数", +// "workflow": "审核状态", +// "clfl": "车辆类型", +// "xsfx": "行驶方向", +// "cpys": "车牌颜色", +// "cplx": "民用双行尾牌", +// "zpsj": "抓拍时间", +// "all_url": "资料地址", +// "cdh": "出单号", +// "sjlx": "审结类型", +// "wzlx": "违章类型", +// "jczxd": "检测置信度", +// "tp1": "第一张图片地址", +// "tp2": "第二张图片地址", +// "tp3": "第三张图片地址", +// "tp4": "第四张图片地址", +// }; + +//4、违章信息查询数据变量 +List listWzxxGetData = []; +List listWzxxGetList2 = [ + { + "id": 1, + "uid": 132, + "uname": "", + "addtime": "2020-05-20 10:10:00", + "uuid": 132, + "uuname": "", + "updatetime": "2020-09-09 20:19:46", + "status": 1, + "sbbh": "S202001", + "sbmc": "摄像头", + "pic_url": "hyc/192.168.4.102/违法数据/20200822/12/6063_2_20200822_125252292_川Q09716_P1.jpg", + "video_url": "hyc/192.168.4.102/违法数据/20200822/12/6063_2_20200822_125252292_川Q09716.mp4", + "car_number": "川Q09716", + "car_type": "大型车", + "car_color": "白色", + "is_yellow": 0, + "lgmzs": 3, + "workflow": 1, + "clfl": "大货车", + "xsfx": "上行", + "cpys": "黄色", + "cplx": "民用双行尾牌", + "zpsj": "20200822125252292", + "all_url": "hyc\\192.168.4.102\\违法数据\\20200822\\12\\6063_2_20200822_125252292_川Q09716", + "cdh": 1, + "sjlx": "", + "wzlx": "黑烟车", + "jczxd": 0, + "tp1": "", + "tp2": "", + "tp3": "", + "tp4": "" + }, + { + "id": 2, + "uid": 132, + "uname": "", + "addtime": "2020-05-20 10:10:00", + "uuid": 132, + "uuname": "", + "updatetime": "2020-09-09 20:19:46", + "status": 1, + "sbbh": "S202001", + "sbmc": "摄像头", + "pic_url": "hyc/192.168.4.102/违法数据/20200822/12/6063_2_20200822_125252292_川Q09716_P1.jpg", + "video_url": "hyc/192.168.4.102/违法数据/20200822/12/6063_2_20200822_125252292_川Q09716.mp4", + "car_number": "川Q09716", + "car_type": "大型车", + "car_color": "白色", + "is_yellow": 0, + "lgmzs": 3, + "workflow": 1, + "clfl": "大货车", + "xsfx": "上行", + "cpys": "黄色", + "cplx": "民用双行尾牌", + "zpsj": "20200822125252292", + "all_url": "hyc\\192.168.4.102\\违法数据\\20200822\\12\\6063_2_20200822_125252292_川Q09716", + "cdh": 1, + "sjlx": "", + "wzlx": "黑烟车", + "jczxd": 0, + "tp1": "", + "tp2": "", + "tp3": "", + "tp4": "" + }, + { + "id": 3, + "uid": 132, + "uname": "", + "addtime": "2020-05-20 10:10:00", + "uuid": 132, + "uuname": "", + "updatetime": "2020-09-09 20:19:46", + "status": 1, + "sbbh": "S202001", + "sbmc": "摄像头", + "pic_url": "hyc/192.168.4.102/违法数据/20200822/12/6063_2_20200822_125252292_川Q09716_P1.jpg", + "video_url": "hyc/192.168.4.102/违法数据/20200822/12/6063_2_20200822_125252292_川Q09716.mp4", + "car_number": "川Q09716", + "car_type": "大型车", + "car_color": "白色", + "is_yellow": 0, + "lgmzs": 3, + "workflow": 1, + "clfl": "大货车", + "xsfx": "上行", + "cpys": "黄色", + "cplx": "民用双行尾牌", + "zpsj": "20200822125252292", + "all_url": "hyc\\192.168.4.102\\违法数据\\20200822\\12\\6063_2_20200822_125252292_川Q09716", + "cdh": 1, + "sjlx": "", + "wzlx": "黑烟车", + "jczxd": 0, + "tp1": "", + "tp2": "", + "tp3": "", + "tp4": "" + }, + { + "id": 4, + "uid": 132, + "uname": "", + "addtime": "2020-05-20 10:10:00", + "uuid": 132, + "uuname": "", + "updatetime": "2020-09-09 20:19:46", + "status": 1, + "sbbh": "S202001", + "sbmc": "摄像头", + "pic_url": "hyc/192.168.4.102/违法数据/20200822/12/6063_2_20200822_125252292_川Q09716_P1.jpg", + "video_url": "hyc/192.168.4.102/违法数据/20200822/12/6063_2_20200822_125252292_川Q09716.mp4", + "car_number": "川Q09716", + "car_type": "大型车", + "car_color": "白色", + "is_yellow": 0, + "lgmzs": 3, + "workflow": 1, + "clfl": "大货车", + "xsfx": "上行", + "cpys": "黄色", + "cplx": "民用双行尾牌", + "zpsj": "20200822125252292", + "all_url": "hyc\\192.168.4.102\\违法数据\\20200822\\12\\6063_2_20200822_125252292_川Q09716", + "cdh": 1, + "sjlx": "", + "wzlx": "黑烟车", + "jczxd": 0, + "tp1": "", + "tp2": "", + "tp3": "", + "tp4": "" + }, + { + "id": 5, + "uid": 132, + "uname": "", + "addtime": "2020-05-20 10:10:00", + "uuid": 132, + "uuname": "", + "updatetime": "2020-09-09 20:19:46", + "status": 1, + "sbbh": "S202001", + "sbmc": "摄像头", + "pic_url": "hyc/192.168.4.102/违法数据/20200822/12/6063_2_20200822_125252292_川Q09716_P1.jpg", + "video_url": "hyc/192.168.4.102/违法数据/20200822/12/6063_2_20200822_125252292_川Q09716.mp4", + "car_number": "川Q09716", + "car_type": "大型车", + "car_color": "白色", + "is_yellow": 0, + "lgmzs": 3, + "workflow": 1, + "clfl": "大货车", + "xsfx": "上行", + "cpys": "黄色", + "cplx": "民用双行尾牌", + "zpsj": "20200822125252292", + "all_url": "hyc\\192.168.4.102\\违法数据\\20200822\\12\\6063_2_20200822_125252292_川Q09716", + "cdh": 1, + "sjlx": "", + "wzlx": "黑烟车", + "jczxd": 0, + "tp1": "", + "tp2": "", + "tp3": "", + "tp4": "" + }, +]; + +Map mapGetWzxxGetData = {}; + +Map mapGetWzxxGetDataRet = { + "ret": 200, + "data": { + "id": 1, + "uid": 132, + "uname": "", + "addtime": "2020-05-20 10:10:00", + "uuid": 132, + "uuname": "", + "updatetime": "2020-09-09 20:19:46", + "status": 1, + "sbbh": "S202001", + "sbmc": "摄像头", + "pic_url": "hyc/192.168.4.102/违法数据/20200822/12/6063_2_20200822_125252292_川Q09716_P1.jpg", + "video_url": "hyc/192.168.4.102/违法数据/20200822/12/6063_2_20200822_125252292_川Q09716.mp4", + "car_number": "川Q09716", + "car_type": "大型车", + "car_color": "白色", + "is_yellow": 0, + "lgmzs": 3, + "workflow": 1, + "clfl": "大货车", + "xsfx": "上行", + "cpys": "黄色", + "cplx": "民用双行尾牌", + "zpsj": "20200822125252292", + "all_url": "hyc\\192.168.4.102\\违法数据\\20200822\\12\\6063_2_20200822_125252292_川Q09716", + "cdh": 1, + "sjlx": "", + "wzlx": "黑烟车", + "jczxd": 0, + "tp1": "", + "tp2": "", + "tp3": "", + "tp4": "" + }, + "msg": "" +}; + +// 返回字段 类型 说明 +// id 整型 主键ID +// car_number 字符串 车牌号 +// car_type 字符串 车型 +// cpys 字符串 车牌颜色 +// zpsj 字符串 抓拍时间 +// workflow 整型 审核状态: 1=>待审 | 2=>已初审 | 1000=>已复审 +// video_url 字符串 视频地址 +// lgmzs 整型 林格曼指数 +// wzlx 字符串 违章类型 +// jczxd 整型 检测置信度 +// tp1 字符串 第一张图片地址 +// tp2 字符串 第二张图片地址 +// tp3 字符串 第三张图片地址 +// tp4 字符串 第四张图片地址 + +//"id": "主键ID" +// "car_number": "车牌号" +// "car_type": "车型" +// "cpys": "车牌颜色" +// "zpsj": "抓拍时间" +// "workflow": "审核状态" +// "video_url": "视频地址" +// "lgmzs": "林格曼指数" +// "wzlx": "违章类型" +// "jczxd": "检测置信度" +// "tp1": "第一张图片地址" +// "tp2": "第二张图片地址" +// "tp3": "第三张图片地址" +// "tp4": "第四张图片地址" + +Map mapGetWzxxGetDataText = { + "id": "主键ID", + "uid": "用户ID", + "uname": "用户名", + "addtime": "添加时间", + "uuid": "更新用户ID", + "uuname": "更新用户名", + "updatetime": "更新时间", + "status": "状态", + "sbbh": "设备编号", + "sbmc": "设备名称", + "pic_url": "图片地址", + "video_url": "视频地址", + "car_number": "车牌号", + "car_type": "车型", + "car_color": "车辆颜色", + "is_yellow": "黄色", + "lgmzs": "林格曼指数", + "workflow": "审核状态", + "clfl": "车辆类型", + "xsfx": "行驶方向", + "cpys": "车牌颜色", + "cplx": "民用双行尾牌", + "zpsj": "抓拍时间", + "all_url": "资料地址", + "cdh": "出单号", + "sjlx": "审结类型", + "wzlx": "违章类型", + "jczxd": "检测置信度", + "tp1": "第一张图片地址", + "tp2": "第二张图片地址", + "tp3": "第三张图片地址", + "tp4": "第四张图片地址", +}; + +Map mapGetWzxxGetDataModifiable = { + "id": false, + "uid": false, + "uname": false, + "addtime": false, + "uuid": false, + "uuname": false, + "updatetime": false, + "status": false, + "sbbh": false, + "sbmc": false, + "pic_url": false, + "video_url": false, + "car_number": false, + "car_type": false, + "car_color": false, + "is_yellow": false, + "lgmzs": false, + "workflow": false, + "clfl": false, + "xsfx": false, + "cpys": false, + "cplx": false, + "zpsj": false, + "all_url": false, + "cdh": false, + "sjlx": true, + "wzlx": false, + "jczxd": false, + "tp1": false, + "tp2": false, + "tp3": false, + "tp4": false, +}; + +//App.Car_Led.GetList接口获取的记录数据结构 +Map mapGetLedXsxxGetData = { + "id": 2, + "xsnr": "绿水青山就是金山银山 宜宾市生态环境局宣。", + "addtime": "2021-01-20 10:16:07", + "updatetime": "2021-02-13 11:48:51" +}; + +Map mapGetLedXsxxGetDataText = { + "id": 'ID号', + "xsnr": "显示内容", + "addtime": '添加时间', + "updatetime": '更新时间', +}; + +Map mapGetLedXsxxGetDataModifiable = { + "id": false, + "xsnr": true, + "addtime": false, + "updatetime": false, +}; + +Map mapGetLedXsxxListRet = { + "ret": 200, + "data": {"items": [], "total": 12, "page": 1, "perpage": 20}, + "msg": "" +}; + +List listLedXsxxGetList2 = [ + { + "id": 2, + "xsnr": "绿水青山就是金山银山 宜宾市生态环境局宣。", + "addtime": "2021-01-20 10:16:07", + "updatetime": "2021-02-13 11:48:51" + }, + { + "id": 1, + "xsnr": "绿水青山就是金山银山 宜宾市生态环境局宣。", + "addtime": "2021-01-06 00:59:43", + "updatetime": "2021-02-03 19:57:55" + }, + { + "id": 3, + "xsnr": "绿水青山就是金山银山 宜宾市生态环境局宣。", + "addtime": "2021-01-20 10:18:50", + "updatetime": "2021-02-03 19:57:43" + }, + { + "id": 8, + "xsnr": "绿水青山就是金山银山 宜宾市生态环境局宣。", + "addtime": "2021-01-20 10:19:22", + "updatetime": "2021-02-03 19:57:34" + }, + { + "id": 6, + "xsnr": "绿水青山就是金山银山 宜宾市生态环境局宣。", + "addtime": "2021-01-20 10:19:09", + "updatetime": "2021-01-22 22:01:06" + }, + { + "id": 5, + "xsnr": "绿水青山就是金山银山 宜宾市生态环境局宣。", + "addtime": "2021-01-20 10:19:02", + "updatetime": "2021-01-22 22:01:02" + }, + { + "id": 7, + "xsnr": "绿水青山就是金山银山 宜宾市生态环境局宣。", + "addtime": "2021-01-20 10:19:16", + "updatetime": "2021-01-22 22:00:49" + }, + { + "id": 4, + "xsnr": "绿水青山就是金山银山 宜宾市生态环境局宣。", + "addtime": "2021-01-20 10:18:56", + "updatetime": "2021-01-22 22:00:41" + }, + { + "id": 9, + "xsnr": "绿水青山就是金山银山 宜宾市生态环境局宣。", + "addtime": "2021-01-20 10:19:29", + "updatetime": "2021-01-22 22:00:21" + }, + { + "id": 10, + "xsnr": "绿水青山就是金山银山 宜宾市生态环境局宣。", + "addtime": "2021-01-20 10:19:36", + "updatetime": "2021-01-22 22:00:16" + }, + { + "id": 11, + "xsnr": "绿水青山就是金山银山 宜宾市生态环境局宣。", + "addtime": "2021-01-20 10:19:50", + "updatetime": "2021-01-22 22:00:08" + }, + { + "id": 12, + "xsnr": "绿水青山就是金山银山 宜宾市生态环境局宣。", + "addtime": "2021-01-20 10:19:58", + "updatetime": "2021-01-22 21:58:48" + } +]; + +//得到 listDwinfoGetList2['dwzt'] 的统计数据 +int getOKdw() { + int i = -1; + for (var item in listDwinfoGetList2) { + if (item['dwzt'] == '正常') { + i++; + } + } + return i; +} + +///获取点位信息数据 +List listDwinfoGetList2 = []; + +///获取点位信息数据 +List listDwinfoGetList0 = [ + { + "id": 1, + "dwip": "172.16.3.1", + "dwmc": "江北振兴大道", + "dwbh": 1, + "dwinfo": "江北振兴大道入城方向", + "dwzb": "104.607091|28.807061", + "dwms": "江北振兴大道入城方向,识别孜岩、红坝路入城排放黑烟车辆", + "dwzt": "正常" + }, + { + "id": 2, + "dwip": "172.16.3.2", + "dwmc": "宜飞路", + "dwbh": 2, + "dwinfo": "宜宾南收费站宜飞路入城方向", + "dwzb": "104.589904|28.787078", + "dwms": "宜宾南收费站宜飞路入城方向,识别屏山、菜坝入城排放黑烟车辆", + "dwzt": "正常" + }, + { + "id": 3, + "dwip": "172.16.3.3", + "dwmc": "宜宾南收费站", + "dwbh": 3, + "dwinfo": "宜宾南收费站出城方向", + "dwzb": "104.603919|28.765568", + "dwms": "宜宾南收费站出城方向,识别宜宾南收费站出城排放黑烟车辆", + "dwzt": "正常" + }, + { + "id": 4, + "dwip": "172.16.3.4", + "dwmc": "一曼路", + "dwbh": 4, + "dwinfo": "叙州区一曼路出城方向", + "dwzb": "104.556797|28.718901", + "dwms": "叙州区一曼路出城方向,识别进入叙州区新城区排放黑烟车辆", + "dwzt": "正常" + }, + { + "id": 5, + "dwip": "172.16.3.5", + "dwmc": "柏溪收费站", + "dwbh": 5, + "dwinfo": "叙州区柏溪收费站出城方向", + "dwzb": "104.533476|28.699059", + "dwms": "叙州区柏溪收费站出城方向,识别叙州区老城区出城车辆排放黑烟", + "dwzt": "正常" + }, + { + "id": 6, + "dwip": "172.16.3.6", + "dwmc": "七星路万达广场", + "dwbh": 6, + "dwinfo": "南岸七星路万达广场附近入城方向", + "dwzb": "104.662376|28.755488", + "dwms": "南岸七星路万达广场附近入城方向,识别高县入城排放黑烟车辆", + "dwzt": "正常" + }, + { + "id": 7, + "dwip": "172.16.3.7", + "dwmc": "宜宾财政局", + "dwbh": 7, + "dwinfo": "高县至宜宾财政局入城方向", + "dwzb": "104.616581|28.731942", + "dwms": "高县至宜宾财政局入城方向,识别高县入城排放黑烟车辆", + "dwzt": "正常" + }, + { + "id": 8, + "dwip": "172.16.3.8", + "dwmc": "宜威路南广镇", + "dwbh": 8, + "dwinfo": "宜威路南广镇附近入城方向", + "dwzb": "104.687767|28.731159", + "dwms": "宜威路南广镇附近入城方向,识别珙县、筠连入城排放黑烟车辆", + "dwzt": "正常" + }, + { + "id": 9, + "dwip": "172.16.3.9", + "dwmc": "宜长路", + "dwbh": 9, + "dwinfo": "宜长路出城方向", + "dwzb": "104.717384|28.763214", + "dwms": "宜长路出城方向,识别长宁、江安方向排放黑烟车辆", + "dwzt": "正常" + }, + { + "id": 10, + "dwip": "172.16.3.10", + "dwmc": "宜南快速通道", + "dwbh": 10, + "dwinfo": "宜南快速通道入城方向", + "dwzb": "104.759928|28.816636", + "dwms": "宜南快速通道入城方向,识别南溪方向入城排放黑烟车辆", + "dwzt": "正常" + }, + { + "id": 11, + "dwip": "172.16.3.11", + "dwmc": "观斗山隧道", + "dwbh": 11, + "dwinfo": "观斗山隧道入城方向出口", + "dwzb": "104.641112|28.814636", + "dwms": "观斗山隧道入城方向出口,识别自观斗山隧道入城排放黑烟车辆", + "dwzt": "正常" + }, + { + "id": 12, + "dwip": "172.16.3.12", + "dwmc": "大麦坝", + "dwbh": 12, + "dwinfo": "吊黄楼入城方向", + "dwzb": "104.63116|28.804407", + "dwms": "大麦坝入城方向,识别省道206入城排放黑烟车辆", + "dwzt": "正常" + }, + { + "id": 13, + "dwip": "172.16.3.13", + "dwmc": "外江路", + "dwbh": 13, + "dwinfo": "外江路往高铁站方向", + "dwzb": "104.623547|28.74798", + "dwms": "外江路往高铁站方向,识别中坝大桥往高铁站排放黑烟车辆", + "dwzt": "正常" + } +]; + +///下拉刷新、上拉加载数据 +Header getHeader() { + return ClassicalHeader( + // bgColor: Colors.green, + //textColor: Colors.black, + refreshText: "下拉刷新", + refreshReadyText: "释放立即刷新", + refreshingText: "正在刷新...", + refreshedText: "刷新完成", + refreshFailedText: "刷新失败", + infoText: "上次更新: %T", + //infoColor: Colors.white, + ); +} + +Footer getFooter() { + return ClassicalFooter( + // bgColor: Colors.green, + // textColor: Colors.white, + loadText: "上拉加载", + loadReadyText: "释放立即加载", + loadingText: "正在加载...", + loadedText: "加载完成", + loadFailedText: "加载失败", + noMoreText: "没有更多了", + infoText: "上次更新: %T", + //showInfo: true, //显示额外信息(默认为时间) + //enableInfiniteLoad: false, //是否开启无限加载 + ); +} + +//去除 mediaUrl 的前导斜杠 +String getMediaUrl(String mediaUrl) { + while ('/' == mediaUrl[0]) { + mediaUrl = mediaUrl.substring(1); + } + return ServiceMediaUrl + mediaUrl; +} + +//通过点位ip获取点位名称 +String getDwmc(String _dwip) { + _dwip = _dwip.trim(); + for (var item in listDwinfoGetList2) { + if (_dwip == item["dwip"].trim()) { + return item["dwmc"].trim(); + } + } + return ''; +} + +// 通过名称获取点位ip +Future getDwip(String _dwmc) async { + _dwmc = _dwmc.trim(); + for (var item in listDwinfoGetList2) { + if (_dwmc == item['dwmc'].trim()) { + return item['dwip'].trim(); + } + } + return ''; +} diff --git a/lib/components/save_data_to_file.dart b/lib/components/save_data_to_file.dart new file mode 100644 index 0000000..7b896a2 --- /dev/null +++ b/lib/components/save_data_to_file.dart @@ -0,0 +1,34 @@ +import 'dart:io'; +import 'dart:async'; +import 'package:path_provider/path_provider.dart'; + +class StorageDataToFile { + static Future get _localPath async { + final _path = await getTemporaryDirectory(); + return _path.path; + } + + static Future get _localFile async { + final path = await _localPath; + + return File('$path/counter.txt'); + } + + static Future readCounter() async { + try { + final file = await _localFile; + + var contents = await file.readAsString(); + + return int.parse(contents); + } catch (e) { + return 0; + } + } + + static Future writeCounter(counter) async { + final file = await _localFile; + + return file.writeAsString('$counter'); + } +} diff --git a/lib/config/Config.dart b/lib/config/Config.dart new file mode 100644 index 0000000..eaf0b6c --- /dev/null +++ b/lib/config/Config.dart @@ -0,0 +1,3 @@ +class Config{ + static String domain="http://jd.itying.com/"; +} \ No newline at end of file diff --git a/lib/config/service_url.dart b/lib/config/service_url.dart new file mode 100644 index 0000000..a40b2fa --- /dev/null +++ b/lib/config/service_url.dart @@ -0,0 +1,129 @@ +//const serviceUrl= 'http://v.jspang.com:8088/baixing/'; +//const serviceUrl= 'http://test.baixingliangfan.cn/baixing/'; + +//http://10.2.10.141:7300/mock/5fbb7f720e0cfd151c87e9f2/hyzp_ybqx/user/login +// const serviceUrl = +// 'http://10.2.10.141:7300/mock/5fbb7f720e0cfd151c87e9f2/hyzp_ybqx/'; + +//20210109又换回该地址 +//const ServiceUrl = 'http://49.235.208.235:9001/'; + +//20210123更换为该地址 +//http://125.64.218.67:9904/docs.php +const ServiceUrl = 'http://125.64.218.67:9904/'; +//图片视频需要添加前缀:http://125.64.218.67:9908 +const ServiceMediaUrl = 'http://125.64.218.67:9908/'; + +//20201222更换为该地址 +//http://125.64.218.67:9901/docs.php +//const ServiceUrl = 'http://125.64.218.67:9901/'; +const ServiceUrlJd = 'http://jd.itying.com/imgupload/'; + +///点位视频(Dwsp)接口Url +const ServiceDwspUrl = 'http://125.64.218.67:9901'; + +class ServicePath { + ///用户登录相关接口 + static const String loginUrl = ServiceUrl + '?s=App.User_User.Login'; //用户名登录 + static const String modifyPwUrl = ServiceUrl + '?s=App.User_User.Cpass'; //根据账号和原密码进行修改密码操作 + static const String loginStateUrl = ServiceUrl + '?s=App.User_User.CheckSession'; + static const String getUserInfoUrl = ServiceUrl + '?s=App.User_User.Profile'; //获取我的个人信息 + static const String uploadImageUrl = ServiceUrl + '?s=App.Car_Upload.Pic'; //上传黑烟图片 + + ///人脸注册和登录相关接口 + static const String uploadFaceregUrl = ServiceUrl + '?s=App.User_User.Facereg'; //人脸注册接口 + static const String uploadFaceloginUrl = ServiceUrl + '?s=App.User_User.Facelogin'; //人脸识别登录接口 + static const String getUserListUrl = ServiceUrl + '?s=App.User_User.GetUserList'; //获取用户分页列表数据 + + ///用户权限管理 + static const String getUserAccessUrl = + ServiceUrl + '?s=App.User_User.GetAccess'; //1、根据用户ID获取用户所属角色 + static const String getUserGroupUrl = ServiceUrl + '?s=App.User_User.GetGroup'; //2、获取后台用户角色分组数据 + static const String getUserGroupListUrl = + ServiceUrl + '?s=App.User_User.GetGroupList'; //3、获取后台用户角色分组分页列表数据 + static const String getUserAuthUrl = ServiceUrl + '?s=App.User_User.GetAuth'; //4、获取后台功能分类数据 + static const String getUserAuthListUrl = + ServiceUrl + '?s=App.User_User.GetAuthList'; //5、获取后台功能分类分页列表数据 + + ///违章信息数据相关接口 + // static const String getWzxxGetDataUrl = ServiceUrl + '?s=App.Car_Yjxx.Get'; //获取违章信息单条数据 + // static const String getWzxxGetAllUrl = ServiceUrl + '?s=App.Car_Yjxx.GetAll'; //获取违章信息全部分页列表数据 + // static const String getWzxxGetListUrl = ServiceUrl + '?s=App.Car_Yjxx.GetList'; //获取违章信息分页列表数据 + // static const String auditWzxxUrl = ServiceUrl + '?s=App.Car_Yjxx.Workflow'; //违章信息审核 + static const String getWzxxGetDataUrl = ServiceUrl + '?s=App.Car_Hyc.Get'; //获取违章信息单条数据 + static const String getWzxxGetAllUrl = ServiceUrl + '?s=App.Car_Hyc.GetAll'; //获取违章信息全部分页列表数据 + static const String getWzxxGetListUrl = ServiceUrl + '?s=App.Car_Hyc.GetList'; //获取违章信息分页列表数据 + static const String updateWzxxUrl = ServiceUrl + '?s=App.Car_Yjxx.Update'; //违章信息更新 + + ////违章信息审核相关接口 + static const String auditWzxxUrl = ServiceUrl + '?s=App.Car_Hyc.Workflow'; //违章信息审核 + static const String getShenheUrl = ServiceUrl + '?s=App.Car_Hyc.GetShenhe'; //获取审核信息 + static const String getNtimeUrl = ServiceUrl + '?s=App.Car_Hyc.GetNtime'; //获取违章间隔时间数据 + + static const String tsjjGetTsStatus = ServiceUrl + '?s=App.Car_Hyc.GetTs'; //获取推送交警状态信息 + static const String tsjjFtpUpftpUrl = ServiceUrl + '?s=App.Car_Ftp.Upftp'; //向交警服务器推送数据 + static const String tsjjFtpUptsztUrl = ServiceUrl + '?s=App.Car_Ftp.Uptszt'; //回写推送交警状态 + + ///Zpjl为抓拍记录缩写 + static const String getZpjlGetUrl = ServiceUrl + '?s=App.Car_Yjxx.Get'; //根据ID获取对应抓拍记录列表数据 + static const String getZpjlGetAllUrl = ServiceUrl + '?s=App.Car_Yjxx.GetAll'; //获取全部抓拍记录分页列表数据 + static const String getZpjlGetListUrl = + ServiceUrl + '?s=App.Car_Yjxx.GetList'; //根据审核状态获取抓拍记录分页列表数据 + + ///获取设备信息接口 + static const String getSbbjGetListUrl = ServiceUrl + '?s=App.Car_Bjxx.GetList'; //获取设备报警信息分页列表数据 + static const String getSbbjGetUrl = ServiceUrl + '?s=App.Car_Bjxx.Get'; //获取设备报警信息单条数据 + static const String auditSbbjUrl = ServiceUrl + '?s=App.Car_Bjxx.Workflow'; //核查处理设备报警信息 + + static const String getMachineGetListUrl = + ServiceUrl + '?s=App.Car_Machine.GetList'; //获取设备管理信息分页列表数据 + //http://125.64.218.67:9901/?s=App.Car_Sbbj.GetList + static const String getMachineGetDataUrl = ServiceUrl + '?s=App.Car_Machine.Get'; //获取设备管理信息单条数据 + + ///LED字幕信息 + static const String getLedXsxxGetListUrl = + ServiceUrl + '?s=App.Car_Led.GetList'; //获取LED显示信息分页列表数据 + static const String getLedXsxxGetUrl = ServiceUrl + '?s=App.Car_Led.Get'; //获取LED信息单条数据 + //static const String insertLedXsxxUrl = ServiceUrl + '?s=App.Car_Led.Insert'; //添加LED字幕,已取消该接口 + static const String updateLedXsxxGetUrl = ServiceUrl + '?s=App.Car_Led.Update'; //更新LED数据 + + ///点位信息 + static const String getDwinfoGetListUrl = ServiceUrl + '?s=App.Car_Dwinfo.GetList'; //获取点位信息分页列表数据 + + ///统计信息 + static const String getStaYjxxUrl = ServiceUrl + '?s=App.Car_Statis.GetStaYjxx'; //获取抓拍统计数据 + static const String getStaHycUrl = ServiceUrl + '?s=App.Car_Statis.GetStaHyc'; //获取审核黑烟车统计数据 + static const String getStaCllUrl = ServiceUrl + '?s=App.Car_Statis.GetStaCll'; //获取车流量统计数据 + static const String getStaAllUrl = ServiceUrl + '?s=App.Car_Statis.GetStaAll'; //获取今日所有统计数据 + + static const String getRStaCllUrl = + ServiceUrl + '?s=App.Car_Statis.GetStaView'; //获取车流量日统计数据(含早晚高峰) + + ///点位视频(Dwsp)播放 + //1、系统登录 + static const String getDwspLoginUrl = ServiceDwspUrl + '/api/v1/system/login'; + + //2、获取设备列表 + static const String getDwspDeviceListUrl = ServiceDwspUrl + '/api/v1/device/list'; + + //'http://125.64.218.67:9908/api/v1/device/list' + //3、根据设备ID获取实时直播接口 - 开始实时直播 + static const String getDwspStartRtmpUrl = ServiceDwspUrl + '/api/v1/stream/start'; + + //4、开始实时直播 + static const String getDwspStopRtmpUrl = ServiceDwspUrl + '/api/v1/stream/stop'; //5、关闭实时直播 + ///测试用,获取点位视频播放地址,类似 'http://125.64.218.67:9908/rtmp/1.php' + ///const ServiceMediaUrl = 'http://125.64.218.67:9908/'; + static const String getDwspUrl = ServiceMediaUrl + 'rtmp/'; + + //5、球机方向控制接口说明: + // 接口地址:http://125.64.218.67:9906/api/ptz/{通道ID}/{球机ID} + static const String setSphericalCameraUrl = 'http://125.64.218.67:9906/api/ptz/'; //球机方向控制接口 + + //6、获取 Apk 下载地址 + // api = http://sctastech.com/download/index.html + // http://www.sctastech.com/download/hyzp_20210425.apk + // 接口地址:http://sctastech.com/download/index.html + static const String getVerUrl = ServiceUrl + '?s=App.Car_Ver.Getver'; //获取最新版本号 + +} diff --git a/lib/custom_icons/README.md b/lib/custom_icons/README.md new file mode 100644 index 0000000..2bf6fbc --- /dev/null +++ b/lib/custom_icons/README.md @@ -0,0 +1,5 @@ +# ListView列表组件 - 示例 + +|1. [Antd图标](./antd_icons.dart)| +|----| +|| diff --git a/lib/custom_icons/icons_data.dart b/lib/custom_icons/icons_data.dart new file mode 100644 index 0000000..17c8a0b --- /dev/null +++ b/lib/custom_icons/icons_data.dart @@ -0,0 +1,995 @@ +import 'package:flutter/material.dart'; + +final List iconList = [ +// Generated code: do not hand-edit., +// See https://github.com/flutter/flutter/wiki/Updating-Material-Design-Fonts, +// BEGIN GENERATED, + + Icons.threesixty, + Icons.threed_rotation, + Icons.four_k, + Icons.ac_unit, + Icons.access_alarm, + Icons.access_alarms, + Icons.access_time, + Icons.accessibility, + Icons.accessibility_new, + Icons.accessible, + Icons.accessible_forward, + Icons.account_balance, + Icons.account_balance_wallet, + Icons.account_box, + Icons.account_circle, + Icons.adb, + Icons.add, + Icons.add_a_photo, + Icons.add_alarm, + Icons.add_alert, + Icons.add_box, + Icons.add_call, + Icons.add_circle, + Icons.add_circle_outline, + Icons.add_comment, + Icons.add_location, + Icons.add_photo_alternate, + Icons.add_shopping_cart, + Icons.add_to_home_screen, + Icons.add_to_photos, + Icons.add_to_queue, + Icons.adjust, + Icons.airline_seat_flat, + Icons.airline_seat_flat_angled, + Icons.airline_seat_individual_suite, + Icons.airline_seat_legroom_extra, + Icons.airline_seat_legroom_normal, + Icons.airline_seat_legroom_reduced, + Icons.airline_seat_recline_extra, + Icons.airline_seat_recline_normal, + Icons.airplanemode_active, + Icons.airplanemode_inactive, + Icons.airplay, + Icons.airport_shuttle, + Icons.alarm, + Icons.alarm_add, + Icons.alarm_off, + Icons.alarm_on, + Icons.album, + Icons.all_inclusive, + Icons.all_out, + Icons.alternate_email, + Icons.android, + Icons.announcement, + Icons.apps, + Icons.archive, + Icons.arrow_back, + Icons.arrow_back_ios, + Icons.arrow_downward, + Icons.arrow_drop_down, + Icons.arrow_drop_down_circle, + Icons.arrow_drop_up, + Icons.arrow_forward, + Icons.arrow_forward_ios, + Icons.arrow_left, + Icons.arrow_right, + Icons.arrow_upward, + Icons.art_track, + Icons.aspect_ratio, + Icons.assessment, + Icons.assignment, + Icons.assignment_ind, + Icons.assignment_late, + Icons.assignment_return, + Icons.assignment_returned, + Icons.assignment_turned_in, + Icons.assistant, + Icons.assistant_photo, + Icons.atm, + Icons.attach_file, + Icons.attach_money, + Icons.attachment, + Icons.audiotrack, + Icons.autorenew, + Icons.av_timer, + Icons.backspace, + Icons.backup, + Icons.battery_alert, + Icons.battery_charging_full, + Icons.battery_full, + Icons.battery_std, + Icons.battery_unknown, + Icons.beach_access, + Icons.beenhere, + Icons.block, + Icons.bluetooth, + Icons.bluetooth_audio, + Icons.bluetooth_connected, + Icons.bluetooth_disabled, + Icons.bluetooth_searching, + Icons.blur_circular, + Icons.blur_linear, + Icons.blur_off, + Icons.blur_on, + Icons.book, + Icons.bookmark, + Icons.bookmark_border, + Icons.border_all, + Icons.border_bottom, + Icons.border_clear, + Icons.border_color, + Icons.border_horizontal, + Icons.border_inner, + Icons.border_left, + Icons.border_outer, + Icons.border_right, + Icons.border_style, + Icons.border_top, + Icons.border_vertical, + Icons.branding_watermark, + Icons.brightness_1, + Icons.brightness_2, + Icons.brightness_3, + Icons.brightness_4, + Icons.brightness_5, + Icons.brightness_6, + Icons.brightness_7, + Icons.brightness_auto, + Icons.brightness_high, + Icons.brightness_low, + Icons.brightness_medium, + Icons.broken_image, + Icons.brush, + Icons.bubble_chart, + Icons.bug_report, + Icons.build, + Icons.burst_mode, + Icons.business, + Icons.business_center, + Icons.cached, + Icons.cake, + Icons.calendar_today, + Icons.calendar_view_day, + Icons.call, + Icons.call_end, + Icons.call_made, + Icons.call_merge, + Icons.call_missed, + Icons.call_missed_outgoing, + Icons.call_received, + Icons.call_split, + Icons.call_to_action, + Icons.camera, + Icons.camera_alt, + Icons.camera_enhance, + Icons.camera_front, + Icons.camera_rear, + Icons.camera_roll, + Icons.cancel, + Icons.card_giftcard, + Icons.card_membership, + Icons.card_travel, + Icons.casino, + Icons.cast, + Icons.cast_connected, + Icons.category, + Icons.center_focus_strong, + Icons.center_focus_weak, + Icons.change_history, + Icons.chat, + Icons.chat_bubble, + Icons.chat_bubble_outline, + Icons.check, + Icons.check_box, + Icons.check_box_outline_blank, + Icons.check_circle, + Icons.check_circle_outline, + Icons.chevron_left, + Icons.chevron_right, + Icons.child_care, + Icons.child_friendly, + Icons.chrome_reader_mode, + Icons.class_, + Icons.clear, + Icons.clear_all, + Icons.close, + Icons.closed_caption, + Icons.cloud, + Icons.cloud_circle, + Icons.cloud_done, + Icons.cloud_download, + Icons.cloud_off, + Icons.cloud_queue, + Icons.cloud_upload, + Icons.code, + Icons.collections, + Icons.collections_bookmark, + Icons.color_lens, + Icons.colorize, + Icons.comment, + Icons.compare, + Icons.compare_arrows, + Icons.computer, + Icons.confirmation_number, + Icons.contact_mail, + Icons.contact_phone, + Icons.contacts, + Icons.content_copy, + Icons.content_cut, + Icons.content_paste, + Icons.control_point, + Icons.control_point_duplicate, + Icons.copyright, + Icons.create, + Icons.create_new_folder, + Icons.credit_card, + Icons.crop, + Icons.crop_16_9, + Icons.crop_3_2, + Icons.crop_5_4, + Icons.crop_7_5, + Icons.crop_din, + Icons.crop_free, + Icons.crop_landscape, + Icons.crop_original, + Icons.crop_portrait, + Icons.crop_rotate, + Icons.crop_square, + Icons.dashboard, + Icons.data_usage, + Icons.date_range, + Icons.dehaze, + Icons.delete, + Icons.delete_forever, + Icons.delete_outline, + Icons.delete_sweep, + Icons.departure_board, + Icons.description, + Icons.desktop_mac, + Icons.desktop_windows, + Icons.details, + Icons.developer_board, + Icons.developer_mode, + Icons.device_hub, + Icons.device_unknown, + Icons.devices, + Icons.devices_other, + Icons.dialer_sip, + Icons.dialpad, + Icons.directions, + Icons.directions_bike, + Icons.directions_boat, + Icons.directions_bus, + Icons.directions_car, + Icons.directions_railway, + Icons.directions_run, + Icons.directions_subway, + Icons.directions_transit, + Icons.directions_walk, + Icons.disc_full, + Icons.dns, + Icons.do_not_disturb, + Icons.do_not_disturb_alt, + Icons.do_not_disturb_off, + Icons.do_not_disturb_on, + Icons.dock, + Icons.domain, + Icons.done, + Icons.done_all, + Icons.done_outline, + Icons.donut_large, + Icons.donut_small, + Icons.drafts, + Icons.drag_handle, + Icons.drive_eta, + Icons.dvr, + Icons.edit, + Icons.edit_attributes, + Icons.edit_location, + Icons.eject, + Icons.email, + Icons.enhanced_encryption, + Icons.equalizer, + Icons.error, + Icons.error_outline, + Icons.euro_symbol, + Icons.ev_station, + Icons.event, + Icons.event_available, + Icons.event_busy, + Icons.event_note, + Icons.event_seat, + Icons.exit_to_app, + Icons.expand_less, + Icons.expand_more, + Icons.explicit, + Icons.explore, + Icons.exposure, + Icons.exposure_neg_1, + Icons.exposure_neg_2, + Icons.exposure_plus_1, + Icons.exposure_plus_2, + Icons.exposure_zero, + Icons.extension, + Icons.face, + Icons.fast_forward, + Icons.fast_rewind, + Icons.fastfood, + Icons.favorite, + Icons.favorite_border, + Icons.featured_play_list, + Icons.featured_video, + Icons.feedback, + Icons.fiber_dvr, + Icons.fiber_manual_record, + Icons.fiber_new, + Icons.fiber_pin, + Icons.fiber_smart_record, + Icons.file_download, + Icons.file_upload, + Icons.filter, + Icons.filter_1, + Icons.filter_2, + Icons.filter_3, + Icons.filter_4, + Icons.filter_5, + Icons.filter_6, + Icons.filter_7, + Icons.filter_8, + Icons.filter_9, + Icons.filter_9_plus, + Icons.filter_b_and_w, + Icons.filter_center_focus, + Icons.filter_drama, + Icons.filter_frames, + Icons.filter_hdr, + Icons.filter_list, + Icons.filter_none, + Icons.filter_tilt_shift, + Icons.filter_vintage, + Icons.find_in_page, + Icons.find_replace, + Icons.fingerprint, + Icons.first_page, + Icons.fitness_center, + Icons.flag, + Icons.flare, + Icons.flash_auto, + Icons.flash_off, + Icons.flash_on, + Icons.flight, + Icons.flight_land, + Icons.flight_takeoff, + Icons.flip, + Icons.flip_to_back, + Icons.flip_to_front, + Icons.folder, + Icons.folder_open, + Icons.folder_shared, + Icons.folder_special, + Icons.font_download, + Icons.format_align_center, + Icons.format_align_justify, + Icons.format_align_left, + Icons.format_align_right, + Icons.format_bold, + Icons.format_clear, + Icons.format_color_fill, + Icons.format_color_reset, + Icons.format_color_text, + Icons.format_indent_decrease, + Icons.format_indent_increase, + Icons.format_italic, + Icons.format_line_spacing, + Icons.format_list_bulleted, + Icons.format_list_numbered, + Icons.format_list_numbered_rtl, + Icons.format_paint, + Icons.format_quote, + Icons.format_shapes, + Icons.format_size, + Icons.format_strikethrough, + Icons.format_textdirection_l_to_r, + Icons.format_textdirection_r_to_l, + Icons.format_underlined, + Icons.forum, + Icons.forward, + Icons.forward_10, + Icons.forward_30, + Icons.forward_5, + Icons.free_breakfast, + Icons.fullscreen, + Icons.fullscreen_exit, + Icons.functions, + Icons.g_translate, + Icons.gamepad, + Icons.games, + Icons.gavel, + Icons.gesture, + Icons.get_app, + Icons.gif, + Icons.golf_course, + Icons.gps_fixed, + Icons.gps_not_fixed, + Icons.gps_off, + Icons.grade, + Icons.gradient, + Icons.grain, + Icons.graphic_eq, + Icons.grid_off, + Icons.grid_on, + Icons.group, + Icons.group_add, + Icons.group_work, + Icons.hd, + Icons.hdr_off, + Icons.hdr_on, + Icons.hdr_strong, + Icons.hdr_weak, + Icons.headset, + Icons.headset_mic, + Icons.headset_off, + Icons.healing, + Icons.hearing, + Icons.help, + Icons.help_outline, + Icons.high_quality, + Icons.highlight, + Icons.highlight_off, + Icons.history, + Icons.home, + Icons.hot_tub, + Icons.hotel, + Icons.hourglass_empty, + Icons.hourglass_full, + Icons.http, + Icons.https, + Icons.image, + Icons.image_aspect_ratio, + Icons.import_contacts, + Icons.import_export, + Icons.important_devices, + Icons.inbox, + Icons.indeterminate_check_box, + Icons.info, + Icons.info_outline, + Icons.input, + Icons.insert_chart, + Icons.insert_comment, + Icons.insert_drive_file, + Icons.insert_emoticon, + Icons.insert_invitation, + Icons.insert_link, + Icons.insert_photo, + Icons.invert_colors, + Icons.invert_colors_off, + Icons.iso, + Icons.keyboard, + Icons.keyboard_arrow_down, + Icons.keyboard_arrow_left, + Icons.keyboard_arrow_right, + Icons.keyboard_arrow_up, + Icons.keyboard_backspace, + Icons.keyboard_capslock, + Icons.keyboard_hide, + Icons.keyboard_return, + Icons.keyboard_tab, + Icons.keyboard_voice, + Icons.kitchen, + Icons.label, + Icons.label_important, + Icons.label_outline, + Icons.landscape, + Icons.language, + Icons.laptop, + Icons.laptop_chromebook, + Icons.laptop_mac, + Icons.laptop_windows, + Icons.last_page, + Icons.launch, + Icons.layers, + Icons.layers_clear, + Icons.leak_add, + Icons.leak_remove, + Icons.lens, + Icons.library_add, + Icons.library_books, + Icons.library_music, + Icons.lightbulb_outline, + Icons.line_style, + Icons.line_weight, + Icons.linear_scale, + Icons.link, + Icons.link_off, + Icons.linked_camera, + Icons.list, + Icons.live_help, + Icons.live_tv, + Icons.local_activity, + Icons.local_airport, + Icons.local_atm, + Icons.local_bar, + Icons.local_cafe, + Icons.local_car_wash, + Icons.local_convenience_store, + Icons.local_dining, + Icons.local_drink, + Icons.local_florist, + Icons.local_gas_station, + Icons.local_grocery_store, + Icons.local_hospital, + Icons.local_hotel, + Icons.local_laundry_service, + Icons.local_library, + Icons.local_mall, + Icons.local_movies, + Icons.local_offer, + Icons.local_parking, + Icons.local_pharmacy, + Icons.local_phone, + Icons.local_pizza, + Icons.local_play, + Icons.local_post_office, + Icons.local_printshop, + Icons.local_see, + Icons.local_shipping, + Icons.local_taxi, + Icons.location_city, + Icons.location_disabled, + Icons.location_off, + Icons.location_on, + Icons.location_searching, + Icons.lock, + Icons.lock_open, + Icons.lock_outline, + Icons.looks, + Icons.looks_3, + Icons.looks_4, + Icons.looks_5, + Icons.looks_6, + Icons.looks_one, + Icons.looks_two, + Icons.loop, + Icons.loupe, + Icons.low_priority, + Icons.loyalty, + Icons.mail, + Icons.mail_outline, + Icons.map, + Icons.markunread, + Icons.markunread_mailbox, + Icons.maximize, + Icons.memory, + Icons.menu, + Icons.merge_type, + Icons.message, + Icons.mic, + Icons.mic_none, + Icons.mic_off, + Icons.minimize, + Icons.missed_video_call, + Icons.mms, + Icons.mobile_screen_share, + Icons.mode_comment, + Icons.mode_edit, + Icons.monetization_on, + Icons.money_off, + Icons.monochrome_photos, + Icons.mood, + Icons.mood_bad, + Icons.more, + Icons.more_horiz, + Icons.more_vert, + Icons.motorcycle, + Icons.mouse, + Icons.move_to_inbox, + Icons.movie, + Icons.movie_creation, + Icons.movie_filter, + Icons.multiline_chart, + Icons.music_note, + Icons.music_video, + Icons.my_location, + Icons.nature, + Icons.nature_people, + Icons.navigate_before, + Icons.navigate_next, + Icons.navigation, + Icons.near_me, + Icons.network_cell, + Icons.network_check, + Icons.network_locked, + Icons.network_wifi, + Icons.new_releases, + Icons.next_week, + Icons.nfc, + Icons.no_encryption, + Icons.no_sim, + Icons.not_interested, + Icons.not_listed_location, + Icons.note, + Icons.note_add, + Icons.notification_important, + Icons.notifications, + Icons.notifications_active, + Icons.notifications_none, + Icons.notifications_off, + Icons.notifications_paused, + Icons.offline_bolt, + Icons.offline_pin, + Icons.ondemand_video, + Icons.opacity, + Icons.open_in_browser, + Icons.open_in_new, + Icons.open_with, + Icons.outlined_flag, + Icons.pages, + Icons.pageview, + Icons.palette, + Icons.pan_tool, + Icons.panorama, + Icons.panorama_fish_eye, + Icons.panorama_horizontal, + Icons.panorama_vertical, + Icons.panorama_wide_angle, + Icons.party_mode, + Icons.pause, + Icons.pause_circle_filled, + Icons.pause_circle_outline, + Icons.payment, + Icons.people, + Icons.people_outline, + Icons.perm_camera_mic, + Icons.perm_contact_calendar, + Icons.perm_data_setting, + Icons.perm_device_information, + Icons.perm_identity, + Icons.perm_media, + Icons.perm_phone_msg, + Icons.perm_scan_wifi, + Icons.person, + Icons.person_add, + Icons.person_outline, + Icons.person_pin, + Icons.person_pin_circle, + Icons.personal_video, + Icons.pets, + Icons.phone, + Icons.phone_android, + Icons.phone_bluetooth_speaker, + Icons.phone_forwarded, + Icons.phone_in_talk, + Icons.phone_iphone, + Icons.phone_locked, + Icons.phone_missed, + Icons.phone_paused, + Icons.phonelink, + Icons.phonelink_erase, + Icons.phonelink_lock, + Icons.phonelink_off, + Icons.phonelink_ring, + Icons.phonelink_setup, + Icons.photo, + Icons.photo_album, + Icons.photo_camera, + Icons.photo_filter, + Icons.photo_library, + Icons.photo_size_select_actual, + Icons.photo_size_select_large, + Icons.photo_size_select_small, + Icons.picture_as_pdf, + Icons.picture_in_picture, + Icons.picture_in_picture_alt, + Icons.pie_chart, + Icons.pie_chart_outlined, + Icons.pin_drop, + Icons.place, + Icons.play_arrow, + Icons.play_circle_filled, + Icons.play_circle_outline, + Icons.play_for_work, + Icons.playlist_add, + Icons.playlist_add_check, + Icons.playlist_play, + Icons.plus_one, + Icons.poll, + Icons.polymer, + Icons.pool, + Icons.portable_wifi_off, + Icons.portrait, + Icons.power, + Icons.power_input, + Icons.power_settings_new, + Icons.pregnant_woman, + Icons.present_to_all, + Icons.print, + Icons.priority_high, + Icons.public, + Icons.publish, + Icons.query_builder, + Icons.question_answer, + Icons.queue, + Icons.queue_music, + Icons.queue_play_next, + Icons.radio, + Icons.radio_button_checked, + Icons.radio_button_unchecked, + Icons.rate_review, + Icons.receipt, + Icons.recent_actors, + Icons.record_voice_over, + Icons.redeem, + Icons.redo, + Icons.refresh, + Icons.remove, + Icons.remove_circle, + Icons.remove_circle_outline, + Icons.remove_from_queue, + Icons.remove_red_eye, + Icons.remove_shopping_cart, + Icons.reorder, + Icons.repeat, + Icons.repeat_one, + Icons.replay, + Icons.replay_10, + Icons.replay_30, + Icons.replay_5, + Icons.reply, + Icons.reply_all, + Icons.report, + Icons.report_off, + Icons.report_problem, + Icons.restaurant, + Icons.restaurant_menu, + Icons.restore, + Icons.restore_from_trash, + Icons.restore_page, + Icons.ring_volume, + Icons.room, + Icons.room_service, + Icons.rotate_90_degrees_ccw, + Icons.rotate_left, + Icons.rotate_right, + Icons.rounded_corner, + Icons.router, + Icons.rowing, + Icons.rss_feed, + Icons.rv_hookup, + Icons.satellite, + Icons.save, + Icons.save_alt, + Icons.scanner, + Icons.scatter_plot, + Icons.schedule, + Icons.school, + Icons.score, + Icons.screen_lock_landscape, + Icons.screen_lock_portrait, + Icons.screen_lock_rotation, + Icons.screen_rotation, + Icons.screen_share, + Icons.sd_card, + Icons.sd_storage, + Icons.search, + Icons.security, + Icons.select_all, + Icons.send, + Icons.sentiment_dissatisfied, + Icons.sentiment_neutral, + Icons.sentiment_satisfied, + Icons.sentiment_very_dissatisfied, + Icons.sentiment_very_satisfied, + Icons.settings, + Icons.settings_applications, + Icons.settings_backup_restore, + Icons.settings_bluetooth, + Icons.settings_brightness, + Icons.settings_cell, + Icons.settings_ethernet, + Icons.settings_input_antenna, + Icons.settings_input_component, + Icons.settings_input_composite, + Icons.settings_input_hdmi, + Icons.settings_input_svideo, + Icons.settings_overscan, + Icons.settings_phone, + Icons.settings_power, + Icons.settings_remote, + Icons.settings_system_daydream, + Icons.settings_voice, + Icons.share, + Icons.shop, + Icons.shop_two, + Icons.shopping_basket, + Icons.shopping_cart, + Icons.short_text, + Icons.show_chart, + Icons.shuffle, + Icons.shutter_speed, + Icons.signal_cellular_4_bar, + Icons.signal_cellular_connected_no_internet_4_bar, + Icons.signal_cellular_no_sim, + Icons.signal_cellular_null, + Icons.signal_cellular_off, + Icons.signal_wifi_4_bar, + Icons.signal_wifi_4_bar_lock, + Icons.signal_wifi_off, + Icons.sim_card, + Icons.sim_card_alert, + Icons.skip_next, + Icons.skip_previous, + Icons.slideshow, + Icons.slow_motion_video, + Icons.smartphone, + Icons.smoke_free, + Icons.smoking_rooms, + Icons.sms, + Icons.sms_failed, + Icons.snooze, + Icons.sort, + Icons.sort_by_alpha, + Icons.spa, + Icons.space_bar, + Icons.speaker, + Icons.speaker_group, + Icons.speaker_notes, + Icons.speaker_notes_off, + Icons.speaker_phone, + Icons.spellcheck, + Icons.star, + Icons.star_border, + Icons.star_half, + Icons.stars, + Icons.stay_current_landscape, + Icons.stay_current_portrait, + Icons.stay_primary_landscape, + Icons.stay_primary_portrait, + Icons.stop, + Icons.stop_screen_share, + Icons.storage, + Icons.store, + Icons.store_mall_directory, + Icons.straighten, + Icons.streetview, + Icons.strikethrough_s, + Icons.style, + Icons.subdirectory_arrow_left, + Icons.subdirectory_arrow_right, + Icons.subject, + Icons.subscriptions, + Icons.subtitles, + Icons.subway, + Icons.supervised_user_circle, + Icons.supervisor_account, + Icons.surround_sound, + Icons.swap_calls, + Icons.swap_horiz, + Icons.swap_horizontal_circle, + Icons.swap_vert, + Icons.swap_vertical_circle, + Icons.switch_camera, + Icons.switch_video, + Icons.sync, + Icons.sync_disabled, + Icons.sync_problem, + Icons.system_update, + Icons.system_update_alt, + Icons.tab, + Icons.tab_unselected, + Icons.table_chart, + Icons.tablet, + Icons.tablet_android, + Icons.tablet_mac, + Icons.tag_faces, + Icons.tap_and_play, + Icons.terrain, + Icons.text_fields, + Icons.text_format, + Icons.text_rotate_up, + Icons.text_rotate_vertical, + Icons.text_rotation_angledown, + Icons.text_rotation_angleup, + Icons.text_rotation_down, + Icons.text_rotation_none, + Icons.textsms, + Icons.texture, + Icons.theaters, + Icons.thumb_down, + Icons.thumb_up, + Icons.thumbs_up_down, + Icons.time_to_leave, + Icons.timelapse, + Icons.timeline, + Icons.timer, + Icons.timer_10, + Icons.timer_3, + Icons.timer_off, + Icons.title, + Icons.toc, + Icons.today, + Icons.toll, + Icons.tonality, + Icons.touch_app, + Icons.toys, + Icons.track_changes, + Icons.traffic, + Icons.train, + Icons.tram, + Icons.transfer_within_a_station, + Icons.transform, + Icons.transit_enterexit, + Icons.translate, + Icons.trending_down, + Icons.trending_flat, + Icons.trending_up, + Icons.trip_origin, + Icons.tune, + Icons.turned_in, + Icons.turned_in_not, + Icons.tv, + Icons.unarchive, + Icons.undo, + Icons.unfold_less, + Icons.unfold_more, + Icons.update, + Icons.usb, + Icons.verified_user, + Icons.vertical_align_bottom, + Icons.vertical_align_center, + Icons.vertical_align_top, + Icons.vibration, + Icons.video_call, + Icons.video_label, + Icons.video_library, + Icons.videocam, + Icons.videocam_off, + Icons.videogame_asset, + Icons.view_agenda, + Icons.view_array, + Icons.view_carousel, + Icons.view_column, + Icons.view_comfy, + Icons.view_compact, + Icons.view_day, + Icons.view_headline, + Icons.view_list, + Icons.view_module, + Icons.view_quilt, + Icons.view_stream, + Icons.view_week, + Icons.vignette, + Icons.visibility, + Icons.visibility_off, + Icons.voice_chat, + Icons.voicemail, + Icons.volume_down, + Icons.volume_mute, + Icons.volume_off, + Icons.volume_up, + Icons.vpn_key, + Icons.vpn_lock, + Icons.wallpaper, + Icons.warning, + Icons.watch, + Icons.watch_later, + Icons.wb_auto, + Icons.wb_cloudy, + Icons.wb_incandescent, + Icons.wb_iridescent, + Icons.wb_sunny, + Icons.wc, + Icons.web, + Icons.web_asset, + Icons.weekend, + Icons.whatshot, + Icons.widgets, + Icons.wifi, + Icons.wifi_lock, + Icons.wifi_tethering, + Icons.work, + Icons.wrap_text, + Icons.youtube_searched_for, + Icons.zoom_in, + Icons.zoom_out, + Icons.zoom_out_map, + +// END GENERATED, +]; diff --git a/lib/custom_icons/icons_name.dart b/lib/custom_icons/icons_name.dart new file mode 100644 index 0000000..0f6c0e5 --- /dev/null +++ b/lib/custom_icons/icons_name.dart @@ -0,0 +1,993 @@ +final List iconNameList = [ +// Generated code: do not hand-edit.', +// See https://github.com/flutter/flutter/wiki/Updating-Material-Design-Fonts', +// BEGIN GENERATED', + + 'threesixty', + 'threed_rotation', + 'four_k', + 'ac_unit', + 'access_alarm', + 'access_alarms', + 'access_time', + 'accessibility', + 'accessibility_new', + 'accessible', + 'accessible_forward', + 'account_balance', + 'account_balance_wallet', + 'account_box', + 'account_circle', + 'adb', + 'add', + 'add_a_photo', + 'add_alarm', + 'add_alert', + 'add_box', + 'add_call', + 'add_circle', + 'add_circle_outline', + 'add_comment', + 'add_location', + 'add_photo_alternate', + 'add_shopping_cart', + 'add_to_home_screen', + 'add_to_photos', + 'add_to_queue', + 'adjust', + 'airline_seat_flat', + 'airline_seat_flat_angled', + 'airline_seat_individual_suite', + 'airline_seat_legroom_extra', + 'airline_seat_legroom_normal', + 'airline_seat_legroom_reduced', + 'airline_seat_recline_extra', + 'airline_seat_recline_normal', + 'airplanemode_active', + 'airplanemode_inactive', + 'airplay', + 'airport_shuttle', + 'alarm', + 'alarm_add', + 'alarm_off', + 'alarm_on', + 'album', + 'all_inclusive', + 'all_out', + 'alternate_email', + 'android', + 'announcement', + 'apps', + 'archive', + 'arrow_back', + 'arrow_back_ios', + 'arrow_downward', + 'arrow_drop_down', + 'arrow_drop_down_circle', + 'arrow_drop_up', + 'arrow_forward', + 'arrow_forward_ios', + 'arrow_left', + 'arrow_right', + 'arrow_upward', + 'art_track', + 'aspect_ratio', + 'assessment', + 'assignment', + 'assignment_ind', + 'assignment_late', + 'assignment_return', + 'assignment_returned', + 'assignment_turned_in', + 'assistant', + 'assistant_photo', + 'atm', + 'attach_file', + 'attach_money', + 'attachment', + 'audiotrack', + 'autorenew', + 'av_timer', + 'backspace', + 'backup', + 'battery_alert', + 'battery_charging_full', + 'battery_full', + 'battery_std', + 'battery_unknown', + 'beach_access', + 'beenhere', + 'block', + 'bluetooth', + 'bluetooth_audio', + 'bluetooth_connected', + 'bluetooth_disabled', + 'bluetooth_searching', + 'blur_circular', + 'blur_linear', + 'blur_off', + 'blur_on', + 'book', + 'bookmark', + 'bookmark_border', + 'border_all', + 'border_bottom', + 'border_clear', + 'border_color', + 'border_horizontal', + 'border_inner', + 'border_left', + 'border_outer', + 'border_right', + 'border_style', + 'border_top', + 'border_vertical', + 'branding_watermark', + 'brightness_1', + 'brightness_2', + 'brightness_3', + 'brightness_4', + 'brightness_5', + 'brightness_6', + 'brightness_7', + 'brightness_auto', + 'brightness_high', + 'brightness_low', + 'brightness_medium', + 'broken_image', + 'brush', + 'bubble_chart', + 'bug_report', + 'build', + 'burst_mode', + 'business', + 'business_center', + 'cached', + 'cake', + 'calendar_today', + 'calendar_view_day', + 'call', + 'call_end', + 'call_made', + 'call_merge', + 'call_missed', + 'call_missed_outgoing', + 'call_received', + 'call_split', + 'call_to_action', + 'camera', + 'camera_alt', + 'camera_enhance', + 'camera_front', + 'camera_rear', + 'camera_roll', + 'cancel', + 'card_giftcard', + 'card_membership', + 'card_travel', + 'casino', + 'cast', + 'cast_connected', + 'category', + 'center_focus_strong', + 'center_focus_weak', + 'change_history', + 'chat', + 'chat_bubble', + 'chat_bubble_outline', + 'check', + 'check_box', + 'check_box_outline_blank', + 'check_circle', + 'check_circle_outline', + 'chevron_left', + 'chevron_right', + 'child_care', + 'child_friendly', + 'chrome_reader_mode', + 'class_', + 'clear', + 'clear_all', + 'close', + 'closed_caption', + 'cloud', + 'cloud_circle', + 'cloud_done', + 'cloud_download', + 'cloud_off', + 'cloud_queue', + 'cloud_upload', + 'code', + 'collections', + 'collections_bookmark', + 'color_lens', + 'colorize', + 'comment', + 'compare', + 'compare_arrows', + 'computer', + 'confirmation_number', + 'contact_mail', + 'contact_phone', + 'contacts', + 'content_copy', + 'content_cut', + 'content_paste', + 'control_point', + 'control_point_duplicate', + 'copyright', + 'create', + 'create_new_folder', + 'credit_card', + 'crop', + 'crop_16_9', + 'crop_3_2', + 'crop_5_4', + 'crop_7_5', + 'crop_din', + 'crop_free', + 'crop_landscape', + 'crop_original', + 'crop_portrait', + 'crop_rotate', + 'crop_square', + 'dashboard', + 'data_usage', + 'date_range', + 'dehaze', + 'delete', + 'delete_forever', + 'delete_outline', + 'delete_sweep', + 'departure_board', + 'description', + 'desktop_mac', + 'desktop_windows', + 'details', + 'developer_board', + 'developer_mode', + 'device_hub', + 'device_unknown', + 'devices', + 'devices_other', + 'dialer_sip', + 'dialpad', + 'directions', + 'directions_bike', + 'directions_boat', + 'directions_bus', + 'directions_car', + 'directions_railway', + 'directions_run', + 'directions_subway', + 'directions_transit', + 'directions_walk', + 'disc_full', + 'dns', + 'do_not_disturb', + 'do_not_disturb_alt', + 'do_not_disturb_off', + 'do_not_disturb_on', + 'dock', + 'domain', + 'done', + 'done_all', + 'done_outline', + 'donut_large', + 'donut_small', + 'drafts', + 'drag_handle', + 'drive_eta', + 'dvr', + 'edit', + 'edit_attributes', + 'edit_location', + 'eject', + 'email', + 'enhanced_encryption', + 'equalizer', + 'error', + 'error_outline', + 'euro_symbol', + 'ev_station', + 'event', + 'event_available', + 'event_busy', + 'event_note', + 'event_seat', + 'exit_to_app', + 'expand_less', + 'expand_more', + 'explicit', + 'explore', + 'exposure', + 'exposure_neg_1', + 'exposure_neg_2', + 'exposure_plus_1', + 'exposure_plus_2', + 'exposure_zero', + 'extension', + 'face', + 'fast_forward', + 'fast_rewind', + 'fastfood', + 'favorite', + 'favorite_border', + 'featured_play_list', + 'featured_video', + 'feedback', + 'fiber_dvr', + 'fiber_manual_record', + 'fiber_new', + 'fiber_pin', + 'fiber_smart_record', + 'file_download', + 'file_upload', + 'filter', + 'filter_1', + 'filter_2', + 'filter_3', + 'filter_4', + 'filter_5', + 'filter_6', + 'filter_7', + 'filter_8', + 'filter_9', + 'filter_9_plus', + 'filter_b_and_w', + 'filter_center_focus', + 'filter_drama', + 'filter_frames', + 'filter_hdr', + 'filter_list', + 'filter_none', + 'filter_tilt_shift', + 'filter_vintage', + 'find_in_page', + 'find_replace', + 'fingerprint', + 'first_page', + 'fitness_center', + 'flag', + 'flare', + 'flash_auto', + 'flash_off', + 'flash_on', + 'flight', + 'flight_land', + 'flight_takeoff', + 'flip', + 'flip_to_back', + 'flip_to_front', + 'folder', + 'folder_open', + 'folder_shared', + 'folder_special', + 'font_download', + 'format_align_center', + 'format_align_justify', + 'format_align_left', + 'format_align_right', + 'format_bold', + 'format_clear', + 'format_color_fill', + 'format_color_reset', + 'format_color_text', + 'format_indent_decrease', + 'format_indent_increase', + 'format_italic', + 'format_line_spacing', + 'format_list_bulleted', + 'format_list_numbered', + 'format_list_numbered_rtl', + 'format_paint', + 'format_quote', + 'format_shapes', + 'format_size', + 'format_strikethrough', + 'format_textdirection_l_to_r', + 'format_textdirection_r_to_l', + 'format_underlined', + 'forum', + 'forward', + 'forward_10', + 'forward_30', + 'forward_5', + 'free_breakfast', + 'fullscreen', + 'fullscreen_exit', + 'functions', + 'g_translate', + 'gamepad', + 'games', + 'gavel', + 'gesture', + 'get_app', + 'gif', + 'golf_course', + 'gps_fixed', + 'gps_not_fixed', + 'gps_off', + 'grade', + 'gradient', + 'grain', + 'graphic_eq', + 'grid_off', + 'grid_on', + 'group', + 'group_add', + 'group_work', + 'hd', + 'hdr_off', + 'hdr_on', + 'hdr_strong', + 'hdr_weak', + 'headset', + 'headset_mic', + 'headset_off', + 'healing', + 'hearing', + 'help', + 'help_outline', + 'high_quality', + 'highlight', + 'highlight_off', + 'history', + 'home', + 'hot_tub', + 'hotel', + 'hourglass_empty', + 'hourglass_full', + 'http', + 'https', + 'image', + 'image_aspect_ratio', + 'import_contacts', + 'import_export', + 'important_devices', + 'inbox', + 'indeterminate_check_box', + 'info', + 'info_outline', + 'input', + 'insert_chart', + 'insert_comment', + 'insert_drive_file', + 'insert_emoticon', + 'insert_invitation', + 'insert_link', + 'insert_photo', + 'invert_colors', + 'invert_colors_off', + 'iso', + 'keyboard', + 'keyboard_arrow_down', + 'keyboard_arrow_left', + 'keyboard_arrow_right', + 'keyboard_arrow_up', + 'keyboard_backspace', + 'keyboard_capslock', + 'keyboard_hide', + 'keyboard_return', + 'keyboard_tab', + 'keyboard_voice', + 'kitchen', + 'label', + 'label_important', + 'label_outline', + 'landscape', + 'language', + 'laptop', + 'laptop_chromebook', + 'laptop_mac', + 'laptop_windows', + 'last_page', + 'launch', + 'layers', + 'layers_clear', + 'leak_add', + 'leak_remove', + 'lens', + 'library_add', + 'library_books', + 'library_music', + 'lightbulb_outline', + 'line_style', + 'line_weight', + 'linear_scale', + 'link', + 'link_off', + 'linked_camera', + 'list', + 'live_help', + 'live_tv', + 'local_activity', + 'local_airport', + 'local_atm', + 'local_bar', + 'local_cafe', + 'local_car_wash', + 'local_convenience_store', + 'local_dining', + 'local_drink', + 'local_florist', + 'local_gas_station', + 'local_grocery_store', + 'local_hospital', + 'local_hotel', + 'local_laundry_service', + 'local_library', + 'local_mall', + 'local_movies', + 'local_offer', + 'local_parking', + 'local_pharmacy', + 'local_phone', + 'local_pizza', + 'local_play', + 'local_post_office', + 'local_printshop', + 'local_see', + 'local_shipping', + 'local_taxi', + 'location_city', + 'location_disabled', + 'location_off', + 'location_on', + 'location_searching', + 'lock', + 'lock_open', + 'lock_outline', + 'looks', + 'looks_3', + 'looks_4', + 'looks_5', + 'looks_6', + 'looks_one', + 'looks_two', + 'loop', + 'loupe', + 'low_priority', + 'loyalty', + 'mail', + 'mail_outline', + 'map', + 'markunread', + 'markunread_mailbox', + 'maximize', + 'memory', + 'menu', + 'merge_type', + 'message', + 'mic', + 'mic_none', + 'mic_off', + 'minimize', + 'missed_video_call', + 'mms', + 'mobile_screen_share', + 'mode_comment', + 'mode_edit', + 'monetization_on', + 'money_off', + 'monochrome_photos', + 'mood', + 'mood_bad', + 'more', + 'more_horiz', + 'more_vert', + 'motorcycle', + 'mouse', + 'move_to_inbox', + 'movie', + 'movie_creation', + 'movie_filter', + 'multiline_chart', + 'music_note', + 'music_video', + 'my_location', + 'nature', + 'nature_people', + 'navigate_before', + 'navigate_next', + 'navigation', + 'near_me', + 'network_cell', + 'network_check', + 'network_locked', + 'network_wifi', + 'new_releases', + 'next_week', + 'nfc', + 'no_encryption', + 'no_sim', + 'not_interested', + 'not_listed_location', + 'note', + 'note_add', + 'notification_important', + 'notifications', + 'notifications_active', + 'notifications_none', + 'notifications_off', + 'notifications_paused', + 'offline_bolt', + 'offline_pin', + 'ondemand_video', + 'opacity', + 'open_in_browser', + 'open_in_new', + 'open_with', + 'outlined_flag', + 'pages', + 'pageview', + 'palette', + 'pan_tool', + 'panorama', + 'panorama_fish_eye', + 'panorama_horizontal', + 'panorama_vertical', + 'panorama_wide_angle', + 'party_mode', + 'pause', + 'pause_circle_filled', + 'pause_circle_outline', + 'payment', + 'people', + 'people_outline', + 'perm_camera_mic', + 'perm_contact_calendar', + 'perm_data_setting', + 'perm_device_information', + 'perm_identity', + 'perm_media', + 'perm_phone_msg', + 'perm_scan_wifi', + 'person', + 'person_add', + 'person_outline', + 'person_pin', + 'person_pin_circle', + 'personal_video', + 'pets', + 'phone', + 'phone_android', + 'phone_bluetooth_speaker', + 'phone_forwarded', + 'phone_in_talk', + 'phone_iphone', + 'phone_locked', + 'phone_missed', + 'phone_paused', + 'phonelink', + 'phonelink_erase', + 'phonelink_lock', + 'phonelink_off', + 'phonelink_ring', + 'phonelink_setup', + 'photo', + 'photo_album', + 'photo_camera', + 'photo_filter', + 'photo_library', + 'photo_size_select_actual', + 'photo_size_select_large', + 'photo_size_select_small', + 'picture_as_pdf', + 'picture_in_picture', + 'picture_in_picture_alt', + 'pie_chart', + 'pie_chart_outlined', + 'pin_drop', + 'place', + 'play_arrow', + 'play_circle_filled', + 'play_circle_outline', + 'play_for_work', + 'playlist_add', + 'playlist_add_check', + 'playlist_play', + 'plus_one', + 'poll', + 'polymer', + 'pool', + 'portable_wifi_off', + 'portrait', + 'power', + 'power_input', + 'power_settings_new', + 'pregnant_woman', + 'present_to_all', + 'print', + 'priority_high', + 'public', + 'publish', + 'query_builder', + 'question_answer', + 'queue', + 'queue_music', + 'queue_play_next', + 'radio', + 'radio_button_checked', + 'radio_button_unchecked', + 'rate_review', + 'receipt', + 'recent_actors', + 'record_voice_over', + 'redeem', + 'redo', + 'refresh', + 'remove', + 'remove_circle', + 'remove_circle_outline', + 'remove_from_queue', + 'remove_red_eye', + 'remove_shopping_cart', + 'reorder', + 'repeat', + 'repeat_one', + 'replay', + 'replay_10', + 'replay_30', + 'replay_5', + 'reply', + 'reply_all', + 'report', + 'report_off', + 'report_problem', + 'restaurant', + 'restaurant_menu', + 'restore', + 'restore_from_trash', + 'restore_page', + 'ring_volume', + 'room', + 'room_service', + 'rotate_90_degrees_ccw', + 'rotate_left', + 'rotate_right', + 'rounded_corner', + 'router', + 'rowing', + 'rss_feed', + 'rv_hookup', + 'satellite', + 'save', + 'save_alt', + 'scanner', + 'scatter_plot', + 'schedule', + 'school', + 'score', + 'screen_lock_landscape', + 'screen_lock_portrait', + 'screen_lock_rotation', + 'screen_rotation', + 'screen_share', + 'sd_card', + 'sd_storage', + 'search', + 'security', + 'select_all', + 'send', + 'sentiment_dissatisfied', + 'sentiment_neutral', + 'sentiment_satisfied', + 'sentiment_very_dissatisfied', + 'sentiment_very_satisfied', + 'settings', + 'settings_applications', + 'settings_backup_restore', + 'settings_bluetooth', + 'settings_brightness', + 'settings_cell', + 'settings_ethernet', + 'settings_input_antenna', + 'settings_input_component', + 'settings_input_composite', + 'settings_input_hdmi', + 'settings_input_svideo', + 'settings_overscan', + 'settings_phone', + 'settings_power', + 'settings_remote', + 'settings_system_daydream', + 'settings_voice', + 'share', + 'shop', + 'shop_two', + 'shopping_basket', + 'shopping_cart', + 'short_text', + 'show_chart', + 'shuffle', + 'shutter_speed', + 'signal_cellular_4_bar', + 'signal_cellular_connected_no_internet_4_bar', + 'signal_cellular_no_sim', + 'signal_cellular_null', + 'signal_cellular_off', + 'signal_wifi_4_bar', + 'signal_wifi_4_bar_lock', + 'signal_wifi_off', + 'sim_card', + 'sim_card_alert', + 'skip_next', + 'skip_previous', + 'slideshow', + 'slow_motion_video', + 'smartphone', + 'smoke_free', + 'smoking_rooms', + 'sms', + 'sms_failed', + 'snooze', + 'sort', + 'sort_by_alpha', + 'spa', + 'space_bar', + 'speaker', + 'speaker_group', + 'speaker_notes', + 'speaker_notes_off', + 'speaker_phone', + 'spellcheck', + 'star', + 'star_border', + 'star_half', + 'stars', + 'stay_current_landscape', + 'stay_current_portrait', + 'stay_primary_landscape', + 'stay_primary_portrait', + 'stop', + 'stop_screen_share', + 'storage', + 'store', + 'store_mall_directory', + 'straighten', + 'streetview', + 'strikethrough_s', + 'style', + 'subdirectory_arrow_left', + 'subdirectory_arrow_right', + 'subject', + 'subscriptions', + 'subtitles', + 'subway', + 'supervised_user_circle', + 'supervisor_account', + 'surround_sound', + 'swap_calls', + 'swap_horiz', + 'swap_horizontal_circle', + 'swap_vert', + 'swap_vertical_circle', + 'switch_camera', + 'switch_video', + 'sync', + 'sync_disabled', + 'sync_problem', + 'system_update', + 'system_update_alt', + 'tab', + 'tab_unselected', + 'table_chart', + 'tablet', + 'tablet_android', + 'tablet_mac', + 'tag_faces', + 'tap_and_play', + 'terrain', + 'text_fields', + 'text_format', + 'text_rotate_up', + 'text_rotate_vertical', + 'text_rotation_angledown', + 'text_rotation_angleup', + 'text_rotation_down', + 'text_rotation_none', + 'textsms', + 'texture', + 'theaters', + 'thumb_down', + 'thumb_up', + 'thumbs_up_down', + 'time_to_leave', + 'timelapse', + 'timeline', + 'timer', + 'timer_10', + 'timer_3', + 'timer_off', + 'title', + 'toc', + 'today', + 'toll', + 'tonality', + 'touch_app', + 'toys', + 'track_changes', + 'traffic', + 'train', + 'tram', + 'transfer_within_a_station', + 'transform', + 'transit_enterexit', + 'translate', + 'trending_down', + 'trending_flat', + 'trending_up', + 'trip_origin', + 'tune', + 'turned_in', + 'turned_in_not', + 'tv', + 'unarchive', + 'undo', + 'unfold_less', + 'unfold_more', + 'update', + 'usb', + 'verified_user', + 'vertical_align_bottom', + 'vertical_align_center', + 'vertical_align_top', + 'vibration', + 'video_call', + 'video_label', + 'video_library', + 'videocam', + 'videocam_off', + 'videogame_asset', + 'view_agenda', + 'view_array', + 'view_carousel', + 'view_column', + 'view_comfy', + 'view_compact', + 'view_day', + 'view_headline', + 'view_list', + 'view_module', + 'view_quilt', + 'view_stream', + 'view_week', + 'vignette', + 'visibility', + 'visibility_off', + 'voice_chat', + 'voicemail', + 'volume_down', + 'volume_mute', + 'volume_off', + 'volume_up', + 'vpn_key', + 'vpn_lock', + 'wallpaper', + 'warning', + 'watch', + 'watch_later', + 'wb_auto', + 'wb_cloudy', + 'wb_incandescent', + 'wb_iridescent', + 'wb_sunny', + 'wc', + 'web', + 'web_asset', + 'weekend', + 'whatshot', + 'widgets', + 'wifi', + 'wifi_lock', + 'wifi_tethering', + 'work', + 'wrap_text', + 'youtube_searched_for', + 'zoom_in', + 'zoom_out', + 'zoom_out_map', + +// END GENERATED', +]; diff --git a/lib/file_manager/common.dart b/lib/file_manager/common.dart new file mode 100644 index 0000000..0deff24 --- /dev/null +++ b/lib/file_manager/common.dart @@ -0,0 +1,77 @@ +class Common { + factory Common() => _getInstance(); + + static Common get instance => _getInstance(); + static Common _instance; // 单例对象 + + static Common _getInstance() { + if (_instance == null) { + _instance = Common._internal(); + } + return _instance; + } + + Common._internal(); + + ///////////////////////////////////////////////////////////// + + String sDCardDir; + + String getFileSize(int fileSize) { + String str = ''; + + if (fileSize < 1024) { + str = '${fileSize.toStringAsFixed(2)}B'; + } else if (1024 <= fileSize && fileSize < 1048576) { + str = '${(fileSize / 1024).toStringAsFixed(2)}KB'; + } else if (1048576 <= fileSize && fileSize < 1073741824) { + str = '${(fileSize / 1024 / 1024).toStringAsFixed(2)}MB'; + } + + return str; + } + + String selectIcon(String ext) { + String iconImg = 'assets/files_icons/unknown.png'; + + switch (ext) { + case '.ppt': + case '.pptx': + iconImg = 'assets/files_icons/ppt.png'; + break; + case '.doc': + case '.docx': + iconImg = 'assets/files_icons/word.png'; + break; + case '.xls': + case '.xlsx': + iconImg = 'assets/files_icons/excel.png'; + break; + case '.jpg': + case '.jpeg': + case '.png': + iconImg = 'assets/files_icons/image.png'; + break; + case '.txt': + iconImg = 'assets/files_icons/txt.png'; + break; + case '.mp3': + iconImg = 'assets/files_icons/mp3.png'; + break; + case '.mp4': + iconImg = 'assets/files_icons/video.png'; + break; + case '.rar': + case '.zip': + iconImg = 'assets/files_icons/zip.png'; + break; + case '.psd': + iconImg = 'assets/files_icons/psd.png'; + break; + default: + iconImg = 'assets/files_icons/file.png'; + break; + } + return iconImg; + } +} diff --git a/lib/file_manager/file_manager_init.dart b/lib/file_manager/file_manager_init.dart new file mode 100644 index 0000000..2ed51d2 --- /dev/null +++ b/lib/file_manager/file_manager_init.dart @@ -0,0 +1,82 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'common.dart'; +//import 'file_manager.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:intl/date_symbol_data_local.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:flutter/services.dart'; +import '../main.dart'; + +checkPermission() { + WidgetsFlutterBinding.ensureInitialized(); + initializeDateFormatting("zh_CN", null).then((value) async{ + bool ret = await getPermission(); + if (ret) { + getSDCardDir().then((value) { + //runApp(MyApp()); + runApp(MaterialApp( + //title: '启动图demo', + debugShowCheckedModeBanner: false, + theme: new ThemeData( + brightness: Brightness.light, + backgroundColor: Colors.white, + platform: TargetPlatform.android), + home: new SplashScreen(), + routes: { + '/home': (BuildContext context) => MyApp() + }, + )); + }); + } else { + Fluttertoast.showToast(msg: '用户未授权,程序无法正常运行!', gravity: ToastGravity.CENTER); + SystemChannels.platform.invokeMethod('SystemNavigator.pop'); + } + }); +} + +// Permission check,适用于 permission_handler: ^5.x.x +Future getPermission() async { + if (Platform.isAndroid) { + // You can request multiple permissions at once. + Map permissionStatuses = await [ + Permission.storage, + Permission.camera, + Permission.microphone, + ].request(); + + if (permissionStatuses[Permission.storage] != PermissionStatus.granted || + permissionStatuses[Permission.camera] != PermissionStatus.granted || + permissionStatuses[Permission.microphone] != PermissionStatus.granted) { + return false; + } + } else if (Platform.isIOS) {} + return true; +} + +// Permission check,适用于 permission_handler: ^3.3.0 +// Future getPermission() async { +// List permissionGroupList = [ +// PermissionGroup.storage, +// PermissionGroup.camera, +// PermissionGroup.microphone, +// ]; +// if (Platform.isAndroid) { +// PermissionStatus permission = await PermissionHandler().checkPermissionStatus(PermissionGroup.storage); +// if (permission != PermissionStatus.granted) { +// await PermissionHandler().requestPermissions(permissionGroupList); +// } +// permission = await PermissionHandler().checkPermissionStatus(PermissionGroup.storage); +// if (permission != PermissionStatus.granted) { +// return false; +// } +// } else if (Platform.isIOS) {} +// return true; +// } + +Future getSDCardDir() async { + Common().sDCardDir = (await getExternalStorageDirectory()).path; +} diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..97b96e3 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,233 @@ +import 'dart:async'; +import 'dart:io' show Platform; +import 'dart:ui'; + +import 'package:badges/badges.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bmfbase/BaiduMap/bmfmap_base.dart' show BMFMapSDK, BMF_COORD_TYPE; +import 'package:flutter_screenutil/screenutil_init.dart'; +import 'package:hyzp_ybqx/pages/Login/LoginTabs2.dart'; +import 'package:hyzp_ybqx/pages/MyMsics/05_updated/MyUpdatedNew.dart'; +import 'package:hyzp_ybqx/pages/Works/TJXX/tj_data.dart'; +import 'package:package_info/package_info.dart'; +// 引入provider +import 'package:provider/provider.dart'; + +import 'components/commonFun.dart'; +import 'file_manager/file_manager_init.dart'; +// import 'provider/Cart.dart'; +// import 'provider/CheckOut.dart'; +import 'pages/Login/LoginTabs.dart'; +import 'provider/player_ratio.dart'; +import 'provider/player_region.dart'; +import 'routers/router.dart'; + +void main() { + //file_manager_load(); + //runApp(MyApp()); + + WidgetsFlutterBinding.ensureInitialized(); //必须要添加这个进行初始化 否则下面会错误 + //Flutter 强制竖屏 + SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, // 纵向,portrait 肖像 + // DeviceOrientation.portraitDown, // 旋转180度 + // DeviceOrientation.landscapeLeft, //顺时针旋转90度 + // DeviceOrientation.landscapeRight, //逆时针旋转90度 + ]).then((_) { + checkPermission(); //请求用户授权 + }); +} + +// class LoadMyApp extends StatefulWidget { +// LoadMyApp({Key key}) : super(key: key); +// +// _LoadMyAppState createState() => _LoadMyAppState(); +// } +// +// class _LoadMyAppState extends State { +// @override +// Widget build(BuildContext context) { +// return new MaterialApp( +// //title: "LoadActivity", +// home: MyApp(), +// ); +// } +// } + +class MyApp extends StatefulWidget { + MyApp({Key key}) : super(key: key); + + _MyAppState createState() => _MyAppState(); +} + +class _MyAppState extends State { + @override + void initState() { + initApp(); + startGetStatisDataNew(); + PackageInfo.fromPlatform().then((PackageInfo packageInfo) async { + String appName = packageInfo.appName; + String packageName = packageInfo.packageName; + String version = packageInfo.version; + String buildNumber = packageInfo.buildNumber; + String buildDate = + '${buildNumber.substring(0, 4)}.${buildNumber.substring(4, 6)}.${buildNumber.substring(6, 8)}'; + + print('appName = $appName'); + print('packageName = $packageName'); + print('version = $version'); + print('buildNumber = $buildNumber'); + print('buildDate = $buildDate'); + // I/flutter (30820): appName = 宜宾黑烟抓拍 + // I/flutter (30820): packageName = com.flutter.hyzp_ybqx + // I/flutter (30820): version = 1.3.1 + // I/flutter (30820): buildNumber = 20210508 + // I/flutter (30820): buildDate = 2021.05.08 + + //Fluttertoast.showToast(msg: '当前版本 v$version。暂无更新', gravity: ToastGravity.CENTER); + // Navigator.of(context).push(MaterialPageRoute( + // builder: (context) => MyUpdated(ver: version, date: buildDate, theContext: context))); + + MyUpdatedNew m = await MyUpdatedNew( + ver: version, date: buildDate, theContext: context, bStartUpdated: true); + }); + super.initState(); + } + + void initApp() async { + // await getFileName2().then((value) { + // readUrlFile2().then((value) => writeUrlFile2()); + // }); + getAndroidId().then((value) { + g_userInfo.thisAndroidId = value; + + // 百度地图sdk初始化鉴权 + if (Platform.isIOS) { + BMFMapSDK.setApiKeyAndCoordType('I022V5cRWKDn8gguTTa1gbxqPMYWU4G0', BMF_COORD_TYPE.BD09LL); + } else if (Platform.isAndroid) { + // Android 目前不支持接口设置Apikey, + // 请在主工程的Manifest文件里设置,详细配置方法请参考官网(https://lbsyun.baidu.com/)demo + BMFMapSDK.setCoordType(BMF_COORD_TYPE.BD09LL); + } + }); + } + + //@override + // Widget build(BuildContext context) { + // return ScreenUtilInit( + // designSize: Size(360, 690), + // allowFontScaling: false, + // builder: () => MaterialApp( + // debugShowCheckedModeBanner: false, + // title: 'Flutter_ScreenUtil', + // theme: ThemeData( + // primarySwatch: Colors.blue, + // ), + // home: HomePage(title: 'FlutterScreenUtil Demo'), + // ), + // ); + // } + + @override + Widget build(BuildContext context) { + //ScreenUtil.instance = ScreenUtil(width: 750, height: 1334)..init(context); + //750:1334 + //默认 width : 1080px , height:1920px , allowFontScaling:false + //double width = MediaQuery.of(context).size.width; + + //sizeMediaQuery = MediaQuery.of(context).size; //这样不对 + print('sizeMediaQuery = $sizeWindowPhysicalSize'); + //size: Size(360.0, 674.7) + + //自动适应安卓手机系统分辨率,解决 S10 手机正方形变形问题 + print('window.physicalSize = ${window.physicalSize}'); + //window.physicalSize = Size(1080.0, 2136.0) + sizeWindowPhysicalSize = window.physicalSize; + //double _heigth = 1080 * 16 / 9; + double _heigth = 1080 * sizeWindowPhysicalSize.height / sizeWindowPhysicalSize.width; + print('_heigth = $_heigth'); + + return ScreenUtilInit( + designSize: Size(1080, _heigth), //安卓手机宽高尺寸,_heigth = 1080 * 16 / 9; + //designSize: Size(1080, 1920), //安卓手机宽高尺寸,_heigth = 1080 * 16 / 9; + //designSize: Size(750, 1334), //统一使用美工设计的宽高尺寸,苹果比例 + //designSize: sizeWindowPhysicalSize, //自动适应安卓手机系统分辨率,解决 S10 手机正方形变形问题 + allowFontScaling: false, + builder: () => MultiProvider( + providers: [ + // ChangeNotifierProvider(builder: (_) => Counter()), + // ChangeNotifierProvider(builder: (_) => Cart()), + // ChangeNotifierProvider(builder: (_) => CheckOut()), +// ChangeNotifierProvider(builder: (_) => PlayerRegionProvide()), +// ChangeNotifierProvider(builder: (_) => PlayerRatioProvide()), + ChangeNotifierProvider(create: (context) => PlayerRegionProvide()), + ChangeNotifierProvider(create: (context) => PlayerRatioProvide()), + ], + child: MaterialApp( + home: LoginTabs2(), + debugShowCheckedModeBanner: false, + initialRoute: '/', + onGenerateRoute: onGenerateRoute, + theme: ThemeData( + // primaryColor: Colors.yellow + primaryColor: Colors.white), + ), + ), + ); + +// return MultiProvider( +// providers: [ +// // ChangeNotifierProvider(builder: (_) => Counter()), +// // ChangeNotifierProvider(builder: (_) => Cart()), +// // ChangeNotifierProvider(builder: (_) => CheckOut()), +// // ChangeNotifierProvider(builder: (_) => PlayerRegionProvide()), +// // ChangeNotifierProvider(builder: (_) => PlayerRatioProvide()), +// ChangeNotifierProvider(create: (context) => PlayerRegionProvide()), +// ChangeNotifierProvider(create: (context) => PlayerRatioProvide()), +// ], +// +// child: MaterialApp( +// home: LoginTabs(), +// debugShowCheckedModeBanner: false, +// initialRoute: '/', +// onGenerateRoute: onGenerateRoute, +// theme: ThemeData( +// // primaryColor: Colors.yellow +// primaryColor: Colors.white), +// ), +// ); + } +} + +class SplashScreen extends StatefulWidget { + @override + _SplashScreenState createState() => new _SplashScreenState(); +} + +class _SplashScreenState extends State { + startTime() async { + //设置启动图生效时间 + var _duration = new Duration(seconds: 2); + return new Timer(_duration, navigationPage); + } + + void navigationPage() { + Navigator.of(context).pushReplacementNamed('/home'); + } + + @override + void initState() { + super.initState(); + startTime(); + } + + @override + Widget build(BuildContext context) { + return new Scaffold( + body: new Center( + child: new Image.asset('assets/images/hyzp_yibin_launche.png'), + ), + ); + } +} diff --git a/lib/model/CateModel.dart b/lib/model/CateModel.dart new file mode 100644 index 0000000..e79b70a --- /dev/null +++ b/lib/model/CateModel.dart @@ -0,0 +1,53 @@ +class CateModel { + List result; + + CateModel({this.result}); + + CateModel.fromJson(Map json) { + if (json['result'] != null) { + result = new List(); + json['result'].forEach((v) { + result.add(new CateItemModel.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + if (this.result != null) { + data['result'] = this.result.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class CateItemModel { + String sId; + String title; + Object status; + String pic; + String pid; + String sort; + + CateItemModel({this.sId, this.title, this.status, this.pic, this.pid, this.sort}); + + CateItemModel.fromJson(Map json) { + sId = json['_id']; + title = json['title']; + status = json['status']; + pic = json['pic']; + pid = json['pid']; + sort = json['sort']; + } + + Map toJson() { + final Map data = new Map(); + data['_id'] = this.sId; + data['title'] = this.title; + data['status'] = this.status; + data['pic'] = this.pic; + data['pid'] = this.pid; + data['sort'] = this.sort; + return data; + } +} \ No newline at end of file diff --git a/lib/model/FocusModel.dart b/lib/model/FocusModel.dart new file mode 100644 index 0000000..0df29d1 --- /dev/null +++ b/lib/model/FocusModel.dart @@ -0,0 +1,48 @@ +// FocusModel.fromJson(json); +class FocusModel { + List result; + FocusModel({this.result}); + FocusModel.fromJson(Map json) { + if (json['result'] != null) { + result = new List(); + json['result'].forEach((v) { + result.add(new FocusItemModel.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + if (this.result != null) { + data['result'] = this.result.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class FocusItemModel { + String sId; + String title; + String status; + String pic; + String url; + + FocusItemModel({this.sId, this.title, this.status, this.pic, this.url}); + FocusItemModel.fromJson(Map json) { + sId = json['_id']; + title = json['title']; + status = json['status']; + pic = json['pic']; + url = json['url']; + } + + Map toJson() { + final Map data = new Map(); + data['_id'] = this.sId; + data['title'] = this.title; + data['status'] = this.status; + data['pic'] = this.pic; + data['url'] = this.url; + return data; + } +} \ No newline at end of file diff --git a/lib/model/OrderModel.dart b/lib/model/OrderModel.dart new file mode 100644 index 0000000..2c4636d --- /dev/null +++ b/lib/model/OrderModel.dart @@ -0,0 +1,133 @@ +class OrderModel { + bool success; + String message; + List result; + + OrderModel({this.success, this.message, this.result}); + + OrderModel.fromJson(Map json) { + success = json['success']; + message = json['message']; + if (json['result'] != null) { + result = new List(); + json['result'].forEach((v) { + result.add(new Result.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + data['success'] = this.success; + data['message'] = this.message; + if (this.result != null) { + data['result'] = this.result.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class Result { + String sId; + String uid; + String name; + String phone; + String address; + String allPrice; + int payStatus; + int orderStatus; + List orderItem; + + Result( + {this.sId, + this.uid, + this.name, + this.phone, + this.address, + this.allPrice, + this.payStatus, + this.orderStatus, + this.orderItem}); + + Result.fromJson(Map json) { + sId = json['_id']; + uid = json['uid']; + name = json['name']; + phone = json['phone']; + address = json['address']; + allPrice = json['all_price']; + payStatus = json['pay_status']; + orderStatus = json['order_status']; + if (json['order_item'] != null) { + orderItem = new List(); + json['order_item'].forEach((v) { + orderItem.add(new OrderItem.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + data['_id'] = this.sId; + data['uid'] = this.uid; + data['name'] = this.name; + data['phone'] = this.phone; + data['address'] = this.address; + data['all_price'] = this.allPrice; + data['pay_status'] = this.payStatus; + data['order_status'] = this.orderStatus; + if (this.orderItem != null) { + data['order_item'] = this.orderItem.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class OrderItem { + String sId; + String orderId; + String productTitle; + String productId; + int productPrice; + String productImg; + int productCount; + String selectedAttr; + int addTime; + + OrderItem( + {this.sId, + this.orderId, + this.productTitle, + this.productId, + this.productPrice, + this.productImg, + this.productCount, + this.selectedAttr, + this.addTime}); + + OrderItem.fromJson(Map json) { + sId = json['_id']; + orderId = json['order_id']; + productTitle = json['product_title']; + productId = json['product_id']; + productPrice = json['product_price']; + productImg = json['product_img']; + productCount = json['product_count']; + selectedAttr = json['selected_attr']; + addTime = json['add_time']; + } + + Map toJson() { + final Map data = new Map(); + data['_id'] = this.sId; + data['order_id'] = this.orderId; + data['product_title'] = this.productTitle; + data['product_id'] = this.productId; + data['product_price'] = this.productPrice; + data['product_img'] = this.productImg; + data['product_count'] = this.productCount; + data['selected_attr'] = this.selectedAttr; + data['add_time'] = this.addTime; + return data; + } +} \ No newline at end of file diff --git a/lib/model/ProductContentModel.dart b/lib/model/ProductContentModel.dart new file mode 100644 index 0000000..8f1aaad --- /dev/null +++ b/lib/model/ProductContentModel.dart @@ -0,0 +1,133 @@ +class ProductContentModel { + ProductContentitem result; + + ProductContentModel({this.result}); + + ProductContentModel.fromJson(Map json) { + result = + json['result'] != null ? new ProductContentitem.fromJson(json['result']) : null; + } + Map toJson() { + final Map data = new Map(); + if (this.result != null) { + data['result'] = this.result.toJson(); + } + return data; + } +} + +class ProductContentitem { + String sId; + String title; + String cid; + Object price; //注意 + String oldPrice; + Object isBest; + Object isHot; + Object isNew; + String status; + String pic; + String content; + String cname; + List attr; + String subTitle; + Object salecount; + //新增 + int count; + String selectedAttr; + + + + ProductContentitem( + {this.sId, + this.title, + this.cid, + this.price, + this.oldPrice, + this.isBest, + this.isHot, + this.isNew, + this.status, + this.pic, + this.content, + this.cname, + this.attr, + this.subTitle, + this.salecount, + this.count, + this.selectedAttr + }); + + ProductContentitem.fromJson(Map json) { + sId = json['_id']; + title = json['title']; + cid = json['cid']; + price = json['price']; + oldPrice = json['old_price']; + isBest = json['is_best']; + isHot = json['is_hot']; + isNew = json['is_new']; + status = json['status']; + pic = json['pic']; + content = json['content']; + cname = json['cname']; + if (json['attr'] != null) { + attr = new List(); + json['attr'].forEach((v) { + attr.add(new Attr.fromJson(v)); + }); + } + subTitle = json['sub_title']; + salecount = json['salecount']; + + //新增 + count=1; + selectedAttr=''; + + + } + + Map toJson() { + final Map data = new Map(); + data['_id'] = this.sId; + data['title'] = this.title; + data['cid'] = this.cid; + data['price'] = this.price; + data['old_price'] = this.oldPrice; + data['is_best'] = this.isBest; + data['is_hot'] = this.isHot; + data['is_new'] = this.isNew; + data['status'] = this.status; + data['pic'] = this.pic; + data['content'] = this.content; + data['cname'] = this.cname; + if (this.attr != null) { + data['attr'] = this.attr.map((v) => v.toJson()).toList(); + } + data['sub_title'] = this.subTitle; + data['salecount'] = this.salecount; + return data; + } +} + +class Attr { + String cate; + List list; + List attrList; + + + Attr({this.cate, this.list}); + + Attr.fromJson(Map json) { + cate = json['cate']; + list = json['list'].cast(); + attrList=[]; + } + + Map toJson() { + final Map data = new Map(); + data['cate'] = this.cate; + data['list'] = this.list; + return data; + } +} \ No newline at end of file diff --git a/lib/model/ProductModel.dart b/lib/model/ProductModel.dart new file mode 100644 index 0000000..00cab85 --- /dev/null +++ b/lib/model/ProductModel.dart @@ -0,0 +1,63 @@ +class ProductModel { + List result; + + ProductModel({this.result}); + + ProductModel.fromJson(Map json) { + if (json['result'] != null) { + result = new List(); + json['result'].forEach((v) { + result.add(new ProductItemModel.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + if (this.result != null) { + data['result'] = this.result.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class ProductItemModel { + String sId; + String title; + String cid; + Object price; //所有的类型都继承 Object + String oldPrice; + String pic; + String sPic; + + ProductItemModel( + {this.sId, + this.title, + this.cid, + this.price, + this.oldPrice, + this.pic, + this.sPic}); + + ProductItemModel.fromJson(Map json) { + sId = json['_id']; + title = json['title']; + cid = json['cid']; + price = json['price']; + oldPrice = json['old_price']; + pic = json['pic']; + sPic = json['s_pic']; + } + + Map toJson() { + final Map data = new Map(); + data['_id'] = this.sId; + data['title'] = this.title; + data['cid'] = this.cid; + data['price'] = this.price; + data['old_price'] = this.oldPrice; + data['pic'] = this.pic; + data['s_pic'] = this.sPic; + return data; + } +} \ No newline at end of file diff --git a/lib/my_extended_image/common/image_picker/_image_picker_io.dart b/lib/my_extended_image/common/image_picker/_image_picker_io.dart new file mode 100644 index 0000000..9459b3e --- /dev/null +++ b/lib/my_extended_image/common/image_picker/_image_picker_io.dart @@ -0,0 +1,93 @@ +// 此文件为插件 extended_image-0.9.0 的 Example 中的代码,路径为: +// r:\Flutter\FlutterProject9\extended_image\example\lib\common\image_picker\_image_picker_io.dart +import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; +//import 'package:image_picker/image_picker.dart' as picker; +import 'package:flutter/cupertino.dart'; +//import 'package:flutter_candies_demo_library/flutter_candies_demo_library.dart'; +import 'package:wechat_assets_picker/wechat_assets_picker.dart'; + +Future pickImage(BuildContext context) async { + List assets = []; + final List result = await AssetPicker.pickAssets( + context, + maxAssets: 1, + pathThumbSize: 84, + gridCount: 3, + pageSize: 300, + selectedAssets: assets, + requestType: RequestType.image, + textDelegate: DefaultAssetsPickerTextDelegate(), + ); + if (result != null) { + assets = List.from(result); + return assets.first.originBytes; + } + return null; + // final File file = + + // await picker.ImagePicker.pickImage(source: picker.ImageSource.gallery); + // return file.readAsBytes(); +} + +class ImageSaver { + static Future save(String name, Uint8List fileData) async { + final AssetEntity imageEntity = + await PhotoManager.editor.saveImage(fileData); + final File file = await imageEntity.file; + return file.path; + } +} + +class PickerTextDelegate implements AssetsPickerTextDelegate { + factory PickerTextDelegate() => _instance; + + PickerTextDelegate._internal(); + + static final PickerTextDelegate _instance = PickerTextDelegate._internal(); + + @override + String confirm = 'OK'; + + @override + String cancel = 'Cancel'; + + @override + String edit = 'Edit'; + + @override + String emptyPlaceHolder = 'empty'; + + @override + String gifIndicator = 'GIF'; + + @override + String heicNotSupported = 'not support HEIC yet'; + + @override + String loadFailed = 'load failed'; + + @override + String original = 'Original'; + + @override + String preview = 'Preview'; + + @override + String select = 'Select'; + + @override + String unSupportedAssetType = 'not support yet'; + + @override + String durationIndicatorBuilder(Duration duration) { + const String separator = ':'; + final String minute = duration.inMinutes.toString().padLeft(2, '0'); + final String second = + ((duration - Duration(minutes: duration.inMinutes)).inSeconds) + .toString() + .padLeft(2, '0'); + return '$minute$separator$second'; + } +} diff --git a/lib/my_flutterPtControl/.gitignore b/lib/my_flutterPtControl/.gitignore new file mode 100644 index 0000000..bb431f0 --- /dev/null +++ b/lib/my_flutterPtControl/.gitignore @@ -0,0 +1,75 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +build/ + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/lib/my_flutterPtControl/.metadata b/lib/my_flutterPtControl/.metadata new file mode 100644 index 0000000..aebb3c4 --- /dev/null +++ b/lib/my_flutterPtControl/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: cf37c2cd07a1d3ba296efff2dc75e19ba65e1665 + channel: stable + +project_type: package diff --git a/lib/my_flutterPtControl/CHANGELOG.md b/lib/my_flutterPtControl/CHANGELOG.md new file mode 100644 index 0000000..5846fe4 --- /dev/null +++ b/lib/my_flutterPtControl/CHANGELOG.md @@ -0,0 +1,5 @@ +# 1.1.5 +增加效果图 + + + diff --git a/lib/my_flutterPtControl/LICENSE b/lib/my_flutterPtControl/LICENSE new file mode 100644 index 0000000..ba75c69 --- /dev/null +++ b/lib/my_flutterPtControl/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/lib/my_flutterPtControl/README.en.md b/lib/my_flutterPtControl/README.en.md new file mode 100644 index 0000000..d251d44 --- /dev/null +++ b/lib/my_flutterPtControl/README.en.md @@ -0,0 +1,36 @@ +# flutter_ptcontrol + +#### Description +视频云台控制,方向按钮 + +#### Software Architecture +Software architecture description + +#### Installation + +1. xxxx +2. xxxx +3. xxxx + +#### Instructions + +1. xxxx +2. xxxx +3. xxxx + +#### Contribution + +1. Fork the repository +2. Create Feat_xxx branch +3. Commit your code +4. Create Pull Request + + +#### Gitee Feature + +1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md +2. Gitee blog [blog.gitee.com](https://blog.gitee.com) +3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) +4. The most valuable open source project [GVP](https://gitee.com/gvp) +5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) +6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/lib/my_flutterPtControl/README.md b/lib/my_flutterPtControl/README.md new file mode 100644 index 0000000..b00902c --- /dev/null +++ b/lib/my_flutterPtControl/README.md @@ -0,0 +1,15 @@ +# flutterptcontrol + +一个flutter版的视频监控中使用的云台控制widget + + +效果图 + +This project is a starting point for a Dart +[package](https://flutter.dev/developing-packages/), +a library module containing code that can be shared easily across +multiple Flutter or Dart projects. + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/lib/my_flutterPtControl/doc/2021-05-05_231020.png b/lib/my_flutterPtControl/doc/2021-05-05_231020.png new file mode 100644 index 0000000..e50a602 Binary files /dev/null and b/lib/my_flutterPtControl/doc/2021-05-05_231020.png differ diff --git a/lib/my_flutterPtControl/example/.gitignore b/lib/my_flutterPtControl/example/.gitignore new file mode 100644 index 0000000..ae1f183 --- /dev/null +++ b/lib/my_flutterPtControl/example/.gitignore @@ -0,0 +1,37 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Exceptions to above rules. +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/lib/my_flutterPtControl/example/.metadata b/lib/my_flutterPtControl/example/.metadata new file mode 100644 index 0000000..8398e24 --- /dev/null +++ b/lib/my_flutterPtControl/example/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: cf37c2cd07a1d3ba296efff2dc75e19ba65e1665 + channel: stable + +project_type: app diff --git a/lib/my_flutterPtControl/example/README.md b/lib/my_flutterPtControl/example/README.md new file mode 100644 index 0000000..68dc153 --- /dev/null +++ b/lib/my_flutterPtControl/example/README.md @@ -0,0 +1,16 @@ +# flutterptcontrol_example + +Demonstrates how to use the iscflutterplugin plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/lib/my_flutterPtControl/example/android/.gitignore b/lib/my_flutterPtControl/example/android/.gitignore new file mode 100644 index 0000000..bc2100d --- /dev/null +++ b/lib/my_flutterPtControl/example/android/.gitignore @@ -0,0 +1,7 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java diff --git a/lib/my_flutterPtControl/example/android/app/build.gradle b/lib/my_flutterPtControl/example/android/app/build.gradle new file mode 100644 index 0000000..e613005 --- /dev/null +++ b/lib/my_flutterPtControl/example/android/app/build.gradle @@ -0,0 +1,61 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 28 + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.king.ptcontrol_example" + minSdkVersion 16 + targetSdkVersion 28 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test:runner:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' +} diff --git a/lib/my_flutterPtControl/example/android/app/src/debug/AndroidManifest.xml b/lib/my_flutterPtControl/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..cd1d463 --- /dev/null +++ b/lib/my_flutterPtControl/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/lib/my_flutterPtControl/example/android/app/src/main/AndroidManifest.xml b/lib/my_flutterPtControl/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..97bf315 --- /dev/null +++ b/lib/my_flutterPtControl/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + diff --git a/lib/my_flutterPtControl/example/android/app/src/main/java/com/king/ptzscontrol_example/MainActivity.java b/lib/my_flutterPtControl/example/android/app/src/main/java/com/king/ptzscontrol_example/MainActivity.java new file mode 100644 index 0000000..58d44aa --- /dev/null +++ b/lib/my_flutterPtControl/example/android/app/src/main/java/com/king/ptzscontrol_example/MainActivity.java @@ -0,0 +1,13 @@ +package com.king.ptcontrol_example; + +import android.os.Bundle; +import io.flutter.app.FlutterActivity; +import io.flutter.plugins.GeneratedPluginRegistrant; + +public class MainActivity extends FlutterActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GeneratedPluginRegistrant.registerWith(this); + } +} diff --git a/lib/my_flutterPtControl/example/android/app/src/main/res/drawable/launch_background.xml b/lib/my_flutterPtControl/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/lib/my_flutterPtControl/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/lib/my_flutterPtControl/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/lib/my_flutterPtControl/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/lib/my_flutterPtControl/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/lib/my_flutterPtControl/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/lib/my_flutterPtControl/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/lib/my_flutterPtControl/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/lib/my_flutterPtControl/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/lib/my_flutterPtControl/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/lib/my_flutterPtControl/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/lib/my_flutterPtControl/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/lib/my_flutterPtControl/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/lib/my_flutterPtControl/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/lib/my_flutterPtControl/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/lib/my_flutterPtControl/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/lib/my_flutterPtControl/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/lib/my_flutterPtControl/example/android/app/src/main/res/values/styles.xml b/lib/my_flutterPtControl/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..00fa441 --- /dev/null +++ b/lib/my_flutterPtControl/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + diff --git a/lib/my_flutterPtControl/example/android/app/src/profile/AndroidManifest.xml b/lib/my_flutterPtControl/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..cd1d463 --- /dev/null +++ b/lib/my_flutterPtControl/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/lib/my_flutterPtControl/example/android/build.gradle b/lib/my_flutterPtControl/example/android/build.gradle new file mode 100644 index 0000000..7f17f23 --- /dev/null +++ b/lib/my_flutterPtControl/example/android/build.gradle @@ -0,0 +1,36 @@ +buildscript { + repositories { + google() + jcenter() +// maven { url 'https://maven.aliyun.com/repository/google' } +// maven { url 'https://maven.aliyun.com/repository/jcenter' } +// maven { url 'http://maven.aliyun.com/nexus/content/groups/public' } + + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0' + } +} + +allprojects { + repositories { + google() + jcenter() +// maven { url 'https://maven.aliyun.com/repository/google' } +// maven { url 'https://maven.aliyun.com/repository/jcenter' } +// maven { url 'http://maven.aliyun.com/nexus/content/groups/public' } + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/lib/my_flutterPtControl/example/android/gradle.properties b/lib/my_flutterPtControl/example/android/gradle.properties new file mode 100644 index 0000000..38c8d45 --- /dev/null +++ b/lib/my_flutterPtControl/example/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/lib/my_flutterPtControl/example/android/gradle/wrapper/gradle-wrapper.properties b/lib/my_flutterPtControl/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..296b146 --- /dev/null +++ b/lib/my_flutterPtControl/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/lib/my_flutterPtControl/example/android/settings.gradle b/lib/my_flutterPtControl/example/android/settings.gradle new file mode 100644 index 0000000..5a2f14f --- /dev/null +++ b/lib/my_flutterPtControl/example/android/settings.gradle @@ -0,0 +1,15 @@ +include ':app' + +def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() + +def plugins = new Properties() +def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') +if (pluginsFile.exists()) { + pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } +} + +plugins.each { name, path -> + def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() + include ":$name" + project(":$name").projectDir = pluginDirectory +} diff --git a/lib/my_flutterPtControl/example/ios/.gitignore b/lib/my_flutterPtControl/example/ios/.gitignore new file mode 100644 index 0000000..e96ef60 --- /dev/null +++ b/lib/my_flutterPtControl/example/ios/.gitignore @@ -0,0 +1,32 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/lib/my_flutterPtControl/example/ios/Flutter/AppFrameworkInfo.plist b/lib/my_flutterPtControl/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..6b4c0f7 --- /dev/null +++ b/lib/my_flutterPtControl/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 8.0 + + diff --git a/lib/my_flutterPtControl/example/ios/Flutter/Debug.xcconfig b/lib/my_flutterPtControl/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..e8efba1 --- /dev/null +++ b/lib/my_flutterPtControl/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/lib/my_flutterPtControl/example/ios/Flutter/Release.xcconfig b/lib/my_flutterPtControl/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..399e934 --- /dev/null +++ b/lib/my_flutterPtControl/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/lib/my_flutterPtControl/example/ios/Podfile b/lib/my_flutterPtControl/example/ios/Podfile new file mode 100644 index 0000000..98a90b8 --- /dev/null +++ b/lib/my_flutterPtControl/example/ios/Podfile @@ -0,0 +1,87 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def parse_KV_file(file, separator='=') + file_abs_path = File.expand_path(file) + if !File.exists? file_abs_path + return []; + end + generated_key_values = {} + skip_line_start_symbols = ["#", "/"] + File.foreach(file_abs_path) do |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + generated_key_values[podname] = podpath + else + puts "Invalid plugin specification: #{line}" + end + end + generated_key_values +end + +target 'Runner' do + # Flutter Pod + + copied_flutter_dir = File.join(__dir__, 'Flutter') + copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') + copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') + unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) + # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. + # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. + # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. + + generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') + unless File.exist?(generated_xcode_build_settings_path) + raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) + cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; + + unless File.exist?(copied_framework_path) + FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) + end + unless File.exist?(copied_podspec_path) + FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) + end + end + + # Keep pod path relative so it can be checked into Podfile.lock. + pod 'Flutter', :path => 'Flutter' + + # Plugin Pods + + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock + # referring to absolute paths on developers' machines. + system('rm -rf .symlinks') + system('mkdir -p .symlinks/plugins') + plugin_pods = parse_KV_file('../.flutter-plugins') + plugin_pods.each do |name, path| + symlink = File.join('.symlinks', 'plugins', name) + File.symlink(path, symlink) + pod name, :path => File.join(symlink, 'ios') + end +end + +# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. +install! 'cocoapods', :disable_input_output_paths => true + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + end + end +end diff --git a/lib/my_flutterPtControl/example/ios/Podfile.lock b/lib/my_flutterPtControl/example/ios/Podfile.lock new file mode 100644 index 0000000..eecae9f --- /dev/null +++ b/lib/my_flutterPtControl/example/ios/Podfile.lock @@ -0,0 +1,22 @@ +PODS: + - Flutter (1.0.0) + - iscflutterplugin (0.0.1): + - Flutter + +DEPENDENCIES: + - Flutter (from `Flutter`) + - iscflutterplugin (from `.symlinks/plugins/iscflutterplugin/ios`) + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + iscflutterplugin: + :path: ".symlinks/plugins/iscflutterplugin/ios" + +SPEC CHECKSUMS: + Flutter: 0e3d915762c693b495b44d77113d4970485de6ec + iscflutterplugin: 4b821515f4f5c3391bd2bee65dc568d476c8dff4 + +PODFILE CHECKSUM: 3dbe063e9c90a5d7c9e4e76e70a821b9e2c1d271 + +COCOAPODS: 1.8.4 diff --git a/lib/my_flutterPtControl/example/ios/Runner.xcodeproj/project.pbxproj b/lib/my_flutterPtControl/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..cff09b0 --- /dev/null +++ b/lib/my_flutterPtControl/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,579 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; + 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + 97FAA71ED6FAB34DB8E13CD2 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F6DB52853A0D7DDB2FC6045 /* libPods-Runner.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 32E3F146EC25FFA8FB828ED3 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; + 6F477566E2112BBA771ACE7B /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 75EE6B503DCA7730497CCE8B /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9F6DB52853A0D7DDB2FC6045 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, + 97FAA71ED6FAB34DB8E13CD2 /* libPods-Runner.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 81E05D4962D2A179DAC58AEC /* Frameworks */ = { + isa = PBXGroup; + children = ( + 9F6DB52853A0D7DDB2FC6045 /* libPods-Runner.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B80C3931E831B6300D905FE /* App.framework */, + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEBA1CF902C7004384FC /* Flutter.framework */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + B605CE0B350A0D9ADA50C6BD /* Pods */, + 81E05D4962D2A179DAC58AEC /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + ); + path = Runner; + sourceTree = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 97C146F21CF9000F007C117D /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + B605CE0B350A0D9ADA50C6BD /* Pods */ = { + isa = PBXGroup; + children = ( + 6F477566E2112BBA771ACE7B /* Pods-Runner.debug.xcconfig */, + 32E3F146EC25FFA8FB828ED3 /* Pods-Runner.release.xcconfig */, + 75EE6B503DCA7730497CCE8B /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9A77DD0339FAD06F2EB23198 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 91131F60B9723BCC2912AD1A /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = "The Chromium Authors"; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + DevelopmentTeam = A6P3VDZQ6H; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + }; + 91131F60B9723BCC2912AD1A /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + 9A77DD0339FAD06F2EB23198 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, + 97C146F31CF9000F007C117D /* main.m in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = A6P3VDZQ6H; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.king.iscflutterpluginExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = A6P3VDZQ6H; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.king.iscflutterpluginExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = A6P3VDZQ6H; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.king.iscflutterpluginExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/lib/my_flutterPtControl/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/lib/my_flutterPtControl/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/lib/my_flutterPtControl/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/lib/my_flutterPtControl/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/lib/my_flutterPtControl/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..a28140c --- /dev/null +++ b/lib/my_flutterPtControl/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/my_flutterPtControl/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/lib/my_flutterPtControl/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/lib/my_flutterPtControl/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/lib/my_flutterPtControl/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/lib/my_flutterPtControl/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/lib/my_flutterPtControl/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/lib/my_flutterPtControl/example/ios/Runner/AppDelegate.h b/lib/my_flutterPtControl/example/ios/Runner/AppDelegate.h new file mode 100644 index 0000000..36e21bb --- /dev/null +++ b/lib/my_flutterPtControl/example/ios/Runner/AppDelegate.h @@ -0,0 +1,6 @@ +#import +#import + +@interface AppDelegate : FlutterAppDelegate + +@end diff --git a/lib/my_flutterPtControl/example/ios/Runner/AppDelegate.m b/lib/my_flutterPtControl/example/ios/Runner/AppDelegate.m new file mode 100644 index 0000000..70e8393 --- /dev/null +++ b/lib/my_flutterPtControl/example/ios/Runner/AppDelegate.m @@ -0,0 +1,13 @@ +#import "AppDelegate.h" +#import "GeneratedPluginRegistrant.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [GeneratedPluginRegistrant registerWithRegistry:self]; + // Override point for customization after application launch. + return [super application:application didFinishLaunchingWithOptions:launchOptions]; +} + +@end diff --git a/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..28c6bf0 Binary files /dev/null and b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..f091b6b Binary files /dev/null and b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cde121 Binary files /dev/null and b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..d0ef06e Binary files /dev/null and b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..dcdc230 Binary files /dev/null and b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..c8f9ed8 Binary files /dev/null and b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..75b2d16 Binary files /dev/null and b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..c4df70d Binary files /dev/null and b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..6a84f41 Binary files /dev/null and b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..d0e1f58 Binary files /dev/null and b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/lib/my_flutterPtControl/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +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. \ No newline at end of file diff --git a/lib/my_flutterPtControl/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/lib/my_flutterPtControl/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/lib/my_flutterPtControl/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/my_flutterPtControl/example/ios/Runner/Base.lproj/Main.storyboard b/lib/my_flutterPtControl/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/lib/my_flutterPtControl/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/my_flutterPtControl/example/ios/Runner/Info.plist b/lib/my_flutterPtControl/example/ios/Runner/Info.plist new file mode 100644 index 0000000..dbbc48d --- /dev/null +++ b/lib/my_flutterPtControl/example/ios/Runner/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + iscflutterplugin_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + io.flutter.embedded_views_preview + + + diff --git a/lib/my_flutterPtControl/example/ios/Runner/main.m b/lib/my_flutterPtControl/example/ios/Runner/main.m new file mode 100644 index 0000000..dff6597 --- /dev/null +++ b/lib/my_flutterPtControl/example/ios/Runner/main.m @@ -0,0 +1,9 @@ +#import +#import +#import "AppDelegate.h" + +int main(int argc, char* argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/lib/my_flutterPtControl/example/lib/main.dart b/lib/my_flutterPtControl/example/lib/main.dart new file mode 100644 index 0000000..2822c78 --- /dev/null +++ b/lib/my_flutterPtControl/example/lib/main.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:flutterptcontrol/flutterptcontrol.dart'; + +void main() => runApp(MyApp()); + +class MyApp extends StatefulWidget { + @override + _MyAppState createState() => _MyAppState(); +} + +class _MyAppState extends State { + final double _outerRadius = 99; + final double _innerRadius = 99 / 2; + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: Text('云台方向按钮widget'), + ), + body: Center( + child: SizedBox( + width: 2 * _outerRadius, + height: 2 * _outerRadius, + child: PtControlWidget( + innerRadius: _innerRadius, + outerRadius: _outerRadius, + callback: (status) { + print('status = $status'); + }), + ), + ), + ), + ); + } +} diff --git a/lib/my_flutterPtControl/example/pubspec.lock b/lib/my_flutterPtControl/example/pubspec.lock new file mode 100644 index 0000000..5345bba --- /dev/null +++ b/lib/my_flutterPtControl/example/pubspec.lock @@ -0,0 +1,160 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.5.0-nullsafety.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.0-nullsafety.1" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0-nullsafety.3" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.0-nullsafety.1" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0-nullsafety.1" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.15.0-nullsafety.3" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.3" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.0-nullsafety.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutterptcontrol: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "1.1.5" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.12.10-nullsafety.1" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.0-nullsafety.3" + path: + dependency: transitive + description: + name: path + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.8.0-nullsafety.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.8.0-nullsafety.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.10.0-nullsafety.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.0-nullsafety.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0-nullsafety.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.0-nullsafety.1" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.19-nullsafety.2" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.0-nullsafety.3" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.0-nullsafety.3" +sdks: + dart: ">=2.10.0-110 <2.11.0" diff --git a/lib/my_flutterPtControl/example/pubspec.yaml b/lib/my_flutterPtControl/example/pubspec.yaml new file mode 100644 index 0000000..16f33ae --- /dev/null +++ b/lib/my_flutterPtControl/example/pubspec.yaml @@ -0,0 +1,21 @@ +name: flutterptcontrol_demo +description: Demonstrates how to use the flutterptcontrol plugin. +publish_to: 'none' + +environment: + sdk: ">=2.1.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + cupertino_icons: ^0.1.2 +# flutterptcontrol: ^1.1.1 + flutterptcontrol: + path: ../ + +dev_dependencies: + flutter_test: + sdk: flutter + +flutter: + uses-material-design: true diff --git a/lib/my_flutterPtControl/images/pt_background_disable.png b/lib/my_flutterPtControl/images/pt_background_disable.png new file mode 100644 index 0000000..dff2c42 Binary files /dev/null and b/lib/my_flutterPtControl/images/pt_background_disable.png differ diff --git a/lib/my_flutterPtControl/images/pt_background_enable.png b/lib/my_flutterPtControl/images/pt_background_enable.png new file mode 100644 index 0000000..d288060 Binary files /dev/null and b/lib/my_flutterPtControl/images/pt_background_enable.png differ diff --git a/lib/my_flutterPtControl/images/pt_bottom.png b/lib/my_flutterPtControl/images/pt_bottom.png new file mode 100644 index 0000000..90dfc0c Binary files /dev/null and b/lib/my_flutterPtControl/images/pt_bottom.png differ diff --git a/lib/my_flutterPtControl/images/pt_center.png b/lib/my_flutterPtControl/images/pt_center.png new file mode 100644 index 0000000..5241041 Binary files /dev/null and b/lib/my_flutterPtControl/images/pt_center.png differ diff --git a/lib/my_flutterPtControl/images/pt_left.png b/lib/my_flutterPtControl/images/pt_left.png new file mode 100644 index 0000000..7e86cd8 Binary files /dev/null and b/lib/my_flutterPtControl/images/pt_left.png differ diff --git a/lib/my_flutterPtControl/images/pt_left_bottom.png b/lib/my_flutterPtControl/images/pt_left_bottom.png new file mode 100644 index 0000000..5bf9c36 Binary files /dev/null and b/lib/my_flutterPtControl/images/pt_left_bottom.png differ diff --git a/lib/my_flutterPtControl/images/pt_left_top.png b/lib/my_flutterPtControl/images/pt_left_top.png new file mode 100644 index 0000000..bd8f90c Binary files /dev/null and b/lib/my_flutterPtControl/images/pt_left_top.png differ diff --git a/lib/my_flutterPtControl/images/pt_right.png b/lib/my_flutterPtControl/images/pt_right.png new file mode 100644 index 0000000..90fa61d Binary files /dev/null and b/lib/my_flutterPtControl/images/pt_right.png differ diff --git a/lib/my_flutterPtControl/images/pt_right_bottom.png b/lib/my_flutterPtControl/images/pt_right_bottom.png new file mode 100644 index 0000000..a94f97d Binary files /dev/null and b/lib/my_flutterPtControl/images/pt_right_bottom.png differ diff --git a/lib/my_flutterPtControl/images/pt_right_top.png b/lib/my_flutterPtControl/images/pt_right_top.png new file mode 100644 index 0000000..8aa82f5 Binary files /dev/null and b/lib/my_flutterPtControl/images/pt_right_top.png differ diff --git a/lib/my_flutterPtControl/images/pt_top.png b/lib/my_flutterPtControl/images/pt_top.png new file mode 100644 index 0000000..d4bc2ec Binary files /dev/null and b/lib/my_flutterPtControl/images/pt_top.png differ diff --git a/lib/my_flutterPtControl/lib/flutterptcontrol.dart b/lib/my_flutterPtControl/lib/flutterptcontrol.dart new file mode 100644 index 0000000..21ec330 --- /dev/null +++ b/lib/my_flutterPtControl/lib/flutterptcontrol.dart @@ -0,0 +1,237 @@ +library flutterptcontrol; + +import 'dart:math' as MATH; + +import 'package:flutter/material.dart'; + +typedef void PtCallback(int status); + +//pan-tilt Control +class PtControlWidget extends StatefulWidget { + PtControlWidget( + {@required this.innerRadius, @required this.outerRadius, @required this.callback}); + + ///内径 + final double innerRadius; + + ///外径 + final double outerRadius; + + ///点击回调 + final PtCallback callback; + + @override + State createState() { + return _PtControlWidgetState(); + } +} + +class _PtControlWidgetState extends State { + int status = -1; + + /* + 3.获取控件的坐标: + RenderBox renderBox = anchorKey.currentContext.findRenderObject(); + var offset = renderBox.localToGlobal(Offset.zero); + 控件的横坐标:offset.dx + 控件的纵坐标:offset.dy + + 如果想获得控件正下方的坐标: + RenderBox renderBox = anchorKey.currentContext.findRenderObject(); + var offset = renderBox.localToGlobal(Offset(0.0, renderBox.size.height)); +    控件下方的横坐标:offset.dx +    控件下方的纵坐标:offset.dy + ———————————————— + 版权声明:本文为CSDN博主「笨鸟不飞 ≧0≦」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 + 原文链接:https://blog.csdn.net/baidu_34120295/article/details/86495861 + */ + + //获得本组件中心点对象:Point(offset.dx, offset.dy) + //Another exception was thrown: type 'Point' is not a subtype of type 'Point' + MATH.Point getCenterPoint() { + RenderBox renderBox = context.findRenderObject(); + //控件中心坐标:(offset.dx, offset.dy) + var offset = + renderBox.localToGlobal(Offset(renderBox.size.width / 2, renderBox.size.height / 2)); + print('(offset.dx, offset.dy) = (${offset.dx}, ${offset.dy})'); + return MATH.Point(offset.dx.toInt(), offset.dy.toInt()); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onPanDown: (details) { + //坐标系转换 + var x = details.localPosition.dx - widget.outerRadius; + var y = -(details.localPosition.dy - widget.outerRadius); + + //判断点击位置是否超出范围 + var dis = MATH.Point(x, y).distanceTo(MATH.Point(0, 0)); + + /* + 计算圆周上坐标点的公式,其中圆心坐标(xo,yo),r为圆半径,α为0~2π,(x,y)为圆上的点 + x = xo + r * cos(α) + y = yo + r * sin(α) + + 原点坐标为(0, 0),x轴向左为正,y轴向上为正,逆时针方向的8个点的弧度为 + 0 π * 1/8 + 1 π * 3/8 + 2 π * 5/8 + 3 π * 7/8 + 4 π * 9/8 + 5 π * 11/8 + 6 π * 13/8 + 7 π * 15/8 + */ + + // dart 计算原点坐标为(0, 0),x轴向左为正,y轴向上为正,逆时针方向的8个点的坐标 + List listInnerPoint = List.generate(8, (index) { + double _radians = MATH.pi * (index * 2 + 1) / 8; + //print('index = $index'); + return { + 'x': MATH.cos(_radians) * widget.innerRadius, + 'y': MATH.sin(_radians) * widget.innerRadius, + }; + }); + + // dart 计算原点坐标为(0, 0),x轴向左为正,y轴向上为正,逆时针方向的8个点的坐标 + List listOuterPoint = List.generate(8, (index) { + double _radians = MATH.pi * (index * 2 + 1) / 8; + //print('index = $index'); + return { + 'x': MATH.cos(_radians) * widget.outerRadius, + 'y': MATH.sin(_radians) * widget.outerRadius, + }; + }); + + print('listInnerPoint = ${listInnerPoint}'); + print('[x, y] = [$x, $y]'); + //超出范围 + if (dis > widget.outerRadius) { + status = -1; + } else if (dis < widget.innerRadius) { + //还原 + status = 8; + } else if (x > listOuterPoint[1]['x'] || + (y > 0 && + x > listInnerPoint[1]['x'] && + y < + listInnerPoint[1]['y'] + + (x - listInnerPoint[1]['x']) / MATH.tan(MATH.pi / 8)) || + (y < 0 && + x > listInnerPoint[6]['x'] && + y > + listInnerPoint[6]['y'] - + (x - listInnerPoint[6]['x']) / MATH.tan(MATH.pi / 8))) { + if (y > listOuterPoint[0]['y'] || + y > listInnerPoint[0]['y'] && + x < + listInnerPoint[0]['x'] + + (y - listInnerPoint[0]['y']) / MATH.tan(MATH.pi / 8)) { + //右上 + status = 1; + } else if (y > listInnerPoint[7]['y'] || + y > listOuterPoint[7]['y'] && + x > + listInnerPoint[7]['x'] + + (listInnerPoint[7]['y'] - y) / MATH.tan(MATH.pi / 8)) { + //右 + status = 0; + } else { + //右下 + status = 7; + } + } else if (x > listInnerPoint[2]['x'] || + y > 0 && + x > listOuterPoint[2]['x'] && + y > listInnerPoint[2]['y'] + (listInnerPoint[2]['x'] - x) / MATH.tan(MATH.pi / 8) || + y < 0 && + x < listInnerPoint[5]['x'] && + y < listInnerPoint[5]['y'] - (listInnerPoint[5]['x'] - x) / MATH.tan(MATH.pi / 8)) { + if (y > 0) { + //上 + status = 2; + } else { + //下 + status = 6; + } + } else { + if (y > listOuterPoint[3]['y'] || + y > listInnerPoint[3]['y'] && + x > + listOuterPoint[3]['x'] - + (y - listOuterPoint[3]['y']) / MATH.tan(MATH.pi / 8)) { + //左上 + status = 3; + } else if (y > listInnerPoint[4]['y'] || + y > listOuterPoint[4]['y'] && + x < + listOuterPoint[4]['x'] + + (listInnerPoint[4]['y'] - y) / MATH.tan(MATH.pi / 8)) { + //左 + status = 4; + } else { + //左下 + status = 5; + } + } + widget.callback(status); + setState(() {}); + }, + onPanEnd: (details) { + status = -1; + setState(() {}); + }, + + ///点击回调 + /// status == -1 超出范围 + /// status == 0 右 + /// status == 1 右上 + /// status == 2 上 + /// status == 3 左上 + /// status == 4 左 + /// status == 5 左下 + /// status == 6 下 + /// status == 7 右下 + /// status == 8 还原 + + /// color: Colors.black12 + child: Stack( + children: [ + Image.asset( + null == widget.callback + ? 'images/pt_background_disable.png' + : 'images/pt_background_enable.png', + package: 'flutterptcontrol'), + status == 0 + ? Image.asset('images/pt_right.png', package: 'flutterptcontrol') + : SizedBox.shrink(), + status == 1 + ? Image.asset('images/pt_right_top.png', package: 'flutterptcontrol') + : SizedBox.shrink(), + status == 2 + ? Image.asset('images/pt_top.png', package: 'flutterptcontrol') + : SizedBox.shrink(), + status == 3 + ? Image.asset('images/pt_left_top.png', package: 'flutterptcontrol') + : SizedBox.shrink(), + status == 4 + ? Image.asset('images/pt_left.png', package: 'flutterptcontrol') + : SizedBox.shrink(), + status == 5 + ? Image.asset('images/pt_left_bottom.png', package: 'flutterptcontrol') + : SizedBox.shrink(), + status == 6 + ? Image.asset('images/pt_bottom.png', package: 'flutterptcontrol') + : SizedBox.shrink(), + status == 7 + ? Image.asset('images/pt_right_bottom.png', package: 'flutterptcontrol') + : SizedBox.shrink(), + // status == 8 + // ? Image.asset('images/pt_center.png', package: 'flutterptcontrol') + // : SizedBox.shrink(), + ], + ), + ); + } +} diff --git a/lib/my_flutterPtControl/pubspec.lock b/lib/my_flutterPtControl/pubspec.lock new file mode 100644 index 0000000..5acf180 --- /dev/null +++ b/lib/my_flutterPtControl/pubspec.lock @@ -0,0 +1,146 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.5.0-nullsafety.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.0-nullsafety.1" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0-nullsafety.3" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.0-nullsafety.1" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0-nullsafety.1" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.15.0-nullsafety.3" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.0-nullsafety.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.12.10-nullsafety.1" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.0-nullsafety.3" + path: + dependency: transitive + description: + name: path + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.8.0-nullsafety.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.8.0-nullsafety.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.10.0-nullsafety.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.0-nullsafety.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0-nullsafety.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.0-nullsafety.1" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.19-nullsafety.2" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.0-nullsafety.3" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.0-nullsafety.3" +sdks: + dart: ">=2.10.0-110 <2.11.0" diff --git a/lib/my_flutterPtControl/pubspec.yaml b/lib/my_flutterPtControl/pubspec.yaml new file mode 100644 index 0000000..ebfe1bb --- /dev/null +++ b/lib/my_flutterPtControl/pubspec.yaml @@ -0,0 +1,54 @@ +name: flutterptcontrol +description: PTZ control widget used in a flutter version of video surveillance +version: 1.1.5 +homepage: https://gitee.com/xiaobug/flutter_ptcontrol + +environment: + sdk: ">=2.1.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + assets: + - images/ +# +# For details regarding assets in packages, see +# https://flutter.dev/assets-and-images/#from-packages +# +# An image asset can refer to one or more resolution-specific "variants", see +# https://flutter.dev/assets-and-images/#resolution-aware. + +# To add custom fonts to your package, add a fonts section here, +# in this "flutter" section. Each entry in this list should have a +# "family" key with the font family name, and a "fonts" key with a +# list giving the asset and other descriptors for the font. For +# example: +# fonts: +# - family: Schyler +# fonts: +# - asset: fonts/Schyler-Regular.ttf +# - asset: fonts/Schyler-Italic.ttf +# style: italic +# - family: Trajan Pro +# fonts: +# - asset: fonts/TrajanPro.ttf +# - asset: fonts/TrajanPro_Bold.ttf +# weight: 700 +# +# For details regarding fonts in packages, see +# https://flutter.dev/custom-fonts/#from-packages diff --git a/lib/my_flutter_superplayer/.gitignore b/lib/my_flutter_superplayer/.gitignore new file mode 100644 index 0000000..02c4c38 --- /dev/null +++ b/lib/my_flutter_superplayer/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +build/ +.idea/ \ No newline at end of file diff --git a/lib/my_flutter_superplayer/.metadata b/lib/my_flutter_superplayer/.metadata new file mode 100644 index 0000000..a48e556 --- /dev/null +++ b/lib/my_flutter_superplayer/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 8af6b2f038c1172e61d418869363a28dffec3cb4 + channel: unknown + +project_type: plugin diff --git a/lib/my_flutter_superplayer/CHANGELOG.md b/lib/my_flutter_superplayer/CHANGELOG.md new file mode 100644 index 0000000..23b1079 --- /dev/null +++ b/lib/my_flutter_superplayer/CHANGELOG.md @@ -0,0 +1,7 @@ +## 0.0.2 + +* Add `setLoop` support + +## 0.0.1 + +* Initial release. diff --git a/lib/my_flutter_superplayer/LICENSE b/lib/my_flutter_superplayer/LICENSE new file mode 100644 index 0000000..a6c09c4 --- /dev/null +++ b/lib/my_flutter_superplayer/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 LiJianying + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/my_flutter_superplayer/README.md b/lib/my_flutter_superplayer/README.md new file mode 100644 index 0000000..d07c773 --- /dev/null +++ b/lib/my_flutter_superplayer/README.md @@ -0,0 +1,90 @@ +# flutter_superplayer + +适用于 Flutter 的腾讯云超级播放器插件 + +[![pub version][pub-image]][pub-url] + +[pub-image]: https://img.shields.io/pub/v/flutter_superplayer.svg +[pub-url]: https://pub.dev/packages/flutter_superplayer + + + + +- [快速开始](#%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B) + - [安装](#%E5%AE%89%E8%A3%85) + - [用法](#%E7%94%A8%E6%B3%95) +- [相关链接](#%E7%9B%B8%E5%85%B3%E9%93%BE%E6%8E%A5) +- [许可证](#%E8%AE%B8%E5%8F%AF%E8%AF%81) + + + +## 快速开始 + +### 安装 + +将此添加到包的 pubspec.yaml 文件中: + +```yaml +dependencies: + flutter_superplayer: ^0.0.1 +``` + +或 + +```yaml +dependencies: + flutter_superplayer: + git: + url: https://github.com/leanflutter/flutter_superplayer.git +``` + +您可以从命令行安装软件包: + +```bash +$ flutter packages get +``` + +### 用法 + +```dart +import 'package:flutter_superplayer/flutter_superplayer.dart'; + +SuperPlayerController _playerController = SuperPlayerController(); +String _controlViewType = kControlViewTypeDefault; + +SuperPlayerView( + controller: _playerController, + controlViewType: _controlViewType, +) +``` + +## 相关链接 + +- https://github.com/tencentyun/SuperPlayer_Android +- https://github.com/tencentyun/SuperPlayer_iOS + +## 许可证 + +``` +MIT License + +Copyright (c) 2021 LiJianying + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` diff --git a/lib/my_flutter_superplayer/android/.gitignore b/lib/my_flutter_superplayer/android/.gitignore new file mode 100644 index 0000000..c6cbe56 --- /dev/null +++ b/lib/my_flutter_superplayer/android/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures diff --git a/lib/my_flutter_superplayer/android/build.gradle b/lib/my_flutter_superplayer/android/build.gradle new file mode 100644 index 0000000..e4ce4c8 --- /dev/null +++ b/lib/my_flutter_superplayer/android/build.gradle @@ -0,0 +1,44 @@ +group 'org.leanflutter.plugins.flutter_superplayer' +version '1.0' + +buildscript { + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0' + } +} + +rootProject.allprojects { + repositories { + google() + jcenter() + } +} + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 28 + + defaultConfig { + minSdkVersion 19 + } + lintOptions { + disable 'InvalidPackage' + } + android { + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } + } + + dependencies { + implementation 'com.tencent.liteav:LiteAVSDK_Professional:latest.release' + implementation 'com.github.ctiao:DanmakuFlameMaster:0.5.4' + } +} diff --git a/lib/my_flutter_superplayer/android/gradle.properties b/lib/my_flutter_superplayer/android/gradle.properties new file mode 100644 index 0000000..38c8d45 --- /dev/null +++ b/lib/my_flutter_superplayer/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/lib/my_flutter_superplayer/android/gradle/wrapper/gradle-wrapper.properties b/lib/my_flutter_superplayer/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..01a286e --- /dev/null +++ b/lib/my_flutter_superplayer/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/lib/my_flutter_superplayer/android/settings.gradle b/lib/my_flutter_superplayer/android/settings.gradle new file mode 100644 index 0000000..49e880e --- /dev/null +++ b/lib/my_flutter_superplayer/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'flutter_superplayer' diff --git a/lib/my_flutter_superplayer/android/src/main/AndroidManifest.xml b/lib/my_flutter_superplayer/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a4c2768 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/SuperPlayerCode.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/SuperPlayerCode.java new file mode 100644 index 0000000..9b17af5 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/SuperPlayerCode.java @@ -0,0 +1,11 @@ +package com.tencent.liteav.demo.superplayer; + +public class SuperPlayerCode { + public static final int OK = 0; + public static final int NET_ERROR = 10001; + public static final int PLAY_URL_EMPTY = 20001; + public static final int LIVE_PLAY_END = 30001; + public static final int LIVE_SHIFT_FAIL = 30002; + public static final int VOD_PLAY_FAIL = 40001; + public static final int VOD_REQUEST_FILE_ID_FAIL = 40002; +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/SuperPlayerDef.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/SuperPlayerDef.java new file mode 100644 index 0000000..dec4ac7 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/SuperPlayerDef.java @@ -0,0 +1,37 @@ +package com.tencent.liteav.demo.superplayer; + +public class SuperPlayerDef { + + public enum PlayerMode { + WINDOW, // 窗口模式 + FULLSCREEN, // 全屏模式 + FLOAT // 悬浮窗模式 + } + + public enum PlayerState { + PLAYING(1), // 播放中 + PAUSE(2), // 暂停中 + LOADING(3), // 缓冲中 + END(4); // 结束播放 + + final int value; + PlayerState(int value) { + this.value = value; + } + + public int intValue() { + return value; + } + } + + public enum PlayerType { + VOD, // 点播 + LIVE, // 直播 + LIVE_SHIFT // 直播会看 + } + + public enum Orientation { + LANDSCAPE, // 横屏 + PORTRAIT // 竖屏 + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/SuperPlayerGlobalConfig.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/SuperPlayerGlobalConfig.java new file mode 100644 index 0000000..4970568 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/SuperPlayerGlobalConfig.java @@ -0,0 +1,67 @@ +package com.tencent.liteav.demo.superplayer; + +import com.tencent.rtmp.TXLiveConstants; + +/** + * Created by yuejiaoli on 2018/7/4. + * + * 超级播放器全局配置类 + */ + +public class SuperPlayerGlobalConfig { + + private static class Singleton { + private static SuperPlayerGlobalConfig sInstance = new SuperPlayerGlobalConfig(); + } + + public static SuperPlayerGlobalConfig getInstance() { + return Singleton.sInstance; + } + + /** + * 默认播放填充模式 ( 默认播放模式为 自适应模式 ) + */ + public int renderMode = TXLiveConstants.RENDER_MODE_ADJUST_RESOLUTION; + + /** + * 播放器最大缓存个数 ( 默认缓存 5 ) + */ + public int maxCacheItem = 5; + + /** + * 是否启用悬浮窗 ( 默认开启 true ) + */ + public boolean enableFloatWindow = true; + + /** + * 是否开启硬件加速 ( 默认开启硬件加速 ) + */ + public boolean enableHWAcceleration = true; + + /** + * 时移域名 (修改为自己app的时移域名) + */ + public String playShiftDomain = "liteavapp.timeshift.qcloud.com"; + + /** + * 悬浮窗位置 ( 默认在左上角,初始化一个宽为 810,高为 540的悬浮窗口 ) + */ + public TXRect floatViewRect = new TXRect(0, 0, 810, 540); + + public final static class TXRect { + public int x; + public int y; + public int width; + public int height; + + TXRect(int x, int y, int width, int height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + public TXRect() { + } + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/SuperPlayerModel.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/SuperPlayerModel.java new file mode 100644 index 0000000..632e197 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/SuperPlayerModel.java @@ -0,0 +1,73 @@ +package com.tencent.liteav.demo.superplayer; + + +import com.tencent.liteav.demo.superplayer.model.entity.SuperPlayerVideoIdV2; + +import java.util.List; + + +/** + * 超级播放器支持三种方式播放视频: + * 1. 视频 URL + * 填写视频 URL, 如需使用直播时移功能,还需填写appId + * 2. 腾讯云点播 File ID 播放 + * 填写 appId 及 videoId (如果使用旧版本V2, 请填写videoIdV2) + * 3. 多码率视频播放 + * 是URL播放方式扩展,可同时传入多条URL,用于进行码率切换 + */ +public class SuperPlayerModel { + + public int appId; // AppId 用于腾讯云点播 File ID 播放及腾讯云直播时移功能 + + /** + * ------------------------------------------------------------------ + * 直接使用URL播放 + *

+ * 支持 RTMP、FLV、MP4、HLS 封装格式 + * 使用腾讯云直播时移功能则需要填写appId + * ------------------------------------------------------------------ + */ + public String url = ""; // 视频URL + + /** + * ------------------------------------------------------------------ + * 多码率视频 URL + *

+ * 用于拥有多个播放地址的多清晰度视频播放 + * ------------------------------------------------------------------ + */ + public List multiURLs; + + public int playDefaultIndex; // 指定多码率情况下,默认播放的连接Index + + + /** + * ------------------------------------------------------------------ + * 腾讯云点播 File ID 播放参数 + * ------------------------------------------------------------------ + */ + public SuperPlayerVideoId videoId; + + /* + * 用于兼容旧版本(V2)腾讯云点播 File ID 播放参数(即将废弃,不推荐使用) + */ + @Deprecated + public SuperPlayerVideoIdV2 videoIdV2; + + public String title = ""; // 视频文件名 (用于显示在UI层);使用file id播放,若未指定title,则使用FileId返回的Title;使用url播放需要指定title,否则title显示为空 + + public static class SuperPlayerURL { + public SuperPlayerURL(String url, String qualityName) { + this.qualityName = qualityName; + this.url = url; + } + + public SuperPlayerURL() { + } + + public String qualityName = "原画"; // 清晰度名称(用于显示在UI层) + + public String url = ""; // 该清晰度对应的地址 + + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/SuperPlayerVideoId.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/SuperPlayerVideoId.java new file mode 100644 index 0000000..623255b --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/SuperPlayerVideoId.java @@ -0,0 +1,19 @@ +package com.tencent.liteav.demo.superplayer; + +/** + * Created by hans on 2019/3/25. + * 使用腾讯云fileId播放 + */ +public class SuperPlayerVideoId { + + public String fileId; // 腾讯云视频fileId + public String pSign; // v4 开启防盗链必填 + + @Override + public String toString() { + return "SuperPlayerVideoId{" + + ", fileId='" + fileId + '\'' + + ", pSign='" + pSign + '\'' + + '}'; + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/SuperPlayerView.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/SuperPlayerView.java new file mode 100644 index 0000000..4725375 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/SuperPlayerView.java @@ -0,0 +1,913 @@ +package com.tencent.liteav.demo.superplayer; + +import android.app.Activity; +import android.app.AppOpsManager; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.graphics.Bitmap; +import android.graphics.PixelFormat; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Binder; +import android.os.Build; +import android.provider.MediaStore; +import android.provider.Settings; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.PopupWindow; +import android.widget.RelativeLayout; +import android.widget.Toast; + +import com.tencent.liteav.basic.log.TXCLog; +import com.tencent.liteav.demo.superplayer.model.SuperPlayer; +import com.tencent.liteav.demo.superplayer.model.SuperPlayerImpl; +import com.tencent.liteav.demo.superplayer.model.SuperPlayerObserver; +import com.tencent.liteav.demo.superplayer.model.entity.PlayImageSpriteInfo; +import com.tencent.liteav.demo.superplayer.model.entity.PlayKeyFrameDescInfo; +import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality; +import com.tencent.liteav.demo.superplayer.model.net.LogReport; +import com.tencent.liteav.demo.superplayer.model.utils.NetWatcher; +import com.tencent.liteav.demo.superplayer.ui.player.FloatPlayer; +import com.tencent.liteav.demo.superplayer.ui.player.FullScreenPlayer; +import com.tencent.liteav.demo.superplayer.ui.player.Player; +import com.tencent.liteav.demo.superplayer.ui.player.WindowPlayer; +import com.tencent.liteav.demo.superplayer.ui.view.DanmuView; +import com.tencent.rtmp.TXLivePlayer; +import com.tencent.rtmp.ui.TXCloudVideoView; + +import org.leanflutter.plugins.flutter_superplayer.R; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.List; + +/** + * 超级播放器view + *

+ * 具备播放器基本功能,此外还包括横竖屏切换、悬浮窗播放、画质切换、硬件加速、倍速播放、镜像播放、手势控制等功能,同时支持直播与点播 + * 使用方式极为简单,只需要在布局文件中引入并获取到该控件,通过{@link #playWithModel(SuperPlayerModel)}传入{@link SuperPlayerModel}即可实现视频播放 + *

+ * 1、播放视频{@link #playWithModel(SuperPlayerModel)} + * 2、设置回调{@link #setPlayerViewCallback(OnSuperPlayerViewCallback)} + * 3、controller回调实现{@link #mControllerCallback} + * 4、退出播放释放内存{@link #resetPlayer()} + */ +public class SuperPlayerView extends RelativeLayout { + private static final String TAG = "SuperPlayerView"; + + private final int OP_SYSTEM_ALERT_WINDOW = 24; // 支持TYPE_TOAST悬浮窗的最高API版本 + + private Context mContext; + + private ViewGroup mRootView; // SuperPlayerView的根view + private TXCloudVideoView mTXCloudVideoView; // 腾讯云视频播放view + private FullScreenPlayer mFullScreenPlayer; // 全屏模式控制view + private WindowPlayer mWindowPlayer; // 窗口模式控制view + private FloatPlayer mFloatPlayer; // 悬浮窗模式控制view + private DanmuView mDanmuView; // 弹幕 + + private ViewGroup.LayoutParams mLayoutParamWindowMode; // 窗口播放时SuperPlayerView的布局参数 + private ViewGroup.LayoutParams mLayoutParamFullScreenMode; // 全屏播放时SuperPlayerView的布局参数 + private LayoutParams mVodControllerWindowParams; // 窗口controller的布局参数 + private LayoutParams mVodControllerFullScreenParams; // 全屏controller的布局参数 + private WindowManager mWindowManager; // 悬浮窗窗口管理器 + private WindowManager.LayoutParams mWindowParams; // 悬浮窗布局参数 + + private OnSuperPlayerViewCallback mPlayerViewCallback; // SuperPlayerView回调 + private NetWatcher mWatcher; // 网络质量监视器 + private SuperPlayer mSuperPlayer; + + public SuperPlayerView(Context context) { + super(context); + initialize(context); + } + + public SuperPlayerView(Context context, AttributeSet attrs) { + super(context, attrs); + initialize(context); + } + + public SuperPlayerView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initialize(context); + } + + private void initialize(Context context) { + mContext = context; + initView(); + initPlayer(); + } + + /** + * 初始化view + */ + private void initView() { + mRootView = (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.superplayer_vod_view, null); + mTXCloudVideoView = (TXCloudVideoView) mRootView.findViewById(R.id.superplayer_cloud_video_view); + mFullScreenPlayer = (FullScreenPlayer) mRootView.findViewById(R.id.superplayer_controller_large); + mWindowPlayer = (WindowPlayer) mRootView.findViewById(R.id.superplayer_controller_small); + mFloatPlayer = (FloatPlayer) mRootView.findViewById(R.id.superplayer_controller_float); + mDanmuView = (DanmuView) mRootView.findViewById(R.id.superplayer_danmuku_view); + + mVodControllerWindowParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + mVodControllerFullScreenParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + + mFullScreenPlayer.setCallback(mControllerCallback); + mWindowPlayer.setCallback(mControllerCallback); + mFloatPlayer.setCallback(mControllerCallback); + + removeAllViews(); + mRootView.removeView(mDanmuView); + mRootView.removeView(mTXCloudVideoView); + mRootView.removeView(mWindowPlayer); + mRootView.removeView(mFullScreenPlayer); + mRootView.removeView(mFloatPlayer); + + addView(mTXCloudVideoView); + addView(mDanmuView); + } + + private void initPlayer() { + mSuperPlayer = new SuperPlayerImpl(mContext, mTXCloudVideoView); + mSuperPlayer.setObserver(mSuperPlayerObserver); + + if (mSuperPlayer.getPlayerMode() == SuperPlayerDef.PlayerMode.FULLSCREEN) { + addView(mFullScreenPlayer); + mFullScreenPlayer.hide(); + } else if (mSuperPlayer.getPlayerMode() == SuperPlayerDef.PlayerMode.WINDOW) { + addView(mWindowPlayer); + mWindowPlayer.hide(); + } + + post(new Runnable() { + @Override + public void run() { + if (mSuperPlayer.getPlayerMode() == SuperPlayerDef.PlayerMode.WINDOW) { + mLayoutParamWindowMode = getLayoutParams(); + } + try { + // 依据上层Parent的LayoutParam类型来实例化一个新的fullscreen模式下的LayoutParam + Class parentLayoutParamClazz = getLayoutParams().getClass(); + Constructor constructor = parentLayoutParamClazz.getDeclaredConstructor(int.class, int.class); + mLayoutParamFullScreenMode = (ViewGroup.LayoutParams) constructor.newInstance(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + LogReport.getInstance().setAppName(mContext); + LogReport.getInstance().setPackageName(mContext); + + if (mWatcher == null) { + mWatcher = new NetWatcher(mContext); + } + } + + /** + * 播放视频 + * + * @param model + */ + public void playWithModel(final SuperPlayerModel model) { + if (model.videoId != null) { + mSuperPlayer.play(model.appId, model.videoId.fileId, model.videoId.pSign); + } else if (model.videoIdV2 != null) { + } else if (model.multiURLs != null && !model.multiURLs.isEmpty()) { + mSuperPlayer.play(model.appId, model.multiURLs, model.playDefaultIndex); + } else { + mSuperPlayer.play(model.url); + } + } + + /** + * 开始播放 + * + * @param url 视频地址 + */ + public void play(String url) { + mSuperPlayer.play(url); + } + + /** + * 开始播放 + * + * @param appId 腾讯云视频appId + * @param url 直播播放地址 + */ + public void play(int appId, String url) { + mSuperPlayer.play(appId, url); + } + + /** + * 开始播放 + * + * @param appId 腾讯云视频appId + * @param fileId 腾讯云视频fileId + * @param psign 防盗链签名,开启防盗链的视频必填,非防盗链视频可不填 + */ + public void play(int appId, String fileId, String psign) { + mSuperPlayer.play(appId, fileId, psign); + } + + /** + * 多分辨率播放 + * + * @param appId 腾讯云视频appId + * @param superPlayerURLS 不同分辨率数据 + * @param defaultIndex 默认播放Index + */ + public void play(int appId, List superPlayerURLS, int defaultIndex) { + mSuperPlayer.play(appId, superPlayerURLS, defaultIndex); + } + + /** + * 更新标题 + * + * @param title 视频名称 + */ + private void updateTitle(String title) { + mWindowPlayer.updateTitle(title); + mFullScreenPlayer.updateTitle(title); + } + + /** + * resume生命周期回调 + */ + public void onResume() { + if (mDanmuView != null && mDanmuView.isPrepared() && mDanmuView.isPaused()) { + mDanmuView.resume(); + } + mSuperPlayer.resume(); + } + + /** + * pause生命周期回调 + */ + public void onPause() { + if (mDanmuView != null && mDanmuView.isPrepared()) { + mDanmuView.pause(); + } + mSuperPlayer.pauseVod(); + } + + /** + * 重置播放器 + */ + public void resetPlayer() { + if (mDanmuView != null) { + mDanmuView.release(); + mDanmuView = null; + } + stopPlay(); + } + + /** + * 停止播放 + */ + private void stopPlay() { + mSuperPlayer.stop(); + if (mWatcher != null) { + mWatcher.stop(); + } + } + + /** + * 设置超级播放器的回掉 + * + * @param callback + */ + public void setPlayerViewCallback(OnSuperPlayerViewCallback callback) { + mPlayerViewCallback = callback; + } + + /** + * 控制是否全屏显示 + */ + private void fullScreen(boolean isFull) { + if (getContext() instanceof Activity) { + Activity activity = (Activity) getContext(); + if (isFull) { + //隐藏虚拟按键,并且全屏 + View decorView = activity.getWindow().getDecorView(); + if (decorView == null) return; + if (Build.VERSION.SDK_INT > 11 && Build.VERSION.SDK_INT < 19) { // lower api + decorView.setSystemUiVisibility(View.GONE); + } else if (Build.VERSION.SDK_INT >= 19) { + int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_FULLSCREEN; + decorView.setSystemUiVisibility(uiOptions); + } + } else { + View decorView = activity.getWindow().getDecorView(); + if (decorView == null) return; + if (Build.VERSION.SDK_INT > 11 && Build.VERSION.SDK_INT < 19) { // lower api + decorView.setSystemUiVisibility(View.VISIBLE); + } else if (Build.VERSION.SDK_INT >= 19) { + decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + } + } + } + } + + /** + * 初始化controller回调 + */ + private Player.Callback mControllerCallback = new Player.Callback() { + @Override + public void onSwitchPlayMode(SuperPlayerDef.PlayerMode playerMode) { + if (playerMode == SuperPlayerDef.PlayerMode.FULLSCREEN) { + fullScreen(true); + } else { + fullScreen(false); + } + mFullScreenPlayer.hide(); + mWindowPlayer.hide(); + mFloatPlayer.hide(); + //请求全屏模式 + if (playerMode == SuperPlayerDef.PlayerMode.FULLSCREEN) { + if (mLayoutParamFullScreenMode == null) { + return; + } + removeView(mWindowPlayer); + addView(mFullScreenPlayer, mVodControllerFullScreenParams); + setLayoutParams(mLayoutParamFullScreenMode); + rotateScreenOrientation(SuperPlayerDef.Orientation.LANDSCAPE); + if (mPlayerViewCallback != null) { + mPlayerViewCallback.onStartFullScreenPlay(); + } + } else if (playerMode == SuperPlayerDef.PlayerMode.WINDOW) {// 请求窗口模式 + // 当前是悬浮窗 + if (mSuperPlayer.getPlayerMode() == SuperPlayerDef.PlayerMode.FLOAT) { + try { + Context viewContext = getContext(); + Intent intent = null; + if (viewContext instanceof Activity) { + intent = new Intent(viewContext, viewContext.getClass()); + } else { + showToast(R.string.superplayer_float_play_fail); + return; + } + mContext.startActivity(intent); + mSuperPlayer.pause(); + if (mLayoutParamWindowMode == null) { + return; + } + mWindowManager.removeView(mFloatPlayer); + mSuperPlayer.setPlayerView(mTXCloudVideoView); + mSuperPlayer.resume(); + } catch (Exception e) { + e.printStackTrace(); + } + } else if (mSuperPlayer.getPlayerMode() == SuperPlayerDef.PlayerMode.FULLSCREEN) { // 当前是全屏模式 + if (mLayoutParamWindowMode == null) { + return; + } + removeView(mFullScreenPlayer); + addView(mWindowPlayer, mVodControllerWindowParams); + setLayoutParams(mLayoutParamWindowMode); + rotateScreenOrientation(SuperPlayerDef.Orientation.PORTRAIT); + if (mPlayerViewCallback != null) { + mPlayerViewCallback.onStopFullScreenPlay(); + } + } + } else if (playerMode == SuperPlayerDef.PlayerMode.FLOAT) {//请求悬浮窗模式 + TXCLog.i(TAG, "requestPlayMode Float :" + Build.MANUFACTURER); + SuperPlayerGlobalConfig prefs = SuperPlayerGlobalConfig.getInstance(); + if (!prefs.enableFloatWindow) { + return; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // 6.0动态申请悬浮窗权限 + if (!Settings.canDrawOverlays(mContext)) { + Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); + intent.setData(Uri.parse("package:" + mContext.getPackageName())); + mContext.startActivity(intent); + return; + } + } else { + if (!checkOp(mContext, OP_SYSTEM_ALERT_WINDOW)) { + showToast(R.string.superplayer_enter_setting_fail); + return; + } + } + mSuperPlayer.pause(); + + mWindowManager = (WindowManager) mContext.getApplicationContext().getSystemService(Context.WINDOW_SERVICE); + mWindowParams = new WindowManager.LayoutParams(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + mWindowParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + } else { + mWindowParams.type = WindowManager.LayoutParams.TYPE_PHONE; + } + mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + mWindowParams.format = PixelFormat.TRANSLUCENT; + mWindowParams.gravity = Gravity.LEFT | Gravity.TOP; + + SuperPlayerGlobalConfig.TXRect rect = prefs.floatViewRect; + mWindowParams.x = rect.x; + mWindowParams.y = rect.y; + mWindowParams.width = rect.width; + mWindowParams.height = rect.height; + try { + mWindowManager.addView(mFloatPlayer, mWindowParams); + } catch (Exception e) { + showToast(R.string.superplayer_float_play_fail); + return; + } + + TXCloudVideoView videoView = mFloatPlayer.getFloatVideoView(); + if (videoView != null) { + mSuperPlayer.setPlayerView(videoView); + mSuperPlayer.resume(); + } + // 悬浮窗上报 + LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_FLOATMOE, 0, 0); + } + mSuperPlayer.switchPlayMode(playerMode); + } + + @Override + public void onBackPressed(SuperPlayerDef.PlayerMode playMode) { + switch (playMode) { + case FULLSCREEN:// 当前是全屏模式,返回切换成窗口模式 + onSwitchPlayMode(SuperPlayerDef.PlayerMode.WINDOW); + break; + case WINDOW:// 当前是窗口模式,返回退出播放器 + if (mPlayerViewCallback != null) { + mPlayerViewCallback.onClickSmallReturnBtn(); + } + break; + case FLOAT:// 当前是悬浮窗,退出 + mWindowManager.removeView(mFloatPlayer); + if (mPlayerViewCallback != null) { + mPlayerViewCallback.onClickFloatCloseBtn(); + } + break; + default: + break; + } + } + + @Override + public void onFloatPositionChange(int x, int y) { + mWindowParams.x = x; + mWindowParams.y = y; + mWindowManager.updateViewLayout(mFloatPlayer, mWindowParams); + } + + @Override + public void onPause() { + mSuperPlayer.pause(); + if (mSuperPlayer.getPlayerType() != SuperPlayerDef.PlayerType.VOD) { + if (mWatcher != null) { + mWatcher.stop(); + } + } + } + + @Override + public void onResume() { + if (mSuperPlayer.getPlayerState() == SuperPlayerDef.PlayerState.END) { //重播 + mSuperPlayer.reStart(); + } else if (mSuperPlayer.getPlayerState() == SuperPlayerDef.PlayerState.PAUSE) { //继续播放 + mSuperPlayer.resume(); + } + } + + @Override + public void onSeekTo(int position) { + mSuperPlayer.seek(position); + } + + @Override + public void onResumeLive() { + mSuperPlayer.resumeLive(); + } + + @Override + public void onDanmuToggle(boolean isOpen) { + if (mDanmuView != null) { + mDanmuView.toggle(isOpen); + } + } + + @Override + public void onSnapshot() { + mSuperPlayer.snapshot(new TXLivePlayer.ITXSnapshotListener() { + @Override + public void onSnapshot(Bitmap bitmap) { + if (bitmap != null) { + showSnapshotWindow(bitmap); + } else { + showToast(R.string.superplayer_screenshot_fail); + } + } + }); + } + + @Override + public void onQualityChange(VideoQuality quality) { + mFullScreenPlayer.updateVideoQuality(quality); + mSuperPlayer.switchStream(quality); + } + + @Override + public void onSpeedChange(float speedLevel) { + mSuperPlayer.setRate(speedLevel); + } + + @Override + public void onMirrorToggle(boolean isMirror) { + mSuperPlayer.setMirror(isMirror); + } + + @Override + public void onHWAccelerationToggle(boolean isAccelerate) { + mSuperPlayer.enableHardwareDecode(isAccelerate); + } + }; + + /** + * 显示截图窗口 + * + * @param bmp + */ + private void showSnapshotWindow(final Bitmap bmp) { + final PopupWindow popupWindow = new PopupWindow(mContext); + popupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT); + popupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT); + View view = LayoutInflater.from(mContext).inflate(R.layout.superplayer_layout_new_vod_snap, null); + ImageView imageView = (ImageView) view.findViewById(R.id.superplayer_iv_snap); + imageView.setImageBitmap(bmp); + popupWindow.setContentView(view); + popupWindow.setOutsideTouchable(true); + popupWindow.showAtLocation(mRootView, Gravity.TOP, 1800, 300); + AsyncTask.execute(new Runnable() { + @Override + public void run() { + save2MediaStore(mContext, bmp); + } + }); + postDelayed(new Runnable() { + @Override + public void run() { + popupWindow.dismiss(); + } + }, 3000); + } + + /** + * 旋转屏幕方向 + * + * @param orientation + */ + private void rotateScreenOrientation(SuperPlayerDef.Orientation orientation) { + switch (orientation) { + case LANDSCAPE: + ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + break; + case PORTRAIT: + ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + break; + } + } + + /** + * 检查悬浮窗权限 + *

+ * API <18,默认有悬浮窗权限,不需要处理。无法接收无法接收触摸和按键事件,不需要权限和无法接受触摸事件的源码分析 + * API >= 19 ,可以接收触摸和按键事件 + * API >=23,需要在manifest中申请权限,并在每次需要用到权限的时候检查是否已有该权限,因为用户随时可以取消掉。 + * API >25,TYPE_TOAST 已经被谷歌制裁了,会出现自动消失的情况 + */ + private boolean checkOp(Context context, int op) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + try { + Method method = AppOpsManager.class.getDeclaredMethod("checkOp", int.class, int.class, String.class); + return AppOpsManager.MODE_ALLOWED == (int) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName()); + } catch (Exception e) { + TXCLog.e(TAG, Log.getStackTraceString(e)); + } + } + return true; + } + + public void setControlViewType(String controlViewType) { + if (controlViewType.equals("without")) { + removeView(this.mFullScreenPlayer); + removeView(this.mWindowPlayer); + } else { + mControllerCallback.onSwitchPlayMode(mSuperPlayer.getPlayerMode()); + } + } + + public void uiHideDanmu() { + mFullScreenPlayer.hideDanmu(); + } + + public void uiHideReplay() { + mWindowPlayer.hideReplay(); + mFullScreenPlayer.hideReplay(); + } + + + public SuperPlayer getSuperPlayer() { + return mSuperPlayer; + } + + public Player.Callback getControllerCallback() { + return mControllerCallback; + } + + /** + * SuperPlayerView的回调接口 + */ + public interface OnSuperPlayerViewCallback { + + /** + * 开始全屏播放 + */ + void onStartFullScreenPlay(); + + /** + * 结束全屏播放 + */ + void onStopFullScreenPlay(); + + /** + * 点击悬浮窗模式下的x按钮 + */ + void onClickFloatCloseBtn(); + + /** + * 点击小播放模式的返回按钮 + */ + void onClickSmallReturnBtn(); + + /** + * 开始悬浮窗播放 + */ + void onStartFloatWindowPlay(); + + /** + * 播放状态发生变化 + */ + void onPlayStateChange(SuperPlayerDef.PlayerState playerState); + + /** + * 播放进度发生变化 + */ + void onPlayProgressChange(long current, long duration); + } + + public void release() { + if (mWindowPlayer != null) { + mWindowPlayer.release(); + } + if (mFullScreenPlayer != null) { + mFullScreenPlayer.release(); + } + if (mFloatPlayer != null) { + mFloatPlayer.release(); + } + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + try { + release(); + } catch (Throwable e) { + TXCLog.e(TAG, Log.getStackTraceString(e)); + } + } + + public void switchPlayMode(SuperPlayerDef.PlayerMode playerMode) { + if (playerMode == SuperPlayerDef.PlayerMode.WINDOW) { + if (mControllerCallback != null) { + mControllerCallback.onSwitchPlayMode(SuperPlayerDef.PlayerMode.WINDOW); + } + } else if (playerMode == SuperPlayerDef.PlayerMode.FLOAT) { + if (mPlayerViewCallback != null) { + mPlayerViewCallback.onStartFloatWindowPlay(); + } + if (mControllerCallback != null) { + mControllerCallback.onSwitchPlayMode(SuperPlayerDef.PlayerMode.FLOAT); + } + } + } + + public SuperPlayerDef.PlayerMode getPlayerMode() { + return mSuperPlayer.getPlayerMode(); + } + + public SuperPlayerDef.PlayerState getPlayerState() { + return mSuperPlayer.getPlayerState(); + } + + public float getPlayerRate() { + return mSuperPlayer.getPlayerRate(); + } + + private SuperPlayerObserver mSuperPlayerObserver = new SuperPlayerObserver() { + @Override + public void onPlayBegin(String name) { + mPlayerViewCallback.onPlayStateChange(SuperPlayerDef.PlayerState.PLAYING); + mWindowPlayer.updatePlayState(SuperPlayerDef.PlayerState.PLAYING); + mFullScreenPlayer.updatePlayState(SuperPlayerDef.PlayerState.PLAYING); + updateTitle(name); + mWindowPlayer.hideBackground(); + if (mDanmuView != null && mDanmuView.isPrepared() && mDanmuView.isPaused()) { + mDanmuView.resume(); + } + if (mWatcher != null) { + mWatcher.exitLoading(); + } + } + + @Override + public void onPlayPause() { + mPlayerViewCallback.onPlayStateChange(SuperPlayerDef.PlayerState.PAUSE); + mWindowPlayer.updatePlayState(SuperPlayerDef.PlayerState.PAUSE); + mFullScreenPlayer.updatePlayState(SuperPlayerDef.PlayerState.PAUSE); + } + + @Override + public void onPlayStop() { + mPlayerViewCallback.onPlayStateChange(SuperPlayerDef.PlayerState.END); + mWindowPlayer.updatePlayState(SuperPlayerDef.PlayerState.END); + mFullScreenPlayer.updatePlayState(SuperPlayerDef.PlayerState.END); + // 清空关键帧和视频打点信息 + if (mWatcher != null) { + mWatcher.stop(); + } + } + + @Override + public void onPlayLoading() { + mPlayerViewCallback.onPlayStateChange(SuperPlayerDef.PlayerState.LOADING); + mWindowPlayer.updatePlayState(SuperPlayerDef.PlayerState.LOADING); + mFullScreenPlayer.updatePlayState(SuperPlayerDef.PlayerState.LOADING); + if (mWatcher != null) { + mWatcher.enterLoading(); + } + } + + @Override + public void onPlayProgress(long current, long duration) { + mPlayerViewCallback.onPlayProgressChange(current, duration); + mWindowPlayer.updateVideoProgress(current, duration); + mFullScreenPlayer.updateVideoProgress(current, duration); + } + + @Override + public void onSeek(int position) { + if (mSuperPlayer.getPlayerType() != SuperPlayerDef.PlayerType.VOD) { + if (mWatcher != null) { + mWatcher.stop(); + } + } + } + + @Override + public void onSwitchStreamStart(boolean success, SuperPlayerDef.PlayerType playerType, VideoQuality quality) { + if (playerType == SuperPlayerDef.PlayerType.LIVE) { + if (success) { + Toast.makeText(mContext, "正在切换到" + quality.title + "...", Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(mContext, "切换" + quality.title + "清晰度失败,请稍候重试", Toast.LENGTH_SHORT).show(); + } + } + } + + @Override + public void onSwitchStreamEnd(boolean success, SuperPlayerDef.PlayerType playerType, VideoQuality quality) { + if (playerType == SuperPlayerDef.PlayerType.LIVE) { + if (success) { + Toast.makeText(mContext, "清晰度切换成功", Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(mContext, "清晰度切换失败", Toast.LENGTH_SHORT).show(); + } + } + } + + @Override + public void onPlayerTypeChange(SuperPlayerDef.PlayerType playType) { + mWindowPlayer.updatePlayType(playType); + mFullScreenPlayer.updatePlayType(playType); + } + + @Override + public void onPlayTimeShiftLive(TXLivePlayer player, String url) { + if (mWatcher == null) { + mWatcher = new NetWatcher(mContext); + } + mWatcher.start(url, player); + } + + @Override + public void onVideoQualityListChange(List videoQualities, VideoQuality defaultVideoQuality) { + mFullScreenPlayer.setVideoQualityList(videoQualities); + mFullScreenPlayer.updateVideoQuality(defaultVideoQuality); + } + + @Override + public void onVideoImageSpriteAndKeyFrameChanged(PlayImageSpriteInfo info, List list) { + mFullScreenPlayer.updateImageSpriteInfo(info); + mFullScreenPlayer.updateKeyFrameDescInfo(list); + } + + @Override + public void onError(int code, String message) { + showToast(message); + } + }; + + private void showToast(String message) { + Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show(); + } + + private void showToast(int resId) { + Toast.makeText(mContext, resId, Toast.LENGTH_SHORT).show(); + } + + public static void save2MediaStore(Context context, Bitmap image) { + File sdcardDir = context.getExternalFilesDir(null); + if (sdcardDir == null) { + Log.e(TAG, "sdcardDir is null"); + return; + } + File appDir = new File(sdcardDir, "superplayer"); + if (!appDir.exists()) { + appDir.mkdir(); + } + + long dateSeconds = System.currentTimeMillis() / 1000; + String fileName = dateSeconds + ".jpg"; + File file = new File(appDir, fileName); + + String filePath = file.getAbsolutePath(); + + File f = new File(filePath); + if (f.exists()) { + f.delete(); + } + FileOutputStream fos = null; + try { + fos = new FileOutputStream(f); + image.compress(Bitmap.CompressFormat.JPEG, 100, fos); + fos.flush(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (fos != null) { + try { + fos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + try { + // Save the screenshot to the MediaStore + ContentValues values = new ContentValues(); + ContentResolver resolver = context.getContentResolver(); + values.put(MediaStore.Images.ImageColumns.DATA, filePath); + values.put(MediaStore.Images.ImageColumns.TITLE, fileName); + values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, fileName); + values.put(MediaStore.Images.ImageColumns.DATE_ADDED, dateSeconds); + values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, dateSeconds); + values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/jpeg"); + values.put(MediaStore.Images.ImageColumns.WIDTH, image.getWidth()); + values.put(MediaStore.Images.ImageColumns.HEIGHT, image.getHeight()); + Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); + + OutputStream out = resolver.openOutputStream(uri); + image.compress(Bitmap.CompressFormat.JPEG, 100, out); + out.flush(); + out.close(); + + // update file size in the database + values.clear(); + values.put(MediaStore.Images.ImageColumns.SIZE, new File(filePath).length()); + resolver.update(uri, values, null, null); + + } catch (Exception e) { + TXCLog.e(TAG, Log.getStackTraceString(e)); + } + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/SuperPlayer.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/SuperPlayer.java new file mode 100644 index 0000000..d875203 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/SuperPlayer.java @@ -0,0 +1,148 @@ +package com.tencent.liteav.demo.superplayer.model; + +import com.tencent.liteav.demo.superplayer.SuperPlayerDef; +import com.tencent.liteav.demo.superplayer.SuperPlayerModel; +import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality; +import com.tencent.rtmp.TXLivePlayer; +import com.tencent.rtmp.ui.TXCloudVideoView; + +import java.util.List; + +public interface SuperPlayer { + + /** + * 开始播放 + * + * @param url 视频地址 + */ + void play(String url); + + /** + * 开始播放 + * + * @param appId 腾讯云视频appId + * @param url 直播播放地址 + */ + void play(int appId, String url); + + /** + * 开始播放 + * + * @param appId 腾讯云视频appId + * @param fileId 腾讯云视频fileId + * @param psign 防盗链签名,开启防盗链的视频必填,非防盗链视频可不填 + */ + void play(int appId, String fileId, String psign); + + /** + * 多分辨率播放 + * @param appId 腾讯云视频appId + * @param superPlayerURLS 不同分辨率数据 + * @param defaultIndex 默认播放Index + */ + void play(int appId, List superPlayerURLS, int defaultIndex); + + /** + * 重播 + */ + void reStart(); + + /** + * 暂停播放 + */ + void pause(); + + /** + * 暂停点播视频 + */ + void pauseVod(); + + /** + * 恢复播放 + */ + void resume(); + + /** + * 恢复直播播放,从直播时移播放中,恢复到直播播放。 + */ + void resumeLive(); + + /** + * 停止播放 + */ + void stop(); + + /** + * 销毁播放器 + */ + void destroy(); + + /** + * 切换播放器模式 + * + * @param playerMode {@link SuperPlayerDef.PlayerMode#WINDOW } 窗口模式 + * {@link SuperPlayerDef.PlayerMode#FULLSCREEN } 全屏模式 + * {@link SuperPlayerDef.PlayerMode#FLOAT } 悬浮窗模式 + */ + void switchPlayMode(SuperPlayerDef.PlayerMode playerMode); + + void enableHardwareDecode(boolean enable); + + void setPlayerView(TXCloudVideoView videoView); + + void seek(int position); + + void snapshot(TXLivePlayer.ITXSnapshotListener listener); + + void setRate(float speedLevel); + + void setMirror(boolean isMirror); + + void switchStream(VideoQuality quality); + + void setLoop(boolean isLoop); + + String getPlayURL(); + + /** + * 获取当前播放器模式 + * + * @return {@link SuperPlayerDef.PlayerMode#WINDOW } 窗口模式 + * {@link SuperPlayerDef.PlayerMode#FULLSCREEN } 全屏模式 + * {@link SuperPlayerDef.PlayerMode#FLOAT } 悬浮窗模式 + */ + SuperPlayerDef.PlayerMode getPlayerMode(); + + /** + * 获取当前播放器状态 + * + * @return {@link SuperPlayerDef.PlayerState#PLAYING } 播放中 + * {@link SuperPlayerDef.PlayerState#PAUSE } 暂停中 + * {@link SuperPlayerDef.PlayerState#LOADING } 缓冲中 + * {@link SuperPlayerDef.PlayerState#END } 结束播放 + */ + SuperPlayerDef.PlayerState getPlayerState(); + + /** + * 获取当前播放器类型 + * + * @return {@link SuperPlayerDef.PlayerType#LIVE } 直播 + * {@link SuperPlayerDef.PlayerType#LIVE_SHIFT } 直播时移 + * {@link SuperPlayerDef.PlayerType#VOD } 点播 + */ + SuperPlayerDef.PlayerType getPlayerType(); + + + /** + * 获取当前播放器速率 + * + */ + float getPlayerRate(); + + /** + * 设置播放器状态回调 + * + * @param observer {@link SuperPlayerObserver} + */ + void setObserver(SuperPlayerObserver observer); +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/SuperPlayerImpl.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/SuperPlayerImpl.java new file mode 100644 index 0000000..7fc5c74 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/SuperPlayerImpl.java @@ -0,0 +1,914 @@ +package com.tencent.liteav.demo.superplayer.model; + +import android.content.Context; +import android.net.Uri; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; + +import com.tencent.liteav.basic.log.TXCLog; +import com.tencent.liteav.demo.superplayer.SuperPlayerCode; +import com.tencent.liteav.demo.superplayer.SuperPlayerDef; +import com.tencent.liteav.demo.superplayer.SuperPlayerGlobalConfig; +import com.tencent.liteav.demo.superplayer.SuperPlayerModel; +import com.tencent.liteav.demo.superplayer.SuperPlayerVideoId; +import com.tencent.liteav.demo.superplayer.model.entity.PlayImageSpriteInfo; +import com.tencent.liteav.demo.superplayer.model.entity.PlayKeyFrameDescInfo; +import com.tencent.liteav.demo.superplayer.model.entity.ResolutionName; +import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality; +import com.tencent.liteav.demo.superplayer.model.net.LogReport; +import com.tencent.liteav.demo.superplayer.model.protocol.IPlayInfoProtocol; +import com.tencent.liteav.demo.superplayer.model.protocol.IPlayInfoRequestCallback; +import com.tencent.liteav.demo.superplayer.model.protocol.PlayInfoParams; +import com.tencent.liteav.demo.superplayer.model.protocol.PlayInfoProtocolV2; +import com.tencent.liteav.demo.superplayer.model.protocol.PlayInfoProtocolV4; +import com.tencent.liteav.demo.superplayer.model.utils.VideoQualityUtils; +import com.tencent.rtmp.ITXLivePlayListener; +import com.tencent.rtmp.ITXVodPlayListener; +import com.tencent.rtmp.TXBitrateItem; +import com.tencent.rtmp.TXLiveBase; +import com.tencent.rtmp.TXLiveConstants; +import com.tencent.rtmp.TXLivePlayConfig; +import com.tencent.rtmp.TXLivePlayer; +import com.tencent.rtmp.TXVodPlayConfig; +import com.tencent.rtmp.TXVodPlayer; +import com.tencent.rtmp.ui.TXCloudVideoView; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class SuperPlayerImpl implements SuperPlayer, ITXVodPlayListener, ITXLivePlayListener { + + private static final String TAG = "SuperPlayerImpl"; + private static final int SUPERPLAYER_MODE = 1; + private static final int SUPPORT_MAJOR_VERSION = 8; + private static final int SUPPORT_MINOR_VERSION = 5; + + private Context mContext; + private TXCloudVideoView mVideoView; // 腾讯云视频播放view + + private IPlayInfoProtocol mCurrentProtocol; // 当前视频信息协议类 + private TXVodPlayer mVodPlayer; // 点播播放器 + private TXVodPlayConfig mVodPlayConfig; // 点播播放器配置 + private TXLivePlayer mLivePlayer; // 直播播放器 + private TXLivePlayConfig mLivePlayConfig; // 直播播放器配置 + + private SuperPlayerModel mCurrentModel; // 当前播放的model + private SuperPlayerObserver mObserver; + private VideoQuality mVideoQuality; + + private SuperPlayerDef.PlayerType mCurrentPlayType = SuperPlayerDef.PlayerType.VOD; // 当前播放类型 + private SuperPlayerDef.PlayerMode mCurrentPlayMode = SuperPlayerDef.PlayerMode.WINDOW; // 当前播放模式 + private SuperPlayerDef.PlayerState mCurrentPlayState = SuperPlayerDef.PlayerState.PLAYING; // 当前播放状态 + private float mCurrentPlayRate = 1; // 当前播放速率 + + private String mCurrentPlayVideoURL; // 当前播放的URL + + private int mSeekPos; // 记录切换硬解时的播放时间 + + private long mReportLiveStartTime = -1; // 直播开始时间,用于上报使用时长 + private long mReportVodStartTime = -1; // 点播开始时间,用于上报使用时长 + private long mMaxLiveProgressTime; // 观看直播的最大时长 + + private boolean mIsMultiBitrateStream; // 是否是多码流url播放 + private boolean mIsPlayWithFileId; // 是否是腾讯云fileId播放 + private boolean mDefaultQualitySet; // 标记播放多码流url时是否设置过默认画质 + private boolean mChangeHWAcceleration; // 切换硬解后接收到第一个关键帧前的标记位 + private String mFileId; + private int mAppId; + + public SuperPlayerImpl(Context context, TXCloudVideoView videoView) { + initialize(context, videoView); + } + + /** + * 直播播放器事件回调 + * + * @param event + * @param param + */ + @Override + public void onPlayEvent(int event, Bundle param) { + if (event != TXLiveConstants.PLAY_EVT_PLAY_PROGRESS) { + String playEventLog = "TXLivePlayer onPlayEvent event: " + event + ", " + param.getString(TXLiveConstants.EVT_DESCRIPTION); + TXCLog.d(TAG, playEventLog); + } + switch (event) { + case TXLiveConstants.PLAY_EVT_VOD_PLAY_PREPARED: //视频播放开始 + case TXLiveConstants.PLAY_EVT_PLAY_BEGIN: + updatePlayerState(SuperPlayerDef.PlayerState.PLAYING); + break; + case TXLiveConstants.PLAY_ERR_NET_DISCONNECT: + case TXLiveConstants.PLAY_EVT_PLAY_END: + if (mCurrentPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) { // 直播时移失败,返回直播 + mLivePlayer.resumeLive(); + updatePlayerType(SuperPlayerDef.PlayerType.LIVE); + onError(SuperPlayerCode.LIVE_SHIFT_FAIL, "时移失败,返回直播"); + updatePlayerState(SuperPlayerDef.PlayerState.PLAYING); + } else { + stop(); + updatePlayerState(SuperPlayerDef.PlayerState.END); + if (event == TXLiveConstants.PLAY_ERR_NET_DISCONNECT) { + onError(SuperPlayerCode.NET_ERROR, "网络不给力,点击重试"); + } else { + onError(SuperPlayerCode.LIVE_PLAY_END, param.getString(TXLiveConstants.EVT_DESCRIPTION)); + } + } + break; + case TXLiveConstants.PLAY_EVT_PLAY_LOADING: +// case TXLiveConstants.PLAY_WARNING_RECONNECT: //暂时去掉,回调该状态时,播放画面可能是正常的,loading 状态只在 TXLiveConstants.PLAY_EVT_PLAY_LOADING 处理 + updatePlayerState(SuperPlayerDef.PlayerState.LOADING); + break; + case TXLiveConstants.PLAY_EVT_RCV_FIRST_I_FRAME: + break; + case TXLiveConstants.PLAY_EVT_STREAM_SWITCH_SUCC: + updateStreamEndStatus(true, SuperPlayerDef.PlayerType.LIVE, mVideoQuality); + break; + case TXLiveConstants.PLAY_ERR_STREAM_SWITCH_FAIL: + updateStreamEndStatus(false, SuperPlayerDef.PlayerType.LIVE, mVideoQuality); + break; + case TXLiveConstants.PLAY_EVT_PLAY_PROGRESS: + int progress = param.getInt(TXLiveConstants.EVT_PLAY_PROGRESS_MS); + mMaxLiveProgressTime = progress > mMaxLiveProgressTime ? progress : mMaxLiveProgressTime; + updatePlayProgress(progress / 1000, mMaxLiveProgressTime / 1000); + break; + default: + break; + } + } + + /** + * 直播播放器网络状态回调 + * + * @param bundle + */ + @Override + public void onNetStatus(Bundle bundle) { + + } + + /** + * 点播播放器事件回调 + * + * @param player + * @param event + * @param param + */ + @Override + public void onPlayEvent(TXVodPlayer player, int event, Bundle param) { + if (event != TXLiveConstants.PLAY_EVT_PLAY_PROGRESS) { + String playEventLog = "TXVodPlayer onPlayEvent event: " + event + ", " + param.getString(TXLiveConstants.EVT_DESCRIPTION); + TXCLog.d(TAG, playEventLog); + } + switch (event) { + case TXLiveConstants.PLAY_EVT_VOD_PLAY_PREPARED://视频播放开始 + updatePlayerState(SuperPlayerDef.PlayerState.PLAYING); + if (mIsMultiBitrateStream) { + List bitrateItems = mVodPlayer.getSupportedBitrates(); + if (bitrateItems == null || bitrateItems.size() == 0) { + return; + } + Collections.sort(bitrateItems); //masterPlaylist多清晰度,按照码率排序,从低到高 + List videoQualities = new ArrayList<>(); + int size = bitrateItems.size(); + List resolutionNames = (mCurrentProtocol != null) ? mCurrentProtocol.getResolutionNameList() : null; + for (int i = 0; i < size; i++) { + TXBitrateItem bitrateItem = bitrateItems.get(i); + VideoQuality quality; + if (resolutionNames != null) { + quality = VideoQualityUtils.convertToVideoQuality(bitrateItem, mCurrentProtocol.getResolutionNameList()); + } else { + quality = VideoQualityUtils.convertToVideoQuality(bitrateItem, i); + } + videoQualities.add(quality); + } + if (!mDefaultQualitySet) { + mVodPlayer.setBitrateIndex(bitrateItems.get(bitrateItems.size() - 1).index); //默认播放码率最高的 + mDefaultQualitySet = true; + } + updateVideoQualityList(videoQualities, null); + } + break; + case TXLiveConstants.PLAY_EVT_RCV_FIRST_I_FRAME: + if (mChangeHWAcceleration) { //切换软硬解码器后,重新seek位置 + TXCLog.i(TAG, "seek pos:" + mSeekPos); + seek(mSeekPos); + mChangeHWAcceleration = false; + } + break; + case TXLiveConstants.PLAY_EVT_PLAY_END: + updatePlayerState(SuperPlayerDef.PlayerState.END); + break; + case TXLiveConstants.PLAY_EVT_PLAY_PROGRESS: + int progress = param.getInt(TXLiveConstants.EVT_PLAY_PROGRESS_MS); + int duration = param.getInt(TXLiveConstants.EVT_PLAY_DURATION_MS); + updatePlayProgress(progress / 1000, duration / 1000); + break; + case TXLiveConstants.PLAY_EVT_PLAY_BEGIN: + updatePlayerState(SuperPlayerDef.PlayerState.PLAYING); + break; + default: + break; + } + if (event < 0) {// 播放点播文件失败 + mVodPlayer.stopPlay(true); + updatePlayerState(SuperPlayerDef.PlayerState.PAUSE); + onError(SuperPlayerCode.VOD_PLAY_FAIL, param.getString(TXLiveConstants.EVT_DESCRIPTION)); + } + } + + /** + * 点播播放器网络状态回调 + * + * @param player + * @param bundle + */ + @Override + public void onNetStatus(TXVodPlayer player, Bundle bundle) { + + } + + private void initialize(Context context, TXCloudVideoView videoView) { + mContext = context; + mVideoView = videoView; + initLivePlayer(mContext); + initVodPlayer(mContext); + } + + /** + * 初始化点播播放器 + * + * @param context + */ + private void initVodPlayer(Context context) { + mVodPlayer = new TXVodPlayer(context); + SuperPlayerGlobalConfig config = SuperPlayerGlobalConfig.getInstance(); + mVodPlayConfig = new TXVodPlayConfig(); + + File sdcardDir = context.getExternalFilesDir(null); + if (sdcardDir != null) { + mVodPlayConfig.setCacheFolderPath(sdcardDir.getPath() + "/txcache"); + } + mVodPlayConfig.setMaxCacheItems(config.maxCacheItem); + mVodPlayer.setConfig(mVodPlayConfig); + mVodPlayer.setRenderMode(config.renderMode); + mVodPlayer.setVodListener(this); + mVodPlayer.enableHardwareDecode(config.enableHWAcceleration); + } + + /** + * 初始化直播播放器 + * + * @param context + */ + private void initLivePlayer(Context context) { + mLivePlayer = new TXLivePlayer(context); + SuperPlayerGlobalConfig config = SuperPlayerGlobalConfig.getInstance(); + mLivePlayConfig = new TXLivePlayConfig(); + mLivePlayer.setConfig(mLivePlayConfig); + mLivePlayer.setRenderMode(config.renderMode); + mLivePlayer.setRenderRotation(TXLiveConstants.RENDER_ROTATION_PORTRAIT); + mLivePlayer.setPlayListener(this); + mLivePlayer.enableHardwareDecode(config.enableHWAcceleration); + } + + /** + * 播放视频 + * + * @param model + */ + public void playWithModel(final SuperPlayerModel model) { + mCurrentModel = model; + stop(); + PlayInfoParams params = new PlayInfoParams(); + params.appId = model.appId; + if (model.videoId != null) { + params.fileId = model.videoId.fileId; + params.videoId = model.videoId; + mCurrentProtocol = new PlayInfoProtocolV4(params); + } else if (model.videoIdV2 != null) { + params.fileId = model.videoIdV2.fileId; + params.videoIdV2 = model.videoIdV2; + mCurrentProtocol = new PlayInfoProtocolV2(params); + } else { + mCurrentProtocol = null; // 当前播放的是非v2和v4协议视频,将其置空 + } + mFileId = params.fileId; + mAppId = params.appId; + updateVideoImageSpriteAndKeyFrame(null, null); + if (model.videoId != null || model.videoIdV2 != null) { // 根据FileId播放 + mCurrentProtocol.sendRequest(new IPlayInfoRequestCallback() { + @Override + public void onSuccess(IPlayInfoProtocol protocol, PlayInfoParams param) { + TXCLog.i(TAG, "onSuccess: protocol params = " + param.toString()); + mReportVodStartTime = System.currentTimeMillis(); + mVodPlayer.setPlayerView(mVideoView); + playModeVideo(mCurrentProtocol); + updatePlayerType(SuperPlayerDef.PlayerType.VOD); + updatePlayProgress(0, 0); + updateVideoImageSpriteAndKeyFrame(mCurrentProtocol.getImageSpriteInfo(), mCurrentProtocol.getKeyFrameDescInfo()); + } + + @Override + public void onError(int errCode, String message) { + TXCLog.i(TAG, "onFail: errorCode = " + errCode + " message = " + message); + SuperPlayerImpl.this.onError(SuperPlayerCode.VOD_REQUEST_FILE_ID_FAIL, "播放视频文件失败 code = " + errCode + " msg = " + message); + } + }); + } else { // 根据URL播放 + String videoURL = null; + List videoQualities = new ArrayList<>(); + VideoQuality defaultVideoQuality = null; + if (model.multiURLs != null && !model.multiURLs.isEmpty()) {// 多码率URL播放 + int i = 0; + for (SuperPlayerModel.SuperPlayerURL superPlayerURL : model.multiURLs) { + if (i == model.playDefaultIndex) { + videoURL = superPlayerURL.url; + } + videoQualities.add(new VideoQuality(i++, superPlayerURL.qualityName, superPlayerURL.url)); + } + defaultVideoQuality = videoQualities.get(model.playDefaultIndex); + } else if (!TextUtils.isEmpty(model.url)) { // 传统URL模式播放 + videoURL = model.url; + } + + if (TextUtils.isEmpty(videoURL)) { + onError(SuperPlayerCode.PLAY_URL_EMPTY, "播放视频失败,播放链接为空"); + return; + } + if (isRTMPPlay(videoURL)) { // 直播播放器:普通RTMP流播放 + mReportLiveStartTime = System.currentTimeMillis(); + mLivePlayer.setPlayerView(mVideoView); + playLiveURL(videoURL, TXLivePlayer.PLAY_TYPE_LIVE_RTMP); + } else if (isFLVPlay(videoURL)) { // 直播播放器:直播FLV流播放 + mReportLiveStartTime = System.currentTimeMillis(); + mLivePlayer.setPlayerView(mVideoView); + playTimeShiftLiveURL(model.appId, videoURL); + if (model.multiURLs != null && !model.multiURLs.isEmpty()) { + startMultiStreamLiveURL(videoURL); + } + } else { // 点播播放器:播放点播文件 + mReportVodStartTime = System.currentTimeMillis(); + mVodPlayer.setPlayerView(mVideoView); + playVodURL(videoURL); + } + boolean isLivePlay = (isRTMPPlay(videoURL) || isFLVPlay(videoURL)); + updatePlayerType(isLivePlay ? SuperPlayerDef.PlayerType.LIVE : SuperPlayerDef.PlayerType.VOD); + updatePlayProgress(0, 0); + updateVideoQualityList(videoQualities, defaultVideoQuality); + } + } + + /** + * 播放v2或v4协议视频 + * + * @param protocol + */ + private void playModeVideo(IPlayInfoProtocol protocol) { + playVodURL(protocol.getUrl()); + List videoQualities = protocol.getVideoQualityList(); + mIsMultiBitrateStream = videoQualities == null; + VideoQuality defaultVideoQuality = protocol.getDefaultVideoQuality(); + updateVideoQualityList(videoQualities, defaultVideoQuality); + } + + /** + * 播放非v2和v4协议视频 + * + * @param model + */ + private void playModeVideo(SuperPlayerModel model) { + if (model.multiURLs != null && !model.multiURLs.isEmpty()) {// 多码率URL播放 + for (int i = 0; i < model.multiURLs.size(); i++) { + if (i == model.playDefaultIndex) { + playVodURL(model.multiURLs.get(i).url); + } + } + } else if (!TextUtils.isEmpty(model.url)) { + playVodURL(model.url); + } + } + + /** + * 播放直播URL + */ + private void playLiveURL(String url, int playType) { + mCurrentPlayVideoURL = url; + if (mLivePlayer != null) { + mLivePlayer.setPlayListener(this); + int result = mLivePlayer.startPlay(url, playType); // result返回值:0 success; -1 empty url; -2 invalid url; -3 invalid playType; + if (result != 0) { + TXCLog.e(TAG, "playLiveURL videoURL:" + url + ",result:" + result); + } else { + updatePlayerState(SuperPlayerDef.PlayerState.PLAYING); + } + } + } + + /** + * 播放点播url + */ + private void playVodURL(String url) { + if (url == null || "".equals(url)) { + return; + } + mCurrentPlayVideoURL = url; + if (url.contains(".m3u8")) { + mIsMultiBitrateStream = true; + } + if (mVodPlayer != null) { + mDefaultQualitySet = false; + mVodPlayer.setStartTime(0); + mVodPlayer.setAutoPlay(true); + mVodPlayer.setVodListener(this); + String drmType = "plain"; + if (mCurrentProtocol != null) { + TXCLog.d(TAG, "TOKEN: " + mCurrentProtocol.getToken()); + mVodPlayer.setToken(mCurrentProtocol.getToken()); + String type = mCurrentProtocol.getDRMType(); + if (type!=null && !type.isEmpty()) { + drmType = type; + } + } else { + mVodPlayer.setToken(null); + } + int ret = 0; + if (isVersionSupportAppendUrl()) { + Uri uri = Uri.parse(url); + String query = uri.getQuery(); + if(query==null || query.isEmpty()) { + query = ""; + } else { + query = query + "&"; + if (query.contains("spfileid") || query.contains("spdrmtype") || query.contains("spappid")) { + TXCLog.e(TAG, "url contains superplay key. " + query); + } + } + query += "spfileid=" + mFileId + "&spdrmtype=" + drmType + "&spappid=" + mAppId; + Uri newUri = uri.buildUpon().query(query).build(); + TXCLog.i(TAG, "playVodURL: newurl = " + Uri.decode(newUri.toString()) + " ;url= " + url); + ret = mVodPlayer.startPlay(Uri.decode(newUri.toString())); + } else { + ret = mVodPlayer.startPlay(url); + } + + if (ret == 0) { + updatePlayerState(SuperPlayerDef.PlayerState.PLAYING); + } + } + mIsPlayWithFileId = false; + } + + private boolean isVersionSupportAppendUrl() { + String strVersion = TXLiveBase.getSDKVersionStr(); + String[] strVers = strVersion.split("\\."); + if (strVers.length <= 1) { + return false; + } + int majorVer = 0; + int minorVer = 0; + try{ + majorVer = Integer.parseInt(strVers[0]); + minorVer = Integer.parseInt(strVers[1]); + } + catch (NumberFormatException e){ + TXCLog.e(TAG, "parse version failed.", e); + majorVer = 0; + minorVer = 0; + } + Log.i(TAG, strVersion + " , " + majorVer + " , " + minorVer); + return majorVer > SUPPORT_MAJOR_VERSION || (majorVer == SUPPORT_MAJOR_VERSION && minorVer >= SUPPORT_MINOR_VERSION) ; + } + + /** + * 播放时移直播url + */ + private void playTimeShiftLiveURL(int appId, String url) { + final String bizid = url.substring(url.indexOf("//") + 2, url.indexOf(".")); + final String domian = SuperPlayerGlobalConfig.getInstance().playShiftDomain; + final String streamid = url.substring(url.lastIndexOf("/") + 1, url.lastIndexOf(".")); + TXCLog.i(TAG, "bizid:" + bizid + ",streamid:" + streamid + ",appid:" + appId); + playLiveURL(url, TXLivePlayer.PLAY_TYPE_LIVE_FLV); + int bizidNum = -1; + try { + bizidNum = Integer.parseInt(bizid); + } catch (NumberFormatException e) { + e.printStackTrace(); + TXCLog.e(TAG, "playTimeShiftLiveURL: bizidNum error = " + bizid); + } + mLivePlayer.prepareLiveSeek(domian, bizidNum); + } + + /** + * 配置多码流url + * + * @param url + */ + private void startMultiStreamLiveURL(String url) { + mLivePlayConfig.setAutoAdjustCacheTime(false); + mLivePlayConfig.setMaxAutoAdjustCacheTime(5); + mLivePlayConfig.setMinAutoAdjustCacheTime(5); + mLivePlayer.setConfig(mLivePlayConfig); + if (mObserver != null) { + mObserver.onPlayTimeShiftLive(mLivePlayer, url); + } + } + + /** + * 上报播放时长 + */ + private void reportPlayTime() { + if (mReportLiveStartTime != -1) { + long reportEndTime = System.currentTimeMillis(); + long diff = (reportEndTime - mReportLiveStartTime) / 1000; + LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_LIVE_TIME, diff, 0); + mReportLiveStartTime = -1; + } + if (mReportVodStartTime != -1) { + long reportEndTime = System.currentTimeMillis(); + long diff = (reportEndTime - mReportVodStartTime) / 1000; + LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_VOD_TIME, diff, mIsPlayWithFileId ? 1 : 0); + mReportVodStartTime = -1; + } + } + + /** + * 更新播放进度 + * + * @param current 当前播放进度(秒) + * @param duration 总时长(秒) + */ + private void updatePlayProgress(long current, long duration) { + if (mObserver != null) { + mObserver.onPlayProgress(current, duration); + } + } + + /** + * 更新播放类型 + * + * @param playType + */ + private void updatePlayerType(SuperPlayerDef.PlayerType playType) { + if (playType != mCurrentPlayType) { + mCurrentPlayType = playType; + } + if (mObserver != null) { + mObserver.onPlayerTypeChange(playType); + } + } + + /** + * 更新播放状态 + * + * @param playState + */ + private void updatePlayerState(SuperPlayerDef.PlayerState playState) { + mCurrentPlayState = playState; + if (mObserver == null) { + return; + } + switch (playState) { + case PLAYING: + mObserver.onPlayBegin(getPlayName()); + break; + case PAUSE: + mObserver.onPlayPause(); + break; + case LOADING: + mObserver.onPlayLoading(); + break; + case END: + mObserver.onPlayStop(); + break; + } + } + + private void updateStreamStartStatus(boolean success, SuperPlayerDef.PlayerType playerType, VideoQuality quality) { + if (mObserver != null) { + mObserver.onSwitchStreamStart(success, playerType, quality); + } + } + + private void updateStreamEndStatus(boolean success, SuperPlayerDef.PlayerType playerType, VideoQuality quality) { + if (mObserver != null) { + mObserver.onSwitchStreamEnd(success, playerType, quality); + } + } + + private void updateVideoQualityList(List videoQualities, VideoQuality defaultVideoQuality) { + if (mObserver != null) { + mObserver.onVideoQualityListChange(videoQualities, defaultVideoQuality); + } + } + + private void updateVideoImageSpriteAndKeyFrame(PlayImageSpriteInfo info, List list) { + if (mObserver != null) { + mObserver.onVideoImageSpriteAndKeyFrameChanged(info, list); + } + } + + private void onError(int code, String message) { + if (mObserver != null) { + mObserver.onError(code, message); + } + } + + private String getPlayName() { + String title = ""; + if (mCurrentModel != null && !TextUtils.isEmpty(mCurrentModel.title)) { + title = mCurrentModel.title; + } else if (mCurrentProtocol != null && !TextUtils.isEmpty(mCurrentProtocol.getName())) { + title = mCurrentProtocol.getName(); + } + return title; + } + + /** + * 是否是RTMP协议 + * + * @param videoURL + * @return + */ + private boolean isRTMPPlay(String videoURL) { + return !TextUtils.isEmpty(videoURL) && videoURL.startsWith("rtmp"); + } + + /** + * 是否是HTTP-FLV协议 + * + * @param videoURL + * @return + */ + private boolean isFLVPlay(String videoURL) { + return (!TextUtils.isEmpty(videoURL) && videoURL.startsWith("http://") + || videoURL.startsWith("https://")) && videoURL.contains(".flv"); + } + + @Override + public void play(String url) { + SuperPlayerModel model = new SuperPlayerModel(); + model.url = url; + playWithModel(model); + } + + @Override + public void play(int appId, String url) { + SuperPlayerModel model = new SuperPlayerModel(); + model.appId = appId; + model.url = url; + playWithModel(model); + } + + @Override + public void play(int appId, String fileId, String psign) { + SuperPlayerVideoId videoId = new SuperPlayerVideoId(); + videoId.fileId = fileId; + videoId.pSign = psign; + + SuperPlayerModel model = new SuperPlayerModel(); + model.appId = appId; + model.videoId = videoId; + playWithModel(model); + } + + @Override + public void play(int appId, List superPlayerURLS, int defaultIndex) { + SuperPlayerModel model = new SuperPlayerModel(); + model.appId = appId; + model.multiURLs = superPlayerURLS; + model.playDefaultIndex = defaultIndex; + playWithModel(model); + } + + @Override + public void reStart() { + if (mCurrentPlayType == SuperPlayerDef.PlayerType.LIVE || mCurrentPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) { + if (isRTMPPlay(mCurrentPlayVideoURL)) { + playLiveURL(mCurrentPlayVideoURL, TXLivePlayer.PLAY_TYPE_LIVE_RTMP); + } else if (isFLVPlay(mCurrentPlayVideoURL)) { + playTimeShiftLiveURL(mCurrentModel.appId, mCurrentPlayVideoURL); + if (mCurrentModel.multiURLs != null && !mCurrentModel.multiURLs.isEmpty()) { + startMultiStreamLiveURL(mCurrentPlayVideoURL); + } + } + } else { + playVodURL(mCurrentPlayVideoURL); + } + } + + @Override + public void pause() { + if (mCurrentPlayType == SuperPlayerDef.PlayerType.VOD) { + mVodPlayer.pause(); + } else { + mLivePlayer.pause(); + } + updatePlayerState(SuperPlayerDef.PlayerState.PAUSE); + } + + @Override + public void pauseVod() { + if (mCurrentPlayType == SuperPlayerDef.PlayerType.VOD) { + mVodPlayer.pause(); + } + updatePlayerState(SuperPlayerDef.PlayerState.PAUSE); + } + + @Override + public void resume() { + if (mCurrentPlayType == SuperPlayerDef.PlayerType.VOD) { + mVodPlayer.resume(); + } else { + mLivePlayer.resume(); + } + updatePlayerState(SuperPlayerDef.PlayerState.PLAYING); + } + + @Override + public void resumeLive() { + if (mCurrentPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) { + mLivePlayer.resumeLive(); + } + updatePlayerType(SuperPlayerDef.PlayerType.LIVE); + } + + @Override + public void stop() { + if (mVodPlayer != null) { + mVodPlayer.stopPlay(false); + } + if (mLivePlayer != null) { + mLivePlayer.stopPlay(false); + mVideoView.removeVideoView(); + } + updatePlayerState(SuperPlayerDef.PlayerState.END); + reportPlayTime(); + } + + @Override + public void destroy() { + + } + + @Override + public void switchPlayMode(SuperPlayerDef.PlayerMode playerMode) { + if (mCurrentPlayMode == playerMode) { + return; + } + mCurrentPlayMode = playerMode; + } + + @Override + public void enableHardwareDecode(boolean enable) { + if (mCurrentPlayType == SuperPlayerDef.PlayerType.VOD) { + mChangeHWAcceleration = true; + mVodPlayer.enableHardwareDecode(enable); + mSeekPos = (int) mVodPlayer.getCurrentPlaybackTime(); + TXCLog.i(TAG, "save pos:" + mSeekPos); + stop(); + if (mCurrentProtocol == null) { // 当protocol为空时,则说明当前播放视频为非v2和v4视频 + playModeVideo(mCurrentModel); + } else { + playModeVideo(mCurrentProtocol); + } + } else { + mLivePlayer.enableHardwareDecode(enable); + playWithModel(mCurrentModel); + } + // 硬件加速上报 + if (enable) { + LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_HW_DECODE, 0, 0); + } else { + LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_SOFT_DECODE, 0, 0); + } + } + + @Override + public void setPlayerView(TXCloudVideoView videoView) { + if (mCurrentPlayType == SuperPlayerDef.PlayerType.VOD) { + mVodPlayer.setPlayerView(videoView); + } else { + mLivePlayer.setPlayerView(videoView); + } + } + + @Override + public void seek(int position) { + if (mCurrentPlayType == SuperPlayerDef.PlayerType.VOD) { + if (mVodPlayer != null) { + mVodPlayer.seek(position); + } + } else { + updatePlayerType(SuperPlayerDef.PlayerType.LIVE_SHIFT); + LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_TIMESHIFT, 0, 0); + if (mLivePlayer != null) { + mLivePlayer.seek(position); + } + } + if (mObserver != null) { + mObserver.onSeek(position); + } + } + + @Override + public void snapshot(TXLivePlayer.ITXSnapshotListener listener) { + if (mCurrentPlayType == SuperPlayerDef.PlayerType.VOD) { + mVodPlayer.snapshot(listener); + } else if (mCurrentPlayType == SuperPlayerDef.PlayerType.LIVE) { + mLivePlayer.snapshot(listener); + } else { + listener.onSnapshot(null); + } + } + + @Override + public void setRate(float speedLevel) { + if (mCurrentPlayType == SuperPlayerDef.PlayerType.VOD) { + mVodPlayer.setRate(speedLevel); + mCurrentPlayRate = speedLevel; + } + //速度改变上报 + LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_CHANGE_SPEED, 0, 0); + + } + + @Override + public void setMirror(boolean isMirror) { + if (mCurrentPlayType == SuperPlayerDef.PlayerType.VOD) { + mVodPlayer.setMirror(isMirror); + } + if (isMirror) { + LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_MIRROR, 0, 0); + } + } + + @Override + public void switchStream(VideoQuality quality) { + mVideoQuality = quality; + if (mCurrentPlayType == SuperPlayerDef.PlayerType.VOD) { + if (mVodPlayer != null) { + if (quality.url != null) { // br!=0;index=-1;url!=null //br=0;index!=-1;url!=null + // 说明是非多bitrate的m3u8子流,需要手动seek + float currentTime = mVodPlayer.getCurrentPlaybackTime(); + mVodPlayer.stopPlay(true); + TXCLog.i(TAG, "onQualitySelect quality.url:" + quality.url); + mVodPlayer.setStartTime(currentTime); + mVodPlayer.startPlay(quality.url); + } else { //br!=0;index!=-1;url=null + TXCLog.i(TAG, "setBitrateIndex quality.index:" + quality.index); + // 说明是多bitrate的m3u8子流,会自动无缝seek + mVodPlayer.setBitrateIndex(quality.index); + } + updateStreamStartStatus(true, SuperPlayerDef.PlayerType.VOD, quality); + } + } else { + boolean success = false; + if (mLivePlayer != null && !TextUtils.isEmpty(quality.url)) { + int result = mLivePlayer.switchStream(quality.url); + success = result >= 0; + } + updateStreamStartStatus(success, SuperPlayerDef.PlayerType.LIVE, quality); + } + //清晰度上报 + LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_CHANGE_RESOLUTION, 0, 0); + } + + @Override + public void setLoop(boolean isLoop) { + if (mCurrentPlayType == SuperPlayerDef.PlayerType.VOD) { + mVodPlayer.setLoop(isLoop); + } + } + + @Override + public String getPlayURL() { + return mCurrentPlayVideoURL; + } + + @Override + public SuperPlayerDef.PlayerMode getPlayerMode() { + return mCurrentPlayMode; + } + + @Override + public SuperPlayerDef.PlayerState getPlayerState() { + return mCurrentPlayState; + } + + @Override + public SuperPlayerDef.PlayerType getPlayerType() { + return mCurrentPlayType; + } + + public float getPlayerRate() { + return mCurrentPlayRate; + } + + @Override + public void setObserver(SuperPlayerObserver observer) { + mObserver = observer; + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/SuperPlayerObserver.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/SuperPlayerObserver.java new file mode 100644 index 0000000..858a552 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/SuperPlayerObserver.java @@ -0,0 +1,57 @@ +package com.tencent.liteav.demo.superplayer.model; + +import com.tencent.liteav.demo.superplayer.SuperPlayerDef; +import com.tencent.liteav.demo.superplayer.model.entity.PlayImageSpriteInfo; +import com.tencent.liteav.demo.superplayer.model.entity.PlayKeyFrameDescInfo; +import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality; +import com.tencent.rtmp.TXLivePlayer; + +import java.util.List; + +public abstract class SuperPlayerObserver { + + /** + * 开始播放 + * @param name 当前视频名称 + */ + public void onPlayBegin(String name) {} + + /** + * 播放暂停 + */ + public void onPlayPause() {} + + /** + * 播放器停止 + */ + public void onPlayStop() {} + + /** + * 播放器进入Loading状态 + */ + public void onPlayLoading() {} + + /** + * 播放进度回调 + * + * @param current + * @param duration + */ + public void onPlayProgress(long current, long duration) {} + + public void onSeek(int position) {} + + public void onSwitchStreamStart(boolean success, SuperPlayerDef.PlayerType playerType, VideoQuality quality){} + + public void onSwitchStreamEnd(boolean success, SuperPlayerDef.PlayerType playerType, VideoQuality quality){} + + public void onError(int code, String message) {} + + public void onPlayerTypeChange(SuperPlayerDef.PlayerType playType) {} + + public void onPlayTimeShiftLive(TXLivePlayer player, String url) {} + + public void onVideoQualityListChange(List videoQualities, VideoQuality defaultVideoQuality) {} + + public void onVideoImageSpriteAndKeyFrameChanged(PlayImageSpriteInfo info, List list) {} +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/entity/EncryptedStreamingInfo.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/entity/EncryptedStreamingInfo.java new file mode 100644 index 0000000..2c35ec7 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/entity/EncryptedStreamingInfo.java @@ -0,0 +1,21 @@ +package com.tencent.liteav.demo.superplayer.model.entity; + +/** + * Created by hans on 2019/3/25. + *

+ * 自适应码流信息 + */ + +public class EncryptedStreamingInfo { + + public String drmType; + public String url; + + @Override + public String toString() { + return "TCEncryptedStreamingInfo{" + + ", drmType='" + drmType + '\'' + + ", url='" + url + '\'' + + '}'; + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/entity/PlayImageSpriteInfo.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/entity/PlayImageSpriteInfo.java new file mode 100644 index 0000000..ab557a9 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/entity/PlayImageSpriteInfo.java @@ -0,0 +1,20 @@ +package com.tencent.liteav.demo.superplayer.model.entity; + +import java.util.List; + +/** + * 视频雪碧图信息 + */ +public class PlayImageSpriteInfo { + + public List imageUrls; // 图片链接URL + public String webVttUrl; // web vtt描述文件下载URL + + @Override + public String toString() { + return "TCPlayImageSpriteInfo{" + + "imageUrls=" + imageUrls + + ", webVttUrl='" + webVttUrl + '\'' + + '}'; + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/entity/PlayInfoStream.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/entity/PlayInfoStream.java new file mode 100644 index 0000000..148545a --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/entity/PlayInfoStream.java @@ -0,0 +1,67 @@ +package com.tencent.liteav.demo.superplayer.model.entity; + +/** + * Created by annidy on 2017/12/20. + *

+ * 视频播放信息 + */ + +public class PlayInfoStream { + public int height; + public int width; + public int size; + public int duration; + public int bitrate; + public int definition; + public String id; + public String name; + public String url; + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public int getHeight() { + return height; + } + + public int getDuration() { + return duration; + } + + public int getBitrate() { + return bitrate; + } + + public void setHeight(int height) { + this.height = height; + } + + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getSize() { + return size; + } + + public void setSize(int size) { + this.size = size; + } + + public void setDuration(int duration) { + this.duration = duration; + } + + public void setBitrate(int bitrate) { + this.bitrate = bitrate; + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/entity/PlayKeyFrameDescInfo.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/entity/PlayKeyFrameDescInfo.java new file mode 100644 index 0000000..bb13738 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/entity/PlayKeyFrameDescInfo.java @@ -0,0 +1,18 @@ +package com.tencent.liteav.demo.superplayer.model.entity; + +/** + * 视频关键帧信息 + */ +public class PlayKeyFrameDescInfo { + + public String content; // 描述信息 + public float time; // 关键帧时间(秒) + + @Override + public String toString() { + return "TCPlayKeyFrameDescInfo{" + + "content='" + content + '\'' + + ", time=" + time + + '}'; + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/entity/ResolutionName.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/entity/ResolutionName.java new file mode 100644 index 0000000..5b2848a --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/entity/ResolutionName.java @@ -0,0 +1,22 @@ +package com.tencent.liteav.demo.superplayer.model.entity; + +/** + * 自适应码流视频画质别名 + */ +public class ResolutionName { + + public String name; // 画质名称 + public String type; // 类型 可能的取值有 video 和 audio + public int width; + public int height; + + @Override + public String toString() { + return "TCResolutionName{" + + "width='" + width + '\'' + + "height='" + height + '\'' + + "type='" + type + '\'' + + ", name=" + name + + '}'; + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/entity/SuperPlayerVideoIdV2.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/entity/SuperPlayerVideoIdV2.java new file mode 100644 index 0000000..212ea4d --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/entity/SuperPlayerVideoIdV2.java @@ -0,0 +1,22 @@ +package com.tencent.liteav.demo.superplayer.model.entity; + +public class SuperPlayerVideoIdV2 { + + public String fileId; // 腾讯云视频fileId + public String timeout; // 【可选】加密链接超时时间戳,转换为16进制小写字符串,腾讯云 CDN 服务器会根据该时间判断该链接是否有效。 + public String us; // 【可选】唯一标识请求,增加链接唯一性 + public String sign; // 【可选】防盗链签名 + + public int exper = -1; // 【V2可选】试看时长,单位:秒。可选 + + @Override + public String toString() { + return "SuperPlayerVideoId{" + + ", fileId='" + fileId + '\'' + + ", timeout='" + timeout + '\'' + + ", exper=" + exper + + ", us='" + us + '\'' + + ", sign='" + sign + '\'' + + '}'; + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/entity/VideoClassification.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/entity/VideoClassification.java new file mode 100644 index 0000000..a506047 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/entity/VideoClassification.java @@ -0,0 +1,40 @@ +package com.tencent.liteav.demo.superplayer.model.entity; + +import java.util.List; + +/** + * Created by yuejiaoli on 2018/7/6. + * + * 视频画质信息 + */ + +public class VideoClassification { + + private String id; + private String name; + private List definitionList; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getDefinitionList() { + return definitionList; + } + + public void setDefinitionList(List definitionList) { + this.definitionList = definitionList; + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/entity/VideoQuality.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/entity/VideoQuality.java new file mode 100644 index 0000000..f7662a4 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/entity/VideoQuality.java @@ -0,0 +1,25 @@ +package com.tencent.liteav.demo.superplayer.model.entity; + +/** + * Created by yuejiaoli on 2018/7/7. + *

+ * 清晰度 + */ + +public class VideoQuality { + + public int index; + public int bitrate; + public String name; + public String title; + public String url; + + public VideoQuality() { + } + + public VideoQuality(int index, String title, String url) { + this.index = index; + this.title = title; + this.url = url; + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/net/HttpURLClient.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/net/HttpURLClient.java new file mode 100644 index 0000000..77a9a88 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/net/HttpURLClient.java @@ -0,0 +1,144 @@ +package com.tencent.liteav.demo.superplayer.model.net; + +import android.os.AsyncTask; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; + +/** + * Created by hans on 2018/9/11. + *

+ * 超级播放器模块由于涉及查询视频信息,所以需要有一个内置的HTTP请求模块 + *

+ * 为了不引入额外的网络请求库,这里使用原生的Java HTTPURLConnection实现 + *

+ * 推荐您修改网络模块,使用您项目中的网络请求库,如okHTTP、Volley等 + */ +public class HttpURLClient { + + private static class Holder { + static final HttpURLClient INSTANCE = new HttpURLClient(); + } + + public static HttpURLClient getInstance() { + return Holder.INSTANCE; + } + + /** + * get请求 + * + * @param urlStr + * @param callback + */ + public void get(final String urlStr, final OnHttpCallback callback) { + AsyncTask.execute(new Runnable() { + @Override + public void run() { + BufferedReader bufferedReader = null; + try { + URL url = new URL(urlStr); + URLConnection connection = url.openConnection(); + connection.setConnectTimeout(15000); + connection.setReadTimeout(15000); + connection.connect(); + InputStream in = connection.getInputStream(); + if (in == null) { + if (callback != null) + callback.onError(); + return; + } + bufferedReader = new BufferedReader(new InputStreamReader(in)); + String line = null; + StringBuilder sb = new StringBuilder(); + while ((line = bufferedReader.readLine()) != null) { + sb.append(line); + } + if (callback != null) + callback.onSuccess(sb.toString()); + } catch (IOException e) { + e.printStackTrace(); + if (callback != null) + callback.onError(); + } finally { + if (bufferedReader != null) { + try { + bufferedReader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + }); + } + + /** + * post json数据请求 + * + * @param urlStr + * @param callback + */ + public void postJson(final String urlStr, final String json, final OnHttpCallback callback) { + AsyncTask.execute(new Runnable() { + @Override + public void run() { + BufferedReader bufferedReader = null; + try { + URL url = new URL(urlStr); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setConnectTimeout(15000); + connection.setReadTimeout(15000); + connection.setRequestMethod("POST"); + connection.addRequestProperty("Content-Type", "application/json; charset=utf-8"); + connection.setDoInput(true); + connection.setDoOutput(true); + connection.connect(); + + OutputStream outputStream = connection.getOutputStream(); + outputStream.write(json.getBytes()); + outputStream.flush(); + outputStream.close(); + + InputStream in = connection.getInputStream(); + if (in == null) { + if (callback != null) + callback.onError(); + return; + } + bufferedReader = new BufferedReader(new InputStreamReader(in)); + String line = null; + StringBuilder sb = new StringBuilder(); + while ((line = bufferedReader.readLine()) != null) { + sb.append(line); + } + if (callback != null) + callback.onSuccess(sb.toString()); + } catch (IOException e) { + e.printStackTrace(); + if (callback != null) + callback.onError(); + } finally { + if (bufferedReader != null) { + try { + bufferedReader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + }); + } + + public interface OnHttpCallback { + void onSuccess(String result); + + void onError(); + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/net/LogReport.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/net/LogReport.java new file mode 100644 index 0000000..e750233 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/net/LogReport.java @@ -0,0 +1,102 @@ +package com.tencent.liteav.demo.superplayer.model.net; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; + +import com.tencent.liteav.basic.log.TXCLog; + +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Created by liyuejiao on 2018/7/19. + * + * 数据上报模块 + */ +public class LogReport { + + private static final String TAG = "TCLogReport"; + private String mAppName; + private String mPackageName; + //ELK上报事件 + public static final String ELK_ACTION_CHANGE_RESOLUTION = "change_resolution"; + public static final String ELK_ACTION_TIMESHIFT = "timeshift"; + public static final String ELK_ACTION_FLOATMOE = "floatmode"; + public static final String ELK_ACTION_LIVE_TIME = "superlive"; + public static final String ELK_ACTION_VOD_TIME = "supervod"; + public static final String ELK_ACTION_CHANGE_SPEED = "change_speed"; + public static final String ELK_ACTION_MIRROR = "mirror"; + public static final String ELK_ACTION_SOFT_DECODE = "soft_decode"; + public static final String ELK_ACTION_HW_DECODE = "hw_decode"; + public static final String ELK_ACTION_IMAGE_SPRITE = "image_sprite"; + public static final String ELK_ACTION_PLAYER_POINT = "player_point"; + + private LogReport() { + } + + private static class Holder { + private static LogReport instance = new LogReport(); + } + + public static LogReport getInstance() { + return Holder.instance; + } + + public void uploadLogs(String action, long usedtime, int fileid) { + String reqUrl = "https://ilivelog.qcloud.com"; + String body = ""; + try { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("action", action); + jsonObject.put("fileid", fileid); + jsonObject.put("type", "log"); + jsonObject.put("bussiness", "superplayer"); + jsonObject.put("usedtime", usedtime); + jsonObject.put("platform", "android"); + if (mAppName != null) { + jsonObject.put("appname", mAppName); + } + if (mPackageName != null) { + jsonObject.put("appidentifier", mPackageName); + } + body = jsonObject.toString(); + TXCLog.d(TAG, body); + } catch (JSONException e) { + e.printStackTrace(); + } + HttpURLClient.getInstance().postJson(reqUrl, body, new HttpURLClient.OnHttpCallback() { + @Override + public void onSuccess(String result) { + + } + + @Override + public void onError() { + + } + }); + } + + public void setAppName(Context context) { + if (context == null) { + return; + } + ApplicationInfo applicationInfo = context.getApplicationInfo(); + int stringId = applicationInfo.labelRes; + mAppName = stringId == 0 ? applicationInfo.nonLocalizedLabel.toString() : context.getString(stringId); + } + + public void setPackageName(Context context) { + if (context == null) { + return; + } + try { + PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); + // 当前版本的包名 + mPackageName = info.packageName; + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/IPlayInfoParser.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/IPlayInfoParser.java new file mode 100644 index 0000000..ac26424 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/IPlayInfoParser.java @@ -0,0 +1,83 @@ +package com.tencent.liteav.demo.superplayer.model.protocol; + +import com.tencent.liteav.demo.superplayer.model.entity.PlayImageSpriteInfo; +import com.tencent.liteav.demo.superplayer.model.entity.PlayKeyFrameDescInfo; +import com.tencent.liteav.demo.superplayer.model.entity.ResolutionName; +import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality; + +import java.util.List; + +/** + * 视频信息协议解析接口 + */ +public interface IPlayInfoParser { + /** + * 获取未加密视频播放url,若没有获取sampleaes url + * + * @return url字符串 + */ + String getURL(); + + /** + * 获取加密视频播放url + * + * @return url字符串 + */ + String getEncryptedURL(PlayInfoConstant.EncryptedURLType type); + + /** + * 获取加密token + * + * @return token字符串 + */ + String getToken(); + + /** + * 获取视频名称 + * + * @return 视频名称字符串 + */ + String getName(); + + /** + * 获取雪碧图信息 + * + * @return 雪碧图信息对象 + */ + PlayImageSpriteInfo getImageSpriteInfo(); + + /** + * 获取关键帧信息 + * + * @return 关键帧信息数组 + */ + List getKeyFrameDescInfo(); + + /** + * 获取画质信息 + * + * @return 画质信息数组 + */ + List getVideoQualityList(); + + /** + * 获取默认画质信息 + * + * @return 默认画质信息对象 + */ + VideoQuality getDefaultVideoQuality(); + + /** + * 获取视频画质别名列表 + * + * @return 画质别名数组 + */ + List getResolutionNameList(); + + /** + * 获取 DRM 加密类型 + * @return + */ + String getDRMType(); + +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/IPlayInfoProtocol.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/IPlayInfoProtocol.java new file mode 100644 index 0000000..7a14fc9 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/IPlayInfoProtocol.java @@ -0,0 +1,102 @@ +package com.tencent.liteav.demo.superplayer.model.protocol; + +import com.tencent.liteav.demo.superplayer.model.entity.PlayImageSpriteInfo; +import com.tencent.liteav.demo.superplayer.model.entity.PlayKeyFrameDescInfo; +import com.tencent.liteav.demo.superplayer.model.entity.ResolutionName; +import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality; + +import java.util.List; + +/** + * 视频信息协议接口 + */ +public interface IPlayInfoProtocol { + /** + * 发送视频信息协议网络请求 + * + * @param callback 协议请求回调 + */ + void sendRequest(IPlayInfoRequestCallback callback); + + /** + * 中途取消请求 + */ + void cancelRequest(); + + /** + * 获取视频播放url + * + * @return 视频播放url字符串 + */ + String getUrl(); + + /** + * 获取加密视频播放url + * + * @return url字符串 + */ + String getEncyptedUrl(PlayInfoConstant.EncryptedURLType type); + + /** + * 获取加密token + * + * @return token字符串 + */ + String getToken(); + + /** + * 获取视频名称 + * + * @return 视频名称字符串 + */ + String getName(); + + /** + * 获取雪碧图信息 + * + * @return 雪碧图信息对象 + */ + PlayImageSpriteInfo getImageSpriteInfo(); + + /** + * 获取关键帧信息 + * + * @return 关键帧信息数组 + */ + List getKeyFrameDescInfo(); + + /** + * 获取画质信息 + * + * @return 画质信息数组 + */ + List getVideoQualityList(); + + /** + * 获取默认画质 + * + * @return 默认画质信息对象 + */ + VideoQuality getDefaultVideoQuality(); + + /** + * 获取视频画质别名列表 + * + * @return 画质别名数组 + */ + List getResolutionNameList(); + + /** + * 透传内容 + * + * @return 透传内容 + */ + String getPenetrateContext(); + + + /** + * 获取 DRM 加密类型 + * @return + */ + String getDRMType(); +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/IPlayInfoRequestCallback.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/IPlayInfoRequestCallback.java new file mode 100644 index 0000000..276ef63 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/IPlayInfoRequestCallback.java @@ -0,0 +1,23 @@ +package com.tencent.liteav.demo.superplayer.model.protocol; + +/** + * 视频信息协议请求回调接口 + */ +public interface IPlayInfoRequestCallback { + + /** + * 成功回调 + * + * @param protocol 视频信息协议实现类 + * @param param 视频信息协议输入参数 + */ + void onSuccess(IPlayInfoProtocol protocol, PlayInfoParams param); + + /** + * 错误回调 + * + * @param errCode 错误码 + * @param message 错误信息 + */ + void onError(int errCode, String message); +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/PlayInfoConstant.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/PlayInfoConstant.java new file mode 100644 index 0000000..408640b --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/PlayInfoConstant.java @@ -0,0 +1,20 @@ +package com.tencent.liteav.demo.superplayer.model.protocol; + +public class PlayInfoConstant { + + public enum EncryptedURLType { + + SIMPLEAES("SimpleAES"), + WIDEVINE("widevine"); + + EncryptedURLType(String type){ + value = type; + } + + private String value; + + public String getValue(){ + return value; + } + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/PlayInfoParams.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/PlayInfoParams.java new file mode 100644 index 0000000..585ea4f --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/PlayInfoParams.java @@ -0,0 +1,29 @@ +package com.tencent.liteav.demo.superplayer.model.protocol; + +import com.tencent.liteav.demo.superplayer.SuperPlayerVideoId; +import com.tencent.liteav.demo.superplayer.model.entity.SuperPlayerVideoIdV2; + +/** + * 视频信息协议解析需要传入的参数 + */ +public class PlayInfoParams { + //必选 + public int appId; // 腾讯云视频appId + public String fileId; // 腾讯云视频fileId + + public SuperPlayerVideoId videoId; //v4 协议参数 + public SuperPlayerVideoIdV2 videoIdV2; //v2 协议参数 + + public PlayInfoParams() { + } + + @Override + public String toString() { + return "TCPlayInfoParams{" + + ", appId='" + appId + '\'' + + ", fileId='" + fileId + '\'' + + ", v4='" + (videoId != null ? videoId.toString() : "") + '\'' + + ", v2='" + (videoIdV2 != null ? videoIdV2.toString() : "") + '\'' + + '}'; + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/PlayInfoParserV2.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/PlayInfoParserV2.java new file mode 100644 index 0000000..90880f3 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/PlayInfoParserV2.java @@ -0,0 +1,455 @@ +package com.tencent.liteav.demo.superplayer.model.protocol; + +import android.util.Log; + +import com.tencent.liteav.basic.log.TXCLog; +import com.tencent.liteav.demo.superplayer.model.entity.PlayImageSpriteInfo; +import com.tencent.liteav.demo.superplayer.model.entity.PlayInfoStream; +import com.tencent.liteav.demo.superplayer.model.entity.PlayKeyFrameDescInfo; +import com.tencent.liteav.demo.superplayer.model.entity.ResolutionName; +import com.tencent.liteav.demo.superplayer.model.entity.VideoClassification; +import com.tencent.liteav.demo.superplayer.model.utils.VideoQualityUtils; +import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; + +/** + * V2视频信息协议解析实现类 + * + * 负责解析V2视频信息协议请求响应的Json数据 + */ +public class PlayInfoParserV2 implements IPlayInfoParser{ + private static final String TAG = "TCPlayInfoParserV2"; + + private JSONObject mResponse; // 协议请求返回的Json数据 + + //播放器配置信息 + private String mDefaultVideoClassification; // 默认视频清晰度名称 + private List mVideoClassificationList; // 视频清晰度信息列表 + + private PlayImageSpriteInfo mImageSpriteInfo; // 雪碧图信息 + private List mKeyFrameDescInfo; // 关键帧打点信息 + //视频信息 + private String mName; // 视频名称 + private PlayInfoStream mSourceStream; // 源视频流信息 + private PlayInfoStream mMasterPlayList; // 主播放视频流信息 + + private LinkedHashMap mTranscodePlayList; // 转码视频信息列表 + + private String mURL; // 视频播放url + private List mVideoQualityList; // 视频画质信息列表 + private VideoQuality mDefaultVideoQuality; // 默认视频画质 + + public PlayInfoParserV2(JSONObject response) { + mResponse = response; + parsePlayInfo(); + } + + /** + * 从视频信息协议请求响应的Json数据中解析出视频信息 + * + * 解析流程: + * + * 1、解析播放器信息(playerInfo)字段,获取视频清晰度列表{@link #mVideoClassificationList}以及默认清晰度{@link #mDefaultVideoClassification} + * + * 2、解析雪碧图信息(imageSpriteInfo)字段,获取雪碧图信息{@link #mImageSpriteInfo} + * + * 3、解析关键帧信息(keyFrameDescInfo)字段,获取关键帧信息{@link #mKeyFrameDescInfo} + * + * 4、解析视频信息(videoInfo)字段,获取视频名称{@link #mName}、源视频信息{@link #mSourceStream}、 + * 主视频列表{@link #mMasterPlayList}、转码视频列表{@link #mTranscodePlayList} + * + * 5、从主视频列表、转码视频列表、源视频信息中解析出视频播放url{@link #mURL}、画质信息{@link #mVideoQualityList}、 + * 默认画质{@link #mDefaultVideoQuality} + */ + private void parsePlayInfo() { + try { + JSONObject playerInfo = mResponse.optJSONObject("playerInfo"); + if (playerInfo != null) { + mDefaultVideoClassification = parseDefaultVideoClassification(playerInfo); + mVideoClassificationList = parseVideoClassificationList(playerInfo); + } + JSONObject imageSpriteInfo = mResponse.optJSONObject("imageSpriteInfo"); + if (imageSpriteInfo != null) { + mImageSpriteInfo = parseImageSpriteInfo(imageSpriteInfo); + } + JSONObject keyFrameDescInfo = mResponse.optJSONObject("keyFrameDescInfo"); + if (keyFrameDescInfo != null) { + mKeyFrameDescInfo = parseKeyFrameDescInfo(keyFrameDescInfo); + } + JSONObject videoInfo = mResponse.optJSONObject("videoInfo"); + if (videoInfo != null) { + mName = parseName(videoInfo); + mSourceStream = parseSourceStream(videoInfo); + mMasterPlayList = parseMasterPlayList(videoInfo); + mTranscodePlayList = parseTranscodePlayList(videoInfo); + } + parseVideoInfo(); + } catch (JSONException e) { + TXCLog.e(TAG, Log.getStackTraceString(e)); + } + } + + /** + * 解析默认视频清晰度信息 + * + * @param playerInfo 包含默认视频清晰度信息的Json对象 + * @return 默认视频清晰度名称字符串 + */ + private String parseDefaultVideoClassification(JSONObject playerInfo) throws JSONException { + return playerInfo.getString("defaultVideoClassification"); + } + + /** + * 解析视频清晰度信息 + * + * @param playerInfo 包含默认视频类别信息的Json对象 + * @return 视频清晰度信息数组 + */ + private List parseVideoClassificationList(JSONObject playerInfo) throws JSONException { + List arrayList = new ArrayList<>(); + JSONArray videoClassificationArray = playerInfo.getJSONArray("videoClassification"); + if (videoClassificationArray != null) { + for (int i = 0; i < videoClassificationArray.length(); i++) { + JSONObject object = videoClassificationArray.getJSONObject(i); + + VideoClassification classification = new VideoClassification(); + classification.setId(object.getString("id")); + classification.setName(object.getString("name")); + + List definitionList = new ArrayList<>(); + JSONArray array = object.getJSONArray("definitionList"); + if (array != null) { + for (int j = 0; j < array.length(); j++) { + int definition = array.getInt(j); + definitionList.add(definition); + } + } + classification.setDefinitionList(definitionList); + arrayList.add(classification); + } + } + return arrayList; + } + + /** + * 解析雪碧图信息 + * + * @param imageSpriteInfo 包含雪碧图信息的Json对象 + * @return 雪碧图信息对象 + */ + private PlayImageSpriteInfo parseImageSpriteInfo(JSONObject imageSpriteInfo) throws JSONException { + JSONArray imageSpriteList = imageSpriteInfo.getJSONArray("imageSpriteList"); + if (imageSpriteList != null) { + JSONObject spriteJSONObject = imageSpriteList.getJSONObject(imageSpriteList.length() - 1); //获取最后一个来解析 + PlayImageSpriteInfo info = new PlayImageSpriteInfo(); + info.webVttUrl = spriteJSONObject.getString("webVttUrl"); + JSONArray jsonArray = spriteJSONObject.getJSONArray("imageUrls"); + List imageUrls = new ArrayList<>(); + for (int i = 0; i < jsonArray.length(); i++) { + String url = jsonArray.getString(i); + imageUrls.add(url); + } + info.imageUrls = imageUrls; + return info; + } + return null; + } + + /** + *解析关键帧打点信息 + * + * @param keyFrameDescInfo 包含关键帧信息的Json对象 + * @return 关键帧信息数组 + */ + private List parseKeyFrameDescInfo(JSONObject keyFrameDescInfo) throws JSONException { + JSONArray jsonArr = keyFrameDescInfo.getJSONArray("keyFrameDescList"); + if (jsonArr != null) { + List infoList = new ArrayList<>(); + for (int i = 0; i < jsonArr.length(); i++) { + String content = jsonArr.getJSONObject(i).getString("content"); + long time = jsonArr.getJSONObject(i).getLong("timeOffset"); + float timeS = (float) (time / 1000.0);//转换为秒 + PlayKeyFrameDescInfo info = new PlayKeyFrameDescInfo(); + try { + info.content = URLDecoder.decode(content, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + info.content = ""; + } + info.time = timeS; + infoList.add(info); + } + return infoList; + } + return null; + } + + /** + * 解析视频名称 + * + * @param videoInfo 包含视频名称信息的Json对象 + * @return 视频名称字符串 + * @throws JSONException + */ + private String parseName(JSONObject videoInfo) throws JSONException { + JSONObject basicInfo = videoInfo.getJSONObject("basicInfo"); + if (basicInfo != null) { + return basicInfo.getString("name"); + } + return null; + } + + /** + * 解析源视频流信息 + * + * @param videoInfo 包含源视频流信息的Json对象 + * @return 源视频流信息对象 + */ + private PlayInfoStream parseSourceStream(JSONObject videoInfo) throws JSONException { + if (!videoInfo.has("sourceVideo")) + return null; + JSONObject sourceVideo = videoInfo.getJSONObject("sourceVideo"); + if (sourceVideo != null) { + PlayInfoStream stream = new PlayInfoStream(); + stream.url = sourceVideo.getString("url"); + stream.duration = sourceVideo.getInt("duration"); + stream.width = sourceVideo.getInt("width"); + stream.height = sourceVideo.getInt("height"); + stream.size = sourceVideo.getInt("size"); + stream.bitrate = sourceVideo.getInt("bitrate"); + return stream; + } + return null; + } + + /** + * 解析主播放视频流信息 + * + * @param videoInfo 包含主播放视频流信息的Json对象 + * @return 主播放视频流信息对象 + */ + private PlayInfoStream parseMasterPlayList(JSONObject videoInfo) throws JSONException { + if (!videoInfo.has("masterPlayList")) + return null; + JSONObject masterPlayList = videoInfo.getJSONObject("masterPlayList"); + if (masterPlayList != null) { + PlayInfoStream stream = new PlayInfoStream(); + stream.url = masterPlayList.getString("url"); + return stream; + } + return null; + } + + /** + * 解析转码视频流信息 + * + * 转码视频流信息{@link #mTranscodePlayList}中不包含清晰度名称,需要与视频清晰度信息{@link #mVideoClassificationList}做匹配 + * + * @param videoInfo 包含转码视频流信息的Json对象 + * @return 转码视频信息列表 key: 清晰度名称 value: 视频流信息 + */ + private LinkedHashMap parseTranscodePlayList(JSONObject videoInfo) throws JSONException { + List transcodeList = parseStreamList(videoInfo); + if (transcodeList == null) return mTranscodePlayList; + for (int i = 0; i < transcodeList.size(); i++) { + PlayInfoStream stream = transcodeList.get(i); + // 匹配清晰度 + if (mVideoClassificationList != null) { + for (int j = 0; j < mVideoClassificationList.size(); j++) { + VideoClassification classification = mVideoClassificationList.get(j); + List definitionList = classification.getDefinitionList(); + if (definitionList.contains(stream.definition)) { + stream.id = classification.getId(); + stream.name = classification.getName(); + } + } + } + } + //清晰度去重 + LinkedHashMap idList = new LinkedHashMap<>(); + for (int i = 0; i < transcodeList.size(); i++) { + PlayInfoStream stream = transcodeList.get(i); + if (!idList.containsKey(stream.id)) { + idList.put(stream.id, stream); + } else { + PlayInfoStream copy = idList.get(stream.id); + if (copy.getUrl().endsWith("mp4")) { // 列表中url是mp4,则进行下一步 + continue; + } + if (stream.getUrl().endsWith("mp4")) { // 新判断的url是mp4,则替换列表中 + idList.remove(copy.id); + idList.put(stream.id, stream); + } + } + } + //按清晰度排序 + return idList; + } + + /** + * 解析转码视频信息 + * + * @param videoInfo 包含转码视频信息的Json对象 + * @return 转码视频是信息数组 + */ + private List parseStreamList(JSONObject videoInfo) throws JSONException { + List streamList = new ArrayList<>(); + JSONArray transcodeList = videoInfo.optJSONArray("transcodeList"); + if (transcodeList != null) { + for (int i = 0; i < transcodeList.length(); i++) { + JSONObject transcode = transcodeList.getJSONObject(i); + PlayInfoStream stream = new PlayInfoStream(); + stream.url = transcode.getString("url"); + stream.duration = transcode.getInt("duration"); + stream.width = transcode.getInt("width"); + stream.height = transcode.getInt("height"); + stream.size = transcode.getInt("size"); + stream.bitrate = transcode.getInt("bitrate"); + stream.definition = transcode.getInt("definition"); + streamList.add(stream); + } + } + return streamList; + } + + /** + * 解析视频播放url、画质列表、默认画质 + * + * V2协议响应Json数据中可能包含多个视频播放信息:主播放视频信息{@link #mMasterPlayList}、转码视频{@link #mTranscodePlayList}、 + * 源视频{@link #mSourceStream}, 播放优先级依次递减 + * + * 从优先级最高的视频信息中解析出播放信息 + */ + private void parseVideoInfo() { + //有主播放视频信息时,从中解析出支持多码率播放的url + if (mMasterPlayList != null) { + mURL = mMasterPlayList.getUrl(); + return; + } + //无主播放信息,从转码视频信息中解析出各码流信息 + if (mTranscodePlayList != null && mTranscodePlayList.size() != 0) { + PlayInfoStream stream = mTranscodePlayList.get(mDefaultVideoClassification); + String videoURL = null; + if (stream != null) { + videoURL = stream.getUrl(); + } else { + for (PlayInfoStream stream1 : mTranscodePlayList.values()) { + if (stream1 != null && stream1.getUrl() != null) { + stream = stream1; + videoURL = stream1.getUrl(); + break; + } + } + } + if (videoURL != null) { + mVideoQualityList = VideoQualityUtils.convertToVideoQualityList(mTranscodePlayList); + mDefaultVideoQuality = VideoQualityUtils.convertToVideoQuality(stream); + mURL = videoURL; + return; + } + } + //无主播放信息、转码信息,从源视频信息中解析出播放信息 + if (mSourceStream != null) { + if (mDefaultVideoClassification != null) { + mDefaultVideoQuality = VideoQualityUtils.convertToVideoQuality(mSourceStream, mDefaultVideoClassification); + mVideoQualityList = new ArrayList<>(); + mVideoQualityList.add(mDefaultVideoQuality); + } + mURL = mSourceStream.getUrl(); + } + } + + /** + * 获取视频播放url + * + * @return url字符串 + */ + @Override + public String getURL() { + return mURL; + } + + @Override + public String getEncryptedURL(PlayInfoConstant.EncryptedURLType type) { + return null; + } + + @Override + public String getToken() { + return null; + } + + /** + * 获取视频名称 + * + * @return 视频名称字符串 + */ + @Override + public String getName() { + return mName; + } + + /** + * 获取雪碧图信息 + * + * @return 雪碧图信息对象 + */ + @Override + public PlayImageSpriteInfo getImageSpriteInfo() { + return mImageSpriteInfo; + } + + /** + * 获取关键帧信息 + * + * @return 关键帧信息数组 + */ + @Override + public List getKeyFrameDescInfo() { + return mKeyFrameDescInfo; + } + + /** + * 获取画质信息 + * + * @return 画质信息数组 + */ + @Override + public List getVideoQualityList() { + return mVideoQualityList; + } + + /** + * 获取默认画质信息 + * + * @return 默认画质信息对象 + */ + @Override + public VideoQuality getDefaultVideoQuality() { + return mDefaultVideoQuality; + } + + /** + * 获取视频画质别名列表 + * + * @return 画质别名数组 + */ + @Override + public List getResolutionNameList() { + return null; + } + + @Override + public String getDRMType() { + return ""; + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/PlayInfoParserV4.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/PlayInfoParserV4.java new file mode 100644 index 0000000..f1fe3c7 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/PlayInfoParserV4.java @@ -0,0 +1,228 @@ +package com.tencent.liteav.demo.superplayer.model.protocol; + +import android.text.TextUtils; +import android.util.Log; + +import com.tencent.liteav.basic.log.TXCLog; +import com.tencent.liteav.demo.superplayer.model.entity.PlayImageSpriteInfo; +import com.tencent.liteav.demo.superplayer.model.entity.PlayKeyFrameDescInfo; +import com.tencent.liteav.demo.superplayer.model.entity.EncryptedStreamingInfo; +import com.tencent.liteav.demo.superplayer.model.entity.ResolutionName; +import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +/** + * V4视频协议解析实现类 + * + * 负责解析V4视频信息协议请求响应的Json数据 + */ +public class PlayInfoParserV4 implements IPlayInfoParser { + + private static final String TAG = "TCPlayInfoParserV4"; + + private JSONObject mResponse; // 协议请求返回的Json数据 + private String mName; // 视频名称 + private String mURL; // 未加密视频播放url + private String mToken; // DRM token + + private List mEncryptedStreamingInfoList;// 加密视频播放url 数组 + private PlayImageSpriteInfo mImageSpriteInfo; // 雪碧图信息 + private List mKeyFrameDescInfo; // 关键帧信息 + private List mResolutionNameList; // 自适应码流画质名称匹配信息 + private String mDRMType; + + public PlayInfoParserV4(JSONObject response) { + mResponse = response; + parsePlayInfo(); + } + + private void parseSubStreams(JSONArray substreams) throws JSONException { + if (substreams != null && substreams.length() > 0) { + mResolutionNameList = new ArrayList<>(); + for (int i = 0; i < substreams.length(); i++) { + JSONObject jsonObject = substreams.getJSONObject(i); + ResolutionName resolutionName = new ResolutionName(); + int width = jsonObject.optInt("width"); + int height = jsonObject.optInt("height"); + resolutionName.width = width; + resolutionName.height = height; + resolutionName.name = jsonObject.optString("resolutionName"); + resolutionName.type = jsonObject.optString("type"); + mResolutionNameList.add(resolutionName); + } + } + } + + /** + * 从视频信息协议请求响应的Json数据中解析出视频信息 + */ + private void parsePlayInfo() { + try { + JSONObject media = mResponse.getJSONObject("media"); + if (media != null) { + //解析视频名称 + JSONObject basicInfo = media.optJSONObject("basicInfo"); + if (basicInfo != null) { + mName = basicInfo.optString("name"); + } + //解析视频播放url + JSONObject streamingInfo = media.getJSONObject("streamingInfo"); + if (streamingInfo != null) { + JSONObject plainoutObj = streamingInfo.optJSONObject("plainOutput");//未加密的输出 + if (plainoutObj != null) { + mURL = plainoutObj.optString("url");//未加密直接解析出视频url + parseSubStreams(plainoutObj.optJSONArray("subStreams")); + } + JSONArray drmoutputobj = streamingInfo.optJSONArray("drmOutput");//加密输出 + if (drmoutputobj != null && drmoutputobj.length() > 0) { + mEncryptedStreamingInfoList = new ArrayList<>(); + for (int i = 0; i < drmoutputobj.length(); i++) { + JSONObject jsonObject = drmoutputobj.optJSONObject(i); + String drmType = jsonObject.optString("type"); + String url = jsonObject.optString("url"); + EncryptedStreamingInfo info = new EncryptedStreamingInfo(); + info.drmType = drmType; + info.url = url; + mDRMType = drmType; + mEncryptedStreamingInfoList.add(info); + parseSubStreams(jsonObject.optJSONArray("subStreams")); + } + } + mToken = streamingInfo.optString("drmToken"); + } + //解析雪碧图信息 + JSONObject imageSpriteInfo = media.optJSONObject("imageSpriteInfo"); + if (imageSpriteInfo != null) { + mImageSpriteInfo = new PlayImageSpriteInfo(); + mImageSpriteInfo.webVttUrl = imageSpriteInfo.getString("webVttUrl"); + JSONArray jsonArray = imageSpriteInfo.optJSONArray("imageUrls"); + if (jsonArray != null && jsonArray.length() > 0) { + List imageUrls = new ArrayList<>(); + for (int i = 0; i < jsonArray.length(); i++) { + String url = jsonArray.getString(i); + imageUrls.add(url); + } + mImageSpriteInfo.imageUrls = imageUrls; + } + } + //解析关键帧信息 + JSONObject keyFrameDescInfo = media.optJSONObject("keyFrameDescInfo"); + if (keyFrameDescInfo != null) { + mKeyFrameDescInfo = new ArrayList<>(); + JSONArray keyFrameDescList = keyFrameDescInfo.optJSONArray("keyFrameDescList"); + if (keyFrameDescList != null && keyFrameDescList.length() > 0) { + for (int i = 0; i < keyFrameDescList.length(); i++) { + JSONObject jsonObject = keyFrameDescList.getJSONObject(i); + PlayKeyFrameDescInfo info = new PlayKeyFrameDescInfo(); + info.time = jsonObject.optLong("timeOffset"); + info.content = jsonObject.optString("content"); + mKeyFrameDescInfo.add(info); + } + } + } + } + } catch (JSONException e) { + TXCLog.e(TAG, Log.getStackTraceString(e)); + } + } + + /** + * 获取视频播放url + * + * @return url字符串 + */ + @Override + public String getURL() { + String url = mURL; + if (!TextUtils.isEmpty(mToken)) { + url = getEncryptedURL(PlayInfoConstant.EncryptedURLType.SIMPLEAES); + } + return url; + } + + @Override + public String getEncryptedURL(PlayInfoConstant.EncryptedURLType type) { + for (EncryptedStreamingInfo info : mEncryptedStreamingInfoList) { + if (info.drmType != null && info.drmType.equalsIgnoreCase(type.getValue())) { + return info.url; + } + } + return null; + } + + @Override + public String getToken() { + return TextUtils.isEmpty(mToken) ? null : mToken; + } + + /** + * 获取视频名称 + * + * @return 视频名称字符串 + */ + @Override + public String getName() { + return mName; + } + + /** + * 获取雪碧图信息 + * + * @return 雪碧图信息对象 + */ + @Override + public PlayImageSpriteInfo getImageSpriteInfo() { + return mImageSpriteInfo; + } + + /** + * 获取关键帧信息 + * + * @return 关键帧信息数组 + */ + @Override + public List getKeyFrameDescInfo() { + return mKeyFrameDescInfo; + } + + /** + * 获取画质信息 + * + * @return 画质信息数组 + */ + @Override + public List getVideoQualityList() { + return null; + } + + /** + * 获取默认画质信息 + * + * @return 默认画质信息对象 + */ + @Override + public VideoQuality getDefaultVideoQuality() { + return null; + } + + /** + * 获取视频画质别名列表 + * + * @return 画质别名数组 + */ + @Override + public List getResolutionNameList() { + return mResolutionNameList; + } + + @Override + public String getDRMType() { + return mDRMType; + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/PlayInfoProtocolV2.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/PlayInfoProtocolV2.java new file mode 100644 index 0000000..807e1a8 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/PlayInfoProtocolV2.java @@ -0,0 +1,275 @@ +package com.tencent.liteav.demo.superplayer.model.protocol; + +import android.os.Handler; +import android.os.Looper; +import android.text.TextUtils; + +import com.tencent.liteav.basic.log.TXCLog; +import com.tencent.liteav.demo.superplayer.model.entity.PlayImageSpriteInfo; +import com.tencent.liteav.demo.superplayer.model.entity.PlayKeyFrameDescInfo; +import com.tencent.liteav.demo.superplayer.model.entity.ResolutionName; +import com.tencent.liteav.demo.superplayer.model.net.HttpURLClient; +import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.List; + +/** + * V2视频信息协议实现类 + *

+ * 负责V2视频信息协议的请求控制与数据获取 + */ +public class PlayInfoProtocolV2 implements IPlayInfoProtocol { + + private static final String TAG = "TCPlayInfoProtocolV2"; + + private final String BASE_URLS_V2 = "https://playvideo.qcloud.com/getplayinfo/v2"; // V2协议请求地址 + + private Handler mMainHandler; // 用于切换线程 + private PlayInfoParams mParams; // 协议请求输入的参数 + private IPlayInfoParser mParser; // 协议请求返回Json的解析对象 + + public PlayInfoProtocolV2(PlayInfoParams params) { + mParams = params; + mMainHandler = new Handler(Looper.getMainLooper()); + } + + /** + * 发送视频信息协议网络请求 + * + * @param callback 协议请求回调 + */ + @Override + public void sendRequest(final IPlayInfoRequestCallback callback) { + if (mParams.fileId == null) { + return; + } + String urlStr = makeUrlString(); + TXCLog.i(TAG, "getVodByFileId: url = " + urlStr); + HttpURLClient.getInstance().get(urlStr, new HttpURLClient.OnHttpCallback() { + @Override + public void onSuccess(String result) { + TXCLog.i(TAG, "http request success: result = " + result); + parseJson(result, callback); + runOnMainThread(new Runnable() { + @Override + public void run() { + callback.onSuccess(PlayInfoProtocolV2.this, mParams); + } + }); + } + + @Override + public void onError() { + runOnMainThread(new Runnable() { + @Override + public void run() { + if (callback != null) { + callback.onError(-1, "http request error."); + } + } + }); + } + }); + } + + /** + * 拼装协议请求url + * + * @return 协议请求url字符串 + */ + private String makeUrlString() { + String urlStr = String.format("%s/%d/%s", BASE_URLS_V2, mParams.appId, mParams.fileId); + if (mParams.videoIdV2 != null) { + String query = makeQueryString(mParams.videoIdV2.timeout, mParams.videoIdV2.us, mParams.videoIdV2.exper, mParams.videoIdV2.sign); + if (query != null) { + urlStr = urlStr + "?" + query; + } + } + return urlStr; + } + + /** + * 拼装协议请求url中的query字段 + * + * @param timeout 加密链接超时时间戳 + * @param us 唯一标识请求 + * @param exper 试看时长,单位:秒,十进制数值 + * @param sign 签名字符串 + * @return query字段字符串 + */ + private String makeQueryString(String timeout, String us, int exper, String sign) { + StringBuilder str = new StringBuilder(); + if (timeout != null) { + str.append("t=" + timeout + "&"); + } + if (us != null) { + str.append("us=" + us + "&"); + } + if (sign != null) { + str.append("sign=" + sign + "&"); + } + if (exper >= 0) { + str.append("exper=" + exper + "&"); + } + if (str.length() > 1) { + str.deleteCharAt(str.length() - 1); + } + return str.toString(); + } + + /** + * 中途取消请求 + */ + @Override + public void cancelRequest() { + + } + + /** + * 获取视频播放url + * + * @return 视频播放url字符串 + */ + @Override + public String getUrl() { + return mParser == null ? null : mParser.getURL(); + } + + @Override + public String getEncyptedUrl(PlayInfoConstant.EncryptedURLType type) { + return mParser.getEncryptedURL(type); + } + + @Override + public String getToken() { + return mParser.getToken(); + } + + /** + * 获取视频名称 + * + * @return 视频名称字符串 + */ + @Override + public String getName() { + return mParser == null ? null : mParser.getName(); + } + + /** + * 获取雪碧图信息 + * + * @return 雪碧图信息对象 + */ + @Override + public PlayImageSpriteInfo getImageSpriteInfo() { + return mParser == null ? null : mParser.getImageSpriteInfo(); + } + + /** + * 获取关键帧信息 + * + * @return 关键帧信息数组 + */ + @Override + public List getKeyFrameDescInfo() { + return mParser == null ? null : mParser.getKeyFrameDescInfo(); + } + + /** + * 获取画质信息 + * + * @return 画质信息数组 + */ + @Override + public List getVideoQualityList() { + return mParser == null ? null : mParser.getVideoQualityList(); + } + + /** + * 获取默认画质 + * + * @return 默认画质信息对象 + */ + @Override + public VideoQuality getDefaultVideoQuality() { + return mParser == null ? null : mParser.getDefaultVideoQuality(); + } + + /** + * 解析视频信息协议请求响应的Json数据 + * + * @param content 响应Json字符串 + * @param callback 协议请求回调 + */ + private boolean parseJson(String content, final IPlayInfoRequestCallback callback) { + if (TextUtils.isEmpty(content)) { + TXCLog.e(TAG, "parseJsonV2 err, content is empty!"); + runOnMainThread(new Runnable() { + @Override + public void run() { + callback.onError(-1, "request return error!"); + } + }); + return false; + } + try { + JSONObject jsonObject = new JSONObject(content); + final int code = jsonObject.getInt("code"); + final String message = jsonObject.optString("message"); + TXCLog.e(TAG, message); + if (code == 0) { + mParser = new PlayInfoParserV2(jsonObject); + } else { + runOnMainThread(new Runnable() { + @Override + public void run() { + callback.onError(code, message); + } + }); + return false; + } + } catch (JSONException e) { + e.printStackTrace(); + TXCLog.e(TAG, "parseJson err"); + } + return true; + } + + /** + * 切换到主线程 + *

+ * 从视频协议请求回调的子线程切换回主线程 + * + * @param r 需要在主线程中执行的任务 + */ + private void runOnMainThread(Runnable r) { + if (Looper.myLooper() == mMainHandler.getLooper()) { + r.run(); + } else { + mMainHandler.post(r); + } + } + + /** + * 获取视频画质别名列表 + * + * @return 画质别名数组 + */ + @Override + public List getResolutionNameList() { + return mParser == null ? null : mParser.getResolutionNameList(); + } + + @Override + public String getPenetrateContext() { + return null; + } + + @Override + public String getDRMType() { + return mParser != null ? mParser.getDRMType() : ""; + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/PlayInfoProtocolV4.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/PlayInfoProtocolV4.java new file mode 100644 index 0000000..ec4b7ff --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/protocol/PlayInfoProtocolV4.java @@ -0,0 +1,291 @@ +package com.tencent.liteav.demo.superplayer.model.protocol; + +import android.os.Handler; +import android.os.Looper; +import android.text.TextUtils; + +import com.tencent.liteav.basic.log.TXCLog; +import com.tencent.liteav.demo.superplayer.model.entity.PlayImageSpriteInfo; +import com.tencent.liteav.demo.superplayer.model.entity.PlayKeyFrameDescInfo; +import com.tencent.liteav.demo.superplayer.model.entity.ResolutionName; +import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality; +import com.tencent.liteav.demo.superplayer.model.net.HttpURLClient; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.List; + +/** + * V4视频信息协议实现类 + * + * 负责V4视频信息协议的请求控制与数据获取 + */ +public class PlayInfoProtocolV4 implements IPlayInfoProtocol { + private static final String TAG = "TCPlayInfoProtocolV4"; + + private final String BASE_URLS_V4 = "https://playvideo.qcloud.com/getplayinfo/v4"; // V4协议请求地址 + + private Handler mMainHandler; // 用于切换线程 + private PlayInfoParams mParams; // 协议请求输入的参数 + private IPlayInfoParser mParser; // 协议请求返回Json的解析对象 + private String mRequestContext;//透传字段 + + public PlayInfoProtocolV4(PlayInfoParams params) { + mParams = params; + mMainHandler = new Handler(Looper.getMainLooper()); + } + + /** + * 发送视频信息协议网络请求 + * + * @param callback 协议请求回调 + */ + @Override + public void sendRequest(final IPlayInfoRequestCallback callback) { + if (mParams.fileId == null) { + return; + } + String urlString = makeUrlString(); + HttpURLClient.getInstance().get(urlString, new HttpURLClient.OnHttpCallback() { + @Override + public void onSuccess(String result) { + boolean ret = parseJson(result, callback); + if (ret) { + runOnMainThread(new Runnable() { + @Override + public void run() { + callback.onSuccess(PlayInfoProtocolV4.this, mParams); + } + }); + } + } + + @Override + public void onError() { + runOnMainThread(new Runnable() { + @Override + public void run() { + if (callback != null) { + callback.onError(-1, "http request error."); + } + } + }); + } + }); + } + + /** + * 解析视频信息协议请求响应的Json数据 + * + * @param content 响应Json字符串 + * @param callback 协议请求回调 + */ + private boolean parseJson(String content, final IPlayInfoRequestCallback callback) { + if (TextUtils.isEmpty(content)) { + TXCLog.e(TAG, "parseJson err, content is empty!"); + runOnMainThread(new Runnable() { + @Override + public void run() { + callback.onError(-1, "request return error!"); + } + }); + return false; + } + try { + JSONObject jsonObject = new JSONObject(content); + final int code = jsonObject.getInt("code"); + final String message = jsonObject.optString("message"); + final String warning = jsonObject.optString("warning"); + mRequestContext = jsonObject.optString("context"); + TXCLog.i(TAG, "context : " + mRequestContext); + TXCLog.i(TAG, "message: " + message); + TXCLog.i(TAG, "warning: " + warning); + if (code == 0) { + int version = jsonObject.getInt("version"); + if (version == 2) { + mParser = new PlayInfoParserV2(jsonObject); + } else if (version == 4) { + mParser = new PlayInfoParserV4(jsonObject); + } + } else { + runOnMainThread(new Runnable() { + @Override + public void run() { + callback.onError(code, message); + } + }); + return false; + } + } catch (JSONException e) { + e.printStackTrace(); + TXCLog.e(TAG, "parseJson err"); + } + return true; + } + + /** + * 拼装协议请求url + * + * @return 协议请求url字符串 + */ + private String makeUrlString() { + String urlStr = String.format("%s/%d/%s", BASE_URLS_V4, mParams.appId, mParams.fileId); + String psign = makeJWTSignature(mParams); + String query = null; + if (mParams.videoId != null) { + query = makeQueryString(null, psign, null); + } + + if (!TextUtils.isEmpty(query)) { + urlStr = urlStr + "?" + query; + } + TXCLog.d(TAG, "request url: " + urlStr); + return urlStr; + } + + public static String makeJWTSignature(PlayInfoParams params) { + if (params.videoId != null && !TextUtils.isEmpty(params.videoId.pSign)) { + return params.videoId.pSign; + } + return null; + } + + + /** + * 拼装协议请求url中的query字段 + * + * @return query字段字符串 + */ + private String makeQueryString(String pcfg, String psign, String content) { + StringBuilder str = new StringBuilder(); + if (!TextUtils.isEmpty(pcfg)) { + str.append("pcfg=" + pcfg + "&"); + } + + if (!TextUtils.isEmpty(psign)) { + str.append("psign=" + psign + "&"); + } + + if (!TextUtils.isEmpty(content)) { + str.append("context=" + content + "&"); + } + if (str.length() > 1) { + str.deleteCharAt(str.length() - 1); + } + return str.toString(); + } + + /** + * 中途取消请求 + */ + @Override + public void cancelRequest() { + + } + + /** + * 获取视频播放url + * + * @return 视频播放url字符串 + */ + @Override + public String getUrl() { + return mParser == null ? null : mParser.getURL(); + } + + @Override + public String getEncyptedUrl(PlayInfoConstant.EncryptedURLType type) { + return mParser == null ? null : mParser.getEncryptedURL(type); + } + + @Override + public String getToken() { + return mParser == null ? null : mParser.getToken(); + } + + /** + * 获取视频名称 + * + * @return 视频名称字符串 + */ + @Override + public String getName() { + return mParser == null ? null : mParser.getName(); + } + + /** + * 获取雪碧图信息 + * + * @return 雪碧图信息对象 + */ + @Override + public PlayImageSpriteInfo getImageSpriteInfo() { + return mParser == null ? null : mParser.getImageSpriteInfo(); + } + + /** + * 获取关键帧信息 + * + * @return 关键帧信息数组 + */ + @Override + public List getKeyFrameDescInfo() { + return mParser == null ? null : mParser.getKeyFrameDescInfo(); + } + + /** + * 获取画质信息 + * + * @return 画质信息数组 + */ + @Override + public List getVideoQualityList() { + return mParser == null ? null : mParser.getVideoQualityList(); + } + + /** + * 获取默认画质 + * + * @return 默认画质信息对象 + */ + @Override + public VideoQuality getDefaultVideoQuality() { + return mParser == null ? null : mParser.getDefaultVideoQuality(); + } + + /** + * 切换到主线程 + *

+ * 从视频协议请求回调的子线程切换回主线程 + * + * @param r 需要在主线程中执行的任务 + */ + private void runOnMainThread(Runnable r) { + if (Looper.myLooper() == mMainHandler.getLooper()) { + r.run(); + } else { + mMainHandler.post(r); + } + } + + /** + * 获取视频画质别名列表 + * + * @return 画质别名数组 + */ + @Override + public List getResolutionNameList() { + return mParser == null ? null : mParser.getResolutionNameList(); + } + + @Override + public String getPenetrateContext() { + return mRequestContext; + } + + @Override + public String getDRMType() { + return mParser != null ? mParser.getDRMType() : ""; + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/utils/NetWatcher.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/utils/NetWatcher.java new file mode 100644 index 0000000..4c03087 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/utils/NetWatcher.java @@ -0,0 +1,133 @@ +package com.tencent.liteav.demo.superplayer.model.utils; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Handler; +import android.os.Looper; +import android.text.TextUtils; +import android.widget.Toast; + +import com.tencent.rtmp.TXLivePlayer; +import com.tencent.rtmp.TXLog; + +import java.lang.ref.WeakReference; + +/** + * 网络质量监视工具 + * + * 当loading次数大于等于3次时,提示用户切换到低清晰度 + */ + +public class NetWatcher { + + private final static int WATCH_TIME = 30000; // 监控总时长ms + private final static int MAX_LOADING_TIME = 10000; // 一次loading的判定时长ms + private final static int MAX_LOADING_COUNT = 3; // 弹出切换清晰度提示框的loading总次数 + + private WeakReference mContext; + private WeakReference mLivePlayer; // 直播播放器 + + private String mPlayURL = ""; // 播放的url + + private int mLoadingCount = 0; // 记录loading次数 + + private long mLoadingTime = 0; // 记录单次loading的时长 + private long mLoadingStartTime = 0; // loading开始的时间 + + private boolean mWatching; // 是否正在监控 + + public NetWatcher(Context context) { + mContext = new WeakReference<>(context); + } + + /** + * 开始监控网络 + * + * @param playUrl 播放的url + * @param player 播放器 + */ + public void start(String playUrl, TXLivePlayer player) { + mWatching = true; + mLivePlayer = new WeakReference<>(player); + mPlayURL = playUrl; + mLoadingCount = 0; + mLoadingTime= 0; + mLoadingStartTime = 0; + TXLog.w("NetWatcher", "net check start watch "); + Handler mainHandler = new Handler(Looper.getMainLooper()); + mainHandler.postDelayed(new Runnable() { + @Override + public void run() { + TXLog.w("NetWatcher", "net check loading count = "+mLoadingCount+" loading time = "+mLoadingTime); + if (mLoadingCount >= MAX_LOADING_COUNT || mLoadingTime >= MAX_LOADING_TIME) { + showSwitchStreamDialog(); + } + mLoadingCount = 0; + mLoadingTime = 0; + } + }, WATCH_TIME); + } + + /** + * 停止监控 + */ + public void stop() { + mWatching = false; + mLoadingCount = 0; + mLoadingTime= 0; + mLoadingStartTime = 0; + mPlayURL = ""; + mLivePlayer = null; + TXLog.w("NetWatcher", "net check stop watch"); + } + + /** + * 开始loading计时 + */ + public void enterLoading() { + if (mWatching) { + mLoadingCount++; + mLoadingStartTime = System.currentTimeMillis(); + } + } + + /** + * 结束loading计时 + */ + public void exitLoading() { + if (mWatching) { + if (mLoadingStartTime != 0) { + mLoadingTime += System.currentTimeMillis() - mLoadingStartTime; + mLoadingStartTime = 0; + } + } + } + + /** + * 弹出切换清晰度的提示框 + */ + private void showSwitchStreamDialog() { + final Context context = mContext.get(); + if (context == null) return; + AlertDialog alertDialog = new AlertDialog.Builder(context).create(); + alertDialog.setMessage("检测到您的网络较差,建议切换清晰度"); + alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + TXLivePlayer player = mLivePlayer!=null ? mLivePlayer.get() : null; + String videoUrl = mPlayURL.replace(".flv","_900.flv"); + if (player != null && !TextUtils.isEmpty(videoUrl)) { + int result = player.switchStream(videoUrl); + if (result < 0) { + Toast.makeText(context,"切换高清清晰度失败,请稍候重试", Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(context,"正在为您切换为高清清晰度,请稍候...", Toast.LENGTH_SHORT).show(); + } + } + dialog.dismiss(); + } + }); + alertDialog.show(); + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/utils/VideoGestureDetector.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/utils/VideoGestureDetector.java new file mode 100644 index 0000000..b8a3748 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/utils/VideoGestureDetector.java @@ -0,0 +1,212 @@ +package com.tencent.liteav.demo.superplayer.model.utils; + +import android.app.Activity; +import android.app.Service; +import android.content.ContentResolver; +import android.content.Context; +import android.media.AudioManager; +import android.provider.Settings; +import android.view.MotionEvent; +import android.view.Window; +import android.view.WindowManager; + +/** + * 手势控制视频播放进度、调节亮度音量的工具 + */ + +public class VideoGestureDetector { + // 手势类型 + private static final int NONE = 0; // 无效果 + private static final int VOLUME = 1; // 音量 + private static final int BRIGHTNESS = 2; // 亮度 + private static final int VIDEO_PROGRESS = 3; // 播放进度 + + private int mScrollMode = NONE; // 手势类型 + + private VideoGestureListener mVideoGestureListener; // 回调 + private int mVideoWidth; // 视频宽度px + + // 亮度相关 + private float mBrightness = 1; // 当前亮度(0.0~1.0) + private Window mWindow; // 当前window + private WindowManager.LayoutParams mLayoutParams; // 用于获取和设置屏幕亮度 + private ContentResolver mResolver; // 用于获取当前屏幕亮度 + + // 音量相关 + private AudioManager mAudioManager; // 音频管理器,用于设置音量 + private int mMaxVolume = 0; // 最大音量值 + private int mOldVolume = 0; // 记录调节音量之前的旧音量值 + + // 视频进度相关 + private int mVideoProgress; // 记录滑动后的进度,在回调中抛出 + private int mDownProgress; // 滑动开始时的视频播放进度 + + /** + * 手势临界值,当两滑动事件坐标的水平差值>20时判定为{@link #VIDEO_PROGRESS}, 否则判定为{@link #VOLUME}或者{@link #BRIGHTNESS} + */ + private int offsetX = 20; + + //手势灵敏度 0.0~1.0 + private float mSensitivity = 0.3f; // 调节音量、亮度的灵敏度 + + public VideoGestureDetector(Context context) { + mAudioManager = (AudioManager) context.getSystemService(Service.AUDIO_SERVICE); + mMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); + if (context instanceof Activity) { + mWindow = ((Activity) context).getWindow(); + mLayoutParams = mWindow.getAttributes(); + mBrightness = mLayoutParams.screenBrightness; + } + mResolver = context.getContentResolver(); + } + + /** + * 设置回调 + * + * @param videoGestureListener + */ + public void setVideoGestureListener(VideoGestureListener videoGestureListener) { + mVideoGestureListener = videoGestureListener; + } + + /** + * 重置数据以开始新的一次滑动 + * + * @param videoWidth 视频宽度px + * @param downProgress 手势按下时视频的播放进度(秒) + */ + public void reset(int videoWidth, int downProgress) { + mVideoProgress = 0; + mVideoWidth = videoWidth; + mScrollMode = NONE; + mOldVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); + mBrightness = mLayoutParams.screenBrightness; + if (mBrightness == -1) { + //一开始是默认亮度的时候,获取系统亮度,计算比例值 + mBrightness = getBrightness() / 255.0f; + } + mDownProgress = downProgress; + } + + /** + * 获取当前是否是视频进度滑动手势 + * + * @return + */ + public boolean isVideoProgressModel() { + return mScrollMode == VIDEO_PROGRESS; + } + + /** + * 获取滑动后对应的视频进度 + * + * @return + */ + public int getVideoProgress() { + return mVideoProgress; + } + + /** + * 滑动手势操控类别判定 + * + * @param height 滑动事件的高度px + * @param downEvent 按下事件 + * @param moveEvent 滑动事件 + * @param distanceX 滑动水平距离 + * @param distanceY 滑动竖直距离 + */ + public void check(int height, MotionEvent downEvent, MotionEvent moveEvent, float distanceX, float distanceY) { + switch (mScrollMode) { + case NONE: + //offset是让快进快退不要那么敏感的值 + if (Math.abs(downEvent.getX() - moveEvent.getX()) > offsetX) { + mScrollMode = VIDEO_PROGRESS; + } else { + int halfVideoWidth = mVideoWidth / 2; + if (downEvent.getX() < halfVideoWidth) { + mScrollMode = BRIGHTNESS; + } else { + mScrollMode = VOLUME; + } + } + break; + case VOLUME: + int value = height / mMaxVolume; + int newVolume = (int) ((downEvent.getY() - moveEvent.getY()) / value * mSensitivity + mOldVolume); + mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, newVolume, AudioManager.FLAG_PLAY_SOUND); + + float volumeProgress = newVolume / Float.valueOf(mMaxVolume) * 100; + if (mVideoGestureListener != null) { + mVideoGestureListener.onVolumeGesture(volumeProgress); + } + break; + case BRIGHTNESS: + float newBrightness = height == 0 ? 0 : (downEvent.getY() - moveEvent.getY()) / height * mSensitivity; + newBrightness += mBrightness; + + if (newBrightness < 0) { + newBrightness = 0; + } else if (newBrightness > 1) { + newBrightness = 1; + } + if (mLayoutParams != null) { + mLayoutParams.screenBrightness = newBrightness; + } + if (mWindow != null) { + mWindow.setAttributes(mLayoutParams); + } + + if (mVideoGestureListener != null) { + mVideoGestureListener.onBrightnessGesture(newBrightness); + } + break; + case VIDEO_PROGRESS: + float dis = moveEvent.getX() - downEvent.getX(); + float percent = dis / mVideoWidth; + mVideoProgress = (int) (mDownProgress + percent * 100); + if (mVideoGestureListener != null) { + mVideoGestureListener.onSeekGesture(mVideoProgress); + } + break; + } + } + + /** + * 获取当前亮度 + * + * @return + */ + private int getBrightness() { + if (mResolver != null) { + return Settings.System.getInt(mResolver, Settings.System.SCREEN_BRIGHTNESS, 255); + } else { + return 255; + } + } + + /** + * 回调 + */ + public interface VideoGestureListener { + /** + * 亮度调节回调 + * + * @param newBrightness 滑动后的新亮度值 + */ + void onBrightnessGesture(float newBrightness); + + /** + * 音量调节回调 + * + * @param volumeProgress 滑动后的新音量值 + */ + void onVolumeGesture(float volumeProgress); + + /** + * 播放进度调节回调 + * + * @param seekProgress 滑动后的新视频进度 + */ + void onSeekGesture(int seekProgress); + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/utils/VideoQualityUtils.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/utils/VideoQualityUtils.java new file mode 100644 index 0000000..cf3e8a2 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/model/utils/VideoQualityUtils.java @@ -0,0 +1,156 @@ +package com.tencent.liteav.demo.superplayer.model.utils; + +import com.tencent.liteav.basic.log.TXCLog; +import com.tencent.liteav.demo.superplayer.model.entity.PlayInfoStream; +import com.tencent.liteav.demo.superplayer.model.entity.ResolutionName; +import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality; +import com.tencent.rtmp.TXBitrateItem; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * Created by yuejiaoli on 2018/7/6. + * + * 清晰度转换工具 + */ + +public class VideoQualityUtils { + + private static final String TAG = "TCVideoQualityUtil"; + + /** + * 从比特流信息转换为清晰度信息 + * + * @param bitrateItem + * @return + */ + public static VideoQuality convertToVideoQuality(TXBitrateItem bitrateItem, int index) { + VideoQuality quality = new VideoQuality(); + quality.bitrate = bitrateItem.bitrate; + quality.index = bitrateItem.index; + switch (index) { + case 0: + quality.name = "FLU"; + quality.title = "流畅"; + break; + case 1: + quality.name = "SD"; + quality.title = "标清"; + break; + case 2: + quality.name = "HD"; + quality.title = "高清"; + break; + case 3: + quality.name = "FHD"; + quality.title = "超清"; + break; + case 4: + quality.name = "2K"; + quality.title = "2K"; + break; + case 5: + quality.name = "4K"; + quality.title = "4K"; + break; + case 6: + quality.name = "8K"; + quality.title = "8K"; + break; + } + return quality; + } + + /** + * 从源视频信息与视频类别信息转换为清晰度信息 + * + * @param sourceStream + * @param classification + * @return + */ + public static VideoQuality convertToVideoQuality(PlayInfoStream sourceStream, String classification) { + VideoQuality quality = new VideoQuality(); + quality.bitrate = sourceStream.getBitrate(); + if (classification.equals("FLU")) { + quality.name = "FLU"; + quality.title = "流畅"; + } else if (classification.equals("SD")) { + quality.name = "SD"; + quality.title = "标清"; + } else if (classification.equals("HD")) { + quality.name = "HD"; + quality.title = "高清"; + } else if (classification.equals("FHD")) { + quality.name = "FHD"; + quality.title = "全高清"; + } else if (classification.equals("2K")) { + quality.name = "2K"; + quality.title = "2K"; + } else if (classification.equals("4K")) { + quality.name = "4K"; + quality.title = "4K"; + } + quality.url = sourceStream.url; + quality.index = -1; + return quality; + } + + /** + * 从{@link PlayInfoStream}转换为{@link VideoQuality} + * + * @param stream + * @return + */ + public static VideoQuality convertToVideoQuality(PlayInfoStream stream) { + VideoQuality qulity = new VideoQuality(); + qulity.bitrate = stream.getBitrate(); + qulity.name = stream.id; + qulity.title = stream.name; + qulity.url = stream.url; + qulity.index = -1; + return qulity; + } + + /** + * 从转码列表转换为清晰度列表 + * + * @param transcodeList + * @return + */ + public static List convertToVideoQualityList(HashMap transcodeList) { + List videoQualities = new ArrayList<>(); + for (String classification : transcodeList.keySet()) { + VideoQuality videoQuality = convertToVideoQuality(transcodeList.get(classification)); + videoQualities.add(videoQuality); + } + return videoQualities; + } + + /** + * 根据视频清晰度别名表从码率信息转换为视频清晰度 + * + * @param bitrateItem 码率 + * @param resolutionNames 清晰度别名表 + * @return + */ + public static VideoQuality convertToVideoQuality(TXBitrateItem bitrateItem, List resolutionNames) { + VideoQuality quality = new VideoQuality(); + quality.bitrate = bitrateItem.bitrate; + quality.index = bitrateItem.index; + boolean getName = false; + for (ResolutionName resolutionName : resolutionNames) { + if (((resolutionName.width == bitrateItem.width && resolutionName.height == bitrateItem.height) || (resolutionName.width == bitrateItem.height && resolutionName.height == bitrateItem.width)) + && "video".equalsIgnoreCase(resolutionName.type)) { + quality.title = resolutionName.name; + getName = true; + break; + } + } + if (!getName) { + TXCLog.i(TAG, "error: could not get quality name!"); + } + return quality; + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/AbsPlayer.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/AbsPlayer.java new file mode 100644 index 0000000..53cde5e --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/AbsPlayer.java @@ -0,0 +1,162 @@ +package com.tencent.liteav.demo.superplayer.ui.player; + +import android.content.Context; +import android.graphics.Bitmap; +import android.util.AttributeSet; +import android.view.View; +import android.widget.RelativeLayout; + +import com.tencent.liteav.demo.superplayer.SuperPlayerDef; +import com.tencent.liteav.demo.superplayer.model.entity.PlayImageSpriteInfo; +import com.tencent.liteav.demo.superplayer.model.entity.PlayKeyFrameDescInfo; +import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality; + +import java.util.List; + +/** + * 播放器公共逻辑 + */ +public abstract class AbsPlayer extends RelativeLayout implements Player { + + protected static final int MAX_SHIFT_TIME = 7200; // demo演示直播时移是MAX_SHIFT_TIMEs,即2小时 + + protected Callback mControllerCallback; // 播放控制回调 + + protected Runnable mHideViewRunnable = new Runnable() { + @Override + public void run() { + hide(); + } + }; + + public AbsPlayer(Context context) { + super(context); + } + + public AbsPlayer(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AbsPlayer(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + public void setCallback(Callback callback) { + mControllerCallback = callback; + } + + @Override + public void setWatermark(Bitmap bmp, float x, float y) { + } + + @Override + public void show() { + + } + + @Override + public void hide() { + + } + + @Override + public void release() { + + } + + @Override + public void updatePlayState(SuperPlayerDef.PlayerState playState) { + + } + + @Override + public void setVideoQualityList(List list) { + + } + + @Override + public void updateTitle(String title) { + + } + + @Override + public void updateVideoProgress(long current, long duration) { + + } + + @Override + public void updatePlayType(SuperPlayerDef.PlayerType type) { + + } + + @Override + public void setBackground(Bitmap bitmap) { + + } + + @Override + public void showBackground() { + + } + + @Override + public void hideBackground() { + + } + + @Override + public void updateVideoQuality(VideoQuality videoQuality) { + + } + + @Override + public void updateImageSpriteInfo(PlayImageSpriteInfo info) { + + } + + @Override + public void updateKeyFrameDescInfo(List list) { + + } + + /** + * 设置控件的可见性 + * + * @param view 目标控件 + * @param isVisible 显示:true 隐藏:false + */ + protected void toggleView(View view, boolean isVisible) { + view.setVisibility(isVisible ? View.VISIBLE : View.GONE); + } + + /** + * 将秒数转换为hh:mm:ss的格式 + * + * @param second + * @return + */ + protected String formattedTime(long second) { + String formatTime; + long h, m, s; + h = second / 3600; + m = (second % 3600) / 60; + s = (second % 3600) % 60; + if (h == 0) { + formatTime = asTwoDigit(m) + ":" + asTwoDigit(s); + } else { + formatTime = asTwoDigit(h) + ":" + asTwoDigit(m) + ":" + asTwoDigit(s); + } + return formatTime; + } + + protected String asTwoDigit(long digit) { + String value = ""; + if (digit < 10) { + value = "0"; + } + value += String.valueOf(digit); + return value; + } + +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/FloatPlayer.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/FloatPlayer.java new file mode 100644 index 0000000..bb711b1 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/FloatPlayer.java @@ -0,0 +1,150 @@ +package com.tencent.liteav.demo.superplayer.ui.player; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.widget.ImageView; + +import com.tencent.liteav.demo.superplayer.SuperPlayerDef; +import com.tencent.liteav.demo.superplayer.SuperPlayerGlobalConfig; +import com.tencent.rtmp.ui.TXCloudVideoView; + +import org.leanflutter.plugins.flutter_superplayer.R; + +import java.lang.reflect.Field; + +/** + * 悬浮窗模式播放控件 + *

+ * 1、滑动以移动悬浮窗,点击悬浮窗回到窗口模式{@link #onTouchEvent(MotionEvent)} + *

+ * 2、关闭悬浮窗{@link #onClick(View)} + */ +public class FloatPlayer extends AbsPlayer implements View.OnClickListener { + + private TXCloudVideoView mFloatVideoView; // 悬浮窗中的视频播放view + + private int mStatusBarHeight; // 系统状态栏的高度 + private float mXDownInScreen; // 按下事件距离屏幕左边界的距离 + private float mYDownInScreen; // 按下事件距离屏幕上边界的距离 + private float mXInScreen; // 滑动事件距离屏幕左边界的距离 + private float mYInScreen; // 滑动事件距离屏幕上边界的距离 + private float mXInView; // 滑动事件距离自身左边界的距离 + private float mYInView; // 滑动事件距离自身上边界的距离 + + public FloatPlayer(Context context) { + super(context); + initView(context); + } + + public FloatPlayer(Context context, AttributeSet attrs) { + super(context, attrs); + initView(context); + } + + public FloatPlayer(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initView(context); + } + + /** + * 初始化view + */ + private void initView(Context context) { + LayoutInflater.from(context).inflate(R.layout.superplayer_vod_player_float, this); + mFloatVideoView = (TXCloudVideoView) findViewById(R.id.superplayer_float_cloud_video_view); + ImageView ivClose = (ImageView) findViewById(R.id.superplayer_iv_close); + ivClose.setOnClickListener(this); + } + + /** + * 获取悬浮窗中的视频播放view + */ + public TXCloudVideoView getFloatVideoView() { + return mFloatVideoView; + } + + /** + * 设置点击事件监听,实现点击关闭按钮后关闭悬浮窗 + */ + @Override + public void onClick(View view) { + int i = view.getId(); + if (i == R.id.superplayer_iv_close) { + if (mControllerCallback != null) { + mControllerCallback.onBackPressed(SuperPlayerDef.PlayerMode.FLOAT); + } + } + } + + /** + * 重写触摸事件监听,实现悬浮窗随手指移动 + */ + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + mXInView = event.getX(); + mYInView = event.getY(); + mXDownInScreen = event.getRawX(); + mYDownInScreen = event.getRawY() - getStatusBarHeight(); + mXInScreen = event.getRawX(); + mYInScreen = event.getRawY() - getStatusBarHeight(); + + break; + case MotionEvent.ACTION_MOVE: //悬浮窗随手指移动 + mXInScreen = event.getRawX(); + mYInScreen = event.getRawY() - getStatusBarHeight(); + updateViewPosition(); + + break; + case MotionEvent.ACTION_UP: + if (mXDownInScreen == mXInScreen && mYDownInScreen == mYInScreen) {//手指没有滑动视为点击,回到窗口模式 + if (mControllerCallback != null) { + mControllerCallback.onSwitchPlayMode(SuperPlayerDef.PlayerMode.WINDOW); + } + } + break; + default: + break; + } + + return true; + } + + /** + * 获取系统状态栏高度 + */ + private int getStatusBarHeight() { + if (mStatusBarHeight == 0) { + try { + Class c = Class.forName("com.android.internal.R$dimen"); + Object o = c.newInstance(); + Field field = c.getField("status_bar_height"); + int x = (Integer) field.get(o); + mStatusBarHeight = getResources().getDimensionPixelSize(x); + } catch (Exception e) { + e.printStackTrace(); + } + } + return mStatusBarHeight; + } + + /** + * 更新悬浮窗的位置信息,在回调{@link Callback#onFloatPositionChange(int, int)}中实现悬浮窗移动 + */ + private void updateViewPosition() { + int x = (int) (mXInScreen - mXInView); + int y = (int) (mYInScreen - mYInView); + SuperPlayerGlobalConfig.TXRect rect = SuperPlayerGlobalConfig.getInstance().floatViewRect; + if (rect != null) { + rect.x = x; + rect.y = y; + } + if (mControllerCallback != null) { + mControllerCallback.onFloatPositionChange(x, y); + } + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/FullScreenPlayer.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/FullScreenPlayer.java new file mode 100644 index 0000000..212552b --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/FullScreenPlayer.java @@ -0,0 +1,930 @@ +package com.tencent.liteav.demo.superplayer.ui.player; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.GestureDetector; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.tencent.liteav.demo.superplayer.SuperPlayerDef; +import com.tencent.liteav.demo.superplayer.model.entity.PlayImageSpriteInfo; +import com.tencent.liteav.demo.superplayer.model.entity.PlayKeyFrameDescInfo; +import com.tencent.liteav.demo.superplayer.model.net.LogReport; +import com.tencent.liteav.demo.superplayer.model.utils.VideoGestureDetector; +import com.tencent.liteav.demo.superplayer.ui.view.PointSeekBar; +import com.tencent.liteav.demo.superplayer.ui.view.VideoProgressLayout; +import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality; +import com.tencent.liteav.demo.superplayer.ui.view.VodMoreView; +import com.tencent.liteav.demo.superplayer.ui.view.VodQualityView; +import com.tencent.liteav.demo.superplayer.ui.view.VolumeBrightnessProgressLayout; +import com.tencent.rtmp.TXImageSprite; + +import org.leanflutter.plugins.flutter_superplayer.R; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +/** + * 全屏模式播放控件 + * + * 除{@link WindowPlayer}基本功能外,还包括进度条关键帧打点信息显示与跳转、快进快退时缩略图的显示、切换画质 + * 镜像播放、硬件加速、倍速播放、弹幕、截图等功能 + * + * 1、点击事件监听{@link #onClick(View)} + * + * 2、触摸事件监听{@link #onTouchEvent(MotionEvent)} + * + * 3、进度条滑动事件监听{@link #onProgressChanged(PointSeekBar, int, boolean)} + * {@link #onStartTrackingTouch(PointSeekBar)}{@link #onStopTrackingTouch(PointSeekBar)} + * + * 4、进度条打点信息点击监听{@link #onSeekBarPointClick(View, int)} + * + * 5、切换画质监听{@link #onQualitySelect(VideoQuality)} + * + * 6、倍速播放监听{@link #onSpeedChange(float)} + * + * 7、镜像播放监听{@link #onMirrorChange(boolean)} + * + * 8、硬件加速监听{@link #onHWAcceleration(boolean)} + * + */ +public class FullScreenPlayer extends AbsPlayer implements View.OnClickListener, + VodMoreView.Callback, VodQualityView.Callback, PointSeekBar.OnSeekBarChangeListener, PointSeekBar.OnSeekBarPointClickListener{ + + // UI控件 + private RelativeLayout mLayoutTop; // 顶部标题栏布局 + private LinearLayout mLayoutBottom; // 底部进度条所在布局 + private ImageView mIvPause; // 暂停播放按钮 + private TextView mTvTitle; // 视频名称文本 + private TextView mTvBackToLive; // 返回直播文本 + private ImageView mIvWatermark; // 水印 + private TextView mTvCurrent; // 当前进度文本 + private TextView mTvDuration; // 总时长文本 + private PointSeekBar mSeekBarProgress; // 播放进度条 + private LinearLayout mLayoutReplay; // 重播按钮所在布局 + private ProgressBar mPbLiveLoading; // 加载圈 + private VolumeBrightnessProgressLayout mGestureVolumeBrightnessProgressLayout; // 音量亮度调节布局 + private VideoProgressLayout mGestureVideoProgressLayout; // 手势快进提示布局 + + private TextView mTvQuality; // 当前画质文本 + private ImageView mIvBack; // 顶部标题栏中的返回按钮 + private ImageView mIvDanmu; // 弹幕按钮 + private ImageView mIvSnapshot; // 截屏按钮 + private ImageView mIvLock; // 锁屏按钮 + private ImageView mIvMore; // 更多设置弹窗按钮 + private VodQualityView mVodQualityView; // 画质列表弹窗 + private VodMoreView mVodMoreView; // 更多设置弹窗 + private TextView mTvVttText; // 关键帧打点信息文本 + + private HideLockViewRunnable mHideLockViewRunnable; // 隐藏锁屏按钮子线程 + private GestureDetector mGestureDetector; // 手势检测监听器 + private VideoGestureDetector mVideoGestureDetector; // 手势控制工具 + + private boolean isShowing; // 自身是否可见 + private boolean mIsChangingSeekBarProgress; // 进度条是否正在拖动,避免SeekBar由于视频播放的update而跳动 + private SuperPlayerDef.PlayerType mPlayType; // 当前播放视频类型 + private SuperPlayerDef.PlayerState mCurrentPlayState = SuperPlayerDef.PlayerState.END; // 当前播放状态 + private long mDuration; // 视频总时长 + private long mLivePushDuration; // 直播推流总时长 + private long mProgress; // 当前播放进度 + + private Bitmap mBackgroundBmp; // 背景图 + private Bitmap mWaterMarkBmp; // 水印图 + private float mWaterMarkBmpX; // 水印x坐标 + private float mWaterMarkBmpY; // 水印y坐标 + + private boolean mBarrageOn; // 弹幕是否开启 + private boolean mLockScreen; // 是否锁屏 + private TXImageSprite mTXImageSprite; // 雪碧图信息 + private List mTXPlayKeyFrameDescInfoList; // 关键帧信息 + private int mSelectedPos = -1; // 点击的关键帧时间点 + + private VideoQuality mDefaultVideoQuality; // 默认画质 + private List mVideoQualityList; // 画质列表 + private boolean mFirstShowQuality; // 是都是首次显示画质信息 + + public FullScreenPlayer(Context context) { + super(context); + initialize(context); + } + + public FullScreenPlayer(Context context, AttributeSet attrs) { + super(context, attrs); + initialize(context); + } + + public FullScreenPlayer(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initialize(context); + } + + /** + * 初始化控件、手势检测监听器、亮度/音量/播放进度的回调 + */ + private void initialize(Context context) { + initView(context); + mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onDoubleTap(MotionEvent e) { + if (mLockScreen) return false; + togglePlayState(); + show(); + if (mHideViewRunnable != null) { + removeCallbacks(mHideViewRunnable); + postDelayed(mHideViewRunnable, 7000); + } + return true; + } + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + toggle(); + return true; + } + + @Override + public boolean onScroll(MotionEvent downEvent, MotionEvent moveEvent, float distanceX, float distanceY) { + if (mLockScreen) return false; + if (downEvent == null || moveEvent == null) { + return false; + } + if (mVideoGestureDetector != null && mGestureVolumeBrightnessProgressLayout != null) { + mVideoGestureDetector.check(mGestureVolumeBrightnessProgressLayout.getHeight(), downEvent, moveEvent, distanceX, distanceY); + } + return true; + } + + @Override + public boolean onDown(MotionEvent e) { + if (mLockScreen) return true; + if (mVideoGestureDetector != null) { + mVideoGestureDetector.reset(getWidth(), mSeekBarProgress.getProgress()); + } + return true; + } + + }); + mGestureDetector.setIsLongpressEnabled(false); + + mVideoGestureDetector = new VideoGestureDetector(getContext()); + mVideoGestureDetector.setVideoGestureListener(new VideoGestureDetector.VideoGestureListener() { + @Override + public void onBrightnessGesture(float newBrightness) { + if (mGestureVolumeBrightnessProgressLayout != null) { + mGestureVolumeBrightnessProgressLayout.setProgress((int) (newBrightness * 100)); + mVodMoreView.setBrightProgress((int) (newBrightness * 100)); + mGestureVolumeBrightnessProgressLayout.setImageResource(R.drawable.superplayer_ic_light_max); + mGestureVolumeBrightnessProgressLayout.show(); + } + } + + @Override + public void onVolumeGesture(float volumeProgress) { + if (mGestureVolumeBrightnessProgressLayout != null) { + mGestureVolumeBrightnessProgressLayout.setImageResource(R.drawable.superplayer_ic_volume_max); + mGestureVolumeBrightnessProgressLayout.setProgress((int) volumeProgress); + mGestureVolumeBrightnessProgressLayout.show(); + } + } + + @Override + public void onSeekGesture(int progress) { + mIsChangingSeekBarProgress = true; + if (mGestureVideoProgressLayout != null) { + + if (progress > mSeekBarProgress.getMax()) { + progress = mSeekBarProgress.getMax(); + } + if (progress < 0) { + progress = 0; + } + mGestureVideoProgressLayout.setProgress(progress); + mGestureVideoProgressLayout.show(); + + float percentage = ((float) progress) / mSeekBarProgress.getMax(); + float currentTime = (mDuration * percentage); + if (mPlayType == SuperPlayerDef.PlayerType.LIVE || mPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) { + if (mLivePushDuration > MAX_SHIFT_TIME) { + currentTime = (int) (mLivePushDuration - MAX_SHIFT_TIME * (1 - percentage)); + } else { + currentTime = mLivePushDuration * percentage; + } + mGestureVideoProgressLayout.setTimeText(formattedTime((long) currentTime)); + } else { + mGestureVideoProgressLayout.setTimeText(formattedTime((long) currentTime) + " / " + formattedTime((long) mDuration)); + } + setThumbnail(progress); + } + if (mSeekBarProgress!= null) + mSeekBarProgress.setProgress(progress); + } + }); + } + + /** + * 初始化view + */ + private void initView(Context context) { + mHideLockViewRunnable = new HideLockViewRunnable(this); + LayoutInflater.from(context).inflate(R.layout.superplayer_vod_player_fullscreen, this); + + mLayoutTop = (RelativeLayout) findViewById(R.id.superplayer_rl_top); + mLayoutTop.setOnClickListener(this); + mLayoutBottom = (LinearLayout) findViewById(R.id.superplayer_ll_bottom); + mLayoutBottom.setOnClickListener(this); + mLayoutReplay = (LinearLayout) findViewById(R.id.superplayer_ll_replay); + + mIvBack = (ImageView) findViewById(R.id.superplayer_iv_back); + mIvLock = (ImageView) findViewById(R.id.superplayer_iv_lock); + mTvTitle = (TextView) findViewById(R.id.superplayer_tv_title); + mIvPause = (ImageView) findViewById(R.id.superplayer_iv_pause); + mIvDanmu = (ImageView) findViewById(R.id.superplayer_iv_danmuku); + mIvMore = (ImageView) findViewById(R.id.superplayer_iv_more); + mIvSnapshot = (ImageView) findViewById(R.id.superplayer_iv_snapshot); + mTvCurrent = (TextView) findViewById(R.id.superplayer_tv_current); + mTvDuration = (TextView) findViewById(R.id.superplayer_tv_duration); + + mSeekBarProgress = (PointSeekBar) findViewById(R.id.superplayer_seekbar_progress); + mSeekBarProgress.setProgress(0); + mSeekBarProgress.setOnPointClickListener(this); + mSeekBarProgress.setOnSeekBarChangeListener(this); + mTvQuality = (TextView) findViewById(R.id.superplayer_tv_quality); + mTvBackToLive = (TextView) findViewById(R.id.superplayer_tv_back_to_live); + mPbLiveLoading = (ProgressBar) findViewById(R.id.superplayer_pb_live); + + mVodQualityView = (VodQualityView) findViewById(R.id.superplayer_vod_quality); + mVodQualityView.setCallback(this); + mVodMoreView = (VodMoreView) findViewById(R.id.superplayer_vod_more); + mVodMoreView.setCallback(this); + + mTvBackToLive.setOnClickListener(this); + mLayoutReplay.setOnClickListener(this); + mIvLock.setOnClickListener(this); + mIvBack.setOnClickListener(this); + mIvPause.setOnClickListener(this); + mIvDanmu.setOnClickListener(this); + mIvSnapshot.setOnClickListener(this); + mIvMore.setOnClickListener(this); + mTvQuality.setOnClickListener(this); + mTvVttText = (TextView) findViewById(R.id.superplayer_large_tv_vtt_text); + mTvVttText.setOnClickListener(this); + if (mDefaultVideoQuality != null) { + mTvQuality.setText(mDefaultVideoQuality.title); + } + mGestureVolumeBrightnessProgressLayout = (VolumeBrightnessProgressLayout) findViewById(R.id.superplayer_gesture_progress); + mGestureVideoProgressLayout = (VideoProgressLayout) findViewById(R.id.superplayer_video_progress_layout); + mIvWatermark = (ImageView) findViewById(R.id.superplayer_large_iv_water_mark); + } + + /** + * 切换播放状态 + * + * 双击和点击播放/暂停按钮会触发此方法 + */ + private void togglePlayState() { + switch (mCurrentPlayState) { + case PAUSE: + case END: + if (mControllerCallback != null) { + mControllerCallback.onResume(); + } + break; + case PLAYING: + case LOADING: + if (mControllerCallback != null) { + mControllerCallback.onPause(); + } + mLayoutReplay.setVisibility(View.GONE); + break; + } + show(); + } + + + /** + * 切换自身的可见性 + */ + private void toggle() { + if (!mLockScreen) { + if (isShowing) { + hide(); + } else { + show(); + if (mHideViewRunnable != null) { + removeCallbacks(mHideViewRunnable); + postDelayed(mHideViewRunnable, 7000); + } + } + } else { + mIvLock.setVisibility(VISIBLE); + if (mHideLockViewRunnable!=null) { + removeCallbacks(mHideLockViewRunnable); + postDelayed(mHideLockViewRunnable, 7000); + } + } + if (mVodMoreView.getVisibility() == VISIBLE) { + mVodMoreView.setVisibility(GONE); + } + } + + /** + * 设置水印 + * + * @param bmp 水印图 + * @param x 水印的x坐标 + * @param y 水印的y坐标 + */ + @Override + public void setWatermark(Bitmap bmp, float x, float y) { + mWaterMarkBmp = bmp; + mWaterMarkBmpY = y; + mWaterMarkBmpX = x; + } + + /** + * 显示控件 + */ + @Override + public void show() { + isShowing = true; + mLayoutTop.setVisibility(View.VISIBLE); + mLayoutBottom.setVisibility(View.VISIBLE); + if (mHideLockViewRunnable!=null) { + removeCallbacks(mHideLockViewRunnable); + } + mIvLock.setVisibility(VISIBLE); + if (mPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) { + if (mLayoutBottom.getVisibility() == VISIBLE) + mTvBackToLive.setVisibility(View.VISIBLE); + } + List pointParams = new ArrayList<>(); + if (mTXPlayKeyFrameDescInfoList != null) + for (PlayKeyFrameDescInfo info : mTXPlayKeyFrameDescInfoList) { + int progress = (int) (info.time / mDuration * mSeekBarProgress.getMax()); + pointParams.add(new PointSeekBar.PointParams(progress, Color.WHITE)); + } + mSeekBarProgress.setPointList(pointParams); + } + + /** + * 隐藏控件 + */ + @Override + public void hide() { + isShowing = false; + mLayoutTop.setVisibility(View.GONE); + mLayoutBottom.setVisibility(View.GONE); + mVodQualityView.setVisibility(View.GONE); + mTvVttText.setVisibility(GONE); + mIvLock.setVisibility(GONE); + if (mPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) { + mTvBackToLive.setVisibility(View.GONE); + } + } + + /** + * 释放控件的内存 + */ + @Override + public void release() { + releaseTXImageSprite(); + } + + @Override + public void updatePlayState(SuperPlayerDef.PlayerState playState) { + switch (playState) { + case PLAYING: + mIvPause.setImageResource(R.drawable.superplayer_ic_vod_pause_normal); + toggleView(mPbLiveLoading, false); + toggleView(mLayoutReplay, false); + break; + case LOADING: + mIvPause.setImageResource(R.drawable.superplayer_ic_vod_pause_normal); + toggleView(mPbLiveLoading, true); + toggleView(mLayoutReplay, false); + break; + case PAUSE: + mIvPause.setImageResource(R.drawable.superplayer_ic_vod_play_normal); + toggleView(mLayoutReplay, false); + break; + case END: + mIvPause.setImageResource(R.drawable.superplayer_ic_vod_play_normal); + toggleView(mLayoutReplay, true); + break; + } + mCurrentPlayState = playState; + } + + /** + * 设置视频画质信息 + * + * @param list 画质列表 + */ + @Override + public void setVideoQualityList(List list) { + mVideoQualityList = list; + mFirstShowQuality = false; + } + + /** + * 更新视频名称 + * + * @param title 视频名称 + */ + @Override + public void updateTitle(String title) { + mTvTitle.setText(title); + } + + /** + * 更新是屁播放进度 + * + * @param current 当前进度(秒) + * @param duration 视频总时长(秒) + */ + @Override + public void updateVideoProgress(long current, long duration) { + mProgress = current < 0 ? 0 : current; + mDuration = duration < 0 ? 0 : duration; + mTvCurrent.setText(formattedTime(mProgress)); + + float percentage = mDuration > 0 ? ((float) mProgress / (float) mDuration) : 1.0f; + if (mProgress == 0) { + mLivePushDuration = 0; + percentage = 0; + } + if (mPlayType == SuperPlayerDef.PlayerType.LIVE || mPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) { + mLivePushDuration = mLivePushDuration > mProgress ? mLivePushDuration : mProgress; + long leftTime = mDuration - mProgress; + mDuration = mDuration > MAX_SHIFT_TIME ? MAX_SHIFT_TIME : mDuration; + percentage = 1 - (float) leftTime / (float) mDuration; + } + + if (percentage >= 0 && percentage <= 1) { + int progress = Math.round(percentage * mSeekBarProgress.getMax()); + if (!mIsChangingSeekBarProgress) + mSeekBarProgress.setProgress(progress); + mTvDuration.setText(formattedTime(mDuration)); + } + } + + @Override + public void updatePlayType(SuperPlayerDef.PlayerType type) { + mPlayType = type; + switch (type) { + case VOD: + mTvBackToLive.setVisibility(View.GONE); + mVodMoreView.updatePlayType(SuperPlayerDef.PlayerType.VOD); + mTvDuration.setVisibility(View.VISIBLE); + break; + case LIVE: + mTvBackToLive.setVisibility(View.GONE); + mTvDuration.setVisibility(View.GONE); + mVodMoreView.updatePlayType(SuperPlayerDef.PlayerType.LIVE); + mSeekBarProgress.setProgress(100); + break; + case LIVE_SHIFT: + if (mLayoutBottom.getVisibility() == VISIBLE) { + mTvBackToLive.setVisibility(View.VISIBLE); + } + mTvDuration.setVisibility(View.GONE); + mVodMoreView.updatePlayType(SuperPlayerDef.PlayerType.LIVE_SHIFT); + break; + } + } + + /** + * 更新视频播放画质 + * + * @param videoQuality 画质 + */ + @Override + public void updateVideoQuality(VideoQuality videoQuality) { + if(videoQuality==null){ + mTvQuality.setText(""); + return; + } + mDefaultVideoQuality = videoQuality; + if (mTvQuality != null) { + mTvQuality.setText(videoQuality.title); + } + if (mVideoQualityList != null && mVideoQualityList.size() != 0) { + for (int i = 0 ; i < mVideoQualityList.size(); i++) { + VideoQuality quality = mVideoQualityList.get(i); + if (quality!=null && quality.title!=null &&quality.title.equals(mDefaultVideoQuality.title)) { + mVodQualityView.setDefaultSelectedQuality(i); + break; + } + } + } + } + + /** + * 更新雪碧图信息 + * + * @param info 雪碧图信息 + */ + @Override + public void updateImageSpriteInfo(PlayImageSpriteInfo info) { + if (mTXImageSprite != null) { + releaseTXImageSprite(); + } + // 有缩略图的时候不显示进度 + mGestureVideoProgressLayout.setProgressVisibility(info == null || info.imageUrls == null || info.imageUrls.size() == 0); + if (mPlayType == SuperPlayerDef.PlayerType.VOD) { + mTXImageSprite = new TXImageSprite(getContext()); + if (info != null) { + // 雪碧图ELK上报 + LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_IMAGE_SPRITE, 0, 0); + mTXImageSprite.setVTTUrlAndImageUrls(info.webVttUrl, info.imageUrls); + } else { + mTXImageSprite.setVTTUrlAndImageUrls(null, null); + } + } + } + + private void releaseTXImageSprite() { + if (mTXImageSprite != null) { + mTXImageSprite.release(); + mTXImageSprite = null; + } + } + + /** + * 更新关键帧信息 + * + * @param list 关键帧信息列表 + */ + @Override + public void updateKeyFrameDescInfo(List list) { + mTXPlayKeyFrameDescInfoList = list; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (mGestureDetector != null) + mGestureDetector.onTouchEvent(event); + + if (!mLockScreen) { + if (event.getAction() == MotionEvent.ACTION_UP && mVideoGestureDetector != null && mVideoGestureDetector.isVideoProgressModel()) { + int progress = mVideoGestureDetector.getVideoProgress(); + if (progress > mSeekBarProgress.getMax()) { + progress = mSeekBarProgress.getMax(); + } + if (progress < 0) { + progress = 0; + } + mSeekBarProgress.setProgress(progress); + + int seekTime = 0; + float percentage = progress * 1.0f / mSeekBarProgress.getMax(); + if (mPlayType == SuperPlayerDef.PlayerType.LIVE || mPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) { + if (mLivePushDuration > MAX_SHIFT_TIME) { + seekTime = (int) (mLivePushDuration - MAX_SHIFT_TIME * (1 - percentage)); + } else { + seekTime = (int) (mLivePushDuration * percentage); + } + }else { + seekTime = (int) (percentage * mDuration); + } + if (mControllerCallback != null) { + mControllerCallback.onSeekTo(seekTime); + } + mIsChangingSeekBarProgress = false; + } + } + + if(event.getAction() == MotionEvent.ACTION_DOWN) { + removeCallbacks(mHideViewRunnable); + } else if(event.getAction() == MotionEvent.ACTION_UP) { + postDelayed(mHideViewRunnable, 7000); + } + return true; + } + + /** + * 设置点击事件监听 + */ + @Override + public void onClick(View view) { + int i = view.getId(); + if (i == R.id.superplayer_iv_back || i == R.id.superplayer_tv_title) { //顶部标题栏 + if (mControllerCallback != null) { + mControllerCallback.onBackPressed(SuperPlayerDef.PlayerMode.FULLSCREEN); + } + } else if (i == R.id.superplayer_iv_pause) { //暂停\播放按钮 + togglePlayState(); + } else if (i == R.id.superplayer_iv_danmuku) { //弹幕按钮 + toggleBarrage(); + } else if (i == R.id.superplayer_iv_snapshot) { //截屏按钮 + if (mControllerCallback != null) { + mControllerCallback.onSnapshot(); + } + } else if (i == R.id.superplayer_iv_more) { //更多设置按钮 + showMoreView(); + } else if (i == R.id.superplayer_tv_quality) { //画质按钮 + showQualityView(); + } else if (i == R.id.superplayer_iv_lock) { //锁屏按钮 + toggleLockState(); + } else if (i == R.id.superplayer_ll_replay) { //重播按钮 + replay(); + } else if (i == R.id.superplayer_tv_back_to_live) { //返回直播按钮 + if (mControllerCallback != null) { + mControllerCallback.onResumeLive(); + } + } else if (i == R.id.superplayer_large_tv_vtt_text) { //关键帧打点信息按钮 + seekToKeyFramePos(); + } + } + + /** + * 开关弹幕 + */ + private void toggleBarrage() { + mBarrageOn = !mBarrageOn; + if (mBarrageOn) { + mIvDanmu.setImageResource(R.drawable.superplayer_ic_danmuku_on); + } else { + mIvDanmu.setImageResource(R.drawable.superplayer_ic_danmuku_off); + } + if (mControllerCallback != null) { + mControllerCallback.onDanmuToggle(mBarrageOn); + } + } + + /** + * 显示更多设置弹窗 + */ + private void showMoreView() { + hide(); + mVodMoreView.setVisibility(View.VISIBLE); + } + + /** + * 显示画质列表弹窗 + */ + private void showQualityView() { + if (mVideoQualityList == null || mVideoQualityList.size() == 0) { + return; + } + if(mVideoQualityList.size()==1 && (mVideoQualityList.get(0)==null || TextUtils.isEmpty(mVideoQualityList.get(0).title))){ + return; + } + // 设置默认显示分辨率文字 + mVodQualityView.setVisibility(View.VISIBLE); + if (!mFirstShowQuality && mDefaultVideoQuality != null) { + for (int i = 0 ; i < mVideoQualityList.size(); i++) { + VideoQuality quality = mVideoQualityList.get(i); + if (quality!=null && quality.title!=null &&quality.title.equals(mDefaultVideoQuality.title)) { + mVodQualityView.setDefaultSelectedQuality(i); + break; + } + } + mFirstShowQuality = true; + } + mVodQualityView.setVideoQualityList(mVideoQualityList); + } + + /** + * 切换锁屏状态 + */ + private void toggleLockState() { + mLockScreen = !mLockScreen; + mIvLock.setVisibility(VISIBLE); + if (mHideLockViewRunnable!=null) { + removeCallbacks(mHideLockViewRunnable); + postDelayed(mHideLockViewRunnable, 7000); + } + if (mLockScreen) { + mIvLock.setImageResource(R.drawable.superplayer_ic_player_lock); + hide(); + mIvLock.setVisibility(VISIBLE); + } else { + mIvLock.setImageResource(R.drawable.superplayer_ic_player_unlock); + show(); + } + } + + /** + * 重播 + */ + private void replay() { + toggleView(mLayoutReplay, false); + if (mControllerCallback != null) { + mControllerCallback.onResume(); + } + } + + /** + * 跳转至关键帧打点处 + */ + private void seekToKeyFramePos() { + float time = mTXPlayKeyFrameDescInfoList != null ? mTXPlayKeyFrameDescInfoList.get(mSelectedPos).time : 0; + if (mControllerCallback != null) { + mControllerCallback.onSeekTo((int) time); + mControllerCallback.onResume(); + } + mTvVttText.setVisibility(GONE); + toggleView(mLayoutReplay, false); + } + + @Override + public void onProgressChanged(PointSeekBar seekBar, int progress, boolean isFromUser) { + if (mGestureVideoProgressLayout != null && isFromUser) { + mGestureVideoProgressLayout.show(); + float percentage = ((float) progress) / seekBar.getMax(); + float currentTime = (mDuration * percentage); + if (mPlayType == SuperPlayerDef.PlayerType.LIVE || mPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) { + if (mLivePushDuration > MAX_SHIFT_TIME) { + currentTime = (int) (mLivePushDuration - MAX_SHIFT_TIME * (1 - percentage)); + } else { + currentTime = mLivePushDuration * percentage; + } + mGestureVideoProgressLayout.setTimeText(formattedTime((long) currentTime)); + } else { + mGestureVideoProgressLayout.setTimeText(formattedTime((long) currentTime) + " / " + formattedTime((long) mDuration)); + } + mGestureVideoProgressLayout.setProgress(progress); + } + // 加载点播缩略图 + if (isFromUser && mPlayType == SuperPlayerDef.PlayerType.VOD) { + setThumbnail(progress); + } + } + + @Override + public void onStartTrackingTouch(PointSeekBar seekBar) { + removeCallbacks(mHideViewRunnable); + } + + @Override + public void onStopTrackingTouch(PointSeekBar seekBar) { + int curProgress = seekBar.getProgress(); + int maxProgress = seekBar.getMax(); + + switch (mPlayType) { + case VOD: + if (curProgress >= 0 && curProgress <= maxProgress) { + // 关闭重播按钮 + toggleView(mLayoutReplay, false); + float percentage = ((float) curProgress) / maxProgress; + int position = (int) (mDuration * percentage); + if (mControllerCallback != null) { + mControllerCallback.onSeekTo(position); + mControllerCallback.onResume(); + } + } + break; + case LIVE: + case LIVE_SHIFT: + toggleView(mPbLiveLoading, true); + int seekTime = (int) (mLivePushDuration * curProgress * 1.0f / maxProgress); + if (mLivePushDuration > MAX_SHIFT_TIME) { + seekTime = (int) (mLivePushDuration - MAX_SHIFT_TIME * (maxProgress - curProgress) * 1.0f / maxProgress); + } + if (mControllerCallback != null) { + mControllerCallback.onSeekTo(seekTime); + } + break; + } + postDelayed(mHideViewRunnable, 7000); + } + + @Override + public void onSeekBarPointClick(final View view, final int pos) { + if (mHideLockViewRunnable!=null) { + removeCallbacks(mHideViewRunnable); + postDelayed(mHideViewRunnable, 7000); + } + if (mTXPlayKeyFrameDescInfoList != null) { + //ELK点击上报 + LogReport.getInstance().uploadLogs(LogReport.ELK_ACTION_PLAYER_POINT, 0, 0); + mSelectedPos = pos; + view.post(new Runnable() { + @Override + public void run() { + int[] location = new int[2]; + view.getLocationInWindow(location); + + int viewX = location[0]; + PlayKeyFrameDescInfo info = mTXPlayKeyFrameDescInfoList.get(pos); + String content = info.content; + + mTvVttText.setText(formattedTime((long) info.time) + " " + content); + mTvVttText.setVisibility(VISIBLE); + adjustVttTextViewPos(viewX); + } + }); + } + } + + /** + * 设置播放进度所对应的缩略图 + * + * @param progress 播放进度 + */ + private void setThumbnail(int progress) { + float percentage = ((float) progress) / mSeekBarProgress.getMax(); + float seekTime = (mDuration * percentage); + if (mTXImageSprite != null) { + Bitmap bitmap = mTXImageSprite.getThumbnail(seekTime); + if (bitmap != null) { + mGestureVideoProgressLayout.setThumbnail(bitmap); + } + } + } + + /** + * 计算并设置关键帧打点信息文本显示的位置 + * + * @param viewX 点击的打点view + */ + private void adjustVttTextViewPos(final int viewX) { + mTvVttText.post(new Runnable() { + @Override + public void run() { + int width = mTvVttText.getWidth(); + + int marginLeft = viewX - width / 2; + + LayoutParams params = (LayoutParams) mTvVttText.getLayoutParams(); + params.leftMargin = marginLeft; + + if (marginLeft < 0) { + params.leftMargin = 0; + } + + int screenWidth = getResources().getDisplayMetrics().widthPixels; + if (marginLeft + width > screenWidth) { + params.leftMargin = screenWidth - width; + } + + mTvVttText.setLayoutParams(params); + } + }); + } + + @Override + public void onSpeedChange(float speedLevel) { + if (mControllerCallback != null) { + mControllerCallback.onSpeedChange(speedLevel); + } + } + + @Override + public void onMirrorChange(boolean isMirror) { + if (mControllerCallback != null) { + mControllerCallback.onMirrorToggle(isMirror); + } + } + + @Override + public void onHWAcceleration(boolean isAccelerate) { + if (mControllerCallback != null) { + mControllerCallback.onHWAccelerationToggle(isAccelerate); + } + } + + @Override + public void onQualitySelect(VideoQuality quality) { + if (mControllerCallback != null) { + mControllerCallback.onQualityChange(quality); + } + mVodQualityView.setVisibility(View.GONE); + } + + /** + * 隐藏锁屏按钮的runnable + */ + private static class HideLockViewRunnable implements Runnable{ + private WeakReference mWefControllerFullScreen; + + public HideLockViewRunnable(FullScreenPlayer controller) { + mWefControllerFullScreen = new WeakReference<>(controller); + } + @Override + public void run() { + if (mWefControllerFullScreen!=null && mWefControllerFullScreen.get()!=null) { + mWefControllerFullScreen.get().mIvLock.setVisibility(GONE); + } + } + } + + public void hideDanmu() { + mIvDanmu.setVisibility(View.GONE); + } + + public void hideReplay() { + mLayoutReplay.setVisibility(View.GONE); + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/Player.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/Player.java new file mode 100644 index 0000000..6bf942b --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/Player.java @@ -0,0 +1,223 @@ +package com.tencent.liteav.demo.superplayer.ui.player; + + +import android.graphics.Bitmap; + +import com.tencent.liteav.demo.superplayer.SuperPlayerDef; +import com.tencent.liteav.demo.superplayer.model.entity.PlayImageSpriteInfo; +import com.tencent.liteav.demo.superplayer.model.entity.PlayKeyFrameDescInfo; +import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality; + +import java.util.List; + +/** + * 播放控制接口 + */ +public interface Player { + + /** + * 设置回调 + * + * @param callback 回调接口实现对象 + */ + void setCallback(Callback callback); + + /** + * 设置水印 + * + * @param bmp 水印图 + * @param x 水印的x坐标 + * @param y 水印的y坐标 + */ + void setWatermark(Bitmap bmp, float x, float y); + + /** + * 显示控件 + */ + void show(); + + /** + * 隐藏控件 + */ + void hide(); + + /** + * 释放控件的内存 + */ + void release(); + + /** + * 更新播放状态 + * + * @param playState 正在播放{@link SuperPlayerDef.PlayerState#PLAYING} + * 正在加载{@link SuperPlayerDef.PlayerState#LOADING} + * 暂停 {@link SuperPlayerDef.PlayerState#PAUSE} + * 播放结束{@link SuperPlayerDef.PlayerState#END} + */ + void updatePlayState(SuperPlayerDef.PlayerState playState); + + /** + * 设置视频画质信息 + * + * @param list 画质列表 + */ + void setVideoQualityList(List list); + + /** + * 更新视频名称 + * + * @param title 视频名称 + */ + void updateTitle(String title); + + /** + * 更新是屁播放进度 + * + * @param current 当前进度(秒) + * @param duration 视频总时长(秒) + */ + void updateVideoProgress(long current, long duration); + + /** + * 更新播放类型 + * + * @param type 点播 {@link SuperPlayerDef.PlayerType#VOD} + * 点播 {@link SuperPlayerDef.PlayerType#LIVE} + * 直播回看 {@link SuperPlayerDef.PlayerType#LIVE_SHIFT} + */ + void updatePlayType(SuperPlayerDef.PlayerType type); + + /** + * 设置背景 + * + * @param bitmap 背景图 + */ + void setBackground(final Bitmap bitmap); + + /** + * 显示背景 + */ + void showBackground(); + + /** + * 隐藏背景 + */ + void hideBackground(); + + /** + * 更新视频播放画质 + * + * @param videoQuality 画质 + */ + void updateVideoQuality(VideoQuality videoQuality); + + /** + * 更新雪碧图信息 + * + * @param info 雪碧图信息 + */ + void updateImageSpriteInfo(PlayImageSpriteInfo info); + + /** + * 更新关键帧信息 + * + * @param list 关键帧信息列表 + */ + void updateKeyFrameDescInfo(List list); + + /** + * 播放控制回调接口 + */ + interface Callback { + + /** + * 切换播放模式回调 + * + * @param playMode 切换后的播放模式: + * 窗口模式 {@link SuperPlayerDef.PlayerMode#WINDOW } + * 全屏模式 {@link SuperPlayerDef.PlayerMode#FULLSCREEN } + * 悬浮窗模式 {@link SuperPlayerDef.PlayerMode#FLOAT } + */ + void onSwitchPlayMode(SuperPlayerDef.PlayerMode playMode); + + /** + * 返回点击事件回调 + * + * @param playMode 当前播放模式: + * 窗口模式 {@link SuperPlayerDef.PlayerMode#WINDOW } + * 全屏模式 {@link SuperPlayerDef.PlayerMode#FULLSCREEN } + * 悬浮窗模式 {@link SuperPlayerDef.PlayerMode#FLOAT } + */ + void onBackPressed(SuperPlayerDef.PlayerMode playMode); + + /** + * 悬浮窗位置更新回调 + * + * @param x 悬浮窗x坐标 + * @param y 悬浮窗y坐标 + */ + void onFloatPositionChange(int x, int y); + + /** + * 播放暂停回调 + */ + void onPause(); + + /** + * 播放继续回调 + */ + void onResume(); + + /** + * 播放跳转回调 + * + * @param position 跳转的位置(秒) + */ + void onSeekTo(int position); + + /** + * 恢复直播回调 + */ + void onResumeLive(); + + /** + * 弹幕开关回调 + * + * @param isOpen 开启:true 关闭:false + */ + void onDanmuToggle(boolean isOpen); + + /** + * 屏幕截图回调 + */ + void onSnapshot(); + + /** + * 更新画质回调 + * + * @param quality 画质 + */ + void onQualityChange(VideoQuality quality); + + /** + * 更新播放速度回调 + * + * @param speedLevel 播放速度 + */ + void onSpeedChange(float speedLevel); + + /** + * 镜像开关回调 + * + * @param isMirror 开启:true 关闭:close + */ + void onMirrorToggle(boolean isMirror); + + /** + * 硬件加速开关回调 + * + * @param isAccelerate 开启:true 关闭:false + */ + void onHWAccelerationToggle(boolean isAccelerate); + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/WindowPlayer.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/WindowPlayer.java new file mode 100644 index 0000000..97e3478 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/player/WindowPlayer.java @@ -0,0 +1,644 @@ +package com.tencent.liteav.demo.superplayer.ui.player; + +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.os.Build; +import android.util.AttributeSet; +import android.view.GestureDetector; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.tencent.liteav.demo.superplayer.SuperPlayerDef; +import com.tencent.liteav.demo.superplayer.model.utils.VideoGestureDetector; +import com.tencent.liteav.demo.superplayer.ui.view.PointSeekBar; +import com.tencent.liteav.demo.superplayer.ui.view.VideoProgressLayout; +import com.tencent.liteav.demo.superplayer.ui.view.VolumeBrightnessProgressLayout; + +import org.leanflutter.plugins.flutter_superplayer.R; + +/** + * 窗口模式播放控件 + * + * 除基本播放控制外,还有手势控制快进快退、手势调节亮度音量等 + * + * 1、点击事件监听{@link #onClick(View)} + * + * 2、触摸事件监听{@link #onTouchEvent(MotionEvent)} + * + * 2、进度条事件监听{@link #onProgressChanged(PointSeekBar, int, boolean)} + * {@link #onStartTrackingTouch(PointSeekBar)} + * {@link #onStopTrackingTouch(PointSeekBar)} + */ +public class WindowPlayer extends AbsPlayer implements View.OnClickListener, + PointSeekBar.OnSeekBarChangeListener { + + // UI控件 + private LinearLayout mLayoutTop; // 顶部标题栏布局 + private LinearLayout mLayoutBottom; // 底部进度条所在布局 + private ImageView mIvPause; // 暂停播放按钮 + private ImageView mIvFullScreen; // 全屏按钮 + private TextView mTvTitle; // 视频名称文本 + private TextView mTvBackToLive; // 返回直播文本 + private ImageView mBackground; // 背景 + private ImageView mIvWatermark; // 水印 + private TextView mTvCurrent; // 当前进度文本 + private TextView mTvDuration; // 总时长文本 + private PointSeekBar mSeekBarProgress; // 播放进度条 + private LinearLayout mLayoutReplay; // 重播按钮所在布局 + private ProgressBar mPbLiveLoading; // 加载圈 + private VolumeBrightnessProgressLayout mGestureVolumeBrightnessProgressLayout; // 音量亮度调节布局 + private VideoProgressLayout mGestureVideoProgressLayout; // 手势快进提示布局 + + private GestureDetector mGestureDetector; // 手势检测监听器 + private VideoGestureDetector mVideoGestureDetector; // 手势控制工具 + + private boolean isShowing; // 自身是否可见 + private boolean mIsChangingSeekBarProgress; // 进度条是否正在拖动,避免SeekBar由于视频播放的update而跳动 + private SuperPlayerDef.PlayerType mPlayType; // 当前播放视频类型 + private SuperPlayerDef.PlayerState mCurrentPlayState = SuperPlayerDef.PlayerState.END; // 当前播放状态 + private long mDuration; // 视频总时长 + private long mLivePushDuration; // 直播推流总时长 + private long mProgress; // 当前播放进度 + + private Bitmap mBackgroundBmp; // 背景图 + private Bitmap mWaterMarkBmp; // 水印图 + private float mWaterMarkBmpX; // 水印x坐标 + private float mWaterMarkBmpY; // 水印y坐标 + private long mLastClickTime; // 上次点击事件的时间 + + public WindowPlayer(Context context) { + super(context); + initialize(context); + } + + public WindowPlayer(Context context, AttributeSet attrs) { + super(context, attrs); + initialize(context); + } + + public WindowPlayer(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initialize(context); + } + + /** + * 初始化控件、手势检测监听器、亮度/音量/播放进度的回调 + */ + private void initialize(Context context) { + initView(context); + mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onDoubleTap(MotionEvent e) { + togglePlayState(); + show(); + if (mHideViewRunnable != null) { + removeCallbacks(mHideViewRunnable); + postDelayed(mHideViewRunnable, 7000); + } + return true; + } + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + toggle(); + return true; + } + + @Override + public boolean onScroll(MotionEvent downEvent, MotionEvent moveEvent, float distanceX, float distanceY) { + if (downEvent == null || moveEvent == null) { + return false; + } + if (mVideoGestureDetector != null && mGestureVolumeBrightnessProgressLayout != null) { + mVideoGestureDetector.check(mGestureVolumeBrightnessProgressLayout.getHeight(), downEvent, moveEvent, distanceX, distanceY); + } + return true; + } + + @Override + public boolean onDown(MotionEvent e) { + if (mVideoGestureDetector != null) { + mVideoGestureDetector.reset(getWidth(), mSeekBarProgress.getProgress()); + } + return true; + } + + }); + mGestureDetector.setIsLongpressEnabled(false); + + mVideoGestureDetector = new VideoGestureDetector(getContext()); + mVideoGestureDetector.setVideoGestureListener(new VideoGestureDetector.VideoGestureListener() { + @Override + public void onBrightnessGesture(float newBrightness) { + if (mGestureVolumeBrightnessProgressLayout != null) { + mGestureVolumeBrightnessProgressLayout.setProgress((int) (newBrightness * 100)); + mGestureVolumeBrightnessProgressLayout.setImageResource(R.drawable.superplayer_ic_light_max); + mGestureVolumeBrightnessProgressLayout.show(); + } + } + + @Override + public void onVolumeGesture(float volumeProgress) { + if (mGestureVolumeBrightnessProgressLayout != null) { + mGestureVolumeBrightnessProgressLayout.setImageResource(R.drawable.superplayer_ic_volume_max); + mGestureVolumeBrightnessProgressLayout.setProgress((int) volumeProgress); + mGestureVolumeBrightnessProgressLayout.show(); + } + } + + @Override + public void onSeekGesture(int progress) { + mIsChangingSeekBarProgress = true; + if (mGestureVideoProgressLayout != null) { + + if (progress > mSeekBarProgress.getMax()) { + progress = mSeekBarProgress.getMax(); + } + if (progress < 0) { + progress = 0; + } + mGestureVideoProgressLayout.setProgress(progress); + mGestureVideoProgressLayout.show(); + + float percentage = ((float) progress) / mSeekBarProgress.getMax(); + float currentTime = (mDuration * percentage); + if (mPlayType == SuperPlayerDef.PlayerType.LIVE || mPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) { + if (mLivePushDuration > MAX_SHIFT_TIME) { + currentTime = (int) (mLivePushDuration - MAX_SHIFT_TIME * (1 - percentage)); + } else { + currentTime = mLivePushDuration * percentage; + } + mGestureVideoProgressLayout.setTimeText(formattedTime((long) currentTime)); + } else { + mGestureVideoProgressLayout.setTimeText(formattedTime((long) currentTime) + " / " + formattedTime((long) mDuration)); + } + + } + if (mSeekBarProgress!= null) + mSeekBarProgress.setProgress(progress); + } + }); + } + + /** + * 初始化view + */ + private void initView(Context context) { + LayoutInflater.from(context).inflate(R.layout.superplayer_vod_player_window, this); + + mLayoutTop = (LinearLayout) findViewById(R.id.superplayer_rl_top); + mLayoutTop.setOnClickListener(this); + mLayoutBottom = (LinearLayout) findViewById(R.id.superplayer_ll_bottom); + mLayoutBottom.setOnClickListener(this); + mLayoutReplay = (LinearLayout) findViewById(R.id.superplayer_ll_replay); + mTvTitle = (TextView) findViewById(R.id.superplayer_tv_title); + mIvPause = (ImageView) findViewById(R.id.superplayer_iv_pause); + mTvCurrent = (TextView) findViewById(R.id.superplayer_tv_current); + mTvDuration = (TextView) findViewById(R.id.superplayer_tv_duration); + mSeekBarProgress = (PointSeekBar) findViewById(R.id.superplayer_seekbar_progress); + mSeekBarProgress.setProgress(0); + mSeekBarProgress.setMax(100); + mIvFullScreen = (ImageView) findViewById(R.id.superplayer_iv_fullscreen); + mTvBackToLive = (TextView) findViewById(R.id.superplayer_tv_back_to_live); + mPbLiveLoading = (ProgressBar) findViewById(R.id.superplayer_pb_live); + + mTvBackToLive.setOnClickListener(this); + mIvPause.setOnClickListener(this); + mIvFullScreen.setOnClickListener(this); + mLayoutTop.setOnClickListener(this); + mLayoutReplay.setOnClickListener(this); + + mSeekBarProgress.setOnSeekBarChangeListener(this); + + mGestureVolumeBrightnessProgressLayout = (VolumeBrightnessProgressLayout)findViewById(R.id.superplayer_gesture_progress); + + mGestureVideoProgressLayout = (VideoProgressLayout) findViewById(R.id.superplayer_video_progress_layout); + + mBackground = (ImageView)findViewById(R.id.superplayer_small_iv_background); + setBackground(mBackgroundBmp); + + mIvWatermark = (ImageView)findViewById(R.id.superplayer_small_iv_water_mark); + } + + /** + * 切换播放状态 + * + * 双击和点击播放/暂停按钮会触发此方法 + */ + private void togglePlayState() { + switch (mCurrentPlayState) { + case PAUSE: + case END: + if (mControllerCallback != null) { + mControllerCallback.onResume(); + } + break; + case PLAYING: + case LOADING: + if (mControllerCallback != null) { + mControllerCallback.onPause(); + } + mLayoutReplay.setVisibility(View.GONE); + break; + } + show(); + } + + /** + * 切换自身的可见性 + */ + private void toggle() { + if (isShowing) { + hide(); + } else { + show(); + if (mHideViewRunnable != null) { + removeCallbacks(mHideViewRunnable); + postDelayed(mHideViewRunnable, 7000); + } + } + } + + /** + * 设置水印 + * + * @param bmp 水印图 + * @param x 水印的x坐标 + * @param y 水印的y坐标 + */ + @Override + public void setWatermark(final Bitmap bmp, float x, float y) { + mWaterMarkBmp = bmp; + mWaterMarkBmpX = x; + mWaterMarkBmpY = y; + if (bmp != null) { + this.post(new Runnable() { + @Override + public void run() { + int width = WindowPlayer.this.getWidth(); + int height = WindowPlayer.this.getHeight(); + + int x = (int) (width * mWaterMarkBmpX) - bmp.getWidth() / 2; + int y = (int) (height * mWaterMarkBmpY) - bmp.getHeight() / 2; + + mIvWatermark.setX(x); + mIvWatermark.setY(y); + + mIvWatermark.setVisibility(VISIBLE); + setBitmap(mIvWatermark, bmp); + } + }); + } else { + mIvWatermark.setVisibility(GONE); + } + } + + /** + * 显示控件 + */ + @Override + public void show() { + isShowing = true; + mLayoutTop.setVisibility(View.VISIBLE); + mLayoutBottom.setVisibility(View.VISIBLE); + + if (mPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) { + mTvBackToLive.setVisibility(View.VISIBLE); + } + } + + /** + * 隐藏控件 + */ + @Override + public void hide() { + isShowing = false; + mLayoutTop.setVisibility(View.GONE); + mLayoutBottom.setVisibility(View.GONE); + + if (mPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) { + mTvBackToLive.setVisibility(View.GONE); + } + } + + @Override + public void updatePlayState(SuperPlayerDef.PlayerState playState) { + switch (playState) { + case PLAYING: + mIvPause.setImageResource(R.drawable.superplayer_ic_vod_pause_normal); + toggleView(mPbLiveLoading, false); + toggleView(mLayoutReplay, false); + break; + case LOADING: + mIvPause.setImageResource(R.drawable.superplayer_ic_vod_pause_normal); + toggleView(mPbLiveLoading, true); + toggleView(mLayoutReplay, false); + break; + case PAUSE: + mIvPause.setImageResource(R.drawable.superplayer_ic_vod_play_normal); + toggleView(mPbLiveLoading, false); + toggleView(mLayoutReplay, false); + break; + case END: + mIvPause.setImageResource(R.drawable.superplayer_ic_vod_play_normal); + toggleView(mPbLiveLoading, false); + toggleView(mLayoutReplay, true); + break; + } + mCurrentPlayState = playState; + } + + /** + * 更新视频名称 + * + * @param title 视频名称 + */ + @Override + public void updateTitle(String title) { + mTvTitle.setText(title); + } + + /** + * 更新视频播放进度 + * + * @param current 当前进度(秒) + * @param duration 视频总时长(秒) + */ + @Override + public void updateVideoProgress(long current, long duration) { + mProgress = current < 0 ? 0 : current; + mDuration = duration < 0 ? 0 : duration; + mTvCurrent.setText(formattedTime(mProgress)); + + float percentage = mDuration > 0 ? ((float) mProgress / (float) mDuration) : 1.0f; + if (mProgress == 0) { + mLivePushDuration = 0; + percentage = 0; + } + if (mPlayType == SuperPlayerDef.PlayerType.LIVE || mPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) { + mLivePushDuration = mLivePushDuration > mProgress ? mLivePushDuration : mProgress; + long leftTime = mDuration - mProgress; + mDuration = mDuration > MAX_SHIFT_TIME ? MAX_SHIFT_TIME : mDuration; + percentage = 1 - (float) leftTime / (float) mDuration; + } + + if (percentage >= 0 && percentage <= 1) { + int progress = Math.round(percentage * mSeekBarProgress.getMax()); + if (!mIsChangingSeekBarProgress) { + if (mPlayType == SuperPlayerDef.PlayerType.LIVE) { + mSeekBarProgress.setProgress(mSeekBarProgress.getMax()); + } else { + mSeekBarProgress.setProgress(progress); + } + } + mTvDuration.setText(formattedTime(mDuration)); + } + } + + @Override + public void updatePlayType(SuperPlayerDef.PlayerType type) { + mPlayType = type; + switch (type) { + case VOD: + mTvBackToLive.setVisibility(View.GONE); + mTvDuration.setVisibility(View.VISIBLE); + break; + case LIVE: + mTvBackToLive.setVisibility(View.GONE); + mTvDuration.setVisibility(View.GONE); + mSeekBarProgress.setProgress(100); + break; + case LIVE_SHIFT: + if (mLayoutBottom.getVisibility() == VISIBLE) + mTvBackToLive.setVisibility(View.VISIBLE); + mTvDuration.setVisibility(View.GONE); + break; + } + } + + /** + * 设置背景 + * + * @param bitmap 背景图 + */ + @Override + public void setBackground(final Bitmap bitmap) { + this.post(new Runnable() { + @Override + public void run() { + if (bitmap == null) return; + if (mBackground == null) { + mBackgroundBmp = bitmap; + } else { + setBitmap(mBackground, mBackgroundBmp); + } + } + }); + } + + /** + * 设置目标ImageView显示的图片 + */ + private void setBitmap(ImageView view, Bitmap bitmap) { + if (view == null || bitmap == null) return; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + view.setBackground(new BitmapDrawable(getContext().getResources(), bitmap)); + } else { + view.setBackgroundDrawable(new BitmapDrawable(getContext().getResources(), bitmap)); + } + } + + /** + * 显示背景 + */ + @Override + public void showBackground() { + post(new Runnable() { + @Override + public void run() { + ValueAnimator alpha = ValueAnimator.ofFloat(0.0f, 1); + alpha.setDuration(500); + alpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float value = (Float) animation.getAnimatedValue(); + mBackground.setAlpha(value); + if (value == 1) { + mBackground.setVisibility(VISIBLE); + } + } + }); + alpha.start(); + } + }); + } + + /** + * 隐藏背景 + */ + @Override + public void hideBackground() { + post(new Runnable() { + @Override + public void run() { + if (mBackground.getVisibility() != View.VISIBLE) return; + ValueAnimator alpha = ValueAnimator.ofFloat(1.0f, 0.0f); + alpha.setDuration(500); + alpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float value = (Float) animation.getAnimatedValue(); + mBackground.setAlpha(value); + if (value == 0) { + mBackground.setVisibility(GONE); + } + } + }); + alpha.start(); + } + }); + } + + /** + * 重写触摸事件监听,实现手势调节亮度、音量以及播放进度 + */ + @Override + public boolean onTouchEvent(MotionEvent event) { + if (mGestureDetector != null) + mGestureDetector.onTouchEvent(event); + + if (event.getAction() == MotionEvent.ACTION_UP && mVideoGestureDetector != null && mVideoGestureDetector.isVideoProgressModel()) { + int progress = mVideoGestureDetector.getVideoProgress(); + if (progress > mSeekBarProgress.getMax()) { + progress = mSeekBarProgress.getMax(); + } + if (progress < 0) { + progress = 0; + } + mSeekBarProgress.setProgress(progress); + + int seekTime; + float percentage = progress * 1.0f / mSeekBarProgress.getMax(); + if (mPlayType == SuperPlayerDef.PlayerType.LIVE || mPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) { + if (mLivePushDuration > MAX_SHIFT_TIME) { + seekTime = (int) (mLivePushDuration - MAX_SHIFT_TIME * (1 - percentage)); + } else { + seekTime = (int) (mLivePushDuration * percentage); + } + }else { + seekTime = (int) (percentage * mDuration); + } + if (mControllerCallback != null) { + mControllerCallback.onSeekTo(seekTime); + } + mIsChangingSeekBarProgress = false; + } + + if(event.getAction() == MotionEvent.ACTION_DOWN) { + removeCallbacks(mHideViewRunnable); + } else if(event.getAction() == MotionEvent.ACTION_UP) { + postDelayed(mHideViewRunnable, 7000); + } + return true; + } + + /** + * 设置点击事件监听 + */ + @Override + public void onClick(View view) { + if (System.currentTimeMillis() - mLastClickTime < 300) { //限制点击频率 + return; + } + mLastClickTime = System.currentTimeMillis(); + int id = view.getId(); + if (id == R.id.superplayer_rl_top) { //顶部标题栏 + if (mControllerCallback != null) { + mControllerCallback.onBackPressed(SuperPlayerDef.PlayerMode.WINDOW); + } + } else if (id == R.id.superplayer_iv_pause) { //暂停\播放按钮 + togglePlayState(); + } else if (id == R.id.superplayer_iv_fullscreen) { //全屏按钮 + if (mControllerCallback != null) { + mControllerCallback.onSwitchPlayMode(SuperPlayerDef.PlayerMode.FULLSCREEN); + } + } else if (id == R.id.superplayer_ll_replay) { //重播按钮 + if (mControllerCallback != null) { + mControllerCallback.onResume(); + } + } else if (id == R.id.superplayer_tv_back_to_live) { //返回直播按钮 + if (mControllerCallback != null) { + mControllerCallback.onResumeLive(); + } + } + } + + @Override + public void onProgressChanged(PointSeekBar seekBar, int progress, boolean fromUser) { + if (mGestureVideoProgressLayout != null && fromUser) { + mGestureVideoProgressLayout.show(); + float percentage = ((float) progress) / seekBar.getMax(); + float currentTime = (mDuration * percentage); + if (mPlayType == SuperPlayerDef.PlayerType.LIVE || mPlayType == SuperPlayerDef.PlayerType.LIVE_SHIFT) { + if (mLivePushDuration > MAX_SHIFT_TIME) { + currentTime = (int) (mLivePushDuration - MAX_SHIFT_TIME * (1 - percentage)); + } else { + currentTime = mLivePushDuration * percentage; + } + mGestureVideoProgressLayout.setTimeText(formattedTime((long) currentTime)); + } else { + mGestureVideoProgressLayout.setTimeText(formattedTime((long) currentTime) + " / " + formattedTime((long) mDuration)); + } + mGestureVideoProgressLayout.setProgress(progress); + } + } + + @Override + public void onStartTrackingTouch(PointSeekBar seekBar) { + removeCallbacks(mHideViewRunnable); + } + + @Override + public void onStopTrackingTouch(PointSeekBar seekBar) { + int curProgress = seekBar.getProgress(); + int maxProgress = seekBar.getMax(); + + switch (mPlayType) { + case VOD: + if (curProgress >= 0 && curProgress <= maxProgress) { + // 关闭重播按钮 + toggleView(mLayoutReplay, false); + float percentage = ((float) curProgress) / maxProgress; + int position = (int) (mDuration * percentage); + if (mControllerCallback != null) { + mControllerCallback.onSeekTo(position); + mControllerCallback.onResume(); + } + } + break; + case LIVE: + case LIVE_SHIFT: + toggleView(mPbLiveLoading, true); + int seekTime = (int) (mLivePushDuration * curProgress * 1.0f / maxProgress); + if (mLivePushDuration > MAX_SHIFT_TIME) { + seekTime = (int) (mLivePushDuration - MAX_SHIFT_TIME * (maxProgress - curProgress) * 1.0f / maxProgress); + } + if (mControllerCallback != null) { + mControllerCallback.onSeekTo(seekTime); + } + break; + } + postDelayed(mHideViewRunnable, 7000); + } + + public void hideReplay() { + mLayoutReplay.setVisibility(View.GONE); + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/view/DanmuView.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/view/DanmuView.java new file mode 100644 index 0000000..3e3175f --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/view/DanmuView.java @@ -0,0 +1,182 @@ +package com.tencent.liteav.demo.superplayer.ui.view; + +import android.content.Context; +import android.graphics.Color; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.util.AttributeSet; + +import com.tencent.liteav.basic.log.TXCLog; + +import java.util.Random; + +import master.flame.danmaku.controller.DrawHandler; +import master.flame.danmaku.danmaku.model.BaseDanmaku; +import master.flame.danmaku.danmaku.model.DanmakuTimer; +import master.flame.danmaku.danmaku.model.IDanmakus; +import master.flame.danmaku.danmaku.model.android.DanmakuContext; +import master.flame.danmaku.danmaku.model.android.Danmakus; +import master.flame.danmaku.danmaku.parser.BaseDanmakuParser; +import master.flame.danmaku.ui.widget.DanmakuView; + +/** + * Created by liyuejiao on 2018/1/29. + * + * 全功能播放器中的弹幕View + * + * 1、随机发送弹幕{@link #addDanmaku(String, boolean)} + * + * 2、弹幕操作所在线程的Handler{@link DanmuHandler} + */ +public class DanmuView extends DanmakuView { + private Context mContext; + private DanmakuContext mDanmakuContext; + private boolean mShowDanma; // 弹幕是否开启 + private HandlerThread mHandlerThread; // 发送弹幕的线程 + private DanmuHandler mDanmuHandler; // 弹幕线程handler + + public DanmuView(Context context) { + super(context); + init(context); + } + + public DanmuView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public DanmuView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context); + } + + + private void init(Context context) { + mContext = context; + enableDanmakuDrawingCache(true); + setCallback(new DrawHandler.Callback() { + @Override + public void prepared() { + mShowDanma = true; + start(); + generateDanmaku(); + } + + @Override + public void updateTimer(DanmakuTimer timer) { + + } + + @Override + public void danmakuShown(BaseDanmaku danmaku) { + + } + + @Override + public void drawingFinished() { + + } + }); + mDanmakuContext = DanmakuContext.create(); + prepare(mParser, mDanmakuContext); + } + + @Override + public void release() { + super.release(); + mShowDanma = false; + if (mDanmuHandler != null) { + mDanmuHandler.removeCallbacksAndMessages(null); + mDanmuHandler = null; + } + if (mHandlerThread != null) { + mHandlerThread.quit(); + mHandlerThread = null; + } + } + + private BaseDanmakuParser mParser = new BaseDanmakuParser() { + @Override + protected IDanmakus parse() { + return new Danmakus(); + } + }; + + /** + * 随机生成一些弹幕内容以供测试 + */ + private void generateDanmaku() { + mHandlerThread = new HandlerThread("Danmu"); + mHandlerThread.start(); + mDanmuHandler = new DanmuHandler(mHandlerThread.getLooper()); + } + + /** + * 向弹幕View中添加一条弹幕 + * + * @param content 弹幕的具体内容 + * @param withBorder 弹幕是否有边框 + */ + private void addDanmaku(String content, boolean withBorder) { + BaseDanmaku danmaku = mDanmakuContext.mDanmakuFactory.createDanmaku(BaseDanmaku.TYPE_SCROLL_RL); + if (danmaku != null) { + danmaku.text = content; + danmaku.padding = 5; + danmaku.textSize = sp2px(mContext, 20.0f); + danmaku.textColor = Color.WHITE; + danmaku.setTime(getCurrentTime()); + if (withBorder) { + danmaku.borderColor = Color.GREEN; + } + addDanmaku(danmaku); + } + } + + /** + * sp单位转px + * + * @param context + * @param spValue + * @return + */ + public int sp2px(Context context, float spValue) { + final float scale = context.getResources().getDisplayMetrics().density; + return (int) (spValue * scale + 0.5f); + } + + public void toggle(boolean on) { + TXCLog.i(TAG, "onToggleControllerView on:" + on); + if (on) { + mDanmuHandler.sendEmptyMessageAtTime(DanmuHandler.MSG_SEND_DANMU, 100); + } else { + mDanmuHandler.removeMessages(DanmuHandler.MSG_SEND_DANMU); + } + } + + public class DanmuHandler extends Handler { + public static final int MSG_SEND_DANMU = 1001; + + public DanmuHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SEND_DANMU: + sendDanmu(); + int time = new Random().nextInt(1000); + mDanmuHandler.sendEmptyMessageDelayed(MSG_SEND_DANMU, time); + break; + } + } + + private void sendDanmu() { + int time = new Random().nextInt(300); + String content = "弹幕" + time + time; + addDanmaku(content, false); + } + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/view/PointSeekBar.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/view/PointSeekBar.java new file mode 100644 index 0000000..3381d34 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/view/PointSeekBar.java @@ -0,0 +1,553 @@ +package com.tencent.liteav.demo.superplayer.ui.view; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.widget.RelativeLayout; + +import org.leanflutter.plugins.flutter_superplayer.R; + +import java.util.List; + +/** + * 一个带有打点的,模仿seekbar的view + * + * 除seekbar基本功能外,还具备关键帧信息打点的功能 + * + * 1、添加打点信息{@link #addPoint(PointParams, int)} + * + * 2、自定义thumb{@link TCThumbView} + * + * 3、打点view{@link TCPointView} + * + * 4、打点信息参数{@link PointParams} + */ +public class PointSeekBar extends RelativeLayout { + + private int mWidth; // 自身宽度 + private int mHeight; // 自身高度 + private int mSeekBarLeft; // SeekBar的起点位置 + private int mSeekBarRight; // SeekBar的终点位置 + private int mBgTop; // 进度条距离父布局上边界的距离 + private int mBgBottom; // 进度条距离父布局下边界的距离 + private int mRoundSize; // 进度条圆角大小 + private int mViewEnd; // 自身的右边界 + + private Paint mNormalPaint; // seekbar背景画笔 + private Paint mProgressPaint; // seekbar进度条画笔 + private Paint mPointerPaint; // 打点view画笔 + + private Drawable mThumbDrawable; // 拖动块图片 + private int mHalfDrawableWidth; // Thumb图片宽度的一半 + // Thumb距父布局中的位置 + private float mThumbLeft; // thumb的marginLeft值 + private float mThumbRight; // thumb的marginRight值 + private float mThumbTop; // thumb的marginTop值 + private float mThumbBottom; // thumb的marginBottom值 + + + private boolean mIsOnDrag; // 是否处于拖动状态 + private float mCurrentLeftOffset = 0; // thumb距离打点view的偏移量 + private float mLastX; // 上一次点击事件的横坐标,用于计算偏移量 + + private int mCurrentProgress; // 当前seekbar的数值 + private int mMaxProgress = 100; // seekbar最大数值 + private float mBarHeightPx = 0; // seekbar的高度大小 px + + private TCThumbView mThumbView; // 滑动ThumbView + private List mPointList; // 打点信息的列表 + private OnSeekBarPointClickListener mPointClickListener; // 打点view点击回调 + private boolean mIsChangePointViews; // 打点信息是否更新过 + + public PointSeekBar(Context context) { + super(context); + init(null); + } + + public PointSeekBar(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs); + } + + public PointSeekBar(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs); + } + + /** + * 设置seekbar进度值 + * + * @param progress + */ + public void setProgress(int progress) { + if (progress < 0) { + progress = 0; + } + if (progress > mMaxProgress) { + progress = mMaxProgress; + } + if (!mIsOnDrag) { + mCurrentProgress = progress; + invalidate(); + callbackProgressInternal(progress, false); + } + } + + /** + * 设置seekbar最大值 + * + * @param max + */ + public void setMax(int max) { + mMaxProgress = max; + } + + /** + * 获取seekbar进度值 + * + * @return + */ + public int getProgress() { + return mCurrentProgress; + } + + /** + * 获取seekbar最大值 + * + * @return + */ + public int getMax() { + return mMaxProgress; + } + + private void init(AttributeSet attrs) { + setWillNotDraw(false); + int progressColor = getResources().getColor(R.color.superplayer_default_progress_color); + int backgroundColor = getResources().getColor(R.color.superplayer_default_progress_background_color); + if (attrs != null) { + TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.SuperPlayerTCPointSeekBar); + mThumbDrawable = a.getDrawable(R.styleable.SuperPlayerTCPointSeekBar_psb_thumbBackground); + mHalfDrawableWidth = mThumbDrawable.getIntrinsicWidth() / 2; + progressColor = a.getColor(R.styleable.SuperPlayerTCPointSeekBar_psb_progressColor, progressColor); + backgroundColor = a.getColor(R.styleable.SuperPlayerTCPointSeekBar_psb_backgroundColor, backgroundColor); + mCurrentProgress = a.getInt(R.styleable.SuperPlayerTCPointSeekBar_psb_progress, 0); + mMaxProgress = a.getInt(R.styleable.SuperPlayerTCPointSeekBar_psb_max, 100); + + mBarHeightPx = a.getDimension(R.styleable.SuperPlayerTCPointSeekBar_psb_progressHeight, 8); + a.recycle(); + } + mNormalPaint = new Paint(); + mNormalPaint.setColor(backgroundColor); + + mPointerPaint = new Paint(); + mPointerPaint.setColor(Color.RED); + + mProgressPaint = new Paint(); + mProgressPaint.setColor(progressColor); + this.post(new Runnable() { + @Override + public void run() { + addThumbView(); + } + }); + } + + + private void changeThumbPos() { + LayoutParams params = (LayoutParams) mThumbView.getLayoutParams(); + params.leftMargin = (int) mThumbLeft; + params.topMargin = (int) mThumbTop; + mThumbView.setLayoutParams(params); + } + + private void addThumbView() { + mThumbView = new TCThumbView(getContext(), mThumbDrawable); + LayoutParams thumbParams = new LayoutParams(mThumbDrawable.getIntrinsicHeight(), mThumbDrawable.getIntrinsicHeight()); + mThumbView.setLayoutParams(thumbParams); + addView(mThumbView); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + mWidth = w; + mHeight = h; + + mSeekBarLeft = mHalfDrawableWidth; + mSeekBarRight = mWidth - mHalfDrawableWidth; + + + float barPaddingTop = (mHeight - mBarHeightPx) / 2; + mBgTop = (int) barPaddingTop; + mBgBottom = (int) (mHeight - barPaddingTop); + mRoundSize = mHeight / 2; + + mViewEnd = mWidth; + + } + + private void calProgressDis() { + float dis = (mSeekBarRight - mSeekBarLeft) * (mCurrentProgress * 1.0f / mMaxProgress); + mThumbLeft = dis; + mLastX = mThumbLeft; + mCurrentLeftOffset = 0; + calculatePointerRect(); + } + + + private void addThumbAndPointViews() { + this.post(new Runnable() { + @Override + public void run() { + if (mIsChangePointViews) { + PointSeekBar.this.removeAllViews(); + if (mPointList != null) { + for (int i = 0; i < mPointList.size(); i++) { + PointParams params = mPointList.get(i); + addPoint(params, i); + } + } + addThumbView(); + mIsChangePointViews = false; + } + if(!mIsOnDrag) { + calProgressDis(); + changeThumbPos(); + } + } + }); + } + + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + //draw bg + RectF rectF = new RectF(); + rectF.left = mSeekBarLeft; + rectF.right = mSeekBarRight; + rectF.top = mBgTop; + rectF.bottom = mBgBottom; + canvas.drawRoundRect(rectF, mRoundSize, mRoundSize, mNormalPaint); + + //draw progress + RectF pRecf = new RectF(); + pRecf.left = mSeekBarLeft; + pRecf.top = mBgTop; + pRecf.right = mThumbRight - mHalfDrawableWidth; + pRecf.bottom = mBgBottom; + canvas.drawRoundRect(pRecf, + mRoundSize, mRoundSize, mProgressPaint); + + addThumbAndPointViews(); + } + + /** + * 添加打点view + * + * @param pointParams + * @param index + */ + public void addPoint(PointParams pointParams, final int index) { + float percent = pointParams.progress * 1.0f / mMaxProgress; + int pointSize = mBgBottom - mBgTop; + float leftMargin = percent * (mSeekBarRight - mSeekBarLeft); + + float rectLeft = (mThumbDrawable.getIntrinsicWidth() - pointSize) / 2; + float rectTop = mBgTop; + float rectBottom = mBgBottom; + float rectRight = rectLeft + pointSize; + + final TCPointView view = new TCPointView(getContext()); + LayoutParams params = new LayoutParams(mThumbDrawable.getIntrinsicWidth(), mThumbDrawable.getIntrinsicWidth()); + params.leftMargin = (int) leftMargin; + view.setDrawRect(rectLeft, rectTop, rectBottom, rectRight); + view.setLayoutParams(params); + view.setColor(pointParams.color); + view.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mPointClickListener != null) { + mPointClickListener.onSeekBarPointClick(view, index); + } + } + }); + addView(view); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (!isEnabled()) return false; + + boolean isHandle = false; + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + isHandle = handleDownEvent(event); + break; + case MotionEvent.ACTION_MOVE: + isHandle = handleMoveEvent(event); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + isHandle = handleUpEvent(event); + break; + + } + return isHandle; + } + + private boolean handleUpEvent(MotionEvent event) { + float x = event.getX(); + float y = event.getY(); + if (mIsOnDrag) { + mIsOnDrag = false; + if (mListener != null) { + mListener.onStopTrackingTouch(this); + } + return true; + } + return false; + } + + private boolean handleMoveEvent(MotionEvent event) { + float x = event.getX(); + float y = event.getY(); + if (mIsOnDrag) { + mCurrentLeftOffset = x - mLastX; + //计算出标尺的Rect + calculatePointerRect(); + if (mThumbRight - mHalfDrawableWidth <= mSeekBarLeft) { + mThumbLeft = 0; + mThumbRight = mThumbLeft + mThumbDrawable.getIntrinsicWidth(); + } + if (mThumbLeft + mHalfDrawableWidth >= mSeekBarRight) { + mThumbRight = mWidth; + mThumbLeft = mWidth - mThumbDrawable.getIntrinsicWidth(); + } + changeThumbPos(); + invalidate(); + callbackProgress(); + mLastX = x; + return true; + } + return false; + } + + private void callbackProgress() { + if (mThumbLeft == 0) { + callbackProgressInternal(0, true); + } else if (mThumbRight == mWidth) { + callbackProgressInternal(mMaxProgress, true); + } else { + float pointerMiddle = mThumbLeft + mHalfDrawableWidth; + if (pointerMiddle >= mViewEnd) { + callbackProgressInternal(mMaxProgress, true); + } else { + float percent = pointerMiddle / mViewEnd * 1.0f; + int progress = (int) (percent * mMaxProgress); + if (progress > mMaxProgress) { + progress = mMaxProgress; + } + callbackProgressInternal(progress, true); + } + } + } + + private void callbackProgressInternal(int progress, boolean isFromUser) { + mCurrentProgress = progress; + if (mListener != null) { + mListener.onProgressChanged(this, progress, isFromUser); + } + } + + + private boolean handleDownEvent(MotionEvent event) { + float x = event.getX(); + float y = event.getY(); + if (x >= mThumbLeft - 100 && x <= mThumbRight + 100) { + if (mListener != null) + mListener.onStartTrackingTouch(this); + mIsOnDrag = true; + mLastX = x; + return true; + } + return false; + } + + private void calculatePointerRect() { + //draw pointer + float pointerLeft = getPointerLeft(mCurrentLeftOffset); + float pointerRight = pointerLeft + mThumbDrawable.getIntrinsicWidth(); + mThumbLeft = pointerLeft; + mThumbRight = pointerRight; + mThumbTop = 0; + mThumbBottom = mHeight; + } + + + private float getPointerLeft(float offset) { + return mThumbLeft + offset; + } + + private OnSeekBarChangeListener mListener; + + public void setOnSeekBarChangeListener(OnSeekBarChangeListener listener) { + mListener = listener; + } + + public interface OnSeekBarChangeListener { + + void onProgressChanged(PointSeekBar seekBar, int progress, boolean fromUser); + + void onStartTrackingTouch(PointSeekBar seekBar); + + void onStopTrackingTouch(PointSeekBar seekBar); + } + + /** + * 设置监听 + * + * @param listener + */ + public void setOnPointClickListener(OnSeekBarPointClickListener listener) { + mPointClickListener = listener; + } + + /** + * 打点view点击回调 + */ + public interface OnSeekBarPointClickListener { + void onSeekBarPointClick(View view, int pos); + } + + /** + * 设置打点信息列表 + * + * @param pointList + */ + public void setPointList(List pointList) { + mPointList = pointList; + mIsChangePointViews = true; + invalidate(); + } + + /** + * 打点信息 + */ + public static class PointParams { + int progress = 0; // 视频进度值(秒) + int color = Color.RED; // 打点view的颜色 + + public PointParams(int progress, int color) { + this.progress = progress; + this.color = color; + } + } + + /** + * 打点view + */ + private static class TCPointView extends View { + private int mColor = Color.WHITE; // view颜色 + private Paint mPaint; // 画笔 + private RectF mRectF; // 打点view的位置信息(矩形) + + public TCPointView(Context context) { + super(context); + init(); + } + + public TCPointView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public TCPointView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + mPaint = new Paint(); + mPaint.setAntiAlias(true); + mPaint.setColor(mColor); + mRectF = new RectF(); + } + + /** + * 设置打点颜色 + * + * @param color + */ + public void setColor(int color) { + mColor = color; + mPaint.setColor(mColor); + } + + /** + * 设置打点view的位置信息 + * + * @param left + * @param top + * @param right + * @param bottom + */ + public void setDrawRect(float left, float top, float right, float bottom) { + mRectF.left = left; + mRectF.top = top; + mRectF.right = right; + mRectF.bottom = bottom; + } + + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + canvas.drawRect(mRectF, mPaint); + } + } + + /** + * 拖动块view + */ + private static class TCThumbView extends View { + private Paint mPaint; // 画笔 + private Rect mRect; // 位置信息(矩形) + private Drawable mThumbDrawable;// thumb图片 + + public TCThumbView(Context context, Drawable drawable) { + super(context); + mThumbDrawable = drawable; + mPaint = new Paint(); + mPaint.setAntiAlias(true); + mRect = new Rect(); + } + + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + mRect.left = 0; + mRect.top = 0; + mRect.right = w; + mRect.bottom = h; + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + mThumbDrawable.setBounds(mRect); + mThumbDrawable.draw(canvas); + } + } + + +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/view/VideoProgressLayout.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/view/VideoProgressLayout.java new file mode 100644 index 0000000..fb9c322 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/view/VideoProgressLayout.java @@ -0,0 +1,111 @@ +package com.tencent.liteav.demo.superplayer.ui.view; + +import android.content.Context; +import android.graphics.Bitmap; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import org.leanflutter.plugins.flutter_superplayer.R; + + +/** + * 滑动手势控制播放进度时显示的进度提示view + */ + +public class VideoProgressLayout extends RelativeLayout { + private ImageView mIvThumbnail; // 视频缩略图 + private TextView mTvTime; // 视频进度文本 + private ProgressBar mProgressBar; // 进度条 + private HideRunnable mHideRunnable; // 隐藏自身的线程 + private int duration = 1000; // 自身消失的延迟事件ms + + public VideoProgressLayout(Context context) { + super(context); + init(context); + } + + public VideoProgressLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + private void init(Context context) { + LayoutInflater.from(context).inflate(R.layout.superplayer_video_progress_layout, this); + mIvThumbnail = (ImageView) findViewById(R.id.superplayer_iv_progress_thumbnail); + mProgressBar = (ProgressBar) findViewById(R.id.superplayer_pb_progress_bar); + mTvTime = (TextView) findViewById(R.id.superplayer_tv_progress_time); + setVisibility(GONE); + mHideRunnable = new HideRunnable(); + } + + /** + * 显示view + */ + public void show() { + setVisibility(VISIBLE); + removeCallbacks(mHideRunnable); + postDelayed(mHideRunnable, duration); + } + + /** + * 设置视频进度事件文本 + * + * @param text + */ + public void setTimeText(String text) { + mTvTime.setText(text); + } + + /** + * 设置progressbar的进度值 + * + * @param progress + */ + public void setProgress(int progress) { + mProgressBar.setProgress(progress); + } + + /** + * 设置view消失延迟的时间 + * + * @param duration + */ + public void setDuration(int duration) { + this.duration = duration; + } + + /** + * 设置缩略图图片 + * + * @param bitmap + */ + public void setThumbnail(Bitmap bitmap) { + mIvThumbnail.setVisibility(VISIBLE); + mIvThumbnail.setImageBitmap(bitmap); + } + + /** + * 设置progressbar的可见性 + * + * @param enable + */ + public void setProgressVisibility(boolean enable) { + mProgressBar.setVisibility(enable ? VISIBLE : GONE); + } + + /** + * 隐藏view的线程 + */ + private class HideRunnable implements Runnable { + @Override + public void run() { + mIvThumbnail.setImageBitmap(null); + mIvThumbnail.setVisibility(GONE); + VideoProgressLayout.this.setVisibility(GONE); + } + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/view/VodMoreView.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/view/VodMoreView.java new file mode 100644 index 0000000..1091900 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/view/VodMoreView.java @@ -0,0 +1,372 @@ +package com.tencent.liteav.demo.superplayer.ui.view; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.CompoundButton; +import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import android.widget.RelativeLayout; +import android.widget.SeekBar; +import android.widget.Switch; + +import com.tencent.liteav.demo.superplayer.SuperPlayerDef; +import com.tencent.liteav.demo.superplayer.SuperPlayerGlobalConfig; + +import org.leanflutter.plugins.flutter_superplayer.R; + +/** + * Created by yuejiaoli on 2018/7/4. + * + * 更多选项弹框 + * + * 1、声音调节seekBar回调{@link #mVolumeChangeListener} + * + * 2、亮度调节seekBar回调{@link #mLightChangeListener} + * + * 3、倍速选择回调{@link #onCheckedChanged(RadioGroup, int)} + * + * 4、镜像、硬件加速开关回调{@link #onCheckedChanged(CompoundButton, boolean)} + */ + +public class VodMoreView extends RelativeLayout implements RadioGroup.OnCheckedChangeListener, CompoundButton.OnCheckedChangeListener { + + private static final String VOLUME_CHANGED_ACTION = "android.media.VOLUME_CHANGED_ACTION"; + private static final String EXTRA_VOLUME_STREAM_TYPE = "android.media.EXTRA_VOLUME_STREAM_TYPE"; + + private Context mContext; + + private SeekBar mSeekBarVolume; // 音量seekBar + private SeekBar mSeekBarLight; // 亮度seekBar + private Switch mSwitchMirror; // 镜像开关 + private Switch mSwitchAccelerate; // 硬解开关 + private Callback mCallback; // 回调 + private AudioManager mAudioManager; // 音频管理器 + private RadioGroup mRadioGroup; // 倍速选择radioGroup + private RadioButton mRbSpeed1; // 1.0倍速按钮 + private RadioButton mRbSpeed125; // 1.25倍速按钮 + private RadioButton mRbSpeed15; // 1.5倍速按钮 + private RadioButton mRbSpeed2; // 2.0倍速按钮 + private LinearLayout mLayoutSpeed; // 倍速按钮所在布局 + private LinearLayout mLayoutMirror; // 镜像按钮所在布局 + + private VolumeBroadcastReceiver mVolumeBroadcastReceiver; + + public VodMoreView(Context context) { + super(context); + init(context); + } + + public VodMoreView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public VodMoreView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + private void init(Context context) { + mContext = context; + LayoutInflater.from(mContext).inflate(R.layout.superplayer_more_popup_view, this); + + mLayoutSpeed = (LinearLayout) findViewById(R.id.superplayer_ll_speed); + mRadioGroup = (RadioGroup) findViewById(R.id.superplayer_rg); + mRbSpeed1 = (RadioButton) findViewById(R.id.superplayer_rb_speed1); + mRbSpeed125 = (RadioButton) findViewById(R.id.superplayer_rb_speed125); + mRbSpeed15 = (RadioButton) findViewById(R.id.superplayer_rb_speed15); + mRbSpeed2 = (RadioButton) findViewById(R.id.superplayer_rb_speed2); + + mRadioGroup.setOnCheckedChangeListener(this); + mSeekBarVolume = (SeekBar) findViewById(R.id.superplayer_sb_audio); + mSeekBarLight = (SeekBar) findViewById(R.id.superplayer_sb_light); + + mLayoutMirror = (LinearLayout) findViewById(R.id.superplayer_ll_mirror); + mSwitchMirror = (Switch) findViewById(R.id.superplayer_switch_mirror); + + mSwitchAccelerate = (Switch) findViewById(R.id.superplayer_switch_accelerate); + SuperPlayerGlobalConfig config = SuperPlayerGlobalConfig.getInstance(); + mSwitchAccelerate.setChecked(config.enableHWAcceleration); + + mSeekBarVolume.setOnSeekBarChangeListener(mVolumeChangeListener); + mSeekBarLight.setOnSeekBarChangeListener(mLightChangeListener); + + mSwitchMirror.setOnCheckedChangeListener(this); + mSwitchAccelerate.setOnCheckedChangeListener(this); + + mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + updateCurrentVolume(); + updateCurrentLight(); + } + + private void updateCurrentVolume() { + int curVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); + int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); + + float percentage = (float) curVolume / maxVolume; + + final int progress = (int) (percentage * mSeekBarVolume.getMax()); + mSeekBarVolume.setProgress(progress); + } + + private void updateCurrentLight() { + Activity activity = (Activity) mContext; + Window window = activity.getWindow(); + + WindowManager.LayoutParams params = window.getAttributes(); + params.screenBrightness = getActivityBrightness((Activity) mContext); + window.setAttributes(params); + if (params.screenBrightness == -1) { + mSeekBarLight.setProgress(100); + return; + } + mSeekBarLight.setProgress((int) (params.screenBrightness * 100)); + } + + /** + * 获取当前亮度 + * + * @param activity + * @return + */ + public static float getActivityBrightness(Activity activity) { + Window localWindow = activity.getWindow(); + WindowManager.LayoutParams params = localWindow.getAttributes(); + return params.screenBrightness; + } + + private SeekBar.OnSeekBarChangeListener mVolumeChangeListener = new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + updateVolumeProgress(progress); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }; + + private void updateVolumeProgress(int progress) { + float percentage = (float) progress / mSeekBarVolume.getMax(); + + if (percentage < 0 || percentage > 1) + return; + + if (mAudioManager != null) { + int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); + int newVolume = (int) (percentage * maxVolume); + mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, newVolume, 0); + } + } + + private SeekBar.OnSeekBarChangeListener mLightChangeListener = new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + updateBrightProgress(progress); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }; + + private void updateBrightProgress(int progress) { + Activity activity = (Activity) mContext; + Window window = activity.getWindow(); + WindowManager.LayoutParams params = window.getAttributes(); + params.screenBrightness = progress * 1.0f / 100; + if (params.screenBrightness > 1.0f) { + params.screenBrightness = 1.0f; + } + if (params.screenBrightness <= 0.01f) { + params.screenBrightness = 0.01f; + } + + window.setAttributes(params); + mSeekBarLight.setProgress(progress); + } + + /** + * 镜像、硬解开关监听 + * + * @param compoundButton + * @param isChecked + */ + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) { + if (compoundButton.getId() == R.id.superplayer_switch_mirror) { + if (mCallback != null) { + mCallback.onMirrorChange(isChecked); + } + } else if (compoundButton.getId() == R.id.superplayer_switch_accelerate) { + SuperPlayerGlobalConfig config = SuperPlayerGlobalConfig.getInstance(); + config.enableHWAcceleration = !config.enableHWAcceleration; + mSwitchAccelerate.setChecked(config.enableHWAcceleration); + if (mCallback != null) { + mCallback.onHWAcceleration(config.enableHWAcceleration); + } + } + } + + /** + * 设置回调 + * + * @param callback + */ + public void setCallback(Callback callback) { + mCallback = callback; + } + + /** + * 倍速选择监听 + * + * @param radioGroup + * @param checkedId + */ + @Override + public void onCheckedChanged(RadioGroup radioGroup, int checkedId) { + if (checkedId == R.id.superplayer_rb_speed1) { + mRbSpeed1.setChecked(true); + if (mCallback != null) { + mCallback.onSpeedChange(1.0f); + } + + } else if (checkedId == R.id.superplayer_rb_speed125) { + mRbSpeed125.setChecked(true); + if (mCallback != null) { + mCallback.onSpeedChange(1.25f); + } + + } else if (checkedId == R.id.superplayer_rb_speed15) { + mRbSpeed15.setChecked(true); + if (mCallback != null) { + mCallback.onSpeedChange(1.5f); + } + + } else if (checkedId == R.id.superplayer_rb_speed2) { + mRbSpeed2.setChecked(true); + if (mCallback != null) { + mCallback.onSpeedChange(2.0f); + } + + } + } + + @Override + public void setVisibility(int visibility) { + super.setVisibility(visibility); + if (visibility == View.VISIBLE) { + updateCurrentVolume(); + updateCurrentLight(); + registerReceiver(); + }else { + unregisterReceiver(); + } + } + + public void setBrightProgress(int progress) { + updateBrightProgress(progress); + } + + + /** + * 更新播放视频类型 + * + * @param playType + */ + public void updatePlayType(SuperPlayerDef.PlayerType playType) { + if (playType == SuperPlayerDef.PlayerType.VOD) { + mLayoutSpeed.setVisibility(View.VISIBLE); + mLayoutMirror.setVisibility(View.VISIBLE); + } else { + mLayoutSpeed.setVisibility(View.GONE); + mLayoutMirror.setVisibility(View.GONE); + } + } + + private class VolumeBroadcastReceiver extends BroadcastReceiver { + + public void onReceive(Context context, Intent intent) { + //媒体音量改变才通知 + if (VOLUME_CHANGED_ACTION.equals(intent.getAction()) + && (intent.getIntExtra(EXTRA_VOLUME_STREAM_TYPE, -1) == AudioManager.STREAM_MUSIC)) { + updateCurrentVolume(); + } + } + } + + /** + * 注册音量广播接收器 + * @return + */ + public void registerReceiver() { + mVolumeBroadcastReceiver = new VolumeBroadcastReceiver(); + IntentFilter filter = new IntentFilter(); + filter.addAction(VOLUME_CHANGED_ACTION); + mContext.registerReceiver(mVolumeBroadcastReceiver, filter); + } + + /** + * 反注册音量广播监听器,需要与 registerReceiver 成对使用 + */ + public void unregisterReceiver() { + try { + mContext.unregisterReceiver(mVolumeBroadcastReceiver); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 回调 + */ + public interface Callback { + /** + * 播放速度更新回调 + * + * @param speedLevel + */ + void onSpeedChange(float speedLevel); + + /** + * 镜像开关回调 + * + * @param isMirror + */ + void onMirrorChange(boolean isMirror); + + /** + * 硬解开关回调 + * + * @param isAccelerate + */ + void onHWAcceleration(boolean isAccelerate); + } + +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/view/VodQualityView.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/view/VodQualityView.java new file mode 100644 index 0000000..cf72c3f --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/view/VodQualityView.java @@ -0,0 +1,203 @@ +package com.tencent.liteav.demo.superplayer.ui.view; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.ListView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.tencent.liteav.demo.superplayer.model.entity.VideoQuality; + +import org.leanflutter.plugins.flutter_superplayer.R; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by yuejiaoli on 2018/7/4. + * + * 视频画质选择弹框 + * + * 1、设置画质列表{@link #setVideoQualityList(List)} + * + * 2、设置默认选中的画质{@link #setDefaultSelectedQuality(int)} + */ + +public class VodQualityView extends RelativeLayout { + private Context mContext; + private Callback mCallback; // 回调 + private ListView mListView; // 画质listView + private QualityAdapter mAdapter; // 画质列表适配器 + private List mList; // 画质列表 + private int mClickPos = -1; // 当前的画质下表 + + public VodQualityView(Context context) { + super(context); + init(context); + } + + public VodQualityView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public VodQualityView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + private void init(Context context) { + mContext = context; + mList = new ArrayList(); + LayoutInflater.from(mContext).inflate(R.layout.superplayer_quality_popup_view, this); + mListView = (ListView) findViewById(R.id.superplayer_lv_quality); + mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + if (mCallback != null) { + if (mList != null && mList.size() > 0) { + VideoQuality quality = mList.get(position); + if (quality != null && position != mClickPos) { + mCallback.onQualitySelect(quality); + } + } + } + mClickPos = position; + mAdapter.notifyDataSetChanged(); + } + }); + mAdapter = new QualityAdapter(); + mListView.setAdapter(mAdapter); + } + + /** + * 设置回调 + * + * @param callback + */ + public void setCallback(Callback callback) { + mCallback = callback; + } + + /** + * 设置画质列表 + * + * @param list + */ + public void setVideoQualityList(List list) { + mList.clear(); + mList.addAll(list); + + if (mAdapter != null) { + mAdapter.notifyDataSetChanged(); + } + } + + /** + * 设置默认选中的清晰度 + * + * @param position + */ + public void setDefaultSelectedQuality(int position) { + if (position < 0) position = 0; + mClickPos = position; + mAdapter.notifyDataSetChanged(); + } + + class QualityAdapter extends BaseAdapter { + + @Override + public int getCount() { + return mList.size(); + } + + @Override + public Object getItem(int position) { + return position; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = new QualityItemView(mContext); + } + QualityItemView itemView = (QualityItemView) convertView; + itemView.setSelected(false); + VideoQuality quality = mList.get(position); + itemView.setQualityName(quality.title); + if (mClickPos == position) { + itemView.setSelected(true); + } + return itemView; + } + } + + /** + * 画质item view + */ + class QualityItemView extends RelativeLayout { + + private TextView mTvQuality; + + public QualityItemView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context); + } + + public QualityItemView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public QualityItemView(Context context) { + super(context); + init(context); + } + + private void init(Context context) { + LayoutInflater.from(context).inflate(R.layout.superplayer_quality_item_view, this); + mTvQuality = (TextView) findViewById(R.id.superplayer_tv_quality); + } + + /** + * 设置画质名称 + * + * @param qualityName + */ + public void setQualityName(String qualityName) { + mTvQuality.setText(qualityName); + } + + /** + * 设置画质item是否为选择状态 + * + * @param isChecked + */ + public void setSelected(boolean isChecked) { + mTvQuality.setSelected(isChecked); + } + } + + /** + * 回调 + */ + public interface Callback { + /** + * 画质选择回调 + * + * @param quality + */ + void onQualitySelect(VideoQuality quality); + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/view/VolumeBrightnessProgressLayout.java b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/view/VolumeBrightnessProgressLayout.java new file mode 100644 index 0000000..41531f8 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/com/tencent/liteav/demo/superplayer/ui/view/VolumeBrightnessProgressLayout.java @@ -0,0 +1,84 @@ +package com.tencent.liteav.demo.superplayer.ui.view; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.RelativeLayout; + +import org.leanflutter.plugins.flutter_superplayer.R; + +/** + * 滑动手势设置音量、亮度时显示的提示view + */ +public class VolumeBrightnessProgressLayout extends RelativeLayout { + private ImageView mImageCenter; // 中心图片:亮度提示、音量提示 + private ProgressBar mProgressBar; // 进度条 + private HideRunnable mHideRunnable; // 隐藏view的runnable + private int mDuration = 1000; // view消失延迟时间(秒) + + public VolumeBrightnessProgressLayout(Context context) { + super(context); + init(context); + } + + public VolumeBrightnessProgressLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + private void init(Context context){ + LayoutInflater.from(context).inflate(R.layout.superplayer_video_volume_brightness_progress_layout,this); + mImageCenter = (ImageView) findViewById(R.id.superplayer_iv_center); + mProgressBar = (ProgressBar) findViewById(R.id.superplayer_pb_progress_bar); + mHideRunnable = new HideRunnable(); + setVisibility(GONE); + } + + /** + * 显示 + */ + public void show(){ + setVisibility(VISIBLE); + removeCallbacks(mHideRunnable); + postDelayed(mHideRunnable, mDuration); + } + + /** + * 设置progressBar的进度值 + * + * @param progress + */ + public void setProgress(int progress){ + mProgressBar.setProgress(progress); + } + + /** + * 设置view消失的延迟时间 + * + * @param duration + */ + public void setDuration(int duration) { + this.mDuration = duration; + } + + /** + * 设置显示的图片,亮度提示图片或者音量提示图片 + * + * @param resource + */ + public void setImageResource(int resource){ + mImageCenter.setImageResource(resource); + } + + /** + * 隐藏view的runnable + */ + private class HideRunnable implements Runnable{ + @Override + public void run() { + VolumeBrightnessProgressLayout.this.setVisibility(GONE); + } + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/org/leanflutter/plugins/flutter_superplayer/Constants.java b/lib/my_flutter_superplayer/android/src/main/java/org/leanflutter/plugins/flutter_superplayer/Constants.java new file mode 100644 index 0000000..de27fda --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/org/leanflutter/plugins/flutter_superplayer/Constants.java @@ -0,0 +1,8 @@ +package org.leanflutter.plugins.flutter_superplayer; + +public class Constants { + public static final String CHANNEL_NAME = "flutter_superplayer"; + public static final String SUPER_PLAYER_VIEW_TYPE = "leanflutter.org/superplayer_view"; + public static final String SUPER_PLAYER_VIEW_CHANNEL_NAME = "leanflutter.org/superplayer_view/channel"; + public static final String SUPER_PLAYER_VIEW_EVENT_CHANNEL_NAME = "leanflutter.org/superplayer_view/event_channel"; +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/org/leanflutter/plugins/flutter_superplayer/FlutterSuperPlayerView.java b/lib/my_flutter_superplayer/android/src/main/java/org/leanflutter/plugins/flutter_superplayer/FlutterSuperPlayerView.java new file mode 100644 index 0000000..01ad949 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/org/leanflutter/plugins/flutter_superplayer/FlutterSuperPlayerView.java @@ -0,0 +1,318 @@ +package org.leanflutter.plugins.flutter_superplayer; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.view.Gravity; +import android.view.View; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; + +import com.tencent.liteav.demo.superplayer.SuperPlayerDef; +import com.tencent.liteav.demo.superplayer.SuperPlayerGlobalConfig; +import com.tencent.liteav.demo.superplayer.SuperPlayerModel; +import com.tencent.liteav.demo.superplayer.SuperPlayerVideoId; +import com.tencent.liteav.demo.superplayer.SuperPlayerView; + +import java.util.HashMap; +import java.util.Map; + +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.EventChannel; +import io.flutter.plugin.common.EventChannel.StreamHandler; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.MethodCallHandler; +import io.flutter.plugin.common.MethodChannel.Result; +import io.flutter.plugin.platform.PlatformView; + +import static org.leanflutter.plugins.flutter_superplayer.Constants.SUPER_PLAYER_VIEW_CHANNEL_NAME; +import static org.leanflutter.plugins.flutter_superplayer.Constants.SUPER_PLAYER_VIEW_EVENT_CHANNEL_NAME; + +public class FlutterSuperPlayerView implements PlatformView, MethodCallHandler, StreamHandler, SuperPlayerView.OnSuperPlayerViewCallback { + private final MethodChannel methodChannel; + private final EventChannel eventChannel; + private final Handler platformThreadHandler = new Handler(Looper.getMainLooper()); + + private EventChannel.EventSink eventSink; + + private Context context; + private FrameLayout containerView; + private SuperPlayerView superPlayerView; + + private long playProgressCurrent = 0; + + FlutterSuperPlayerView( + final Context context, + BinaryMessenger messenger, + int viewId, + Map params) { + + this.context = context; + + methodChannel = new MethodChannel(messenger, SUPER_PLAYER_VIEW_CHANNEL_NAME + "_" + viewId); + methodChannel.setMethodCallHandler(this); + + eventChannel = new EventChannel(messenger, SUPER_PLAYER_VIEW_EVENT_CHANNEL_NAME + "_" + viewId); + eventChannel.setStreamHandler(this); + + SuperPlayerGlobalConfig.getInstance().enableFloatWindow = false; + + FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT, + Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL + ); + containerView = new FrameLayout(context); + containerView.setLayoutParams(layoutParams); + + superPlayerView = new SuperPlayerView(context); + superPlayerView.setPlayerViewCallback(this); + containerView.addView(superPlayerView); + + String controlViewType = (String) params.get("controlViewType"); + setControlViewType(controlViewType); + } + + @Override + public View getView() { + return containerView; + } + + @Override + public void dispose() { + if (superPlayerView.getPlayerState() == SuperPlayerDef.PlayerState.PLAYING) { + superPlayerView.resetPlayer(); + } + } + + @Override + public void onListen(Object args, EventChannel.EventSink eventSink) { + this.eventSink = eventSink; + } + + @Override + public void onCancel(Object args) { + this.eventSink = null; + } + + @Override + public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + if (call.method.equals("setControlViewType")) { + setControlViewType(call, result); + } else if (call.method.equals("getPlayMode")) { + getPlayMode(call, result); + } else if (call.method.equals("getPlayState")) { + getPlayState(call, result); + } else if (call.method.equals("getPlayRate")) { + getPlayRate(call, result); + } else if (call.method.equals("setPlayRate")) { + setPlayRate(call, result); + } else if (call.method.equals("resetPlayer")) { + resetPlayer(call, result); + } else if (call.method.equals("requestPlayMode")) { + requestPlayMode(call, result); + } else if (call.method.equals("playWithModel")) { + playWithModel(call, result); + } else if (call.method.equals("pause")) { + pause(call, result); + } else if (call.method.equals("resume")) { + resume(call, result); + } else if (call.method.equals("release")) { + release(call, result); + } else if (call.method.equals("seekTo")) { + seekTo(call, result); + } else if (call.method.equals("setLoop")) { + setLoop(call, result); + } else if (call.method.equals("uiHideDanmu")) { + uiHideDanmu(call, result); + } else if (call.method.equals("uiHideReplay")) { + uiHideReplay(call, result); + } else { + result.notImplemented(); + } + } + + void setControlViewType(String controlViewType) { + superPlayerView.setControlViewType(controlViewType); + } + + void setControlViewType(@NonNull MethodCall call, @NonNull Result result) { + String controlViewType = (String) call.argument("controlViewType"); + superPlayerView.setControlViewType(controlViewType); + } + + + void getPlayMode(@NonNull MethodCall call, @NonNull Result result) { + int playMode = superPlayerView.getPlayerMode().ordinal(); + result.success(playMode); + } + + void getPlayState(@NonNull MethodCall call, @NonNull Result result) { + SuperPlayerDef.PlayerState playerState = superPlayerView.getPlayerState(); + result.success(playerState.intValue()); + } + + void getPlayRate(@NonNull MethodCall call, @NonNull Result result) { + float playRate = superPlayerView.getPlayerRate(); + result.success(playRate); + } + + void setPlayRate(@NonNull MethodCall call, @NonNull Result result) { + Number playRate = (Number) call.argument("playRate"); + superPlayerView.getControllerCallback().onSpeedChange(playRate.floatValue()); + } + + void resetPlayer(@NonNull MethodCall call, @NonNull Result result) { + superPlayerView.resetPlayer(); + } + + void requestPlayMode(@NonNull MethodCall call, @NonNull Result result) { + int playMode = (int) call.argument("playMode"); + superPlayerView.switchPlayMode(SuperPlayerDef.PlayerMode.values()[playMode]); + } + + private void playWithModel(@NonNull MethodCall call, @NonNull Result result) { + SuperPlayerModel model = new SuperPlayerModel(); + + if (call.hasArgument("appId")) + model.appId = (int) call.argument("appId"); + if (call.hasArgument("url")) + model.url = (String) call.argument("url"); + if (call.hasArgument("title")) + model.title = (String) call.argument("title"); + + if (call.hasArgument("videoId")) { + HashMap videoIdJson = call.argument("videoId"); + assert videoIdJson != null; + + SuperPlayerVideoId videoId = new SuperPlayerVideoId(); + if (videoIdJson.containsKey("fileId")) + videoId.fileId = (String) videoIdJson.get("fileId"); + if (videoIdJson.containsKey("pSign")) + videoId.pSign = (String) videoIdJson.get("pSign"); + + model.videoId = videoId; + } + + superPlayerView.playWithModel(model); + } + + void pause(@NonNull MethodCall call, @NonNull Result result) { + superPlayerView.getControllerCallback().onPause(); + } + + void resume(@NonNull MethodCall call, @NonNull Result result) { + superPlayerView.getControllerCallback().onResume(); + } + + void release(@NonNull MethodCall call, @NonNull Result result) { + superPlayerView.release(); + } + + void seekTo(@NonNull MethodCall call, @NonNull Result result) { + int time = (int) call.argument("time"); + superPlayerView.getControllerCallback().onSeekTo(time); + } + + void setLoop(@NonNull MethodCall call, @NonNull Result result) { + boolean isLoop = (boolean) call.argument("isLoop"); + superPlayerView.getSuperPlayer().setLoop(isLoop); + } + + void uiHideDanmu(@NonNull MethodCall call, @NonNull Result result) { + superPlayerView.uiHideDanmu(); + } + + void uiHideReplay(@NonNull MethodCall call, @NonNull Result result) { + superPlayerView.uiHideReplay(); + } + + @Override + public void onStartFullScreenPlay() { + final Map dataMap = new HashMap<>(); + dataMap.put("isFullScreen", true); + + final Map eventData = new HashMap<>(); + eventData.put("listener", "SuperPlayerListener"); + eventData.put("method", "onFullScreenChange"); + eventData.put("data", dataMap); + + eventSink.success(eventData); + } + + @Override + public void onStopFullScreenPlay() { + final Map dataMap = new HashMap<>(); + dataMap.put("isFullScreen", false); + + final Map eventData = new HashMap<>(); + eventData.put("listener", "SuperPlayerListener"); + eventData.put("method", "onFullScreenChange"); + eventData.put("data", dataMap); + + eventSink.success(eventData); + } + + @Override + public void onClickFloatCloseBtn() { + final Map eventData = new HashMap<>(); + eventData.put("listener", "SuperPlayerListener"); + eventData.put("method", "onClickFloatCloseBtn"); + + eventSink.success(eventData); + } + + @Override + public void onClickSmallReturnBtn() { + final Map eventData = new HashMap<>(); + eventData.put("listener", "SuperPlayerListener"); + eventData.put("method", "onClickSmallReturnBtn"); + + eventSink.success(eventData); + + } + + @Override + public void onStartFloatWindowPlay() { + final Map eventData = new HashMap<>(); + eventData.put("listener", "SuperPlayerListener"); + eventData.put("method", "onStartFloatWindowPlay"); + + eventSink.success(eventData); + + } + + @Override + public void onPlayStateChange(SuperPlayerDef.PlayerState playState) { + final Map dataMap = new HashMap<>(); + dataMap.put("playState", playState.intValue()); + + final Map eventData = new HashMap<>(); + eventData.put("listener", "SuperPlayerListener"); + eventData.put("method", "onPlayStateChange"); + eventData.put("data", dataMap); + + eventSink.success(eventData); + } + + @Override + public void onPlayProgressChange(long current, long duration) { + boolean isProgressChange = playProgressCurrent == current; + playProgressCurrent = current; + + if (isProgressChange) return; + + final Map dataMap = new HashMap<>(); + dataMap.put("current", current); + dataMap.put("duration", duration); + + final Map eventData = new HashMap<>(); + eventData.put("listener", "SuperPlayerListener"); + eventData.put("method", "onPlayProgressChange"); + eventData.put("data", dataMap); + + eventSink.success(eventData); + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/org/leanflutter/plugins/flutter_superplayer/FlutterSuperplayerPlugin.java b/lib/my_flutter_superplayer/android/src/main/java/org/leanflutter/plugins/flutter_superplayer/FlutterSuperplayerPlugin.java new file mode 100644 index 0000000..55bd208 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/org/leanflutter/plugins/flutter_superplayer/FlutterSuperplayerPlugin.java @@ -0,0 +1,101 @@ +package org.leanflutter.plugins.flutter_superplayer; + +import androidx.annotation.NonNull; + +import com.tencent.rtmp.TXLiveBase; + +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.MethodCallHandler; +import io.flutter.plugin.common.MethodChannel.Result; +import io.flutter.plugin.common.PluginRegistry.Registrar; +import io.flutter.plugin.platform.PlatformViewFactory; +import io.flutter.plugin.platform.PlatformViewRegistry; + +import static org.leanflutter.plugins.flutter_superplayer.Constants.CHANNEL_NAME; +import static org.leanflutter.plugins.flutter_superplayer.Constants.SUPER_PLAYER_VIEW_TYPE; + +/** + * FlutterSuperplayerPlugin + */ +public class FlutterSuperplayerPlugin implements FlutterPlugin, ActivityAware, MethodCallHandler { + private FlutterPluginBinding pluginBinding; + /// The MethodChannel that will the communication between Flutter and native Android + /// + /// This local reference serves to register the plugin with the Flutter Engine and unregister it + /// when the Flutter Engine is detached from the Activity + private MethodChannel channel; + + @Override + public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { + channel = new MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), CHANNEL_NAME); + channel.setMethodCallHandler(this); + + pluginBinding = flutterPluginBinding; + } + + // This static function is optional and equivalent to onAttachedToEngine. It supports the old + // pre-Flutter-1.12 Android projects. You are encouraged to continue supporting + // plugin registration via this function while apps migrate to use the new Android APIs + // post-flutter-1.12 via https://flutter.dev/go/android-project-migration. + // + // It is encouraged to share logic between onAttachedToEngine and registerWith to keep + // them functionally equivalent. Only one of onAttachedToEngine or registerWith will be called + // depending on the user's project. onAttachedToEngine or registerWith must both be defined + // in the same class. + public static void registerWith(Registrar registrar) { + final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME); + channel.setMethodCallHandler(new FlutterSuperplayerPlugin()); + + PlatformViewFactory superPlayerViewFactory = new SuperPlayerViewFactory(registrar.activity(), registrar.messenger()); + PlatformViewRegistry platformViewRegistry = registrar.platformViewRegistry(); + platformViewRegistry.registerViewFactory(SUPER_PLAYER_VIEW_TYPE, superPlayerViewFactory); + + registrar.addViewDestroyListener(view -> { + // skip + return false; // We are not interested in assuming ownership of the NativeView. + }); + } + + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + channel.setMethodCallHandler(null); + + pluginBinding = null; + } + + @Override + public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { + if (call.method.equals("getSDKVersion")) { + result.success(TXLiveBase.getSDKVersionStr()); + } else { + result.notImplemented(); + } + } + + + @Override + public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { + PlatformViewFactory superPlayerViewFactory = new SuperPlayerViewFactory(binding.getActivity(), pluginBinding.getBinaryMessenger()); + PlatformViewRegistry platformViewRegistry = pluginBinding.getFlutterEngine().getPlatformViewsController().getRegistry(); + platformViewRegistry.registerViewFactory(SUPER_PLAYER_VIEW_TYPE, superPlayerViewFactory); + } + + @Override + public void onDetachedFromActivityForConfigChanges() { + + } + + @Override + public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { + + } + + @Override + public void onDetachedFromActivity() { + + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/java/org/leanflutter/plugins/flutter_superplayer/SuperPlayerViewFactory.java b/lib/my_flutter_superplayer/android/src/main/java/org/leanflutter/plugins/flutter_superplayer/SuperPlayerViewFactory.java new file mode 100644 index 0000000..7a40bc2 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/java/org/leanflutter/plugins/flutter_superplayer/SuperPlayerViewFactory.java @@ -0,0 +1,30 @@ +package org.leanflutter.plugins.flutter_superplayer; + +import android.app.Activity; +import android.content.Context; + +import java.util.Map; + +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.StandardMessageCodec; +import io.flutter.plugin.platform.PlatformView; +import io.flutter.plugin.platform.PlatformViewFactory; + +public final class SuperPlayerViewFactory extends PlatformViewFactory { + private final Activity activity; + private final BinaryMessenger messenger; + + + public SuperPlayerViewFactory(Activity activity, BinaryMessenger messenger) { + super(StandardMessageCodec.INSTANCE); + + this.activity = activity; + this.messenger = messenger; + } + + @Override + public PlatformView create(Context context, int viewId, Object args) { + Map params = (Map) args; + return new FlutterSuperPlayerView(activity, messenger, viewId, params); + } +} diff --git a/lib/my_flutter_superplayer/android/src/main/res/color/superplayer_text_radio_color.xml b/lib/my_flutter_superplayer/android/src/main/res/color/superplayer_text_radio_color.xml new file mode 100644 index 0000000..669e748 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/color/superplayer_text_radio_color.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/lib/my_flutter_superplayer/android/src/main/res/color/superplayer_vod_player_text_color.xml b/lib/my_flutter_superplayer/android/src/main/res/color/superplayer_vod_player_text_color.xml new file mode 100644 index 0000000..bb4a51e --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/color/superplayer_vod_player_text_color.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_bottom_shadow.png b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_bottom_shadow.png new file mode 100644 index 0000000..e67cb36 Binary files /dev/null and b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_bottom_shadow.png differ diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_btn_back_play.png b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_btn_back_play.png new file mode 100644 index 0000000..3473b14 Binary files /dev/null and b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_btn_back_play.png differ diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_danmuku_off.png b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_danmuku_off.png new file mode 100644 index 0000000..fc8caab Binary files /dev/null and b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_danmuku_off.png differ diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_danmuku_on.png b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_danmuku_on.png new file mode 100644 index 0000000..7eca851 Binary files /dev/null and b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_danmuku_on.png differ diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_float_close.png b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_float_close.png new file mode 100644 index 0000000..297bc63 Binary files /dev/null and b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_float_close.png differ diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_light_max.png b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_light_max.png new file mode 100644 index 0000000..d9c8e08 Binary files /dev/null and b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_light_max.png differ diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_light_min.png b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_light_min.png new file mode 100644 index 0000000..968c284 Binary files /dev/null and b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_light_min.png differ diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_play.png b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_play.png new file mode 100644 index 0000000..5032be7 Binary files /dev/null and b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_play.png differ diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_player_lock.png b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_player_lock.png new file mode 100644 index 0000000..0d51698 Binary files /dev/null and b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_player_lock.png differ diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_player_unlock.png b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_player_unlock.png new file mode 100644 index 0000000..d6a2c98 Binary files /dev/null and b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_player_unlock.png differ diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_replay.png b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_replay.png new file mode 100644 index 0000000..58d71ae Binary files /dev/null and b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_replay.png differ diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_vod_cover_top.png b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_vod_cover_top.png new file mode 100644 index 0000000..ae880ac Binary files /dev/null and b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_vod_cover_top.png differ diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_vod_fullscreen.png b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_vod_fullscreen.png new file mode 100644 index 0000000..e6d32f4 Binary files /dev/null and b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_vod_fullscreen.png differ diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_vod_more_normal.png b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_vod_more_normal.png new file mode 100644 index 0000000..e3d930a Binary files /dev/null and b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_vod_more_normal.png differ diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_vod_pause_normal.png b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_vod_pause_normal.png new file mode 100644 index 0000000..8697beb Binary files /dev/null and b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_vod_pause_normal.png differ diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_vod_play_normal.png b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_vod_play_normal.png new file mode 100644 index 0000000..97e9aaa Binary files /dev/null and b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_vod_play_normal.png differ diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_vod_snapshot_normal.png b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_vod_snapshot_normal.png new file mode 100644 index 0000000..9182185 Binary files /dev/null and b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_vod_snapshot_normal.png differ diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_vod_thumb.png b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_vod_thumb.png new file mode 100644 index 0000000..08c802a Binary files /dev/null and b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_vod_thumb.png differ diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_volume_max.png b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_volume_max.png new file mode 100644 index 0000000..07d170e Binary files /dev/null and b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_volume_max.png differ diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_volume_min.png b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_volume_min.png new file mode 100644 index 0000000..01bef48 Binary files /dev/null and b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_ic_volume_min.png differ diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_top_shadow.png b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_top_shadow.png new file mode 100644 index 0000000..1a3cfd2 Binary files /dev/null and b/lib/my_flutter_superplayer/android/src/main/res/drawable-xxhdpi/superplayer_top_shadow.png differ diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_biz_video_progressbar.xml b/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_biz_video_progressbar.xml new file mode 100644 index 0000000..8af1285 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_biz_video_progressbar.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_gray_thumb.xml b/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_gray_thumb.xml new file mode 100644 index 0000000..36988e1 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_gray_thumb.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_gray_track.xml b/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_gray_track.xml new file mode 100644 index 0000000..2e74908 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_gray_track.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_green_thumb.xml b/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_green_thumb.xml new file mode 100644 index 0000000..134dc35 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_green_thumb.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_green_track.xml b/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_green_track.xml new file mode 100644 index 0000000..e50eb4f --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_green_track.xml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_layer_list_progress_bar.xml b/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_layer_list_progress_bar.xml new file mode 100644 index 0000000..8b9072b --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_layer_list_progress_bar.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_laylist_vod_video_progress.xml b/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_laylist_vod_video_progress.xml new file mode 100644 index 0000000..73de6c7 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_laylist_vod_video_progress.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_shape_round_bg.xml b/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_shape_round_bg.xml new file mode 100644 index 0000000..8a24986 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_shape_round_bg.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_shape_vtt_text_bg.xml b/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_shape_vtt_text_bg.xml new file mode 100644 index 0000000..dd59f78 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_shape_vtt_text_bg.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_thumb.xml b/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_thumb.xml new file mode 100644 index 0000000..59aa77b --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_thumb.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_track.xml b/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_track.xml new file mode 100644 index 0000000..9a62024 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/drawable/superplayer_track.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_item_new_vod.xml b/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_item_new_vod.xml new file mode 100644 index 0000000..dbb8d85 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_item_new_vod.xml @@ -0,0 +1,36 @@ + + + + + + + + + \ No newline at end of file diff --git a/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_layout_new_vod_snap.xml b/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_layout_new_vod_snap.xml new file mode 100644 index 0000000..56f24cd --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_layout_new_vod_snap.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_more_popup_view.xml b/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_more_popup_view.xml new file mode 100644 index 0000000..c7ac84b --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_more_popup_view.xml @@ -0,0 +1,221 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_quality_item_view.xml b/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_quality_item_view.xml new file mode 100644 index 0000000..73b5ed7 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_quality_item_view.xml @@ -0,0 +1,15 @@ + + + + + + diff --git a/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_quality_popup_view.xml b/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_quality_popup_view.xml new file mode 100644 index 0000000..b60d12f --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_quality_popup_view.xml @@ -0,0 +1,25 @@ + + + + + + + + \ No newline at end of file diff --git a/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_video_progress_layout.xml b/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_video_progress_layout.xml new file mode 100644 index 0000000..9d24645 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_video_progress_layout.xml @@ -0,0 +1,35 @@ + + + + + + + + + + diff --git a/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_video_volume_brightness_progress_layout.xml b/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_video_volume_brightness_progress_layout.xml new file mode 100644 index 0000000..5c2dc6a --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_video_volume_brightness_progress_layout.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_vod_player_float.xml b/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_vod_player_float.xml new file mode 100644 index 0000000..ae86c44 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_vod_player_float.xml @@ -0,0 +1,21 @@ + + + + + + + + \ No newline at end of file diff --git a/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_vod_player_fullscreen.xml b/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_vod_player_fullscreen.xml new file mode 100644 index 0000000..30eae04 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_vod_player_fullscreen.xml @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_vod_player_window.xml b/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_vod_player_window.xml new file mode 100644 index 0000000..b3019e8 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_vod_player_window.xml @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_vod_view.xml b/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_vod_view.xml new file mode 100644 index 0000000..d9ef2f5 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/layout/superplayer_vod_view.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/my_flutter_superplayer/android/src/main/res/values/colors.xml b/lib/my_flutter_superplayer/android/src/main/res/values/colors.xml new file mode 100644 index 0000000..1a6011e --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/values/colors.xml @@ -0,0 +1,27 @@ + + + #ffffff + #000000 + #00000000 + + #00b2ff + #ffffff + #6b6666 + + #7E000000 + + + #aa000000 + + + #FF4C58 + + + #1a000000 + + + #787878 + + #FF4081 + #BBBBBB + diff --git a/lib/my_flutter_superplayer/android/src/main/res/values/dimens.xml b/lib/my_flutter_superplayer/android/src/main/res/values/dimens.xml new file mode 100644 index 0000000..63c98ae --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/values/dimens.xml @@ -0,0 +1,6 @@ + + + + 48dip + + \ No newline at end of file diff --git a/lib/my_flutter_superplayer/android/src/main/res/values/strings.xml b/lib/my_flutter_superplayer/android/src/main/res/values/strings.xml new file mode 100644 index 0000000..f3c3f91 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/values/strings.xml @@ -0,0 +1,16 @@ + + + 硬件加速 + 镜像 + 多倍速播放 + 亮度 + 声音 + 清晰度 + 返回直播 + + 原画 + 测试视频 + 截图失败 + 悬浮播放失败 + 进入设置页面失败,请手动开启悬浮窗权限 + diff --git a/lib/my_flutter_superplayer/android/src/main/res/values/styles.xml b/lib/my_flutter_superplayer/android/src/main/res/values/styles.xml new file mode 100644 index 0000000..bd6d898 --- /dev/null +++ b/lib/my_flutter_superplayer/android/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/my_flutter_superplayer/example/.gitignore b/lib/my_flutter_superplayer/example/.gitignore new file mode 100644 index 0000000..1ba9c33 --- /dev/null +++ b/lib/my_flutter_superplayer/example/.gitignore @@ -0,0 +1,43 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Exceptions to above rules. +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/lib/my_flutter_superplayer/example/.metadata b/lib/my_flutter_superplayer/example/.metadata new file mode 100644 index 0000000..5428a48 --- /dev/null +++ b/lib/my_flutter_superplayer/example/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 8af6b2f038c1172e61d418869363a28dffec3cb4 + channel: unknown + +project_type: app diff --git a/lib/my_flutter_superplayer/example/README.md b/lib/my_flutter_superplayer/example/README.md new file mode 100644 index 0000000..a5889b5 --- /dev/null +++ b/lib/my_flutter_superplayer/example/README.md @@ -0,0 +1,16 @@ +# flutter_superplayer_example + +Demonstrates how to use the flutter_superplayer plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/lib/my_flutter_superplayer/example/android/.gitignore b/lib/my_flutter_superplayer/example/android/.gitignore new file mode 100644 index 0000000..bc2100d --- /dev/null +++ b/lib/my_flutter_superplayer/example/android/.gitignore @@ -0,0 +1,7 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java diff --git a/lib/my_flutter_superplayer/example/android/app/build.gradle b/lib/my_flutter_superplayer/example/android/app/build.gradle new file mode 100644 index 0000000..fa62d62 --- /dev/null +++ b/lib/my_flutter_superplayer/example/android/app/build.gradle @@ -0,0 +1,57 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 28 + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "org.leanflutter.plugins.flutter_superplayer_example" + minSdkVersion 19 + targetSdkVersion 28 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName +// ndk { +// abiFilters "armeabi-v7a" +// } + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} diff --git a/lib/my_flutter_superplayer/example/android/app/src/debug/AndroidManifest.xml b/lib/my_flutter_superplayer/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..5eb3fab --- /dev/null +++ b/lib/my_flutter_superplayer/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/lib/my_flutter_superplayer/example/android/app/src/main/AndroidManifest.xml b/lib/my_flutter_superplayer/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..bf93840 --- /dev/null +++ b/lib/my_flutter_superplayer/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/my_flutter_superplayer/example/android/app/src/main/java/dev/learn_flutter/plugins/flutter_superplayer_example/MainActivity.java b/lib/my_flutter_superplayer/example/android/app/src/main/java/dev/learn_flutter/plugins/flutter_superplayer_example/MainActivity.java new file mode 100644 index 0000000..3eebadd --- /dev/null +++ b/lib/my_flutter_superplayer/example/android/app/src/main/java/dev/learn_flutter/plugins/flutter_superplayer_example/MainActivity.java @@ -0,0 +1,6 @@ +package org.leanflutter.plugins.flutter_superplayer_example; + +import io.flutter.embedding.android.FlutterActivity; + +public class MainActivity extends FlutterActivity { +} diff --git a/lib/my_flutter_superplayer/example/android/app/src/main/res/drawable/launch_background.xml b/lib/my_flutter_superplayer/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/lib/my_flutter_superplayer/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/lib/my_flutter_superplayer/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/lib/my_flutter_superplayer/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/lib/my_flutter_superplayer/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/lib/my_flutter_superplayer/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/lib/my_flutter_superplayer/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/lib/my_flutter_superplayer/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/lib/my_flutter_superplayer/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/lib/my_flutter_superplayer/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/lib/my_flutter_superplayer/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/lib/my_flutter_superplayer/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/lib/my_flutter_superplayer/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/lib/my_flutter_superplayer/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/lib/my_flutter_superplayer/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/lib/my_flutter_superplayer/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/lib/my_flutter_superplayer/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/lib/my_flutter_superplayer/example/android/app/src/main/res/values/styles.xml b/lib/my_flutter_superplayer/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..1f83a33 --- /dev/null +++ b/lib/my_flutter_superplayer/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/lib/my_flutter_superplayer/example/android/app/src/profile/AndroidManifest.xml b/lib/my_flutter_superplayer/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..5eb3fab --- /dev/null +++ b/lib/my_flutter_superplayer/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/lib/my_flutter_superplayer/example/android/build.gradle b/lib/my_flutter_superplayer/example/android/build.gradle new file mode 100644 index 0000000..e0d7ae2 --- /dev/null +++ b/lib/my_flutter_superplayer/example/android/build.gradle @@ -0,0 +1,29 @@ +buildscript { + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0' + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/lib/my_flutter_superplayer/example/android/gradle.properties b/lib/my_flutter_superplayer/example/android/gradle.properties new file mode 100644 index 0000000..38c8d45 --- /dev/null +++ b/lib/my_flutter_superplayer/example/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/lib/my_flutter_superplayer/example/android/gradle/wrapper/gradle-wrapper.properties b/lib/my_flutter_superplayer/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..296b146 --- /dev/null +++ b/lib/my_flutter_superplayer/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/lib/my_flutter_superplayer/example/android/settings.gradle b/lib/my_flutter_superplayer/example/android/settings.gradle new file mode 100644 index 0000000..d3b6a40 --- /dev/null +++ b/lib/my_flutter_superplayer/example/android/settings.gradle @@ -0,0 +1,15 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/lib/my_flutter_superplayer/example/ios/.gitignore b/lib/my_flutter_superplayer/example/ios/.gitignore new file mode 100644 index 0000000..e96ef60 --- /dev/null +++ b/lib/my_flutter_superplayer/example/ios/.gitignore @@ -0,0 +1,32 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/lib/my_flutter_superplayer/example/ios/Flutter/AppFrameworkInfo.plist b/lib/my_flutter_superplayer/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..6b4c0f7 --- /dev/null +++ b/lib/my_flutter_superplayer/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 8.0 + + diff --git a/lib/my_flutter_superplayer/example/ios/Flutter/Debug.xcconfig b/lib/my_flutter_superplayer/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..e8efba1 --- /dev/null +++ b/lib/my_flutter_superplayer/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/lib/my_flutter_superplayer/example/ios/Flutter/Release.xcconfig b/lib/my_flutter_superplayer/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..399e934 --- /dev/null +++ b/lib/my_flutter_superplayer/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/lib/my_flutter_superplayer/example/ios/Podfile b/lib/my_flutter_superplayer/example/ios/Podfile new file mode 100644 index 0000000..a4af0a9 --- /dev/null +++ b/lib/my_flutter_superplayer/example/ios/Podfile @@ -0,0 +1,38 @@ +# Uncomment this line to define a global platform for your project +platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/lib/my_flutter_superplayer/example/ios/Podfile.lock b/lib/my_flutter_superplayer/example/ios/Podfile.lock new file mode 100644 index 0000000..2547ae6 --- /dev/null +++ b/lib/my_flutter_superplayer/example/ios/Podfile.lock @@ -0,0 +1,54 @@ +PODS: + - AFNetworking (4.0.1): + - AFNetworking/NSURLSession (= 4.0.1) + - AFNetworking/Reachability (= 4.0.1) + - AFNetworking/Security (= 4.0.1) + - AFNetworking/Serialization (= 4.0.1) + - AFNetworking/UIKit (= 4.0.1) + - AFNetworking/NSURLSession (4.0.1): + - AFNetworking/Reachability + - AFNetworking/Security + - AFNetworking/Serialization + - AFNetworking/Reachability (4.0.1) + - AFNetworking/Security (4.0.1) + - AFNetworking/Serialization (4.0.1) + - AFNetworking/UIKit (4.0.1): + - AFNetworking/NSURLSession + - Flutter (1.0.0) + - flutter_superplayer (0.0.1): + - Flutter + - flutter_superplayer/SuperPlayer_Professional (= 0.0.1) + - flutter_superplayer/SuperPlayer_Professional (0.0.1): + - AFNetworking (~> 4.0) + - Flutter + - Masonry + - TXLiteAVSDK_Professional + - Masonry (1.1.0) + - TXLiteAVSDK_Professional (8.5.10022) + +DEPENDENCIES: + - Flutter (from `Flutter`) + - flutter_superplayer (from `.symlinks/plugins/flutter_superplayer/ios`) + +SPEC REPOS: + trunk: + - AFNetworking + - Masonry + - TXLiteAVSDK_Professional + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + flutter_superplayer: + :path: ".symlinks/plugins/flutter_superplayer/ios" + +SPEC CHECKSUMS: + AFNetworking: 7864c38297c79aaca1500c33288e429c3451fdce + Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c + flutter_superplayer: 50d64a438d4e917295d7c8d8124bcc239f323dad + Masonry: 678fab65091a9290e40e2832a55e7ab731aad201 + TXLiteAVSDK_Professional: 165018e2f0570d2608d7ea2b785fc273558f9920 + +PODFILE CHECKSUM: b1f7a399522c118a74b177b13c01eca692aa7e6d + +COCOAPODS: 1.10.1 diff --git a/lib/my_flutter_superplayer/example/ios/Runner.xcodeproj/project.pbxproj b/lib/my_flutter_superplayer/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..15601f4 --- /dev/null +++ b/lib/my_flutter_superplayer/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,563 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; + 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + A21C28D2B529D0FA6C26080E /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E434E1A8780F268A010D19E9 /* libPods-Runner.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 06FC5A290B12CBF4DA3524F7 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 1BCF6125A6ADEE59D5930E44 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B9CA95C2128B4E10C548B7F2 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + E434E1A8780F268A010D19E9 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A21C28D2B529D0FA6C26080E /* libPods-Runner.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 8741C6BFA91BC36F22CE0F28 /* Frameworks */ = { + isa = PBXGroup; + children = ( + E434E1A8780F268A010D19E9 /* libPods-Runner.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + F3A5F99FA38F636A565D0A1C /* Pods */, + 8741C6BFA91BC36F22CE0F28 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + ); + path = Runner; + sourceTree = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 97C146F21CF9000F007C117D /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + F3A5F99FA38F636A565D0A1C /* Pods */ = { + isa = PBXGroup; + children = ( + 06FC5A290B12CBF4DA3524F7 /* Pods-Runner.debug.xcconfig */, + 1BCF6125A6ADEE59D5930E44 /* Pods-Runner.release.xcconfig */, + B9CA95C2128B4E10C548B7F2 /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + AD3A119BD3225A7AC3A62ACB /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + EFF62BD560582B20FF73FC03 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + AD3A119BD3225A7AC3A62ACB /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + EFF62BD560582B20FF73FC03 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, + 97C146F31CF9000F007C117D /* main.m in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.leanflutter.plugins.flutter_superplayer_example; + PRODUCT_NAME = "$(TARGET_NAME)"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.leanflutter.plugins.flutter_superplayer_example; + PRODUCT_NAME = "$(TARGET_NAME)"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.leanflutter.plugins.flutter_superplayer_example; + PRODUCT_NAME = "$(TARGET_NAME)"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/lib/my_flutter_superplayer/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/lib/my_flutter_superplayer/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/lib/my_flutter_superplayer/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/lib/my_flutter_superplayer/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/lib/my_flutter_superplayer/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/lib/my_flutter_superplayer/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/lib/my_flutter_superplayer/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/lib/my_flutter_superplayer/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/lib/my_flutter_superplayer/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/lib/my_flutter_superplayer/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/lib/my_flutter_superplayer/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..a28140c --- /dev/null +++ b/lib/my_flutter_superplayer/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/my_flutter_superplayer/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/lib/my_flutter_superplayer/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/lib/my_flutter_superplayer/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/lib/my_flutter_superplayer/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/lib/my_flutter_superplayer/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/lib/my_flutter_superplayer/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/lib/my_flutter_superplayer/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/lib/my_flutter_superplayer/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/lib/my_flutter_superplayer/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/lib/my_flutter_superplayer/example/ios/Runner/AppDelegate.h b/lib/my_flutter_superplayer/example/ios/Runner/AppDelegate.h new file mode 100644 index 0000000..36e21bb --- /dev/null +++ b/lib/my_flutter_superplayer/example/ios/Runner/AppDelegate.h @@ -0,0 +1,6 @@ +#import +#import + +@interface AppDelegate : FlutterAppDelegate + +@end diff --git a/lib/my_flutter_superplayer/example/ios/Runner/AppDelegate.m b/lib/my_flutter_superplayer/example/ios/Runner/AppDelegate.m new file mode 100644 index 0000000..70e8393 --- /dev/null +++ b/lib/my_flutter_superplayer/example/ios/Runner/AppDelegate.m @@ -0,0 +1,13 @@ +#import "AppDelegate.h" +#import "GeneratedPluginRegistrant.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [GeneratedPluginRegistrant registerWithRegistry:self]; + // Override point for customization after application launch. + return [super application:application didFinishLaunchingWithOptions:launchOptions]; +} + +@end diff --git a/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..28c6bf0 Binary files /dev/null and b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..f091b6b Binary files /dev/null and b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cde121 Binary files /dev/null and b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..d0ef06e Binary files /dev/null and b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..dcdc230 Binary files /dev/null and b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..2ccbfd9 Binary files /dev/null and b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..c8f9ed8 Binary files /dev/null and b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..a6d6b86 Binary files /dev/null and b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..75b2d16 Binary files /dev/null and b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..c4df70d Binary files /dev/null and b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..6a84f41 Binary files /dev/null and b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..d0e1f58 Binary files /dev/null and b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/lib/my_flutter_superplayer/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +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. \ No newline at end of file diff --git a/lib/my_flutter_superplayer/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/lib/my_flutter_superplayer/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/lib/my_flutter_superplayer/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/my_flutter_superplayer/example/ios/Runner/Base.lproj/Main.storyboard b/lib/my_flutter_superplayer/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/lib/my_flutter_superplayer/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/my_flutter_superplayer/example/ios/Runner/Info.plist b/lib/my_flutter_superplayer/example/ios/Runner/Info.plist new file mode 100644 index 0000000..e59b450 --- /dev/null +++ b/lib/my_flutter_superplayer/example/ios/Runner/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + flutter_superplayer_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + io.flutter.embedded_views_preview + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/lib/my_flutter_superplayer/example/ios/Runner/main.m b/lib/my_flutter_superplayer/example/ios/Runner/main.m new file mode 100644 index 0000000..dff6597 --- /dev/null +++ b/lib/my_flutter_superplayer/example/ios/Runner/main.m @@ -0,0 +1,9 @@ +#import +#import +#import "AppDelegate.h" + +int main(int argc, char* argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/lib/my_flutter_superplayer/example/lib/data_file.dart b/lib/my_flutter_superplayer/example/lib/data_file.dart new file mode 100644 index 0000000..13fec30 --- /dev/null +++ b/lib/my_flutter_superplayer/example/lib/data_file.dart @@ -0,0 +1,221 @@ + +///App.Car_Dwinfo.GetList 获取分页列表数据 +Map mapDwinfo = { + "ret": 200, + "data": { + "items": [ + { + "id": 1, + "dwip": "172.16.3.1", + "dwmc": "锦绣花园", + "dwbh": 1, + "dwinfo": "振兴大道锦绣花园附近", + "dwzb": "104.607091|28.807061", + "dwms": "振兴大道锦绣花园附近,识别孜岩、红坝路入城排放黑烟车辆", + "dwzt": "正常", + "video12": "/rtp/gb_play_34020000001320001012_34020000001320001012", + "video13": "/rtp/gb_play_34020000001320001014_34020000001320001014", + "video14": "/rtp/gb_play_34020000001320001013_34020000001320001013", + "video15": "/rtp/gb_play_34020000001320001015_34020000001320001015", + "video16": "/rtp/gb_play_34020000001320001016_34020000001320001016", + "play_urlhead": "rtmp://125.64.218.67:9901" + }, + { + "id": 2, + "dwip": "172.16.3.2", + "dwmc": "石马溪桥", + "dwbh": 2, + "dwinfo": "宜飞路石马溪大桥附近", + "dwzb": "104.589904|28.787078", + "dwms": "宜飞路石马溪大桥附近,识别屏山、菜坝入城排放黑烟车辆", + "dwzt": "正常", + "video12": "/rtp/gb_play_34020000001320002012_34020000001320002012", + "video13": "/rtp/gb_play_34020000001320002013_34020000001320002013", + "video14": null, + "video15": null, + "video16": "/rtp/gb_play_34020000001320002016_34020000001320002016", + "play_urlhead": "rtmp://125.64.218.67:9901" + }, + { + "id": 3, + "dwip": "172.16.3.3", + "dwmc": "森林小区", + "dwbh": 3, + "dwinfo": "岷江南路森林小区附近", + "dwzb": "104.603919|28.765568", + "dwms": "岷江南路森林小区附近,识别宜宾南收费站出城排放黑烟车辆", + "dwzt": "正常", + "video12": "/rtp/gb_play_34020000001320003012_34020000001320003012", + "video13": "/rtp/gb_play_34020000001320003013_34020000001320003013", + "video14": null, + "video15": null, + "video16": "/rtp/gb_play_34020000001320003016_34020000001320003016", + "play_urlhead": "rtmp://125.64.218.67:9901" + }, + { + "id": 4, + "dwip": "172.16.3.4", + "dwmc": "南山星城", + "dwbh": 4, + "dwinfo": "一曼路南山星城南区附近", + "dwzb": "104.556797|28.718901", + "dwms": "一曼路南山星城南区附近,识别进入叙州区新城区排放黑烟车辆", + "dwzt": "正常", + "video12": "/rtp/gb_play_34020000001320004012_34020000001320004012", + "video13": "/rtp/gb_play_34020000001320004013_34020000001320004013", + "video14": null, + "video15": null, + "video16": "/rtp/gb_play_34020000001320004016_34020000001320004016", + "play_urlhead": "rtmp://125.64.218.67:9901" + }, + { + "id": 5, + "dwip": "172.16.3.5", + "dwmc": "育才学校", + "dwbh": 5, + "dwinfo": "陵园路宜宾育才学校附近", + "dwzb": "104.533476|28.699059", + "dwms": "陵园路宜宾育才学校附近,识别叙州区老城区出城车辆排放黑烟", + "dwzt": "正常", + "video12": "/rtp/gb_play_34020000001320005012_34020000001320005012", + "video13": "/rtp/gb_play_34020000001320005013_34020000001320005013", + "video14": null, + "video15": null, + "video16": "/rtp/gb_play_34020000001320005016_34020000001320005016", + "play_urlhead": "rtmp://125.64.218.67:9901" + }, + { + "id": 6, + "dwip": "172.16.3.6", + "dwmc": "七星花园", + "dwbh": 6, + "dwinfo": "七星路东段七星花园小区附近", + "dwzb": "104.662376|28.755488", + "dwms": "七星路东段七星花园小区附近,识别高县入城排放黑烟车辆", + "dwzt": "正常", + "video12": "/rtp/gb_play_34020000001320006012_34020000001320006012", + "video13": "/rtp/gb_play_34020000001320006013_34020000001320006013", + "video14": null, + "video15": null, + "video16": "/rtp/gb_play_34020000001320006016_34020000001320006016", + "play_urlhead": "rtmp://125.64.218.67:9901" + }, + { + "id": 7, + "dwip": "172.16.3.7", + "dwmc": "市财政局", + "dwbh": 7, + "dwinfo": "南六路宜宾市财政局附近", + "dwzb": "104.616581|28.731942", + "dwms": "南六路宜宾市财政局附近,识别高县入城排放黑烟车辆", + "dwzt": "正常", + "video12": "/rtp/gb_play_34020000001320007012_34020000001320007012", + "video13": "/rtp/gb_play_34020000001320007013_34020000001320007013", + "video14": null, + "video15": null, + "video16": "/rtp/gb_play_34020000001320007016_34020000001320007016", + "play_urlhead": "rtmp://125.64.218.67:9901" + }, + { + "id": 8, + "dwip": "172.16.3.8", + "dwmc": "宜威路", + "dwbh": 8, + "dwinfo": "宜威路7km+800m附近", + "dwzb": "104.687767|28.731159", + "dwms": "宜威路7km+800m附近,识别珙县、筠连入城排放黑烟车辆", + "dwzt": "正常", + "video12": "/rtp/gb_play_34020000001320008012_34020000001320008012", + "video13": "/rtp/gb_play_34020000001320008013_34020000001320008013", + "video14": null, + "video15": null, + "video16": "/rtp/gb_play_34020000001320008016_34020000001320008016", + "play_urlhead": "rtmp://125.64.218.67:9901" + }, + { + "id": 9, + "dwip": "172.16.3.9", + "dwmc": "宜长路", + "dwbh": 9, + "dwinfo": "新宜长路13km+700m附近", + "dwzb": "104.717384|28.763214", + "dwms": "新宜长路13km+700m附近,识别长宁、江安方向排放黑烟车辆", + "dwzt": "正常", + "video12": "/rtp/gb_play_34020000001320009012_34020000001320009012", + "video13": "/rtp/gb_play_34020000001320009013_34020000001320009013", + "video14": null, + "video15": null, + "video16": "/rtp/gb_play_34020000001320009016_34020000001320009016", + "play_urlhead": "rtmp://125.64.218.67:9901" + }, + { + "id": 10, + "dwip": "172.16.3.10", + "dwmc": "临港马家湾", + "dwbh": 10, + "dwinfo": "长江北路四段马家湾附近", + "dwzb": "104.759928|28.816636", + "dwms": "长江北路四段马家湾附近,识别南溪方向入城排放黑烟车辆", + "dwzt": "正常", + "video12": "/rtp/gb_play_34020000001320010012_34020000001320010012", + "video13": "/rtp/gb_play_34020000001320010013_34020000001320010013", + "video14": null, + "video15": null, + "video16": "/rtp/gb_play_34020000001320010016_34020000001320010016", + "play_urlhead": "rtmp://125.64.218.67:9901" + }, + { + "id": 11, + "dwip": "172.16.3.11", + "dwmc": "环城路", + "dwbh": 11, + "dwinfo": "环城路方水中心学校附近", + "dwzb": "104.641112|28.814636", + "dwms": "环城路方水中心学校附近,识别自观斗山隧道入城排放黑烟车辆", + "dwzt": "正常", + "video12": "/rtp/gb_play_34020000001320011012_34020000001320011012", + "video13": "/rtp/gb_play_34020000001320011013_34020000001320011013", + "video14": null, + "video15": null, + "video16": "/rtp/gb_play_34020000001320011016_34020000001320011016", + "play_urlhead": "rtmp://125.64.218.67:9901" + }, + { + "id": 12, + "dwip": "172.16.3.12", + "dwmc": "陶瓷厂", + "dwbh": 12, + "dwinfo": "S206省道美莲陶瓷厂附近", + "dwzb": "104.63116|28.804407", + "dwms": "S206省道美莲陶瓷厂附近,识别省道206入城排放黑烟车辆", + "dwzt": "正常", + "video12": "/rtp/gb_play_34020000001320012012_34020000001320012012", + "video13": "/rtp/gb_play_34020000001320012013_34020000001320012013", + "video14": null, + "video15": null, + "video16": "/rtp/gb_play_34020000001320012016_34020000001320012016", + "play_urlhead": "rtmp://125.64.218.67:9901" + }, + { + "id": 13, + "dwip": "172.16.3.13", + "dwmc": "鑫菁英", + "dwbh": 13, + "dwinfo": "外江路鑫菁英小区附近", + "dwzb": "104.623547|28.74798", + "dwms": "外江路鑫菁英小区附近,识别中坝大桥往高铁站排放黑烟车辆", + "dwzt": "正常", + "video12": "/rtp/gb_play_34020000001320013012_34020000001320013012", + "video13": "/rtp/gb_play_34020000001320013013_34020000001320013013", + "video14": null, + "video15": null, + "video16": "/rtp/gb_play_34020000001320013016_34020000001320013016", + "play_urlhead": "rtmp://125.64.218.67:9901" + } + ], + "total": 13, + "page": 1, + "perpage": 20 + }, + "msg": "" +}; diff --git a/lib/my_flutter_superplayer/example/lib/main.dart b/lib/my_flutter_superplayer/example/lib/main.dart new file mode 100644 index 0000000..efd877f --- /dev/null +++ b/lib/my_flutter_superplayer/example/lib/main.dart @@ -0,0 +1,105 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'superplayer_home.dart'; + +//import 'tc_player.dart'; +import 'data_file.dart'; + +void main() { + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: HomePage(), + theme: ThemeData(primarySwatch: Colors.blue), + ); + } +} + +class HomePage extends StatefulWidget { + @override + _HomePageState createState() => _HomePageState(); +} + +class _HomePageState extends State { + var crashInfo; + bool isLoadOk = false; + List listButton = []; + + @override + void initState() { + getListButton().then((value) { + setState(() {}); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('LiteAVSDK播放 plugin example app'), + ), + body: Center( + child: ListView( + children: listButton, + ), + ), + ); + } + + var isLoad = false; + + void showMsg(String msg) { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + content: Text(msg), + actions: [ + FlatButton( + onPressed: () { + Navigator.pop(context); + }, + child: Text("我知道了")) + ], + ); + }); + } + + Future getListButton() async { + List listDw = mapDwinfo['data']['items']; + + List _listButton = []; + _listButton.addAll([ + getPlayUrl( + 'http://www.yibinu.edu.cn/__local/5/35/DF/264049B7E978EEE2F5849688986_05D4A6FE_152CDB8C.mp4?e=.mp4', + 'yibinu'), + getPlayUrl('https://yongling8808.github.io/test/video/movie/movie.mp4', '网络视频'), + getPlayUrl( + 'http://125.64.218.67:9908/video/2_6063_20210410_155327_川QKK380.mp4', '违章视频-川QKK380'), + getPlayUrl( + 'http://125.64.218.67:9908/video/2_6063_20210409_140608_川Q31715.mp4', '违章视频-川Q31715'), + ]); + + for (Map item in listDw) { + _listButton.add( + getPlayUrl(item["play_urlhead"] + item["video16"], '${item["dwbh"]}.' + item["dwmc"])); + } + + listButton = _listButton; + } + + Widget getPlayUrl(String _url, text) { + return RaisedButton( + child: Text("LiteAVSDK播放-$text"), + onPressed: () async { + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => SuperPlayerPage(url: _url, loop: 0, title: text))); + }, + ); + } +} diff --git a/lib/my_flutter_superplayer/example/lib/superplayer_home.dart b/lib/my_flutter_superplayer/example/lib/superplayer_home.dart new file mode 100644 index 0000000..601b720 --- /dev/null +++ b/lib/my_flutter_superplayer/example/lib/superplayer_home.dart @@ -0,0 +1,162 @@ +import 'dart:convert'; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:flutter_superplayer/flutter_superplayer.dart'; + +const _kControlViewTypes = [kControlViewTypeDefault, kControlViewTypeWithout]; + +class SuperPlayerPage extends StatefulWidget { + SuperPlayerPage({@required this.url, this.loop = 1, this.title = 'Tencent Player', Key key}) + : super(key: key); + String url; + String title; + int loop; //设置播放循环,默认播放器的循环次数是1, 即不循环播放。如果设置循环次数0,表示无限循环。 + + @override + _SuperPlayerPageState createState() => _SuperPlayerPageState(); +} + +class _SuperPlayerPageState extends State with SuperPlayerListener { + SuperPlayerController _playerController = SuperPlayerController(); + + String _sdkVersion = 'Unknown'; + List _logs = []; + bool bFullScreen = false; + + String _controlViewType = _kControlViewTypes.first; + + @override + void initState() { + super.initState(); + //initPlatformState(); + // Future.delayed(const Duration(milliseconds: 1000), () { + // _playerController.playWithModel(SuperPlayerModel(url: widget.url)); + // setState(() { + // }); + // }); + init(); + } + + Future init() async { + await _playerController.addListener(this); + await initPlatformState(); + if (!mounted) return; + print('mounted = ${mounted}'); + // 开启调试日志 + //await FTXPlayerController.setConsoleEnabled(true); + // 初始化播放器 + //await _controller.initialize(onlyAudio: true); + await _playerController.uiHideDanmu(); + //设置播放循环,默认播放器的循环次数是1, 即不循环播放。如果设置循环次数0,表示无限循环。 + if (0 == widget.loop) { + await _playerController.setLoop(true); + } + await _playerController.playWithModel(testSuperPlayerModel); + //_controller.play("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"); + // _controller + // .play('rtmp://125.64.218.67:9901/rtp/gb_play_34020000001320003016_34020000001320003016'); + // 设置循环播放 + //await _controller.setLoop(true); + // 开始播放 + //await _controller.play("http://125.64.218.67:9908/video/2_6063_20210409_140608_川Q31715.mp4"); + } + + SuperPlayerModel get testSuperPlayerModel { + // int appId = 1252463788; + // String fileId = "5285890781763144364"; + + SuperPlayerModel superPlayerModel = SuperPlayerModel( + url: widget.url, + // appId: appId, + // videoId: SuperPlayerVideoId(fileId: fileId), + ); + return superPlayerModel; + } + + // Platform messages are asynchronous, so we initialize in an async method. + Future initPlatformState() async { + String sdkVersion; + // Platform messages may fail, so we use a try/catch PlatformException. + try { + sdkVersion = await FlutterSuperPlayer.sdkVersion; + } on PlatformException { + sdkVersion = 'Failed to get platform version.'; + } + print('sdkVersion = ${sdkVersion}'); + print('mounted = ${mounted}'); + + // If the widget was removed from the tree while the asynchronous platform + // message was in flight, we want to discard the reply rather than calling + // setState to update our non-existent appearance. + if (!mounted) return; + + setState(() { + _sdkVersion = sdkVersion; + }); + } + + void _addLog(String method, dynamic data) { + _logs.add('>>>$method'); + if (data != null) { + _logs.add(data is Map ? json.encode(data) : data); + } + _logs.add(' '); + + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Align( + alignment: Alignment(0, 0), + child: Container( + padding: + EdgeInsets.only(top: bFullScreen ? 0 : MediaQueryData.fromWindow(window).padding.top), + alignment: Alignment(0, 0), + width: MediaQuery.of(context).size.width, + child: SuperPlayerView( + controller: _playerController, + controlViewType: _controlViewType, + ), + ), + ), + ); + } + + @override + void onClickFloatCloseBtn() { + _addLog('onClickFloatCloseBtn', {}); + } + + @override + void onClickSmallReturnBtn() { + _addLog('onClickSmallReturnBtn', {}); + Navigator.maybePop(context); + } + + @override + void onFullScreenChange(bool isFullScreen) { + _addLog('onFullScreenChange', {'isFullScreen': isFullScreen}); + bFullScreen = !bFullScreen; + setState(() {}); + } + + @override + void onPlayProgressChange(int current, int duration) { + _addLog('onPlayProgressChange', {'current': current, 'duration': duration}); + } + + @override + void onPlayStateChange(int playState) { + _addLog('onPlayStateChange', {'playState': playState}); + } + + @override + void onStartFloatWindowPlay() { + _addLog('onStartFloatWindowPlay', {}); + } +} diff --git a/lib/my_flutter_superplayer/example/pubspec.lock b/lib/my_flutter_superplayer/example/pubspec.lock new file mode 100644 index 0000000..75c5cf6 --- /dev/null +++ b/lib/my_flutter_superplayer/example/pubspec.lock @@ -0,0 +1,161 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.5.0-nullsafety.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.0-nullsafety.1" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0-nullsafety.3" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.0-nullsafety.1" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0-nullsafety.1" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.15.0-nullsafety.3" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.0" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.0-nullsafety.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_superplayer: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "0.0.2" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.12.10-nullsafety.1" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.0-nullsafety.3" + path: + dependency: transitive + description: + name: path + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.8.0-nullsafety.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.8.0-nullsafety.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.10.0-nullsafety.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.0-nullsafety.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0-nullsafety.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.0-nullsafety.1" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.19-nullsafety.2" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.0-nullsafety.3" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.0-nullsafety.3" +sdks: + dart: ">=2.10.0 <2.11.0" + flutter: ">=1.20.0 <2.0.0" diff --git a/lib/my_flutter_superplayer/example/pubspec.yaml b/lib/my_flutter_superplayer/example/pubspec.yaml new file mode 100644 index 0000000..a262b9e --- /dev/null +++ b/lib/my_flutter_superplayer/example/pubspec.yaml @@ -0,0 +1,71 @@ +name: flutter_superplayer_example +description: Demonstrates how to use the flutter_superplayer plugin. + +# The following line prevents the package from being accidentally published to +# pub.dev using `pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +environment: + sdk: ">=2.10.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + + flutter_superplayer: + # When depending on this package from a real application you should use: + # flutter_superplayer: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.0 + +dev_dependencies: + flutter_test: + sdk: flutter + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/lib/my_flutter_superplayer/example/scripts/run-android.sh b/lib/my_flutter_superplayer/example/scripts/run-android.sh new file mode 100644 index 0000000..d9ba203 --- /dev/null +++ b/lib/my_flutter_superplayer/example/scripts/run-android.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +flutter build apk --debug --target-platform=android-arm,android-arm64 +flutter run -d $1 --use-application-binary=build/app/outputs/apk/debug/app-debug.apk diff --git a/lib/my_flutter_superplayer/example/test/widget_test.dart b/lib/my_flutter_superplayer/example/test/widget_test.dart new file mode 100644 index 0000000..c3050dd --- /dev/null +++ b/lib/my_flutter_superplayer/example/test/widget_test.dart @@ -0,0 +1,27 @@ +// // This is a basic Flutter widget test. +// // +// // To perform an interaction with a widget in your test, use the WidgetTester +// // utility that Flutter provides. For example, you can send tap and scroll +// // gestures. You can also use WidgetTester to find child widgets in the widget +// // tree, read text, and verify that the values of widget properties are correct. +// +// import 'package:flutter/material.dart'; +// import 'package:flutter_test/flutter_test.dart'; +// +// import 'package:flutter_superplayer_example/main.dart'; +// +// void main() { +// testWidgets('Verify Platform version', (WidgetTester tester) async { +// // Build our app and trigger a frame. +// await tester.pumpWidget(MyApp()); +// +// // Verify that platform version is retrieved. +// expect( +// find.byWidgetPredicate( +// (Widget widget) => widget is Text && +// widget.data.startsWith('Running on:'), +// ), +// findsOneWidget, +// ); +// }); +// } diff --git a/lib/my_flutter_superplayer/ios/.gitignore b/lib/my_flutter_superplayer/ios/.gitignore new file mode 100644 index 0000000..aa479fd --- /dev/null +++ b/lib/my_flutter_superplayer/ios/.gitignore @@ -0,0 +1,37 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/Generated.xcconfig +/Flutter/flutter_export_environment.sh \ No newline at end of file diff --git a/lib/my_flutter_superplayer/ios/Assets/.gitkeep b/lib/my_flutter_superplayer/ios/Assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/my_flutter_superplayer/ios/Classes/FLTSuperPlayerView.h b/lib/my_flutter_superplayer/ios/Classes/FLTSuperPlayerView.h new file mode 100644 index 0000000..e99e091 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/Classes/FLTSuperPlayerView.h @@ -0,0 +1,33 @@ +// +// SuperPlayerView.h +// +// Created by Lijy91 on 2020/9/4. +// + +#import +#import "SuperPlayer.h" + +NS_ASSUME_NONNULL_BEGIN + +// FLTSuperPlayerViewController +@interface FLTSuperPlayerViewController : NSObject + +- (instancetype)initWithFrame:(CGRect)frame + viewIdentifier:(int64_t)viewId + arguments:(id _Nullable)args + binaryMessenger:(NSObject*)messenger; + +- (UIView*)view; + +@end + +// FLTSuperPlayerViewFactory +@interface FLTSuperPlayerViewFactory : NSObject +- (instancetype)initWithMessenger:(NSObject*)messenger; +@end + +// FLTSuperPlayerView +@interface FLTSuperPlayerView : SuperPlayerView +@end + +NS_ASSUME_NONNULL_END diff --git a/lib/my_flutter_superplayer/ios/Classes/FLTSuperPlayerView.m b/lib/my_flutter_superplayer/ios/Classes/FLTSuperPlayerView.m new file mode 100644 index 0000000..7945617 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/Classes/FLTSuperPlayerView.m @@ -0,0 +1,322 @@ +// +// FLTSuperPlayerView.m +// +// Created by Lijy91 on 2020/9/4. +// + +#import "FLTSuperPlayerView.h" + +// FLTSuperPlayerViewController +@implementation FLTSuperPlayerViewController { + UIView* _containerView; + FLTSuperPlayerView* _superPlayerView; + int64_t _viewId; + FlutterMethodChannel* _channel; + FlutterEventChannel* _eventChannel; + FlutterEventSink _eventSink; +} + +- (instancetype)initWithFrame:(CGRect)frame + viewIdentifier:(int64_t)viewId + arguments:(id _Nullable)args + binaryMessenger:(NSObject*)messenger { + if (self = [super init]) { + _viewId = viewId; + + NSString* channelName = [NSString stringWithFormat:@"leanflutter.org/superplayer_view/channel_%lld", viewId]; + _channel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:messenger]; + + NSString* eventChannelName = [NSString stringWithFormat:@"leanflutter.org/superplayer_view/event_channel_%lld", viewId]; + _eventChannel = [FlutterEventChannel eventChannelWithName:eventChannelName + binaryMessenger:messenger]; + [_eventChannel setStreamHandler:self]; + + __weak __typeof__(self) weakSelf = self; + [_channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { + [weakSelf onMethodCall:call result:result]; + }]; + + _containerView = [[UIView alloc] initWithFrame:frame]; + + _superPlayerView = [[FLTSuperPlayerView alloc] init]; + _superPlayerView.fatherView = _containerView; + _superPlayerView.delegate = self; + + [self setControlViewType:args[@"controlViewType"]]; + } + return self; +} + +- (UIView*)view { + return _containerView; +} + +- (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)eventSink { + _eventSink = eventSink; + + return nil; +} + +- (FlutterError*)onCancelWithArguments:(id)arguments { + _eventSink = nil; + + return nil; +} + +- (void)setControlViewType:(NSString *)controlViewType +{ + if ([controlViewType isEqualToString:@"without"]) { + _superPlayerView.controlView = [[SPWithoutControlView alloc] initWithFrame:CGRectZero]; + } else { + _superPlayerView.controlView = [[SPDefaultControlView alloc] initWithFrame:CGRectZero]; + } +} + +- (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { + if ([[call method] isEqualToString:@"getPlayMode"]) { + [self getPlayMode:call result: result]; + } else if ([[call method] isEqualToString:@"getPlayState"]) { + [self getPlayState:call result: result]; + } else if ([[call method] isEqualToString:@"getPlayRate"]) { + [self getPlayRate:call result: result]; + } else if ([[call method] isEqualToString:@"setPlayRate"]) { + [self setPlayRate:call result: result]; + } else if ([[call method] isEqualToString:@"resetPlayer"]) { + [self resetPlayer:call result: result]; + } else if ([[call method] isEqualToString:@"requestPlayMode"]) { + [self requestPlayMode:call result: result]; + } else if ([[call method] isEqualToString:@"playWithModel"]) { + [self playWithModel:call result: result]; + } else if ([[call method] isEqualToString:@"pause"]) { + [self pause:call result: result]; + } else if ([[call method] isEqualToString:@"resume"]) { + [self resume:call result: result]; + } else if ([[call method] isEqualToString:@"release"]) { + [self release:call result: result]; + } else if ([[call method] isEqualToString:@"seekTo"]) { + [self seekTo:call result: result]; + } else if ([[call method] isEqualToString:@"setLoop"]) { + [self setLoop:call result: result]; + } else if ([[call method] isEqualToString:@"uiHideDanmu"]) { + [self uiHideDanmu:call result: result]; + } else if ([[call method] isEqualToString:@"uiHideReplay"]) { + [self uiHideReplay:call result: result]; + } else { + result(FlutterMethodNotImplemented); + } +} + +- (void)getPlayMode:(FlutterMethodCall*)call + result:(FlutterResult)result +{ + [_superPlayerView setPlayerConfig:nil]; +} + +- (void)getPlayState:(FlutterMethodCall*)call + result:(FlutterResult)result +{ + result([NSNumber numberWithInt:_superPlayerView.state]); +} + +- (void)getPlayRate:(FlutterMethodCall*)call + result:(FlutterResult)result +{ + CGFloat playRate = [[_superPlayerView playerConfig] playRate]; + result([NSNumber numberWithDouble:playRate]); +} + +- (void)setPlayRate:(FlutterMethodCall*)call + result:(FlutterResult)result +{ + NSNumber *playRate = call.arguments[@"playRate"]; + [_superPlayerView setPlayRate:playRate.floatValue]; +} + +- (void)resetPlayer:(FlutterMethodCall*)call + result:(FlutterResult)result +{ + [_superPlayerView resetPlayer]; +} + +- (void)requestPlayMode:(FlutterMethodCall*)call + result:(FlutterResult)result +{ + // skip +} + + +- (void)playWithModel:(FlutterMethodCall*)call + result:(FlutterResult)result +{ + SuperPlayerModel *model = [[SuperPlayerModel alloc] init]; + + NSNumber *appId = call.arguments[@"appId"]; + NSString *url = call.arguments[@"url"]; + if (appId) + [model setAppId: appId.longValue]; + if (url) + [model setVideoURL:url]; + + NSDictionary *videoIdJson = call.arguments[@"videoId"]; + if (videoIdJson) { + NSString *fileId = videoIdJson[@"fileId"]; + + SuperPlayerVideoId *videoId = [[SuperPlayerVideoId alloc] init]; + if (fileId) + [videoId setFileId:fileId]; + + [model setVideoId:videoId]; + } + + [_superPlayerView playWithModel:model]; +} + +- (void)pause:(FlutterMethodCall*)call + result:(FlutterResult)result +{ + [_superPlayerView pause]; +} + +- (void)resume:(FlutterMethodCall*)call + result:(FlutterResult)result +{ + [_superPlayerView resume]; +} + +- (void)release:(FlutterMethodCall*)call + result:(FlutterResult)result +{ + // skip +} + +- (void)seekTo:(FlutterMethodCall*)call + result:(FlutterResult)result +{ + NSNumber *time = call.arguments[@"time"]; + [_superPlayerView seekToTime:time.intValue]; +} + +- (void)setLoop:(FlutterMethodCall*)call + result:(FlutterResult)result +{ + NSNumber *isLoop = call.arguments[@"isLoop"]; + [_superPlayerView setLoop:isLoop.boolValue]; +} + +- (void) uiHideDanmu:(FlutterMethodCall*)call + result:(FlutterResult)result +{ + [_superPlayerView uiHideDanmu]; +} + +- (void) uiHideReplay:(FlutterMethodCall*)call + result:(FlutterResult)result +{ + [_superPlayerView uiHideReplay]; +} + +/// 返回事件 +- (void)superPlayerBackAction:(SuperPlayerView *)player { + NSDictionary *eventData = @{ + @"listener": @"SuperPlayerListener", + @"method": @"onClickSmallReturnBtn", + }; + self->_eventSink(eventData); +} + +/// 全屏改变通知 +- (void)superPlayerFullScreenChanged:(SuperPlayerView *)player { + NSDictionary *eventData = @{ + @"listener": @"SuperPlayerListener", + @"method": @"onFullScreenChange", + @"data": @{ + @"isFullScreen": [NSNumber numberWithBool:[player isFullScreen]], + }, + }; + self->_eventSink(eventData); +} + +/// 播放开始通知 +- (void)superPlayerDidStart:(SuperPlayerView *)player { + +} +/// 播放结束通知 +- (void)superPlayerDidEnd:(SuperPlayerView *)player { + +} +/// 播放错误通知 +- (void)superPlayerError:(SuperPlayerView *)player errCode:(int)code errMessage:(NSString *)why { + +} + +/// 播放状态发生变化 +- (void)onPlayStateChange:(int) playState { + NSDictionary *eventData = @{ + @"listener": @"SuperPlayerListener", + @"method": @"onPlayStateChange", + @"data": @{ + @"playState": [NSNumber numberWithInt:playState], + }, + }; + self->_eventSink(eventData); +} + +/// 播放进度发生变化 +- (void)onPlayProgressChange:(int) current duration:(int) duration { + NSDictionary *eventData = @{ + @"listener": @"SuperPlayerListener", + @"method": @"onPlayProgressChange", + @"data": @{ + @"current": [NSNumber numberWithInt:current], + @"duration": [NSNumber numberWithInt:duration], + }, + }; + self->_eventSink(eventData); +} + +@end + +// FLTSuperPlayerViewFactory +@implementation FLTSuperPlayerViewFactory{ + NSObject* _messenger; +} + +- (instancetype)initWithMessenger:(NSObject*)messenger { + self = [super init]; + if (self) { + _messenger = messenger; + } + return self; +} + +- (NSObject*)createArgsCodec { + return [FlutterStandardMessageCodec sharedInstance]; +} + +- (NSObject*)createWithFrame:(CGRect)frame + viewIdentifier:(int64_t)viewId + arguments:(id _Nullable)args { + FLTSuperPlayerViewController* superPlayerViewController = [[FLTSuperPlayerViewController alloc] initWithFrame:frame + viewIdentifier:viewId + arguments:args + binaryMessenger:_messenger]; + return superPlayerViewController; +} +@end + +// FLTSuperPlayerView +@implementation FLTSuperPlayerView +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + + if (self) { + } + + return self; +} + +- (void)layoutSubviews { + [super layoutSubviews]; +} + +@end diff --git a/lib/my_flutter_superplayer/ios/Classes/FlutterSuperplayerPlugin.h b/lib/my_flutter_superplayer/ios/Classes/FlutterSuperplayerPlugin.h new file mode 100644 index 0000000..fa3256f --- /dev/null +++ b/lib/my_flutter_superplayer/ios/Classes/FlutterSuperplayerPlugin.h @@ -0,0 +1,5 @@ +#import +#import "FLTSuperPlayerView.h" + +@interface FlutterSuperplayerPlugin : NSObject +@end diff --git a/lib/my_flutter_superplayer/ios/Classes/FlutterSuperplayerPlugin.m b/lib/my_flutter_superplayer/ios/Classes/FlutterSuperplayerPlugin.m new file mode 100644 index 0000000..1cd0ecc --- /dev/null +++ b/lib/my_flutter_superplayer/ios/Classes/FlutterSuperplayerPlugin.m @@ -0,0 +1,23 @@ +#import "FlutterSuperplayerPlugin.h" + +@implementation FlutterSuperplayerPlugin ++ (void)registerWithRegistrar:(NSObject*)registrar { + FlutterMethodChannel* channel = [FlutterMethodChannel + methodChannelWithName:@"flutter_superplayer" + binaryMessenger:[registrar messenger]]; + FlutterSuperplayerPlugin* instance = [[FlutterSuperplayerPlugin alloc] init]; + [registrar addMethodCallDelegate:instance channel:channel]; + + FLTSuperPlayerViewFactory* platformViewFactory = [[FLTSuperPlayerViewFactory alloc] initWithMessenger:registrar.messenger]; + [registrar registerViewFactory:platformViewFactory withId:@"leanflutter.org/superplayer_view"]; +} + +- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { + if ([@"getSDKVersion" isEqualToString:call.method]) { + result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]); + } else { + result(FlutterMethodNotImplemented); + } +} + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Category/NSString+URL.h b/lib/my_flutter_superplayer/ios/SuperPlayer/Category/NSString+URL.h new file mode 100644 index 0000000..4f147c5 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Category/NSString+URL.h @@ -0,0 +1,31 @@ +// +// NSString+URL.h +// SuperPlayer +// +// Created by xcoderliu on 12/10/20. +// Copyright © 2020 annidy. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSString (URL) +/// 获取 URL GET 参数 +/// @param parameter key +/// @param originUrl url ++ (NSString *)getParameter:(NSString *)parameter WithOriginUrl:(NSString *)originUrl; + +/// 删除 URL GET 参数 +/// @param parameter key +/// @param originUrl url ++ (NSString *)deleteParameter:(NSString *)parameter WithOriginUrl:(NSString *)originUrl; + + +/// 追加 URL GET 参数 +/// @param queryString (@"key=value") +/// @param originUrl url ++ (NSString *)appendParameter:(NSString *)queryString WithOriginUrl:(NSString *)originUrl; +@end + +NS_ASSUME_NONNULL_END diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Category/NSString+URL.m b/lib/my_flutter_superplayer/ios/SuperPlayer/Category/NSString+URL.m new file mode 100644 index 0000000..2f69fb9 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Category/NSString+URL.m @@ -0,0 +1,54 @@ +// +// NSString+URL.m +// SuperPlayer +// +// Created by xcoderliu on 12/10/20. +// Copyright © 2020 annidy. All rights reserved. +// + +#import "NSString+URL.h" + +@implementation NSString (URL) ++ (NSString *)getParameter:(NSString *)parameter WithOriginUrl:(NSString *)originUrl { + NSError *error; + if (originUrl.length == 0) { + return @""; + } + NSString *regTags = [[NSString alloc] initWithFormat:@"(^|&|\\?)+%@=+([^&]*)(&|$)",parameter]; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regTags options:NSRegularExpressionCaseInsensitive error:&error]; + NSArray *matches = [regex matchesInString:originUrl options:0 range:NSMakeRange(0, [originUrl length])]; + for (NSTextCheckingResult *match in matches) { + NSString *tagValue = [originUrl substringWithRange:[match rangeAtIndex:2]]; + return tagValue; + } + return @""; +} + ++ (NSString *)deleteParameter:(NSString *)parameter WithOriginUrl:(NSString *)originUrl { + NSString *finalStr = [NSString string]; + NSMutableString * mutStr = [NSMutableString stringWithString:originUrl]; + NSArray *strArray = [mutStr componentsSeparatedByString:parameter]; + NSMutableString *firstStr = [strArray objectAtIndex:0]; + NSMutableString *lastStr = [strArray lastObject]; + NSRange characterRange = [lastStr rangeOfString:@"&"]; + + if (characterRange.location !=NSNotFound) { + NSArray *lastArray = [lastStr componentsSeparatedByString:@"&"]; + NSMutableArray *mutArray = [NSMutableArray arrayWithArray:lastArray]; + [mutArray removeObjectAtIndex:0]; + NSString *modifiedStr = [mutArray componentsJoinedByString:@"&"]; + finalStr = [[strArray objectAtIndex:0]stringByAppendingString:modifiedStr]; + } else { + finalStr = [firstStr substringToIndex:[firstStr length] -1]; + } + return finalStr; +} + ++ (NSString *)appendParameter:(NSString *)queryString WithOriginUrl:(NSString *)originUrl { + if (![queryString length]) { + return originUrl; + } + return [NSString stringWithFormat:@"%@%@%@", originUrl, + [originUrl rangeOfString:@"?"].length > 0 ? @"&" : @"?", queryString]; +} +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Category/UINavigationController+SuperPlayerRotation.h b/lib/my_flutter_superplayer/ios/SuperPlayer/Category/UINavigationController+SuperPlayerRotation.h new file mode 100644 index 0000000..493cd70 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Category/UINavigationController+SuperPlayerRotation.h @@ -0,0 +1,6 @@ + +#import + +@interface UINavigationController (SuperPlayerRotation) + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Category/UINavigationController+SuperPlayerRotation.m b/lib/my_flutter_superplayer/ios/SuperPlayer/Category/UINavigationController+SuperPlayerRotation.m new file mode 100644 index 0000000..2f2660f --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Category/UINavigationController+SuperPlayerRotation.m @@ -0,0 +1,34 @@ +#import "UINavigationController+SuperPlayerRotation.h" +#import + +@implementation UINavigationController (SuperPlayerRotation) + +/** + * 如果window的根视图是UINavigationController,则会先调用这个Category,然后调用UIViewController+SuperPlayerRotation + * 只需要在支持除竖屏以外方向的页面重新下边三个方法 + */ + +// 是否支持自动转屏 +- (BOOL)shouldAutorotate { + return [self.topViewController shouldAutorotate]; +} + +// 支持哪些屏幕方向 +- (UIInterfaceOrientationMask)supportedInterfaceOrientations { + return [self.topViewController supportedInterfaceOrientations]; +} + +// 默认的屏幕方向(当前ViewController必须是通过模态出来的UIViewController(模态带导航的无效)方式展现出来的,才会调用这个方法) +- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { + return [self.topViewController preferredInterfaceOrientationForPresentation]; +} + +- (UIViewController *)childViewControllerForStatusBarStyle { + return self.topViewController; +} + +- (UIViewController *)childViewControllerForStatusBarHidden { + return self.topViewController; +} + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Category/UIView+MMLayout.h b/lib/my_flutter_superplayer/ios/SuperPlayer/Category/UIView+MMLayout.h new file mode 100644 index 0000000..065733d --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Category/UIView+MMLayout.h @@ -0,0 +1,93 @@ +// +// UIView+MM.h +/* + 杜蒙 iOS开发 + annidy +*/ + +#define m_weakify(object) autoreleasepool {} __weak typeof(object) weak##object = object +#define m_strongify(object) autoreleasepool {} __strong typeof(weak##object) object = weak##object + +#import +@class MMLayout; +@interface UIView (Layout) +@property (strong , nonatomic , readonly) MMLayout *mm_selfLayout; +- (void)setMm_x:(CGFloat)mm_x; ///<< set frame.x +- (CGFloat)mm_x; ///<< get frame.x +- (void)setMm_y:(CGFloat)mm_y; ///<< set frame.y +- (CGFloat)mm_y; ///<< get frame.y +- (void)setMm_w:(CGFloat)mm_w; ///<< set frame.bounds.size.width +- (CGFloat)mm_w; ///<< get frame.bounds.size.width +- (void)setMm_h:(CGFloat)mm_h; ///<< set frame.bounds.size.height +- (CGFloat)mm_h; ///<< get frame.bounds.size.height +- (void)setMm_center:(CGPoint )mm_center; ///<< set frame.origin +- (CGPoint)mm_center; ///<< get frame origin +- (CGFloat)mm_centerX; ///<< get self.center.x +- (CGFloat)mm_centerY; ///<< get self.center.y +- (CGFloat)mm_maxY; ///<< get CGRectGetMaxY +- (CGFloat)mm_minY; ///<< get CGRectGetMinY +- (CGFloat)mm_maxX; ///<< get CGRectGetMaxX +- (CGFloat)mm_minX; ///<< get CGRectGetMinX +- (CGFloat)mm_halfW; ///<< get self.width / 2 +- (CGFloat)mm_halfH; ///<< get self.height / 2 +- (CGFloat)mm_halfX; ///<< get self.x / 2 +- (CGFloat)mm_halfY; ///<< get self.y / 2 +- (CGFloat)mm_halfCenterX; ///<< get self.centerX / 2 +- (CGFloat)mm_halfCenterY; ///<< get self.centerY / 2 +- (void)setMm_size:(CGSize)mm_size; +- (CGSize)mm_size; ///<< get self.bounds.size + +// iPhoneX adapt + +@property (readonly) CGFloat mm_safeAreaBottomGap; +@property (readonly) CGFloat mm_safeAreaTopGap; +@property (readonly) CGFloat mm_safeAreaLeftGap; +@property (readonly) CGFloat mm_safeAreaRightGap; + +/* + 示例链接编程 + self.m_width(100).m_height(100).m_left(10).m_top(10) +*/ +- (UIView * (^)(CGFloat top))m_top; ///< set frame y +- (UIView * (^)(CGFloat right))m_flexToTop; ///< set frame y by change height +- (UIView * (^)(CGFloat bottom))m_bottom; ///< set frame y +- (UIView * (^)(CGFloat right))m_flexToBottom; ///< set frame y by change height +- (UIView * (^)(CGFloat left))m_left; ///< set frame x +- (UIView * (^)(CGFloat right))m_flexToLeft; ///< set frame right by chang width +- (UIView * (^)(CGFloat right))m_right; ///< set frame x +- (UIView * (^)(CGFloat right))m_flexToRight; ///< set frame right by chang width +- (UIView * (^)(CGFloat width))m_width; ///< set frame width +- (UIView * (^)(CGFloat height))m_height; ///< set frame height +- (UIView * (^)(CGSize size))m_size; ///< set frame size +- (UIView * (^)(CGPoint center))m__center; ///< set frame center point +- (UIView * (^)(void))m_center; ///< set frame center 前提是有w h 调用次方法居中父类 +- (UIView * (^)(void))m_centerY; ///< set frame Ycenter 前提是有h调用次方法居中父类 +- (UIView * (^)(void))m_centerX; ///< set frame Xcenter 前提是有w调用次方法居中父类 + +/* + 相对父View + */ +-(UIView *(^)(void))m_fill; ///< 填充 + + +- (UIView * (^)(UIView *obj))m_equalToFrame; /// equalTo frame +- (UIView * (^)(UIView *obj))m_equalToTop; /// equalTo top +- (UIView * (^)(UIView *obj))m_equalToBottom; /// equalTo Bottom +- (UIView * (^)(UIView *obj))m_equalToLeft; /// equalTo left +- (UIView * (^)(UIView *obj))m_equalToRight; /// equalTo right +- (UIView * (^)(UIView *obj))m_equalToWidth; /// equalTo width +- (UIView * (^)(UIView *obj))m_equalToHeight; /// equalTo height +- (UIView * (^)(UIView *obj))m_equalToSize; /// equalTo size +- (UIView * (^)(UIView *obj))m_equalToCenter; /// equalTo center + +- (UIView * (^)(void))m_sizeToFit; /// call sizeToFit + +- (NSData *)mm_createPDF;/// create self PDF + +- (UIViewController *)viewController; //self Responder UIViewControler + +@end + + + + diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Category/UIView+MMLayout.m b/lib/my_flutter_superplayer/ios/SuperPlayer/Category/UIView+MMLayout.m new file mode 100644 index 0000000..82a9f8b --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Category/UIView+MMLayout.m @@ -0,0 +1,525 @@ + + +#import "UIView+MMLayout.h" +#import + +#pragma mark - MMLayout +@interface MMLayout : NSObject +/* + @LayoutView 传入进去 View + */ +-(instancetype)initWithLayoutView:(UIView *)LayoutView; +@property (assign , nonatomic) CGFloat top; ///<< frame y +@property (assign , nonatomic) CGFloat bottom; ///<< y = super.h - LayoutView.h - bottom +@property (assign , nonatomic) CGFloat left; ///<< frame x +@property (assign , nonatomic) CGFloat right; ///<< x = super.w - LayoutView.h - right +@property (assign , nonatomic) CGFloat width; ///<< frame w +@property (assign , nonatomic) CGFloat height; ///<< frame height +@property (assign , nonatomic) CGSize size; ///<< frame bounds size width height +@property (assign , nonatomic) CGPoint point; ///<< frame point +- (void)center; ///<<调用此方法前必须先设置自己的宽高 (默认是居中父控件) +- (void)centerX; ///<<调用此方法前必须先设置自己的宽 (默认是居中父控件) +- (void)centerY; ///<<调用此方法前必须先设置自己的高 (默认是居中父控件) +@property (weak , nonatomic) UIView *layoutView; +@end +@implementation MMLayout +-(instancetype)initWithLayoutView:(UIView *)LayoutView{ + if (self=[super init]) { + self.layoutView = LayoutView; + } + return self; +} +-(void)setLeft:(CGFloat)left{ + CGRect frame = self.layoutView.frame; + frame.origin.x = left; + self.layoutView.frame = frame; +} +- (CGFloat)left { + return self.layoutView.frame.origin.x; +} +-(void)setTop:(CGFloat)top{ + _top = top; + CGRect frame = self.layoutView.frame; + frame.origin.y = top; + self.layoutView.frame = frame; +} +-(void)setRight:(CGFloat)right{ + UIView *superview = self.layoutView.superview; + self.layoutView.mm_x = superview.mm_w - self.layoutView.mm_w - right; +} +- (CGFloat)right { + UIView *superview = self.layoutView.superview; + return superview.mm_w - self.layoutView.mm_x - self.layoutView.mm_w; +} + +-(void)setBottom:(CGFloat)bottom{ + UIView *superview = self.layoutView.superview; + self.layoutView.mm_y = superview.mm_h - self.layoutView.mm_h - bottom; +} + +- (CGFloat)bottom { + UIView *superview = self.layoutView.superview; + return superview.mm_h - self.layoutView.mm_maxY; +} + +-(void)setHeight:(CGFloat)height{ + _height = height; + CGRect frame = self.layoutView.frame; + frame.size.height = height; + self.layoutView.frame = frame; +} +-(void)setWidth:(CGFloat)width{ + _width = width; + CGRect frame = self.layoutView.frame; + frame.size.width = width; + self.layoutView.frame = frame; +} +-(void)setPoint:(CGPoint)point{ + CGRect frame = self.layoutView.frame; + frame.origin = point; + self.layoutView.frame = frame; +} +-(CGPoint)point{ + return CGPointMake(self.layoutView.mm_x, self.layoutView.mm_w); +} +-(void)setSize:(CGSize )size{ + CGRect frame = self.layoutView.frame; + frame.size = size; + self.layoutView.frame = frame; +} +-(CGSize)size{ + return self.layoutView.mm_size; +} + +-(void)setCenter:(CGPoint)center{ + CGRect frame = self.layoutView.frame; + frame.origin = CGPointMake(center.x-frame.size.width/2, center.y-frame.size.height/2); + self.layoutView.frame = frame; +} +-(void)center{ + UIView *superview = self.layoutView.superview; + self.layoutView.mm_x = superview.mm_halfW - self.layoutView.mm_halfW; + self.layoutView.mm_y = superview.mm_halfH - self.layoutView.mm_halfH; +} +-(void)centerY{ + UIView *superview = self.layoutView.superview; + self.layoutView.mm_y = superview.mm_halfH - self.layoutView.mm_halfH; +} +-(void)centerX{ + UIView *superview = self.layoutView.superview; + self.layoutView.mm_x = superview.mm_halfW - self.layoutView.mm_halfW; +} +@end +const void *_layoutKey; +@implementation UIView (Layout) +#pragma mark - frame +- (void)setMm_x:(CGFloat)mm_x{ + [self mm_selfLayout].left = mm_x; +} +- (CGFloat)mm_x{ + return self.frame.origin.x; +} +- (void)setMm_y:(CGFloat)mm_y{ + [self mm_selfLayout].top = mm_y; +} +- (CGFloat)mm_y{ + return self.frame.origin.y; +} +- (void)setMm_w:(CGFloat)mm_w{ + [self mm_selfLayout].width = mm_w; +} +- (CGFloat)mm_w{ + return self.frame.size.width; +} +- (void)setMm_h:(CGFloat)mm_h{ + [self mm_selfLayout].height = mm_h; +} +- (CGFloat)mm_h{ + return self.frame.size.height; +} +-(void)setMm_center:(CGPoint)mm_center{ + [self mm_selfLayout].point = mm_center; + +} +-(CGPoint)mm_center{ + return self.frame.origin; +} +-(CGFloat)mm_centerX{ + return self.center.x; +} +-(CGFloat)mm_centerY{ + return self.center.y; +} +- (CGFloat)mm_maxY{ + return CGRectGetMaxY(self.frame); +} +- (CGFloat)mm_minY{ + return CGRectGetMinY(self.frame); +} +- (CGFloat)mm_maxX{ + return CGRectGetMaxX(self.frame); +} +- (CGFloat)mm_minX{ + return CGRectGetMinX(self.frame); +} +- (CGFloat)mm_halfW{ + return self.mm_w / 2; +} +- (CGFloat)mm_halfH{ + return self.mm_h / 2; +} +- (CGFloat)mm_halfX{ + return self.mm_x / 2; +} +- (CGFloat)mm_halfY{ + return self.mm_y / 2; +} +-(CGFloat)mm_halfCenterX{ + return self.mm_centerX / 2; +} +-(CGFloat)mm_halfCenterY{ + return self.mm_centerY / 2; +} +-(void)setMm_size:(CGSize)mm_size{ + [self mm_selfLayout].size = mm_size; +} +-(CGSize)mm_size{ + return self.bounds.size; +} +#pragma mark - set_ frame +- (MMLayout *)mm_selfLayout{ + MMLayout *layout = objc_getAssociatedObject(self, &_layoutKey); + if (layout == nil) { + layout = [[MMLayout alloc] initWithLayoutView:self]; + objc_setAssociatedObject(self, &_layoutKey, layout, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + return layout; +} +-(UIView *(^)(CGFloat))m_top{ + @m_weakify(self); + return ^(CGFloat m_top){ + @m_strongify(self); + [self mm_selfLayout].top = m_top; + return self; + }; +} +-(UIView *(^)(CGFloat))m_flexToTop{ + @m_weakify(self); + return ^(CGFloat m_flexToTop){ + @m_strongify(self); + CGFloat top = [self mm_selfLayout].top; + [self mm_selfLayout].top = m_flexToTop; + [self mm_selfLayout].height += top-m_flexToTop; + return self; + }; +} +-(UIView *(^)(CGFloat))m_bottom{ + @m_weakify(self); + return ^(CGFloat m_bottom){ + @m_strongify(self); + [self mm_selfLayout].bottom = m_bottom; + return self; + }; +} +-(UIView *(^)(CGFloat))m_flexToBottom{ + @m_weakify(self); + return ^(CGFloat m_flexToBottom){ + @m_strongify(self); + CGFloat bottom = [self mm_selfLayout].bottom; + [self mm_selfLayout].height += bottom-m_flexToBottom; + return self; + }; +} +-(UIView *(^)(CGFloat))m_left{ + @m_weakify(self); + return ^(CGFloat m_left){ + @m_strongify(self); + [self mm_selfLayout].left = m_left; + return self; + }; +} +-(UIView *(^)(CGFloat))m_flexToLeft{ + @m_weakify(self); + return ^(CGFloat m_flexToLeft){ + @m_strongify(self); + CGFloat left = [self mm_selfLayout].left; + [self mm_selfLayout].left = m_flexToLeft; + [self mm_selfLayout].width += left-m_flexToLeft; + return self; + }; +} +-(UIView *(^)(CGFloat))m_right{ + @m_weakify(self); + return ^(CGFloat m_right){ + @m_strongify(self); + [self mm_selfLayout].right = m_right; + return self; + }; +} +-(UIView *(^)(CGFloat))m_flexToRight{ + @m_weakify(self); + return ^(CGFloat m_flexToRight){ + @m_strongify(self); + CGFloat right = [self mm_selfLayout].right; + [self mm_selfLayout].width += right-m_flexToRight; + return self; + }; +} +-(UIView *(^)(CGFloat))m_width{ + @m_weakify(self); + return ^(CGFloat m_width){ + @m_strongify(self); + [self mm_selfLayout].width = m_width; + return self; + }; +} +-(UIView *(^)(CGFloat))m_height{ + @m_weakify(self); + return ^(CGFloat m_height){ + @m_strongify(self); + [self mm_selfLayout].height = m_height; + return self; + }; +} +-(UIView *(^)(CGSize))m_size{ + @m_weakify(self); + return ^(CGSize m_size){ + @m_strongify(self); + [self mm_selfLayout].size = m_size; + return self; + }; +} +- (UIView *(^)(CGPoint))m__center{ + @m_weakify(self); + return ^(CGPoint m__center){ + @m_strongify(self); + [self mm_selfLayout].center = m__center; + return self; + }; + +} +-(UIView *(^)(void))m_center{ + @m_weakify(self); + return ^{ + @m_strongify(self); + [[self mm_selfLayout] center]; + return self; + }; +} + +-(UIView *(^)(void))m_centerY{ + @m_weakify(self); + return ^{ + @m_strongify(self); + [[self mm_selfLayout] centerY]; + return self; + }; +} + +-(UIView *(^)(void))m_centerX{ + @m_weakify(self); + return ^{ + @m_strongify(self); + [[self mm_selfLayout] centerX]; + return self; + }; +} + +-(UIView *(^)(void))m_fill { + @m_weakify(self); + return ^{ + @m_strongify(self); + if (self.superview) { + self.mm_x = self.mm_y = 0; + self.mm_w = self.superview.mm_w; + self.mm_h = self.superview.mm_h; + } + return self; + }; +} + +#pragma mark - m_equalTo +-(UIView *(^)(UIView *))m_equalToFrame{ + @m_weakify(self); + return ^(UIView *obj){ + @m_strongify(self); + self.frame = obj.frame; + return self; + }; +} +-(UIView *(^)(UIView *))m_equalToTop{ + @m_weakify(self); + return ^(UIView *obj){ + @m_strongify(self); + self.mm_selfLayout.top = obj.mm_selfLayout.top; + return self; + }; + +} +-(UIView *(^)(UIView *))m_equalToBottom{ + @m_weakify(self); + return ^(UIView *obj){ + @m_strongify(self); + self.mm_selfLayout.bottom = obj.mm_selfLayout.bottom; + return self; + }; +} +-(UIView *(^)(UIView *))m_equalToLeft{ + @m_weakify(self); + return ^(UIView *obj){ + @m_strongify(self); + self.mm_selfLayout.left = obj.mm_selfLayout.left; + return self; + }; + +} +-(UIView *(^)(UIView *))m_equalToRight{ + @m_weakify(self); + return ^(UIView *obj){ + @m_strongify(self); + self.mm_selfLayout.right = obj.mm_selfLayout.right; + return self; + }; + +} +-(UIView *(^)(UIView *))m_equalToWidth{ + @m_weakify(self); + return ^(UIView *obj){ + @m_strongify(self); + self.mm_selfLayout.width = obj.mm_selfLayout.width; + return self; + }; +} +-(UIView *(^)(UIView *))m_equalToHeight{ + @m_weakify(self); + return ^(UIView *obj){ + @m_strongify(self); + self.mm_selfLayout.height = obj.mm_selfLayout.height; + return self; + }; +} +-(UIView *(^)(UIView *))m_equalToSize{ + @m_weakify(self); + return ^(UIView *obj){ + @m_strongify(self); + [self mm_selfLayout].size = obj.mm_selfLayout.size; + return self; + }; +} +- (UIView *(^)(UIView *))m_equalToCenter{ + @m_weakify(self); + return ^(UIView *obj){ + @m_strongify(self); + [self mm_selfLayout].point = obj.mm_selfLayout.point; + return self; + }; +} + +- (UIView *(^)(void))m_sizeToFit{ + @m_weakify(self); + return ^(void){ + @m_strongify(self); + [self sizeToFit]; + return self; + }; +} + +- (NSData *)mm_createPDF{ + CGRect bounds = self.bounds; + NSMutableData *data = [NSMutableData data]; + CGDataConsumerRef consumer = CGDataConsumerCreateWithCFData((__bridge CFMutableDataRef)data); + CGContextRef context = CGPDFContextCreate(consumer, &bounds, NULL); + CGDataConsumerRelease(consumer); + if (!context) return nil; + CGPDFContextBeginPage(context, NULL); + CGContextTranslateCTM(context, 0, bounds.size.height); + CGContextScaleCTM(context, 1.0, -1.0); + [self.layer renderInContext:context]; + CGPDFContextEndPage(context); + CGPDFContextClose(context); + CGContextRelease(context); + return data; +} + +- (UIViewController *)viewController { + UIView *view = self; + while (view) { + UIResponder *nextResponder = [view nextResponder]; + if ([nextResponder isKindOfClass:[UIViewController class]]) { + return (UIViewController *)nextResponder; + } + view = view.superview; + } + return nil; +} + + +static void *kUIViewLayoutMethodPropertyBottomGap = &kUIViewLayoutMethodPropertyBottomGap; +static void *kUIViewLayoutMethodPropertyTopGap = &kUIViewLayoutMethodPropertyTopGap; +static void *kUIViewLayoutMethodPropertyLeftGap = &kUIViewLayoutMethodPropertyLeftGap; +static void *kUIViewLayoutMethodPropertyRightGap = &kUIViewLayoutMethodPropertyRightGap; + +- (CGFloat)mm_safeAreaBottomGap +{ + NSNumber *gap = objc_getAssociatedObject(self, kUIViewLayoutMethodPropertyBottomGap); + if (gap == nil) { + if (@available(iOS 11, *)) { + if (self.superview.safeAreaLayoutGuide.layoutFrame.size.height > 0) { + gap = @((self.superview.mm_h - self.superview.safeAreaLayoutGuide.layoutFrame.origin.y - self.superview.safeAreaLayoutGuide.layoutFrame.size.height)); + } else { + gap = nil; + } + } else { + gap = @(0); + } + objc_setAssociatedObject(self, kUIViewLayoutMethodPropertyBottomGap, gap, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + return gap.floatValue; +} + +- (CGFloat)mm_safeAreaTopGap +{ + NSNumber *gap = objc_getAssociatedObject(self, kUIViewLayoutMethodPropertyTopGap); + if (gap == nil) { + if (@available(iOS 11, *)) { + gap = @(self.superview.safeAreaLayoutGuide.layoutFrame.origin.y); + } else { + gap = @(0); + } + objc_setAssociatedObject(self, kUIViewLayoutMethodPropertyTopGap, gap, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + return gap.floatValue; +} + +- (CGFloat)mm_safeAreaLeftGap +{ + NSNumber *gap = objc_getAssociatedObject(self, kUIViewLayoutMethodPropertyLeftGap); + if (gap == nil) { + if (@available(iOS 11, *)) { + gap = @(self.superview.safeAreaLayoutGuide.layoutFrame.origin.x); + } else { + gap = @(0); + } + objc_setAssociatedObject(self, kUIViewLayoutMethodPropertyLeftGap, gap, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + return gap.floatValue; +} + +- (CGFloat)mm_safeAreaRightGap +{ + NSNumber *gap = objc_getAssociatedObject(self, kUIViewLayoutMethodPropertyRightGap); + if (gap == nil) { + if (@available(iOS 11, *)) { + gap = @((self.superview.mm_w - self.superview.safeAreaLayoutGuide.layoutFrame.origin.x - self.superview.safeAreaLayoutGuide.layoutFrame.size.width)); + } else { + gap = @(0); + } + objc_setAssociatedObject(self, kUIViewLayoutMethodPropertyRightGap, gap, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + return gap.floatValue; +} + +@end + + + diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Category/UIViewController+SuperPlayerRotation.h b/lib/my_flutter_superplayer/ios/SuperPlayer/Category/UIViewController+SuperPlayerRotation.h new file mode 100644 index 0000000..8223d39 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Category/UIViewController+SuperPlayerRotation.h @@ -0,0 +1,5 @@ +#import + +@interface UIViewController (SuperPlayerRotation) + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Category/UIViewController+SuperPlayerRotation.m b/lib/my_flutter_superplayer/ios/SuperPlayer/Category/UIViewController+SuperPlayerRotation.m new file mode 100644 index 0000000..2d05b58 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Category/UIViewController+SuperPlayerRotation.m @@ -0,0 +1,19 @@ +#import "UIViewController+SuperPlayerRotation.h" + +@implementation UIViewController (SuperPlayerRotation) + +/** + * 默认所有都不支持转屏,如需个别页面支持除竖屏外的其他方向,请在viewController重新下边这三个方法 + */ + +// 支持哪些屏幕方向 +- (UIInterfaceOrientationMask)supportedInterfaceOrientations { + return UIInterfaceOrientationMaskPortrait; +} + +// 默认的屏幕方向(当前ViewController必须是通过模态出来的UIViewController(模态带导航的无效)方式展现出来的,才会调用这个方法) +- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { + return UIInterfaceOrientationPortrait; +} + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Info.plist b/lib/my_flutter_superplayer/ios/SuperPlayer/Info.plist new file mode 100644 index 0000000..1007fd9 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Model/AdaptiveStream.h b/lib/my_flutter_superplayer/ios/SuperPlayer/Model/AdaptiveStream.h new file mode 100644 index 0000000..f97f837 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Model/AdaptiveStream.h @@ -0,0 +1,18 @@ +// +// AdaptiveStream.h +// SuperPlayer +// +// Created by cui on 2019/12/25. +// Copyright © 2019 annidy. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface AdaptiveStream : NSObject +@property NSString *url; +@property NSString *drmType; +@end + +NS_ASSUME_NONNULL_END diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Model/AdaptiveStream.m b/lib/my_flutter_superplayer/ios/SuperPlayer/Model/AdaptiveStream.m new file mode 100644 index 0000000..ecdf7b7 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Model/AdaptiveStream.m @@ -0,0 +1,13 @@ +// +// AdaptiveStream.m +// SuperPlayer +// +// Created by cui on 2019/12/25. +// Copyright © 2019 annidy. All rights reserved. +// + +#import "AdaptiveStream.h" + +@implementation AdaptiveStream + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SPPlayCGIParseResult.h b/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SPPlayCGIParseResult.h new file mode 100644 index 0000000..19de518 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SPPlayCGIParseResult.h @@ -0,0 +1,69 @@ +// +// SPPlayCGIParseResult.h +// SuperPlayer +// +// Created by cui on 2019/12/25. +// Copyright © 2019 annidy. All rights reserved. +// + +#import +//#import "SPResolutionDefination.h" +#import "SPSubStreamInfo.h" +#import "AdaptiveStream.h" +#import "SuperPlayerSprite.h" +#import "SPVideoFrameDescription.h" +#import "SuperPlayerUrl.h" + +@class TXImageSprite; + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSUInteger, SPDrmType) { + SPDrmTypeNone, + SPDrmTypeSimpleAES +}; + +@interface SPPlayCGIParseResult : NSObject +/// 视频播放url +@property (strong, nonatomic) NSString *url; +/// 视频名称 +@property (strong, nonatomic) NSString *name; +/// 雪略图对象 +@property (strong, nonatomic) TXImageSprite *imageSprite; +/// 雪略图打点帧信息 +@property (strong, nonatomic) NSArray *keyFrameDescList; +/// 字流画质信息 +@property (strong, nonatomic) NSArray *resolutionArray; +/// 原视频时长 +@property (assign, nonatomic) NSTimeInterval originalDuration; + +/// 预留字段,暂不使用 +@property (strong, nonatomic) NSArray *adaptiveStreamArray; + +/// V2协议的多码率URL列表 +@property (strong, nonatomic) NSArray *multiVideoURLs; + +/// 加密类型,用于 Drm +@property (assign, nonatomic) SPDrmType drmType; + +/// 加密令牌,用于 Drm +@property (copy, nonatomic) NSString *drmToken; ++ (SPDrmType)drmTypeFromString:(NSString *)typeString; +/** + * 获取画质信息 + * + * @return 画质信息数组 + +List getVideoQualityList(); + + + * 获取默认画质信息 + * + * @return 默认画质信息对象 + +TCVideoQuality getDefaultVideoQuality(); + */ + +@end + +NS_ASSUME_NONNULL_END diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SPPlayCGIParseResult.m b/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SPPlayCGIParseResult.m new file mode 100644 index 0000000..dfa7169 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SPPlayCGIParseResult.m @@ -0,0 +1,20 @@ +// +// SPPlayCGIParseResult.m +// SuperPlayer +// +// Created by cui on 2019/12/25. +// Copyright © 2019 annidy. All rights reserved. +// + +#import "SPPlayCGIParseResult.h" +#import "TXImageSprite.h" + +@implementation SPPlayCGIParseResult ++ (SPDrmType)drmTypeFromString:(NSString *)typeString { + BOOL isSimpleAES = [typeString caseInsensitiveCompare:@"SimpleAES"] == NSOrderedSame; + if (isSimpleAES) { + return SPDrmTypeSimpleAES; + } + return SPDrmTypeNone; +} +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SPSubStreamInfo.h b/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SPSubStreamInfo.h new file mode 100644 index 0000000..6a7d602 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SPSubStreamInfo.h @@ -0,0 +1,22 @@ +// +// SPSubStreamInfo.h +// SuperPlayer +// +// Created by Steven Choi on 2020/2/11. +// Copyright © 2020 annidy. All rights reserved. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/// 自适应码流子流信息 +@interface SPSubStreamInfo : NSObject +@property (assign, nonatomic) CGSize size; +@property (copy, nonatomic) NSString *resolutionName; +@property (copy, nonatomic) NSString *type; ++ (instancetype)infoWithDictionary:(NSDictionary *)dict; +@end + +NS_ASSUME_NONNULL_END diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SPSubStreamInfo.m b/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SPSubStreamInfo.m new file mode 100644 index 0000000..9396a3f --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SPSubStreamInfo.m @@ -0,0 +1,21 @@ +// +// SPSubStreamInfo.m +// SuperPlayer +// +// Created by Steven Choi on 2020/2/11. +// Copyright © 2020 annidy. All rights reserved. +// + +#import "SPSubStreamInfo.h" +#import "J2Obj.h" +@implementation SPSubStreamInfo ++ (instancetype)infoWithDictionary:(NSDictionary *)dict { + SPSubStreamInfo *info = [[SPSubStreamInfo alloc] init]; + double width = [J2Num(dict[@"width"]) doubleValue]; + double height = [J2Num(dict[@"height"]) doubleValue]; + info.size = CGSizeMake(width, height); + info.resolutionName = J2Str(dict[@"name"]); + info.type = J2Str(dict[@"type"]); + return info; +} +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SPVideoFrameDescription.h b/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SPVideoFrameDescription.h new file mode 100644 index 0000000..a5e3893 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SPVideoFrameDescription.h @@ -0,0 +1,21 @@ +// +// SPVideoFrameDescription.h +// SuperPlayer +// +// Created by cui on 2019/12/25. +// Copyright © 2019 annidy. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SPVideoFrameDescription : NSObject +// updates in SuperPlayerView +@property double where; +@property NSString *text; +@property double time; ++ (instancetype)instanceFromDictionary:(NSDictionary *)dict; +@end + +NS_ASSUME_NONNULL_END diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SPVideoFrameDescription.m b/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SPVideoFrameDescription.m new file mode 100644 index 0000000..695757c --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SPVideoFrameDescription.m @@ -0,0 +1,22 @@ +// +// SPVideoFrameDescription.m +// SuperPlayer +// +// Created by cui on 2019/12/25. +// Copyright © 2019 annidy. All rights reserved. +// + +#import "SPVideoFrameDescription.h" +#import "J2Obj.h" + +@implementation SPVideoFrameDescription ++ (instancetype)instanceFromDictionary:(NSDictionary *)keyFrameDesc { + if (![keyFrameDesc isKindOfClass:[NSDictionary class]]) { + return nil; + } + SPVideoFrameDescription *ret = [[SPVideoFrameDescription alloc] init]; + ret.time = [J2Num([keyFrameDesc valueForKeyPath:@"timeOffset"]) intValue]/1000.0; + ret.text = [J2Str([keyFrameDesc valueForKeyPath:@"content"]) stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; + return ret; +} +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SuperPlayerSprite.h b/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SuperPlayerSprite.h new file mode 100644 index 0000000..9c64e3a --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SuperPlayerSprite.h @@ -0,0 +1,18 @@ +// +// SuperPlayerSprite.h +// SuperPlayer +// +// Created by cui on 2019/12/25. +// Copyright © 2019 annidy. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SuperPlayerSprite : NSObject +@property (strong, nonatomic) NSArray *imageURLs; +@property (strong, nonatomic) NSString *webVttURL; +@end + +NS_ASSUME_NONNULL_END diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SuperPlayerSprite.m b/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SuperPlayerSprite.m new file mode 100644 index 0000000..c9923d3 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SuperPlayerSprite.m @@ -0,0 +1,13 @@ +// +// SuperPlayerSprite.m +// SuperPlayer +// +// Created by cui on 2019/12/25. +// Copyright © 2019 annidy. All rights reserved. +// + +#import "SuperPlayerSprite.h" + +@implementation SuperPlayerSprite + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SuperPlayerUrl.h b/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SuperPlayerUrl.h new file mode 100644 index 0000000..185de3d --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SuperPlayerUrl.h @@ -0,0 +1,23 @@ +// +// SuperPlayerUrl.h +// SuperPlayer +// +// Created by cui on 2019/12/25. +// Copyright © 2019 annidy. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN +/** + * 多码率地址 + * 用于有多个播放地址的多清晰度视频的播放 + */ +@interface SuperPlayerUrl : NSObject +/// 播放器展示的对应标题,如“高清”、“低清”等 +@property NSString *title; +/// 播放地址 +@property NSString *url; +@end + +NS_ASSUME_NONNULL_END diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SuperPlayerUrl.m b/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SuperPlayerUrl.m new file mode 100644 index 0000000..093e6ef --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Model/SuperPlayerUrl.m @@ -0,0 +1,13 @@ +// +// SuperPlayerUrl.m +// SuperPlayer +// +// Created by cui on 2019/12/25. +// Copyright © 2019 annidy. All rights reserved. +// + +#import "SuperPlayerUrl.h" + +@implementation SuperPlayerUrl + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/PlayCGI/SPPlayCGIParser.h b/lib/my_flutter_superplayer/ios/SuperPlayer/PlayCGI/SPPlayCGIParser.h new file mode 100644 index 0000000..da2e6fa --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/PlayCGI/SPPlayCGIParser.h @@ -0,0 +1,18 @@ +// +// SPPlayCGIParser.h +// SuperPlayer +// +// Created by cui on 2019/12/26. +// Copyright © 2019 annidy. All rights reserved. +// + +#import +#import "SPPlayCGIParserProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface SPPlayCGIParser : NSObject ++ (Class)parserOfVersion:(NSInteger)version; +@end + +NS_ASSUME_NONNULL_END diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/PlayCGI/SPPlayCGIParser.m b/lib/my_flutter_superplayer/ios/SuperPlayer/PlayCGI/SPPlayCGIParser.m new file mode 100644 index 0000000..7ceb24f --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/PlayCGI/SPPlayCGIParser.m @@ -0,0 +1,22 @@ +// +// SPPlayCGIParser.m +// SuperPlayer +// +// Created by cui on 2019/12/26. +// Copyright © 2019 annidy. All rights reserved. +// + +#import "SPPlayCGIParser.h" +#import "SPPlayCGIParser_V2.h" +#import "SPPlayCGIParser_V4.h" + +@implementation SPPlayCGIParser ++ (Class)parserOfVersion:(NSInteger)version { + if (version == 2) { + return [SPPlayCGIParser_V2 class]; + } else if (version == 4) { + return [SPPlayCGIParser_V4 class]; + } + return Nil; +} +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/PlayCGI/SPPlayCGIParserProtocol.h b/lib/my_flutter_superplayer/ios/SuperPlayer/PlayCGI/SPPlayCGIParserProtocol.h new file mode 100644 index 0000000..8a32a85 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/PlayCGI/SPPlayCGIParserProtocol.h @@ -0,0 +1,18 @@ +// +// PlayCGIParserProtocol.h +// SuperPlayer +// +// Created by cui on 2019/12/25. +// Copyright © 2019 annidy. All rights reserved. +// + +#import +#import "SPPlayCGIParseResult.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol SPPlayCGIParserProtocol ++ (SPPlayCGIParseResult *)parseResponse:(NSDictionary *)jsonResp; +@end + +NS_ASSUME_NONNULL_END diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/PlayCGI/SPPlayCGIParser_V2.h b/lib/my_flutter_superplayer/ios/SuperPlayer/PlayCGI/SPPlayCGIParser_V2.h new file mode 100644 index 0000000..ee9f8a2 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/PlayCGI/SPPlayCGIParser_V2.h @@ -0,0 +1,18 @@ +// +// SPPlayCGIParser_V2.h +// SuperPlayer +// +// Created by cui on 2019/12/25. +// Copyright © 2019 annidy. All rights reserved. +// + +#import +#import "SPPlayCGIParserProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface SPPlayCGIParser_V2 : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/PlayCGI/SPPlayCGIParser_V2.m b/lib/my_flutter_superplayer/ios/SuperPlayer/PlayCGI/SPPlayCGIParser_V2.m new file mode 100644 index 0000000..6dfc411 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/PlayCGI/SPPlayCGIParser_V2.m @@ -0,0 +1,105 @@ +// +// SPPlayCGIParser_V2.m +// SuperPlayer +// +// Created by cui on 2019/12/25. +// Copyright © 2019 annidy. All rights reserved. +// + +#import "SPPlayCGIParser_V2.h" +#import "J2Obj.h" +#import "SuperPlayerUrl.h" +#import "TXBitrateItemHelper.h" +#import "TXImageSprite.h" + +@implementation SPPlayCGIParser_V2 ++ (SPPlayCGIParseResult *)parseResponse:(NSDictionary *)responseObject { + SPPlayCGIParseResult *ret = [[SPPlayCGIParseResult alloc] init]; + NSString *masterUrl = J2Str([responseObject valueForKeyPath:@"videoInfo.masterPlayList.url"]); + // masterUrl = nil; + if (masterUrl.length > 0) { + // 1. 如果有master url,优先用这个 + ret.url = masterUrl; + } else { + NSString *mainDefinition = J2Str([responseObject valueForKeyPath:@"playerInfo.defaultVideoClassification"]); + + NSArray *videoClassification = J2Array([responseObject valueForKeyPath:@"playerInfo.videoClassification"]); + NSArray *transcodeList = J2Array([responseObject valueForKeyPath:@"videoInfo.transcodeList"]); + + NSMutableArray *result = [NSMutableArray new]; + + // 2. 如果有转码的清晰度,用转码流 + for (NSDictionary *transcode in transcodeList) { + SuperPlayerUrl *subModel = [SuperPlayerUrl new]; + subModel.url = J2Str(transcode[@"url"]); + NSNumber *theDefinition = J2Num(transcode[@"definition"]); + + for (NSDictionary *definition in videoClassification) { + for (NSObject *definition2 in J2Array([definition valueForKeyPath:@"definitionList"])) { + + if ([definition2 isEqual:theDefinition]) { + subModel.title = J2Str([definition valueForKeyPath:@"name"]); + NSString *definitionId = J2Str([definition valueForKeyPath:@"id"]); + // 初始播放清晰度 + if ([definitionId isEqualToString:mainDefinition]) { + if (![ret.url containsString:@".mp4"]) + ret.url = subModel.url; + } + break; + } + } + } + // 同一个清晰度可能存在多个转码格式,这里只保留一种格式,且优先mp4类型 + for (SuperPlayerUrl *item in result) { + if ([item.title isEqual:subModel.title]) { + if (![item.url containsString:@".mp4"]) { + item.url = subModel.url; + } + subModel = nil; + break; + } + } + + if (subModel) { + [result addObject:subModel]; + } + } + ret.multiVideoURLs = result; + } + // 3. 以上都没有,用原始地址 + if (ret.url == nil) { + NSString *source = J2Str([responseObject valueForKeyPath:@"videoInfo.sourceVideo.url"]); + ret.url = source; + } + + NSArray *imageSprites = J2Array([responseObject valueForKeyPath:@"imageSpriteInfo.imageSpriteList"]); + if (imageSprites.count > 0) { + // id imageSpriteObj = imageSprites[0]; + id imageSpriteObj = imageSprites.lastObject; + NSString *vtt = J2Str([imageSpriteObj valueForKeyPath:@"webVttUrl"]); + NSArray *imgUrls = J2Array([imageSpriteObj valueForKeyPath:@"imageUrls"]); + NSMutableArray *imgUrlArray = @[].mutableCopy; + for (NSString *url in imgUrls) { + NSURL *nsurl = [NSURL URLWithString:url]; + if (nsurl) { + [imgUrlArray addObject:nsurl]; + } + } + + TXImageSprite *imageSprite = [[TXImageSprite alloc] init]; + [imageSprite setVTTUrl:[NSURL URLWithString:vtt] imageUrls:imgUrlArray]; + ret.imageSprite = imageSprite; + } + + NSArray *keyFrameDescList = J2Array([responseObject valueForKeyPath:@"keyFrameDescInfo.keyFrameDescList"]); + if (keyFrameDescList.count > 0) { + NSMutableArray *checkPoints = [[NSMutableArray alloc] initWithCapacity:keyFrameDescList.count]; + for (NSDictionary *info in keyFrameDescList) { + SPVideoFrameDescription *checkPoint = [SPVideoFrameDescription instanceFromDictionary:info]; + [checkPoints addObject:checkPoint]; + } + ret.keyFrameDescList = checkPoints; + } + return ret; +} +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/PlayCGI/SPPlayCGIParser_V4.h b/lib/my_flutter_superplayer/ios/SuperPlayer/PlayCGI/SPPlayCGIParser_V4.h new file mode 100644 index 0000000..e05886f --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/PlayCGI/SPPlayCGIParser_V4.h @@ -0,0 +1,18 @@ +// +// PlayCGIV4Parser.h +// SuperPlayer +// +// Created by cui on 2019/12/25. +// Copyright © 2019 annidy. All rights reserved. +// + +#import +#import "SPPlayCGIParserProtocol.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface SPPlayCGIParser_V4 : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/PlayCGI/SPPlayCGIParser_V4.m b/lib/my_flutter_superplayer/ios/SuperPlayer/PlayCGI/SPPlayCGIParser_V4.m new file mode 100644 index 0000000..e2297d4 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/PlayCGI/SPPlayCGIParser_V4.m @@ -0,0 +1,122 @@ +// +// PlayCGIV4Parser.m +// SuperPlayer +// +// Created by cui on 2019/12/25. +// Copyright © 2019 annidy. All rights reserved. +// + +#import "SPPlayCGIParser_V4.h" +//#import "SPResolutionDefination.h" +#import "SPSubStreamInfo.h" +#import "AdaptiveStream.h" +#import "TXImageSprite.h" +#import "J2Obj.h" + +@implementation SPPlayCGIParser_V4 + ++ (SPPlayCGIParseResult *)parseResponse:(NSDictionary *)jsonResp { + SPPlayCGIParseResult *ret = [[SPPlayCGIParseResult alloc] init]; + + // 获取媒体信息 + NSDictionary *media = jsonResp[@"media"]; + if ([media isKindOfClass:[NSDictionary class]]) { + do { + //解析视频名称 + ret.name = [media valueForKeyPath:@"basicInfo.name"]; + NSDictionary *streamInfo = [media valueForKeyPath:@"streamingInfo.plainOutput"]; + if (streamInfo == nil) { + NSArray* drmOutputs = [media valueForKeyPath:@"streamingInfo.drmOutput"]; + for (NSDictionary *drmOutput in drmOutputs) { + SPDrmType type = [SPPlayCGIParseResult drmTypeFromString:J2Str(drmOutput[@"type"])]; + if (type == SPDrmTypeSimpleAES) { + streamInfo = drmOutput; + ret.drmType = SPDrmTypeSimpleAES; + break; + } + } + ret.drmToken = [media valueForKeyPath:@"streamingInfo.drmToken"]; + } + if (streamInfo == nil) { + return nil; + } + NSNumber *duration = [media valueForKeyPath:@"basicInfo.duration"]; + if ([duration isKindOfClass:[NSNumber class]]) { + ret.originalDuration = duration.doubleValue; + } + // 解析分辨率名称 + NSArray *subStreamDictArray = J2Array(streamInfo[@"subStreams"]); + NSMutableArray *subStreamInfoArray = [NSMutableArray arrayWithCapacity:subStreamDictArray.count]; + for (NSDictionary *resInfo in subStreamDictArray) { + SuperPlayerUrl *url = [[SuperPlayerUrl alloc] init]; + url.title = J2Str(resInfo[@"resolutionName"]); +// SPSubStreamInfo *info = [SPSubStreamInfo infoWithDictionary:resInfo]; + [subStreamInfoArray addObject:url]; + } + ret.multiVideoURLs = subStreamInfoArray; + + //解析视频播放url + NSString *url = streamInfo[@"url"]; + if ([url isKindOfClass:[NSString class]] && url.length > 0) { + //未加密直接解析出视频url + ret.url = url; + } else { + //有加密,url为空,则解析drm加密的url信息 + NSArray *urlArray = streamInfo[@"drmUrls"]; + if ([urlArray isKindOfClass:[NSArray class]] && + urlArray.count > 0) { + NSMutableArray *drmURLArray = [NSMutableArray arrayWithCapacity:urlArray.count]; + for (NSDictionary *dict in urlArray) { + if ([dict isKindOfClass:[NSDictionary class]]) { + continue; + } + AdaptiveStream *stream = [[AdaptiveStream alloc] init]; + stream.url = dict[@"url"]; + stream.drmType = dict[@"type"]; + [drmURLArray addObject:stream]; + } + ret.adaptiveStreamArray = drmURLArray; + } + } + + //解析略缩图信息 + NSDictionary *imageSpriteInfo = media[@"imageSpriteInfo"]; + + if ([imageSpriteInfo isKindOfClass:[NSDictionary class]]) { + NSString *vttURLString = J2Str(imageSpriteInfo[@"webVttUrl"]); + NSURL *vttURL = [NSURL URLWithString:vttURLString]; + NSArray *imageURLStrings = J2Array(imageSpriteInfo[@"imageUrls"]); + NSMutableArray *imageURLs = [NSMutableArray arrayWithCapacity:imageURLStrings.count]; + for (NSString *urlString in imageURLStrings) { + NSURL *url = [NSURL URLWithString:urlString]; + if (url) { + [imageURLs addObject:url]; + } + } + TXImageSprite *sprite = [[TXImageSprite alloc] init]; + [sprite setVTTUrl:vttURL imageUrls:imageURLs]; + ret.imageSprite = sprite; + } + + //解析关键帧信息 + NSDictionary *keyFrameDescInfo = media[@"keyFrameDescInfo"]; + if ([keyFrameDescInfo isKindOfClass:[NSDictionary class]]) { + NSArray *keyFrameDescList = keyFrameDescInfo[@"keyFrameDescList"]; + if ([keyFrameDescList isKindOfClass:[NSArray class]] && + keyFrameDescList.count > 0) { + NSMutableArray *videoPoints = [NSMutableArray array]; + for (NSDictionary *jsonObject in keyFrameDescList) { + SPVideoFrameDescription *point = [SPVideoFrameDescription instanceFromDictionary:jsonObject]; + if (point) { + [videoPoints addObject:point]; + } + } + ret.keyFrameDescList = videoPoints; + } + } + } while (0); + } + return ret; +} + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/arrow@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/arrow@3x.png new file mode 100644 index 0000000..1224686 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/arrow@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/back_full@2x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/back_full@2x.png new file mode 100644 index 0000000..70ebc2b Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/back_full@2x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/back_full@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/back_full@3x.png new file mode 100644 index 0000000..3473b14 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/back_full@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/bottom_shadow.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/bottom_shadow.png new file mode 100644 index 0000000..e67cb36 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/bottom_shadow.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/brightness@2x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/brightness@2x.png new file mode 100644 index 0000000..9782a11 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/brightness@2x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/capture@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/capture@3x.png new file mode 100644 index 0000000..9182185 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/capture@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/capture_pressed@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/capture_pressed@3x.png new file mode 100644 index 0000000..c73d3be Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/capture_pressed@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/close@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/close@3x.png new file mode 100644 index 0000000..297bc63 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/close@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/copywright_bg@2x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/copywright_bg@2x.png new file mode 100644 index 0000000..6b14ada Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/copywright_bg@2x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/copywright_bg@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/copywright_bg@3x.png new file mode 100644 index 0000000..8fb6b0f Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/copywright_bg@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/cw_img_bg@2x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/cw_img_bg@2x.png new file mode 100644 index 0000000..7787c40 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/cw_img_bg@2x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/cw_img_bg@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/cw_img_bg@3x.png new file mode 100644 index 0000000..edf35c7 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/cw_img_bg@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/danmu@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/danmu@3x.png new file mode 100644 index 0000000..fc8caab Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/danmu@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/danmu_pressed@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/danmu_pressed@3x.png new file mode 100644 index 0000000..7eca851 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/danmu_pressed@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/download@2x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/download@2x.png new file mode 100644 index 0000000..50889fd Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/download@2x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/download@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/download@3x.png new file mode 100644 index 0000000..2b4c9f1 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/download@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/fast_backward@2x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/fast_backward@2x.png new file mode 100644 index 0000000..a2f7d33 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/fast_backward@2x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/fast_backward@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/fast_backward@3x.png new file mode 100644 index 0000000..1f4e89b Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/fast_backward@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/fast_forward@2x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/fast_forward@2x.png new file mode 100644 index 0000000..d6d5dd5 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/fast_forward@2x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/fast_forward@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/fast_forward@3x.png new file mode 100644 index 0000000..0f313be Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/fast_forward@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/fullscreen@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/fullscreen@3x.png new file mode 100644 index 0000000..e6d32f4 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/fullscreen@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/fullscreen_press@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/fullscreen_press@3x.png new file mode 100644 index 0000000..6068804 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/fullscreen_press@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/iknown@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/iknown@3x.png new file mode 100644 index 0000000..0e9b379 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/iknown@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/left_g@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/left_g@3x.png new file mode 100644 index 0000000..e0ff56f Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/left_g@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/light_max@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/light_max@3x.png new file mode 100644 index 0000000..d9c8e08 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/light_max@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/light_min@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/light_min@3x.png new file mode 100644 index 0000000..968c284 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/light_min@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/loading_bgView.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/loading_bgView.png new file mode 100644 index 0000000..6715f1f Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/loading_bgView.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/lock-nor@2x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/lock-nor@2x.png new file mode 100644 index 0000000..91304b9 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/lock-nor@2x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/lock-nor@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/lock-nor@3x.png new file mode 100644 index 0000000..0d51698 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/lock-nor@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/middle_g@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/middle_g@3x.png new file mode 100644 index 0000000..3bbdf49 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/middle_g@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/more@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/more@3x.png new file mode 100644 index 0000000..e3d930a Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/more@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/more_pressed@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/more_pressed@3x.png new file mode 100644 index 0000000..092afd0 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/more_pressed@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/not_download@2x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/not_download@2x.png new file mode 100644 index 0000000..5527b2f Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/not_download@2x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/not_download@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/not_download@3x.png new file mode 100644 index 0000000..39fc62f Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/not_download@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/pause@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/pause@3x.png new file mode 100644 index 0000000..8697beb Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/pause@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/pause_pressed@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/pause_pressed@3x.png new file mode 100644 index 0000000..4b9087d Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/pause_pressed@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/play@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/play@3x.png new file mode 100644 index 0000000..97e9aaa Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/play@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/play_btn@2x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/play_btn@2x.png new file mode 100644 index 0000000..76472b4 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/play_btn@2x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/play_pressed@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/play_pressed@3x.png new file mode 100644 index 0000000..a6f9595 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/play_pressed@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/qg_online_bg@2x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/qg_online_bg@2x.png new file mode 100644 index 0000000..a65decc Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/qg_online_bg@2x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/repeat_video.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/repeat_video.png new file mode 100644 index 0000000..5f8b764 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/repeat_video.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/repeat_video@2x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/repeat_video@2x.png new file mode 100644 index 0000000..a9c8302 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/repeat_video@2x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/repeat_video@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/repeat_video@3x.png new file mode 100644 index 0000000..d5726e8 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/repeat_video@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/right_g@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/right_g@3x.png new file mode 100644 index 0000000..cc0edf3 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/right_g@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/shrinkscreen.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/shrinkscreen.png new file mode 100644 index 0000000..74815d9 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/shrinkscreen.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/shrinkscreen@2x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/shrinkscreen@2x.png new file mode 100644 index 0000000..45274a4 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/shrinkscreen@2x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/shrinkscreen@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/shrinkscreen@3x.png new file mode 100644 index 0000000..e83f58c Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/shrinkscreen@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/slider.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/slider.png new file mode 100644 index 0000000..f3aa3ab Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/slider.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/slider@2x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/slider@2x.png new file mode 100644 index 0000000..7c12663 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/slider@2x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/slider@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/slider@3x.png new file mode 100644 index 0000000..6202052 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/slider@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/slider_thumb@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/slider_thumb@3x.png new file mode 100644 index 0000000..7c48e55 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/slider_thumb@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/slider_thumb_pressed@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/slider_thumb_pressed@3x.png new file mode 100644 index 0000000..c331712 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/slider_thumb_pressed@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/sound_max@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/sound_max@3x.png new file mode 100644 index 0000000..07d170e Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/sound_max@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/sound_min@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/sound_min@3x.png new file mode 100644 index 0000000..01bef48 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/sound_min@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/top_shadow.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/top_shadow.png new file mode 100644 index 0000000..1a3cfd2 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/top_shadow.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/unlock-nor@2x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/unlock-nor@2x.png new file mode 100644 index 0000000..d6a2c98 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/unlock-nor@2x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/unlock-nor@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/unlock-nor@3x.png new file mode 100644 index 0000000..1d1f46c Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/unlock-nor@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_back@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_back@3x.png new file mode 100644 index 0000000..1cc281c Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_back@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_fullscreen@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_fullscreen@3x.png new file mode 100644 index 0000000..850fd9d Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_fullscreen@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_fullscreen_back@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_fullscreen_back@3x.png new file mode 100644 index 0000000..82899f4 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_fullscreen_back@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_more@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_more@3x.png new file mode 100644 index 0000000..06f2a79 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_more@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_pause@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_pause@3x.png new file mode 100644 index 0000000..1970ee1 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_pause@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_play@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_play@3x.png new file mode 100644 index 0000000..4ec06fd Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_play@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_thumb@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_thumb@3x.png new file mode 100644 index 0000000..d432e77 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_thumb@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_volume_off@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_volume_off@3x.png new file mode 100644 index 0000000..41b7260 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_volume_off@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_volume_on@3x.png b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_volume_on@3x.png new file mode 100644 index 0000000..ab30874 Binary files /dev/null and b/lib/my_flutter_superplayer/ios/SuperPlayer/Resource/SuperPlayer.bundle/wb_volume_on@3x.png differ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Skins/SPDefaultControlView.h b/lib/my_flutter_superplayer/ios/SuperPlayer/Skins/SPDefaultControlView.h new file mode 100644 index 0000000..8e04e9d --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Skins/SPDefaultControlView.h @@ -0,0 +1,79 @@ +// +// SPDefaultControlView.h +// SuperPlayer +// +// Created by annidyfeng on 2018/9/30. +// + +#import "SuperPlayerControlView.h" + +@interface SPDefaultControlView : SuperPlayerControlView + + +/** 标题 */ +@property (nonatomic, strong) UILabel *titleLabel; +/** 开始播放按钮 */ +@property (nonatomic, strong) UIButton *startBtn; +/** 当前播放时长label */ +@property (nonatomic, strong) UILabel *currentTimeLabel; +/** 视频总时长label */ +@property (nonatomic, strong) UILabel *totalTimeLabel; +/** 全屏按钮 */ +@property (nonatomic, strong) UIButton *fullScreenBtn; +/** 锁定屏幕方向按钮 */ +@property (nonatomic, strong) UIButton *lockBtn; + +/** 返回按钮*/ +@property (nonatomic, strong) UIButton *backBtn; +/// 是否禁用返回 +@property (nonatomic) BOOL disableBackBtn; +/** bottomView*/ +@property (nonatomic, strong) UIImageView *bottomImageView; +/** topView */ +@property (nonatomic, strong) UIImageView *topImageView; +/** 弹幕按钮 */ +@property (nonatomic, strong) UIButton *danmakuBtn; +/// 是否禁用弹幕 +@property (nonatomic) BOOL disableDanmakuBtn; +/** 截图按钮 */ +@property (nonatomic, strong) UIButton *captureBtn; +/// 是否禁用截图 +@property (nonatomic) BOOL disableCaptureBtn; +/** 更多按钮 */ +@property (nonatomic, strong) UIButton *moreBtn; +/// 是否禁用更多 +@property (nonatomic) BOOL disableMoreBtn; +/** 切换分辨率按钮 */ +@property (nonatomic, strong) UIButton *resolutionBtn; +/** 分辨率的View */ +@property (nonatomic, strong) UIView *resolutionView; +/** 播放按钮 */ +@property (nonatomic, strong) UIButton *playeBtn; +/** 加载失败按钮 */ +@property (nonatomic, strong) UIButton *middleBtn; + +/** 当前选中的分辨率btn按钮 */ +@property (nonatomic, weak ) UIButton *resoultionCurrentBtn; + +/** 分辨率的名称 */ +@property (nonatomic, strong) NSArray *resolutionArray; +/** 更多设置View */ +@property (nonatomic, strong) SuperPlayerSettingsView *moreContentView; +/** 返回直播 */ +@property (nonatomic, strong) UIButton *backLiveBtn; + +/// 画面比例 +@property CGFloat videoRatio; + +/** 滑杆 */ +@property (nonatomic, strong) PlayerSlider *videoSlider; + +/** 重播按钮 */ +@property (nonatomic, strong) UIButton *repeatBtn; + +/** 是否全屏播放 */ +@property (nonatomic, assign,getter=isFullScreen)BOOL fullScreen; +@property (nonatomic, assign,getter=isLockScreen)BOOL isLockScreen; +@property (nonatomic, strong) UIButton *pointJumpBtn; + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Skins/SPDefaultControlView.m b/lib/my_flutter_superplayer/ios/SuperPlayer/Skins/SPDefaultControlView.m new file mode 100644 index 0000000..59b5a6b --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Skins/SPDefaultControlView.m @@ -0,0 +1,774 @@ +#import "SuperPlayerControlView.h" +#import +#import + +#import "SuperPlayerSettingsView.h" +#import "DataReport.h" +#import "SuperPlayerFastView.h" +#import "PlayerSlider.h" +#import "UIView+MMLayout.h" +#import "SuperPlayerView+Private.h" +#import "StrUtils.h" +#import "SPDefaultControlView.h" +#import "UIView+Fade.h" + +#pragma clang diagnostic push +#pragma clang diagnostic ignored"-Wdeprecated-declarations" + +#define MODEL_TAG_BEGIN 20 +#define BOTTOM_IMAGE_VIEW_HEIGHT 50 + +@interface SPDefaultControlView () +@property BOOL isLive; +@end + +@implementation SPDefaultControlView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self addSubview:self.topImageView]; + [self addSubview:self.bottomImageView]; + [self.bottomImageView addSubview:self.startBtn]; + [self.bottomImageView addSubview:self.currentTimeLabel]; + [self.bottomImageView addSubview:self.videoSlider]; + [self.bottomImageView addSubview:self.resolutionBtn]; + [self.bottomImageView addSubview:self.fullScreenBtn]; + [self.bottomImageView addSubview:self.totalTimeLabel]; + + [self.topImageView addSubview:self.captureBtn]; + [self.topImageView addSubview:self.danmakuBtn]; + [self.topImageView addSubview:self.moreBtn]; + [self addSubview:self.lockBtn]; + [self.topImageView addSubview:self.backBtn]; + + [self addSubview:self.playeBtn]; + + [self.topImageView addSubview:self.titleLabel]; + + + [self addSubview:self.backLiveBtn]; + + // 添加子控件的约束 + [self makeSubViewsConstraints]; + + self.captureBtn.hidden = YES; + self.danmakuBtn.hidden = YES; + self.moreBtn.hidden = YES; + self.resolutionBtn.hidden = YES; + self.moreContentView.hidden = YES; + // 初始化时重置controlView + [self playerResetControlView]; + } + return self; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)makeSubViewsConstraints { + + [self.topImageView mas_makeConstraints:^(MASConstraintMaker *make) { + make.leading.trailing.equalTo(self); + make.top.equalTo(self.mas_top).offset(0); + make.height.mas_equalTo(50); + }]; + + [self.backBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.leading.equalTo(self.topImageView.mas_leading).offset(5); + make.top.equalTo(self.topImageView.mas_top).offset(3); + make.width.height.mas_equalTo(40); + }]; + + [self.moreBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.width.mas_equalTo(40); + make.height.mas_equalTo(49); + make.trailing.equalTo(self.topImageView.mas_trailing).offset(-10); + make.centerY.equalTo(self.backBtn.mas_centerY); + }]; + + [self.captureBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.width.mas_equalTo(40); + make.height.mas_equalTo(49); + make.trailing.equalTo(self.moreBtn.mas_leading).offset(-10); + make.centerY.equalTo(self.backBtn.mas_centerY); + }]; + + [self.danmakuBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.width.mas_equalTo(40); + make.height.mas_equalTo(49); + make.trailing.equalTo(self.captureBtn.mas_leading).offset(-10); + make.centerY.equalTo(self.backBtn.mas_centerY); + }]; + + [self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.leading.equalTo(self.backBtn.mas_trailing).offset(5); + make.centerY.equalTo(self.backBtn.mas_centerY); + make.trailing.equalTo(self.captureBtn.mas_leading).offset(-10); + }]; + + [self.bottomImageView mas_makeConstraints:^(MASConstraintMaker *make) { + make.leading.trailing.bottom.mas_equalTo(0); + make.height.mas_equalTo(BOTTOM_IMAGE_VIEW_HEIGHT); + }]; + + [self.startBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.leading.equalTo(self.bottomImageView.mas_leading).offset(5); + make.top.equalTo(self.bottomImageView.mas_top).offset(10); + make.width.height.mas_equalTo(30); + }]; + + [self.currentTimeLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.leading.equalTo(self.startBtn.mas_trailing); + make.centerY.equalTo(self.startBtn.mas_centerY); + make.width.mas_equalTo(60); + }]; + + [self.fullScreenBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.width.height.mas_equalTo(30); + make.trailing.equalTo(self.bottomImageView.mas_trailing).offset(-8); + make.centerY.equalTo(self.startBtn.mas_centerY); + }]; + + [self.resolutionBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.height.mas_equalTo(30); + make.width.mas_greaterThanOrEqualTo(45); + make.trailing.equalTo(self.bottomImageView.mas_trailing).offset(-8); + make.centerY.equalTo(self.startBtn.mas_centerY); + }]; + + [self.totalTimeLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.trailing.equalTo(self.fullScreenBtn.mas_leading); + make.centerY.equalTo(self.startBtn.mas_centerY); + make.width.mas_equalTo(60); + }]; + + [self.videoSlider mas_makeConstraints:^(MASConstraintMaker *make) { + make.leading.equalTo(self.currentTimeLabel.mas_trailing); + make.trailing.equalTo(self.totalTimeLabel.mas_leading); + make.centerY.equalTo(self.currentTimeLabel.mas_centerY).offset(-1); + }]; + + [self.lockBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.leading.equalTo(self.mas_leading).offset(15); + make.centerY.equalTo(self.mas_centerY); + make.width.height.mas_equalTo(32); + }]; + + + [self.playeBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.width.height.mas_equalTo(50); + make.center.equalTo(self); + }]; + + + [self.backLiveBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.bottom.mas_equalTo(self.startBtn.mas_top).mas_offset(-15); + make.width.mas_equalTo(70); + make.centerX.equalTo(self); + }]; +} + + + + +#pragma mark - Action + +/** + * 点击切换分别率按钮 + */ +- (void)changeResolution:(UIButton *)sender { + self.resoultionCurrentBtn.selected = NO; + self.resoultionCurrentBtn.backgroundColor = [UIColor clearColor]; + self.resoultionCurrentBtn = sender; + self.resoultionCurrentBtn.selected = YES; + self.resoultionCurrentBtn.backgroundColor = RGBA(34, 30, 24, 1); + + // topImageView上的按钮的文字 + [self.resolutionBtn setTitle:sender.titleLabel.text forState:UIControlStateNormal]; + [self.delegate controlViewSwitch:self withDefinition:sender.titleLabel.text]; +} + +- (void)backBtnClick:(UIButton *)sender { + if ([self.delegate respondsToSelector:@selector(controlViewBack:)]) { + [self.delegate controlViewBack:self]; + } +} + +- (void)exitFullScreen:(UIButton *)sender { + if ([self.delegate respondsToSelector:@selector(controlViewChangeScreen:withFullScreen:)]) { + [self.delegate controlViewChangeScreen:self withFullScreen:NO]; + } +} + +- (void)lockScrrenBtnClick:(UIButton *)sender { + sender.selected = !sender.selected; + self.isLockScreen = sender.selected; + self.topImageView.hidden = self.isLockScreen; + self.bottomImageView.hidden = self.isLockScreen; + if (self.isLive) { + self.backLiveBtn.hidden = self.isLockScreen; + } + [self.delegate controlViewLockScreen:self withLock:self.isLockScreen]; + [self fadeOut:3]; +} + +- (void)playBtnClick:(UIButton *)sender { + sender.selected = !sender.selected; + if (sender.selected) { + [self.delegate controlViewPlay:self]; + } else { + [self.delegate controlViewPause:self]; + } + [self cancelFadeOut]; +} + +- (void)fullScreenBtnClick:(UIButton *)sender { + sender.selected = !sender.selected; + self.fullScreen = !self.fullScreen; + [self.delegate controlViewChangeScreen:self withFullScreen:YES]; + [self fadeOut:3]; +} + + +- (void)captureBtnClick:(UIButton *)sender { + [self.delegate controlViewSnapshot:self]; + [self fadeOut:3]; +} + +- (void)danmakuBtnClick:(UIButton *)sender { + sender.selected = !sender.selected; + [self fadeOut:3]; +} + +- (void)moreBtnClick:(UIButton *)sender { + self.topImageView.hidden = YES; + self.bottomImageView.hidden = YES; + self.lockBtn.hidden = YES; + + self.moreContentView.playerConfig = self.playerConfig; + [self.moreContentView update]; + self.moreContentView.hidden = NO; + + [self cancelFadeOut]; + self.isShowSecondView = YES; +} + +- (UIView *)resolutionView { + if (!_resolutionView) { + // 添加分辨率按钮和分辨率下拉列表 + + _resolutionView = [[UIView alloc] initWithFrame:CGRectZero]; + _resolutionView.hidden = YES; + [self addSubview:_resolutionView]; + [_resolutionView mas_makeConstraints:^(MASConstraintMaker *make) { + make.width.mas_equalTo(330); + make.height.mas_equalTo(self.mas_height); + make.trailing.equalTo(self.mas_trailing).offset(0); + make.top.equalTo(self.mas_top).offset(0); + }]; + + _resolutionView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5]; + } + return _resolutionView; +} + +- (void)resolutionBtnClick:(UIButton *)sender { + self.topImageView.hidden = YES; + self.bottomImageView.hidden = YES; + self.lockBtn.hidden = YES; + + // 显示隐藏分辨率View + self.resolutionView.hidden = NO; + [DataReport report:@"change_resolution" param:nil]; + + [self cancelFadeOut]; + self.isShowSecondView = YES; +} + +- (void)progressSliderTouchBegan:(UISlider *)sender { + self.isDragging = YES; + [self cancelFadeOut]; +} + +- (void)progressSliderValueChanged:(UISlider *)sender { + if (self.maxPlayableRatio > 0 && sender.value > self.maxPlayableRatio) { + sender.value = self.maxPlayableRatio; + } + [self.delegate controlViewPreview:self where:sender.value]; +} + +- (void)progressSliderTouchEnded:(UISlider *)sender { + [self.delegate controlViewSeek:self where:sender.value]; + self.isDragging = NO; + [self fadeOut:5]; +} + +- (void)backLiveClick:(UIButton *)sender { + [self.delegate controlViewReload:self]; +} + +- (void)pointJumpClick:(UIButton *)sender { + self.pointJumpBtn.hidden = YES; + PlayerPoint *point = [self.videoSlider.pointArray objectAtIndex:self.pointJumpBtn.tag]; + [self.delegate controlViewSeek:self where:point.where]; + [self fadeOut:0.1]; +} + +- (void)setDisableBackBtn:(BOOL)disableBackBtn { + _disableBackBtn = disableBackBtn; + self.backBtn.hidden = disableBackBtn; +} + +- (void)setDisableMoreBtn:(BOOL)disableMoreBtn { + _disableMoreBtn = disableMoreBtn; + if (self.fullScreen) { + self.moreBtn.hidden = disableMoreBtn; + } +} + +- (void)setDisableCaptureBtn:(BOOL)disableCaptureBtn { + _disableCaptureBtn = disableCaptureBtn; + if (self.fullScreen) { + self.captureBtn.hidden = disableCaptureBtn; + } +} + +- (void)setDisableDanmakuBtn:(BOOL)disableDanmakuBtn { + _disableDanmakuBtn = disableDanmakuBtn; + if (self.fullScreen) { + self.danmakuBtn.hidden = disableDanmakuBtn; + } +} +/** + * 屏幕方向发生变化会调用这里 + */ +- (void)setOrientationLandscapeConstraint { + self.fullScreen = YES; + self.lockBtn.hidden = NO; + self.fullScreenBtn.selected = self.isLockScreen; + self.fullScreenBtn.hidden = YES; + self.resolutionBtn.hidden = self.resolutionArray.count == 0; + self.moreBtn.hidden = self.disableMoreBtn; + self.captureBtn.hidden = self.disableCaptureBtn; + self.danmakuBtn.hidden = self.disableDanmakuBtn; + + [self.backBtn setImage:SuperPlayerImage(@"back_full") forState:UIControlStateNormal]; + [self.totalTimeLabel mas_remakeConstraints:^(MASConstraintMaker *make) { + if (self.resolutionArray.count > 0) { + make.trailing.equalTo(self.resolutionBtn.mas_leading); + } else { + make.trailing.equalTo(self.bottomImageView.mas_trailing); + } + make.centerY.equalTo(self.startBtn.mas_centerY); + make.width.mas_equalTo(self.isLive?10:60); + }]; + + [self.bottomImageView mas_updateConstraints:^(MASConstraintMaker *make) { + CGFloat b = self.superview.mm_safeAreaBottomGap; + make.height.mas_equalTo(BOTTOM_IMAGE_VIEW_HEIGHT+b); + }]; + + self.videoSlider.hiddenPoints = NO; +} +/** + * 设置竖屏的约束 + */ +- (void)setOrientationPortraitConstraint { + self.fullScreen = NO; + self.lockBtn.hidden = YES; + self.fullScreenBtn.selected = NO; + self.fullScreenBtn.hidden = NO; + self.resolutionBtn.hidden = YES; + self.moreBtn.hidden = YES; + self.captureBtn.hidden = YES; + self.danmakuBtn.hidden = YES; + self.moreContentView.hidden = YES; + self.resolutionView.hidden = YES; + + [self.totalTimeLabel mas_remakeConstraints:^(MASConstraintMaker *make) { + make.trailing.equalTo(self.fullScreenBtn.mas_leading); + make.centerY.equalTo(self.startBtn.mas_centerY); + make.width.mas_equalTo(self.isLive?10:60); + }]; + + [self.bottomImageView mas_updateConstraints:^(MASConstraintMaker *make) { + make.height.mas_equalTo(BOTTOM_IMAGE_VIEW_HEIGHT); + }]; + + self.videoSlider.hiddenPoints = YES; + self.pointJumpBtn.hidden = YES; +} + +#pragma mark - Private Method + +#pragma mark - setter + +- (UILabel *)titleLabel { + if (!_titleLabel) { + _titleLabel = [[UILabel alloc] init]; + _titleLabel.textColor = [UIColor whiteColor]; + _titleLabel.font = [UIFont systemFontOfSize:15.0]; + } + return _titleLabel; +} + +- (UIButton *)backBtn { + if (!_backBtn) { + _backBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + [_backBtn setImage:SuperPlayerImage(@"back_full") forState:UIControlStateNormal]; + [_backBtn addTarget:self action:@selector(backBtnClick:) forControlEvents:UIControlEventTouchUpInside]; + } + return _backBtn; +} + +- (UIImageView *)topImageView { + if (!_topImageView) { + _topImageView = [[UIImageView alloc] init]; + _topImageView.userInteractionEnabled = YES; + _topImageView.image = SuperPlayerImage(@"top_shadow"); + } + return _topImageView; +} + +- (UIImageView *)bottomImageView { + if (!_bottomImageView) { + _bottomImageView = [[UIImageView alloc] init]; + _bottomImageView.userInteractionEnabled = YES; + _bottomImageView.image = SuperPlayerImage(@"bottom_shadow"); + } + return _bottomImageView; +} + +- (UIButton *)lockBtn { + if (!_lockBtn) { + _lockBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + _lockBtn.exclusiveTouch = YES; + [_lockBtn setImage:SuperPlayerImage(@"unlock-nor") forState:UIControlStateNormal]; + [_lockBtn setImage:SuperPlayerImage(@"lock-nor") forState:UIControlStateSelected]; + [_lockBtn addTarget:self action:@selector(lockScrrenBtnClick:) forControlEvents:UIControlEventTouchUpInside]; + + } + return _lockBtn; +} + +- (UIButton *)startBtn { + if (!_startBtn) { + _startBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + [_startBtn setImage:SuperPlayerImage(@"play") forState:UIControlStateNormal]; + [_startBtn setImage:SuperPlayerImage(@"pause") forState:UIControlStateSelected]; + [_startBtn addTarget:self action:@selector(playBtnClick:) forControlEvents:UIControlEventTouchUpInside]; + } + return _startBtn; +} + +- (UILabel *)currentTimeLabel { + if (!_currentTimeLabel) { + _currentTimeLabel = [[UILabel alloc] init]; + _currentTimeLabel.textColor = [UIColor whiteColor]; + _currentTimeLabel.font = [UIFont systemFontOfSize:12.0f]; + _currentTimeLabel.textAlignment = NSTextAlignmentCenter; + } + return _currentTimeLabel; +} + +- (PlayerSlider *)videoSlider { + if (!_videoSlider) { + _videoSlider = [[PlayerSlider alloc] init]; + [_videoSlider setThumbImage:SuperPlayerImage(@"slider_thumb") forState:UIControlStateNormal]; + _videoSlider.minimumTrackTintColor = TintColor; + // slider开始滑动事件 + [_videoSlider addTarget:self action:@selector(progressSliderTouchBegan:) forControlEvents:UIControlEventTouchDown]; + // slider滑动中事件 + [_videoSlider addTarget:self action:@selector(progressSliderValueChanged:) forControlEvents:UIControlEventValueChanged]; + // slider结束滑动事件 + [_videoSlider addTarget:self action:@selector(progressSliderTouchEnded:) forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchUpOutside]; + _videoSlider.delegate = self; + } + return _videoSlider; +} + +- (UILabel *)totalTimeLabel { + if (!_totalTimeLabel) { + _totalTimeLabel = [[UILabel alloc] init]; + _totalTimeLabel.textColor = [UIColor whiteColor]; + _totalTimeLabel.font = [UIFont systemFontOfSize:12.0f]; + _totalTimeLabel.textAlignment = NSTextAlignmentCenter; + } + return _totalTimeLabel; +} + +- (UIButton *)fullScreenBtn { + if (!_fullScreenBtn) { + _fullScreenBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + [_fullScreenBtn setImage:SuperPlayerImage(@"fullscreen") forState:UIControlStateNormal]; + [_fullScreenBtn addTarget:self action:@selector(fullScreenBtnClick:) forControlEvents:UIControlEventTouchUpInside]; + } + return _fullScreenBtn; +} + +- (UIButton *)captureBtn { + if (!_captureBtn) { + _captureBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + [_captureBtn setImage:SuperPlayerImage(@"capture") forState:UIControlStateNormal]; + [_captureBtn setImage:SuperPlayerImage(@"capture_pressed") forState:UIControlStateSelected]; + [_captureBtn addTarget:self action:@selector(captureBtnClick:) forControlEvents:UIControlEventTouchUpInside]; + } + return _captureBtn; +} + +- (UIButton *)danmakuBtn { + if (!_danmakuBtn) { + _danmakuBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + [_danmakuBtn setImage:SuperPlayerImage(@"danmu") forState:UIControlStateNormal]; + [_danmakuBtn setImage:SuperPlayerImage(@"danmu_pressed") forState:UIControlStateSelected]; + [_danmakuBtn addTarget:self action:@selector(danmakuBtnClick:) forControlEvents:UIControlEventTouchUpInside]; + } + return _danmakuBtn; +} + +- (UIButton *)moreBtn { + if (!_moreBtn) { + _moreBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + [_moreBtn setImage:SuperPlayerImage(@"more") forState:UIControlStateNormal]; + [_moreBtn setImage:SuperPlayerImage(@"more_pressed") forState:UIControlStateSelected]; + [_moreBtn addTarget:self action:@selector(moreBtnClick:) forControlEvents:UIControlEventTouchUpInside]; + } + return _moreBtn; +} + +- (UIButton *)resolutionBtn { + if (!_resolutionBtn) { + _resolutionBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + _resolutionBtn.titleLabel.font = [UIFont systemFontOfSize:12]; + _resolutionBtn.backgroundColor = [UIColor clearColor]; + [_resolutionBtn addTarget:self action:@selector(resolutionBtnClick:) forControlEvents:UIControlEventTouchUpInside]; + } + return _resolutionBtn; +} + +- (UIButton *)backLiveBtn { + if (!_backLiveBtn) { + _backLiveBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + [_backLiveBtn setTitle:@"返回直播" forState:UIControlStateNormal]; + _backLiveBtn.titleLabel.font = [UIFont systemFontOfSize:14]; + UIImage *image = SuperPlayerImage(@"qg_online_bg"); + + UIImage *resizableImage = [image resizableImageWithCapInsets:UIEdgeInsetsMake(33 * 0.5, 33 * 0.5, 33 * 0.5, 33 * 0.5)]; + [_backLiveBtn setBackgroundImage:resizableImage forState:UIControlStateNormal]; + + [_backLiveBtn addTarget:self action:@selector(backLiveClick:) forControlEvents:UIControlEventTouchUpInside]; + } + return _backLiveBtn; +} + +- (UIButton *)pointJumpBtn { + if (!_pointJumpBtn) { + _pointJumpBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + UIImage *image = SuperPlayerImage(@"copywright_bg"); + UIImage *resizableImage = [image resizableImageWithCapInsets:UIEdgeInsetsMake(0, 20, 0, 20) resizingMode:UIImageResizingModeStretch]; + [_pointJumpBtn setBackgroundImage:resizableImage forState:UIControlStateNormal]; + _pointJumpBtn.titleLabel.font = [UIFont systemFontOfSize:14]; + [_pointJumpBtn addTarget:self action:@selector(pointJumpClick:) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:_pointJumpBtn]; + } + return _pointJumpBtn; +} + +- (SuperPlayerSettingsView *)moreContentView { + if (!_moreContentView) { + _moreContentView = [[SuperPlayerSettingsView alloc] initWithFrame:CGRectZero]; + _moreContentView.controlView = self; + _moreContentView.hidden = YES; + [self addSubview:_moreContentView]; + [_moreContentView mas_makeConstraints:^(MASConstraintMaker *make) { + make.width.mas_equalTo(330); + make.height.mas_equalTo(self.mas_height); + make.trailing.equalTo(self.mas_trailing).offset(0); + make.top.equalTo(self.mas_top).offset(0); + }]; + _moreContentView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5]; + } + return _moreContentView; +} + +#pragma mark - UIGestureRecognizerDelegate + +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { + if ([touch.view isKindOfClass:[UISlider class]]) { // 如果在滑块上点击就不响应pan手势 + return NO; + } + return YES; +} + +#pragma mark - Public method + +- (void)setHidden:(BOOL)hidden +{ + [super setHidden:hidden]; + if (hidden) { + self.resolutionView.hidden = YES; + self.moreContentView.hidden = YES; + if (!self.isLockScreen) { + self.topImageView.hidden = NO; + self.bottomImageView.hidden = NO; + } + } + + self.lockBtn.hidden = !self.isFullScreen; + self.isShowSecondView = NO; + self.pointJumpBtn.hidden = YES; +} + +/** 重置ControlView */ +- (void)playerResetControlView { + self.videoSlider.value = 0; + self.videoSlider.progressView.progress = 0; + self.currentTimeLabel.text = @"00:00"; + self.totalTimeLabel.text = @"00:00"; + self.playeBtn.hidden = YES; + self.resolutionView.hidden = YES; + self.backgroundColor = [UIColor clearColor]; + self.moreBtn.enabled = !self.disableMoreBtn; + self.lockBtn.hidden = !self.isFullScreen; + + self.danmakuBtn.enabled = YES; + self.captureBtn.enabled = YES; + self.backLiveBtn.hidden = YES; +} + +- (void)setPointArray:(NSArray *)pointArray +{ + [super setPointArray:pointArray]; + + for (PlayerPoint *holder in self.videoSlider.pointArray) { + [holder.holder removeFromSuperview]; + } + [self.videoSlider.pointArray removeAllObjects]; + + for (SPVideoFrameDescription *p in pointArray) { + PlayerPoint *point = [self.videoSlider addPoint:p.where]; + point.content = p.text; + point.timeOffset = p.time; + } +} + + + +- (void)onPlayerPointSelected:(PlayerPoint *)point { + NSString *text = [NSString stringWithFormat:@" %@ %@ ", [StrUtils timeFormat:point.timeOffset], + point.content]; + + [self.pointJumpBtn setTitle:text forState:UIControlStateNormal]; + [self.pointJumpBtn sizeToFit]; + CGFloat x = self.videoSlider.mm_x + self.videoSlider.mm_w * point.where - self.pointJumpBtn.mm_h/2; + if (x < 0) + x = 0; + if (x + self.pointJumpBtn.mm_h/2 > ScreenWidth) + x = ScreenWidth - self.pointJumpBtn.mm_h/2; + self.pointJumpBtn.tag = [self.videoSlider.pointArray indexOfObject:point]; + self.pointJumpBtn.m_left(x).m_bottom(60); + self.pointJumpBtn.hidden = NO; + + [DataReport report:@"player_point" param:nil]; +} + +- (void)setProgressTime:(NSInteger)currentTime + totalTime:(NSInteger)totalTime + progressValue:(CGFloat)progress + playableValue:(CGFloat)playable { + if (!self.isDragging) { + // 更新slider + self.videoSlider.value = progress; + } + // 更新当前播放时间 + self.currentTimeLabel.text = [StrUtils timeFormat:currentTime]; + // 更新总时间 + self.totalTimeLabel.text = [StrUtils timeFormat:totalTime]; + [self.videoSlider.progressView setProgress:playable animated:NO]; +} + +- (void)resetWithResolutionNames:(NSArray *)resolutionNames + currentResolutionIndex:(NSUInteger)currentResolutionIndex + isLive:(BOOL)isLive + isTimeShifting:(BOOL)isTimeShifting + isPlaying:(BOOL)isPlaying +{ + NSAssert(resolutionNames.count == 0 || currentResolutionIndex < resolutionNames.count, + @"Invalid argument when reseeting %@", NSStringFromClass(self.class)); + + [self setPlayState:isPlaying]; + self.backLiveBtn.hidden = !isTimeShifting; + self.moreContentView.enableSpeedAndMirrorControl = !isLive; + + for (UIView *subview in self.resolutionView.subviews) + [subview removeFromSuperview]; + + _resolutionArray = resolutionNames; + if (_resolutionArray.count > 0) { + [self.resolutionBtn setTitle:resolutionNames[currentResolutionIndex] + forState:UIControlStateNormal]; + } + UILabel *lable = [UILabel new]; + lable.text = @"清晰度"; + lable.textAlignment = NSTextAlignmentCenter; + lable.textColor = [UIColor whiteColor]; + [self.resolutionView addSubview:lable]; + [lable mas_makeConstraints:^(MASConstraintMaker *make) { + make.width.equalTo(self.resolutionView.mas_width); + make.height.mas_equalTo(30); + make.left.equalTo(self.resolutionView.mas_left); + make.top.equalTo(self.resolutionView.mas_top).mas_offset(20); + }]; + + // 分辨率View上边的Btn + for (NSInteger i = 0 ; i < _resolutionArray.count; i++) { + UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom]; + [btn setTitle:_resolutionArray[i] forState:UIControlStateNormal]; + [btn setTitleColor:RGBA(252, 89, 81, 1) forState:UIControlStateSelected]; + [self.resolutionView addSubview:btn]; + [btn addTarget:self action:@selector(changeResolution:) forControlEvents:UIControlEventTouchUpInside]; + [btn mas_makeConstraints:^(MASConstraintMaker *make) { + make.width.equalTo(self.resolutionView.mas_width); + make.height.mas_equalTo(45); + make.left.equalTo(self.resolutionView.mas_left); + make.centerY.equalTo(self.resolutionView.mas_centerY).offset((i-self.resolutionArray.count/2.0+0.5)*45); + }]; + btn.tag = MODEL_TAG_BEGIN+i; + + if (i == currentResolutionIndex) { + btn.selected = YES; + btn.backgroundColor = RGBA(34, 30, 24, 1); + self.resoultionCurrentBtn = btn; + } + } + if (self.isLive != isLive) { + self.isLive = isLive; + [self setNeedsLayout]; + } + // 时移的时候不能切清晰度 + self.resolutionBtn.userInteractionEnabled = !isTimeShifting; +} + +/** 播放按钮状态 */ +- (void)setPlayState:(BOOL)state { + self.startBtn.selected = state; +} + +- (void)setTitle:(NSString *)title +{ + [super setTitle:title]; + self.titleLabel.text = title; +} + +- (void)hideDanmu { + [self setDisableDanmakuBtn:YES]; +} +- (void)hideReplay { + [self.repeatBtn setHidden:YES]; +} + +#pragma clang diagnostic pop + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Skins/SPWeiboControlView.h b/lib/my_flutter_superplayer/ios/SuperPlayer/Skins/SPWeiboControlView.h new file mode 100644 index 0000000..19801da --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Skins/SPWeiboControlView.h @@ -0,0 +1,40 @@ +// +// SPWeiboControlView.h +// SuperPlayer +// +// Created by annidyfeng on 2018/10/8. +// + +#import "SuperPlayerControlView.h" + +@interface SPWeiboControlView : SuperPlayerControlView +/** 分辨率的名称 */ +@property (nonatomic, strong) NSArray *resolutionArray; +/** 开始播放按钮 */ +@property (nonatomic, strong) UIButton *startBtn; +/** 当前播放时长label */ +@property (nonatomic, strong) UILabel *currentTimeLabel; +/** 视频总时长label */ +@property (nonatomic, strong) UILabel *totalTimeLabel; +/** 全屏按钮 */ +@property (nonatomic, strong) UIButton *fullScreenBtn; +/** 滑杆 */ +@property (nonatomic, strong) PlayerSlider *videoSlider; + +@property (nonatomic, strong) UIButton *moreBtn; + +@property (nonatomic, strong) UIButton *backBtn; + +@property (nonatomic, strong) UIButton *muteBtn; + +@property (nonatomic, assign,getter=isFullScreen)BOOL fullScreen; + +/** 切换分辨率按钮 */ +@property (nonatomic, strong) UIButton *resolutionBtn; +@property (nonatomic, strong) UIButton *resoultionCurrentBtn; +/** 分辨率的View */ +@property (nonatomic, strong) UIView *resolutionView; +/** 更多设置View */ +@property (nonatomic, strong) SuperPlayerSettingsView *moreContentView; +@property (nonatomic, strong) UIButton *pointJumpBtn; +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Skins/SPWeiboControlView.m b/lib/my_flutter_superplayer/ios/SuperPlayer/Skins/SPWeiboControlView.m new file mode 100644 index 0000000..3685a52 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Skins/SPWeiboControlView.m @@ -0,0 +1,487 @@ +// +// SPWeiboControlView.m +// SuperPlayer +// +// Created by annidyfeng on 2018/10/8. +// + +#import "SPWeiboControlView.h" +#import +#import "UIView+Fade.h" +#import "UIView+MMLayout.h" +#import "StrUtils.h" +#import "DataReport.h" +#import "SuperPlayer.h" + +@interface SPWeiboControlView() +@property BOOL isLive; +@end + +@implementation SPWeiboControlView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + + [self addSubview:self.startBtn]; + [self addSubview:self.currentTimeLabel]; + [self addSubview:self.totalTimeLabel]; + [self addSubview:self.videoSlider]; + [self addSubview:self.fullScreenBtn]; + [self addSubview:self.resolutionBtn]; + [self addSubview:self.muteBtn]; + [self addSubview:self.moreBtn]; + [self addSubview:self.backBtn]; + + // 添加子控件的约束 + [self makeSubViewsConstraints]; + [self resetControlView]; + } + return self; +} + +- (void)makeSubViewsConstraints { + [self.startBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.center.equalTo(self); + }]; + [self.currentTimeLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.bottom.equalTo(self).offset(-8); + make.left.mas_equalTo(@0); + make.width.mas_equalTo(@60); + }]; + [self.fullScreenBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerY.equalTo(self.currentTimeLabel); + make.right.equalTo(self).offset(-12); + }]; + [self.resolutionBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerY.equalTo(self.currentTimeLabel); + make.right.equalTo(self.fullScreenBtn.mas_left); + make.width.mas_equalTo(0); + }]; + [self.totalTimeLabel mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerY.equalTo(self.currentTimeLabel); + make.right.equalTo(self.resolutionBtn.mas_left); + make.width.mas_equalTo(@60); + }]; + [self.videoSlider mas_makeConstraints:^(MASConstraintMaker *make) { + make.centerY.equalTo(self.currentTimeLabel); + make.left.equalTo(self.currentTimeLabel.mas_right); + make.right.equalTo(self.totalTimeLabel.mas_left); + }]; + + [self.backBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.leading.equalTo(self).offset(5); + make.top.equalTo(self).offset(3); + make.width.mas_equalTo(@60); + }]; + + [self.moreBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.trailing.equalTo(self).offset(-10); + make.centerY.equalTo(self.backBtn); + make.width.mas_equalTo(@40); + }]; + [self.muteBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.trailing.equalTo(self.moreBtn.mas_leading).offset(-10); + make.centerY.equalTo(self.backBtn); + make.width.mas_equalTo(@40); + }]; +} + + +- (UIButton *)startBtn { + if (!_startBtn) { + _startBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + [_startBtn setImage:SuperPlayerImage(@"wb_play") forState:UIControlStateNormal]; + [_startBtn setImage:SuperPlayerImage(@"wb_pause") forState:UIControlStateSelected]; + [_startBtn addTarget:self action:@selector(playBtnClick:) forControlEvents:UIControlEventTouchUpInside]; + } + return _startBtn; +} + +- (UILabel *)currentTimeLabel { + if (!_currentTimeLabel) { + _currentTimeLabel = [[UILabel alloc] init]; + _currentTimeLabel.textColor = [UIColor whiteColor]; + _currentTimeLabel.font = [UIFont systemFontOfSize:12.0f]; + _currentTimeLabel.textAlignment = NSTextAlignmentCenter; + } + return _currentTimeLabel; +} + +- (UIButton *)resolutionBtn { + if (!_resolutionBtn) { + _resolutionBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + _resolutionBtn.titleLabel.font = [UIFont systemFontOfSize:12]; + _resolutionBtn.backgroundColor = [UIColor clearColor]; + [_resolutionBtn addTarget:self action:@selector(resolutionBtnClick:) forControlEvents:UIControlEventTouchUpInside]; + } + return _resolutionBtn; +} + +- (PlayerSlider *)videoSlider { + if (!_videoSlider) { + _videoSlider = [[PlayerSlider alloc] init]; + [_videoSlider setThumbImage:SuperPlayerImage(@"wb_thumb") forState:UIControlStateNormal]; + _videoSlider.minimumTrackTintColor = RGBA(223, 223, 223, 1); + // slider开始滑动事件 + [_videoSlider addTarget:self action:@selector(progressSliderTouchBegan:) forControlEvents:UIControlEventTouchDown]; + // slider滑动中事件 + [_videoSlider addTarget:self action:@selector(progressSliderValueChanged:) forControlEvents:UIControlEventValueChanged]; + // slider结束滑动事件 + [_videoSlider addTarget:self action:@selector(progressSliderTouchEnded:) forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchUpOutside|UIControlEventTouchCancel]; + _videoSlider.delegate = self; + } + return _videoSlider; +} + +- (UILabel *)totalTimeLabel { + if (!_totalTimeLabel) { + _totalTimeLabel = [[UILabel alloc] init]; + _totalTimeLabel.textColor = [UIColor whiteColor]; + _totalTimeLabel.font = [UIFont systemFontOfSize:12.0f]; + _totalTimeLabel.textAlignment = NSTextAlignmentCenter; + } + return _totalTimeLabel; +} + +- (UIButton *)fullScreenBtn { + if (!_fullScreenBtn) { + _fullScreenBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + [_fullScreenBtn setImage:SuperPlayerImage(@"wb_fullscreen") forState:UIControlStateNormal]; + [_fullScreenBtn setImage:SuperPlayerImage(@"wb_fullscreen_back") forState:UIControlStateSelected]; + [_fullScreenBtn addTarget:self action:@selector(fullScreenBtnClick:) forControlEvents:UIControlEventTouchUpInside]; + } + return _fullScreenBtn; +} + +- (UIButton *)backBtn { + if (!_backBtn) { + _backBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + [_backBtn setImage:SuperPlayerImage(@"wb_back") forState:UIControlStateNormal]; + [_backBtn addTarget:self action:@selector(fullScreenBtnClick:) forControlEvents:UIControlEventTouchUpInside]; + } + return _backBtn; +} +- (UIButton *)moreBtn { + if (!_moreBtn) { + _moreBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + [_moreBtn setImage:SuperPlayerImage(@"wb_more") forState:UIControlStateNormal]; + [_moreBtn addTarget:self action:@selector(moreBtnClick:) forControlEvents:UIControlEventTouchUpInside]; + } + return _moreBtn; +} +- (UIButton *)muteBtn { + if (!_muteBtn) { + _muteBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + [_muteBtn setImage:SuperPlayerImage(@"wb_volume_on") forState:UIControlStateNormal]; + [_muteBtn setImage:SuperPlayerImage(@"wb_volume_off") forState:UIControlStateSelected]; + [_muteBtn addTarget:self action:@selector(muteBtnClick:) forControlEvents:UIControlEventTouchUpInside]; + } + return _muteBtn; +} + +- (UIView *)resolutionView { + if (!_resolutionView) { + // 添加分辨率按钮和分辨率下拉列表 + + _resolutionView = [[UIView alloc] initWithFrame:CGRectZero]; + _resolutionView.hidden = YES; + [self addSubview:_resolutionView]; + [_resolutionView mas_makeConstraints:^(MASConstraintMaker *make) { + make.width.mas_equalTo(330); + make.height.mas_equalTo(self.mas_height); + make.trailing.equalTo(self.mas_trailing).offset(0); + make.top.equalTo(self.mas_top).offset(0); + }]; + + _resolutionView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5]; + } + return _resolutionView; +} + +- (SuperPlayerSettingsView *)moreContentView { + if (!_moreContentView) { + _moreContentView = [[SuperPlayerSettingsView alloc] initWithFrame:CGRectZero]; + _moreContentView.controlView = self; + [self addSubview:_moreContentView]; + [_moreContentView mas_makeConstraints:^(MASConstraintMaker *make) { + make.width.mas_equalTo(330); + make.height.mas_equalTo(self.mas_height); + make.trailing.equalTo(self.mas_trailing).offset(0); + make.top.equalTo(self.mas_top).offset(0); + }]; + _moreContentView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5]; + } + return _moreContentView; +} + +- (void)playBtnClick:(UIButton *)sender { + sender.selected = !sender.selected; + if (sender.selected) { + [self.delegate controlViewPlay:self]; + } else { + [self.delegate controlViewPause:self]; + } + [self cancelFadeOut]; +} + +- (void)fullScreenBtnClick:(UIButton *)sender { + sender.selected = !sender.selected; + [self.delegate controlViewChangeScreen:self withFullScreen:sender.selected]; +} + +- (void)progressSliderTouchBegan:(UISlider *)sender { + self.isDragging = YES; + self.startBtn.hidden = YES; // 播放按钮挡住了fastView + [self cancelFadeOut]; +} + +- (void)progressSliderValueChanged:(UISlider *)sender { + if (self.maxPlayableRatio > 0 && sender.value * self.maxPlayableRatio) { + sender.value = self.maxPlayableRatio; + } + [self.delegate controlViewPreview:self where:sender.value]; +} + +- (void)progressSliderTouchEnded:(UISlider *)sender { + [self.delegate controlViewSeek:self where:sender.value]; + self.isDragging = NO; + self.startBtn.hidden = NO; + [self cancelFadeOut]; +} + +- (void)setProgressTime:(NSInteger)currentTime totalTime:(NSInteger)totalTime + progressValue:(CGFloat)progress playableValue:(CGFloat)playable; +{ + if (!self.isDragging) { + // 更新slider + self.videoSlider.value = progress; + // 更新当前播放时间 + self.currentTimeLabel.text = [StrUtils timeFormat:currentTime]; + } + // 更新总时间 + self.totalTimeLabel.text = [StrUtils timeFormat:totalTime]; + [self.videoSlider.progressView setProgress:playable animated:NO]; +} + +- (void)muteBtnClick:(UIButton *)sender +{ + sender.selected = self.playerConfig.mute = !self.playerConfig.mute; + [self.delegate controlViewConfigUpdate:self withReload:NO]; + [self fadeOut:3]; +} + + +/** 重置ControlView */ +- (void)resetControlView { + self.videoSlider.value = 0; + self.videoSlider.progressView.progress = 0; + self.currentTimeLabel.text = @"00:00"; + self.totalTimeLabel.text = @"00:00"; + self.moreContentView.hidden = YES; +} + +- (void)setHidden:(BOOL)hidden +{ + [super setHidden:hidden]; + + for (UIView *view in self.subviews) { + if (view != self.resolutionView && view != self.moreContentView) { + if (!self.isFullScreen && (view == self.backBtn || view == self.moreBtn || view == self.muteBtn)) + view.hidden = YES; + else + view.hidden = NO; + } else { + view.hidden = YES; + } + } + self.isShowSecondView = NO; + self.pointJumpBtn.hidden = YES; +} + +/** 播放按钮状态 */ +- (void)setPlayState:(BOOL)isPlay { + self.startBtn.selected = isPlay; +} + +- (void)resolutionBtnClick:(UIButton *)sender { + for (UIView *view in self.subviews) { + view.hidden = YES; + } + + // 显示分辨率View + self.resolutionView.hidden = NO; + [DataReport report:@"change_resolution" param:nil]; + + [self cancelFadeOut]; + self.isShowSecondView = YES; +} + +- (void)moreBtnClick:(UIButton *)sender { + for (UIView *view in self.subviews) { + view.hidden = YES; + } + + self.moreContentView.playerConfig = self.playerConfig; + self.moreContentView.enableSpeedAndMirrorControl = !self.isLive; + [self.moreContentView update]; + self.moreContentView.hidden = NO; + + [self cancelFadeOut]; + self.isShowSecondView = YES; +} + +- (void)resetWithResolutionNames:(NSArray *)resolutionNames + currentResolutionIndex:(NSUInteger)currentResolutionIndex + isLive:(BOOL)isLive + isTimeShifting:(BOOL)isTimeShifting + isPlaying:(BOOL)isPlaying +{ + [self setPlayState:isPlaying]; + + _resolutionArray = resolutionNames; + NSAssert(currentResolutionIndex < resolutionNames.count, + @"Invalid argument when reseeting %@", NSStringFromClass(self.class)); + if (resolutionNames.count > 0) { + [self.resolutionBtn setTitle:resolutionNames[currentResolutionIndex] + forState:UIControlStateNormal]; + } + for (UIView *subview in self.resolutionView.subviews) + [subview removeFromSuperview]; + + UILabel *lable = [UILabel new]; + lable.text = @"清晰度"; + lable.textAlignment = NSTextAlignmentCenter; + lable.textColor = [UIColor whiteColor]; + [self.resolutionView addSubview:lable]; + [lable mas_makeConstraints:^(MASConstraintMaker *make) { + make.width.equalTo(self.resolutionView.mas_width); + make.height.mas_equalTo(30); + make.left.equalTo(self.resolutionView.mas_left); + make.top.equalTo(self.resolutionView.mas_top).mas_offset(20); + }]; + + // 分辨率View上边的Btn + for (NSInteger i = 0 ; i < _resolutionArray.count; i++) { + UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom]; + [btn setTitle:_resolutionArray[i] forState:UIControlStateNormal]; + [btn setTitleColor:RGBA(252, 89, 81, 1) forState:UIControlStateSelected]; + [self.resolutionView addSubview:btn]; + [btn addTarget:self action:@selector(changeResolution:) forControlEvents:UIControlEventTouchUpInside]; + [btn mas_makeConstraints:^(MASConstraintMaker *make) { + make.width.equalTo(self.resolutionView.mas_width); + make.height.mas_equalTo(45); + make.left.equalTo(self.resolutionView.mas_left); + make.centerY.equalTo(self.resolutionView.mas_centerY).offset((i-self.resolutionArray.count/2.0+0.5)*45); + }]; + + if (i == currentResolutionIndex) { + btn.selected = YES; + btn.backgroundColor = RGBA(34, 30, 24, 1); + self.resoultionCurrentBtn = btn; + } + } + if (self.isLive != isLive) { + self.isLive = isLive; + [self setNeedsLayout]; + } + // 时移的时候不能切清晰度 + self.resolutionBtn.userInteractionEnabled = !isTimeShifting; +} + +/** + * 点击切换分别率按钮 + */ +- (void)changeResolution:(UIButton *)sender { + self.resoultionCurrentBtn.selected = NO; + self.resoultionCurrentBtn.backgroundColor = [UIColor clearColor]; + self.resoultionCurrentBtn = sender; + self.resoultionCurrentBtn.selected = YES; + self.resoultionCurrentBtn.backgroundColor = RGBA(34, 30, 24, 1); + + // topImageView上的按钮的文字 + [self.resolutionBtn setTitle:sender.titleLabel.text forState:UIControlStateNormal]; + [self.delegate controlViewSwitch:self withDefinition:sender.titleLabel.text]; +} + +- (void)setOrientationLandscapeConstraint { + self.fullScreen = YES; + self.fullScreenBtn.selected = YES; + self.moreBtn.hidden = NO; + self.backBtn.hidden = NO; + self.muteBtn.hidden = NO; + [self.resolutionBtn mas_updateConstraints:^(MASConstraintMaker *make) { + make.width.mas_equalTo(@60); + }]; + + self.videoSlider.hiddenPoints = NO; +} + +- (void)setOrientationPortraitConstraint { + self.fullScreen = NO; + self.fullScreenBtn.selected = NO; + self.moreBtn.hidden = YES; + self.backBtn.hidden = YES; + self.muteBtn.hidden = YES; + [self.resolutionBtn mas_updateConstraints:^(MASConstraintMaker *make) { + make.width.mas_equalTo(@0); + }]; + + + self.videoSlider.hiddenPoints = YES; + self.pointJumpBtn.hidden = YES; +} + +- (void)setPointArray:(NSArray *)pointArray +{ + [super setPointArray:pointArray]; + + for (PlayerPoint *holder in self.videoSlider.pointArray) { + [holder.holder removeFromSuperview]; + } + [self.videoSlider.pointArray removeAllObjects]; + + for (SPVideoFrameDescription *p in pointArray) { + PlayerPoint *point = [self.videoSlider addPoint:p.where]; + point.content = p.text; + point.timeOffset = p.time; + } +} + +- (UIButton *)pointJumpBtn { + if (!_pointJumpBtn) { + _pointJumpBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + UIImage *image = SuperPlayerImage(@"copywright_bg"); + UIImage *resizableImage = [image resizableImageWithCapInsets:UIEdgeInsetsMake(0, 20, 0, 20) resizingMode:UIImageResizingModeStretch]; + [_pointJumpBtn setBackgroundImage:resizableImage forState:UIControlStateNormal]; + _pointJumpBtn.titleLabel.font = [UIFont systemFontOfSize:14]; + [_pointJumpBtn addTarget:self action:@selector(pointJumpClick:) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:_pointJumpBtn]; + } + return _pointJumpBtn; +} + +- (void)pointJumpClick:(UIButton *)sender { + PlayerPoint *point = [self.videoSlider.pointArray objectAtIndex:self.pointJumpBtn.tag]; + [self.delegate controlViewSeek:self where:point.where]; + [self fadeOut:0.1]; +} + +- (void)onPlayerPointSelected:(PlayerPoint *)point { + NSString *text = [NSString stringWithFormat:@" %@ %@ ", [StrUtils timeFormat:point.timeOffset], + point.content]; + + [self.pointJumpBtn setTitle:text forState:UIControlStateNormal]; + [self.pointJumpBtn sizeToFit]; + CGFloat x = self.videoSlider.mm_x + self.videoSlider.mm_w * point.where - self.pointJumpBtn.mm_w/2; + if (x < 0) + x = 0; + if (x + self.pointJumpBtn.mm_w/2 > ScreenWidth) + x = ScreenWidth - self.pointJumpBtn.mm_w/2; + self.pointJumpBtn.tag = [self.videoSlider.pointArray indexOfObject:point]; + self.pointJumpBtn.m_left(x).m_bottom(60); + self.pointJumpBtn.hidden = NO; + + [DataReport report:@"player_point" param:nil]; +} +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Skins/SPWithoutControlView.h b/lib/my_flutter_superplayer/ios/SuperPlayer/Skins/SPWithoutControlView.h new file mode 100644 index 0000000..6571b0c --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Skins/SPWithoutControlView.h @@ -0,0 +1,12 @@ +// +// SPWithoutControlView.h +// SuperPlayer +// +// Created by annidyfeng on 2021/4/15. +// + +#import "SuperPlayerControlView.h" + +@interface SPWithoutControlView : SuperPlayerControlView + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Skins/SPWithoutControlView.m b/lib/my_flutter_superplayer/ios/SuperPlayer/Skins/SPWithoutControlView.m new file mode 100644 index 0000000..fee9383 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Skins/SPWithoutControlView.m @@ -0,0 +1,39 @@ +#import "SuperPlayerControlView.h" +#import +#import + +#import "SuperPlayerSettingsView.h" +#import "DataReport.h" +#import "SuperPlayerFastView.h" +#import "PlayerSlider.h" +#import "UIView+MMLayout.h" +#import "SuperPlayerView+Private.h" +#import "StrUtils.h" +#import "SPWithoutControlView.h" +#import "UIView+Fade.h" + +#pragma clang diagnostic push +#pragma clang diagnostic ignored"-Wdeprecated-declarations" + +#define MODEL_TAG_BEGIN 20 +#define BOTTOM_IMAGE_VIEW_HEIGHT 50 + +@interface SPWithoutControlView () +@property BOOL isLive; +@end + +@implementation SPWithoutControlView + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + // skip + } + return self; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayer.h b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayer.h new file mode 100644 index 0000000..d9b7bfc --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayer.h @@ -0,0 +1,9 @@ +#import "SuperPlayerView.h" +#import "SuperPlayerModel.h" +#import "SuperPlayerControlView.h" +#import "SuperPlayerControlViewDelegate.h" +#import "SuperPlayerWindow.h" +#import "SPDefaultControlView.h" +#import "SPWeiboControlView.h" +#import "SPWithoutControlView.h" +#import "SuperPlayerHelpers.h" diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerControlView.h b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerControlView.h new file mode 100644 index 0000000..388ba3e --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerControlView.h @@ -0,0 +1,75 @@ +// +// SuperPlayerControlView.h +// TXLiteAVDemo +// +// Created by annidyfeng on 2018/6/25. +// Copyright © 2018年 Tencent. All rights reserved. +// +#import + +#import "SuperPlayerControlViewDelegate.h" +#import "PlayerSlider.h" +#import "SuperPlayerFastView.h" +#import "MMMaterialDesignSpinner.h" +#import "SuperPlayerSettingsView.h" +#import "SuperPlayerViewConfig.h" +#import "SPVideoFrameDescription.h" + +@interface SuperPlayerControlView : UIView +@property (assign, nonatomic) BOOL compact; +/** + * 点播放试看时间范围 0.0 - 1.0 + * + * 用于试看场景,防止进度条拖动超过试看时长 + */ +@property (assign, nonatomic) float maxPlayableRatio; +/** + * 播放进度 + * @param currentTime 当前播放时长 + * @param totalTime 视频总时长 + * @param progress value(0.0~1.0) + * @param playable value(0.0~1.0) + */ +- (void)setProgressTime:(NSInteger)currentTime totalTime:(NSInteger)totalTime + progressValue:(CGFloat)progress playableValue:(CGFloat)playable; + +/** + * 播放状态 + * @param isPlay YES播放,NO暂停 + */ +- (void)setPlayState:(BOOL)isPlay; + +/** + * 重置播放控制面板 + * @param resolutionNames 清晰度名称 + * @param resolutionIndex 正在播放的清晰度的下标 + * @param isLive 是否为直播流,直播是有时移按钮,不支持镜像与播放速度修改 + * @param isTimeShifting 是否在直播时移 + * @param isPlaying 是否正在播放中,用于调整播放按钮状态 + */ +- (void)resetWithResolutionNames:(NSArray *)resolutionNames + currentResolutionIndex:(NSUInteger)resolutionIndex + isLive:(BOOL)isLive + isTimeShifting:(BOOL)isTimeShifting + isPlaying:(BOOL)isPlaying; + +/// 标题 +@property NSString *title; +/// 打点信息 +@property NSArray *pointArray; +/// 是否在拖动进度 +@property BOOL isDragging; +/// 是否显示二级菜单 +@property BOOL isShowSecondView; +/// 回调delegate +@property (nonatomic, weak) id delegate; +/// 播放配置 +@property SuperPlayerViewConfig *playerConfig; + +- (void)setOrientationPortraitConstraint; +- (void)setOrientationLandscapeConstraint; + +- (void)hideDanmu; +- (void)hideReplay; + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerControlView.m b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerControlView.m new file mode 100644 index 0000000..01ec8e4 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerControlView.m @@ -0,0 +1,60 @@ +#import "SuperPlayerControlView.h" + +@implementation SuperPlayerControlView +- (instancetype)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + _compact =YES; + } + return self; +} + +- (void)layoutSubviews +{ + [super layoutSubviews]; + +// UIInterfaceOrientation currentOrientation = [UIApplication sharedApplication].statusBarOrientation; + if (self.compact) { + [self setOrientationPortraitConstraint]; + } else { + [self setOrientationLandscapeConstraint]; + } + [self.delegate controlViewDidChangeScreen:self]; +} + +- (void)setOrientationPortraitConstraint +{ + +} + +- (void)setOrientationLandscapeConstraint +{ + +} + +- (void)resetWithResolutionNames:(NSArray *)resolutionNames + currentResolutionIndex:(NSUInteger)resolutionIndex + isLive:(BOOL)isLive + isTimeShifting:(BOOL)isTimeShifting + isPlaying:(BOOL)isAutoPlay +{ + +} + +- (void)setPlayState:(BOOL)isPlay { + +} + +- (void)setProgressTime:(NSInteger)currentTime + totalTime:(NSInteger)totalTime + progressValue:(CGFloat)progress + playableValue:(CGFloat)playable +{ + +} + +- (void)hideDanmu {} +- (void)hideReplay {} + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerControlViewDelegate.h b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerControlViewDelegate.h new file mode 100644 index 0000000..4842541 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerControlViewDelegate.h @@ -0,0 +1,37 @@ +#ifndef SuperPlayerControlViewDelegate_h +#define SuperPlayerControlViewDelegate_h + + +@class SuperPlayerUrl; +@class SuperPlayerControlView; + +@protocol SuperPlayerControlViewDelegate + +/** 返回按钮事件 */ +- (void)controlViewBack:(UIView *)controlView; +/** 播放 */ +- (void)controlViewPlay:(UIView *)controlView; +/** 暂停 */ +- (void)controlViewPause:(UIView *)controlView; +/** 播放器全屏 */ +- (void)controlViewChangeScreen:(UIView *)controlView withFullScreen:(BOOL)isFullScreen; +- (void)controlViewDidChangeScreen:(UIView *)controlView; +/** 锁定屏幕方向 */ +- (void)controlViewLockScreen:(UIView *)controlView withLock:(BOOL)islock; +/** 截屏事件 */ +- (void)controlViewSnapshot:(UIView *)controlView; +/** 切换分辨率按钮事件 */ +- (void)controlViewSwitch:(UIView *)controlView withDefinition:(NSString *)definition; +/** 修改配置 */ +- (void)controlViewConfigUpdate:(SuperPlayerControlView *)controlView withReload:(BOOL)reload; +/** 重新播放 */ +- (void)controlViewReload:(UIView *)controlView; +/** seek事件,pos 0~1 */ +- (void)controlViewSeek:(UIView *)controlView where:(CGFloat)pos; +/** 滑动预览,pos 0~1 */ +- (void)controlViewPreview:(UIView *)controlView where:(CGFloat)pos; + +@end + + +#endif /* SuperPlayerControlViewDelegate_h */ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerHelpers.h b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerHelpers.h new file mode 100644 index 0000000..3c8abf2 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerHelpers.h @@ -0,0 +1,31 @@ +// +// SuperPlayerHelpers.h +// Pods +// +// Created by Steven Choi on 2020/3/20. +// + +#ifndef SuperPlayerHelpers_h +#define SuperPlayerHelpers_h + +// player的单例 +#define SuperPlayerShared [SuperPlayerSharedView sharedInstance] +// 屏幕的宽 +#define ScreenWidth [[UIScreen mainScreen] bounds].size.width +// 屏幕的高 +#define ScreenHeight [[UIScreen mainScreen] bounds].size.height +// 颜色值RGB +#define RGBA(r,g,b,a) [UIColor colorWithRed:r/255.0f green:g/255.0f blue:b/255.0f alpha:a] +// 图片路径 +#define SuperPlayerImage(file) [UIImage imageNamed:[@"SuperPlayer.bundle" stringByAppendingPathComponent:file]] + +#define IsIPhoneX (ScreenHeight >= 812 || ScreenWidth >= 812) + +// 小窗单例 +#define SuperPlayerWindowShared [SuperPlayerWindow sharedInstance] + +#define TintColor RGBA(252, 89, 81, 1) + +#define LOG_ME NSLog(@"%s", __func__); + +#endif /* SuperPlayerHelpers_h */ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerModel.h b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerModel.h new file mode 100644 index 0000000..dce7dea --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerModel.h @@ -0,0 +1,109 @@ +#import +#import +#import "SuperPlayerUrl.h" + +/** + * SuperPlayerModel + * + * 超级播放器支持三种方式播放视频: + * 1. 视频 URL + * 填写视频 URL, 如需使用直播时移功能,还需填写appId + * 2. 腾讯云点播 File ID 播放 + * 填写 appId 及 videoId (如果使用旧版本V2, 请填写videoIdV2) + * 3. 多码率视频播放 + * 是URL播放方式扩展,可同时传入多条URL,用于进行码率切换 + * + */ +@class SuperPlayerVideoId; +@class SuperPlayerVideoIdV2; +@interface SuperPlayerModel : NSObject + +/// AppId 用于腾讯云点播 File ID 播放及腾讯云直播时移功能 +@property long appId; + +// ------------------------------------------------------------------ +// URL 播放方式 +// ------------------------------------------------------------------ + +/** + * 直接使用URL播放 + * + * 支持 RTMP、FLV、MP4、HLS 封装格式 + * 使用腾讯云直播时移功能则需要填写appId + */ +@property (nonatomic, strong) NSString *videoURL; + +/** + * 多码率视频 URL + * + * 用于拥有多个播放地址的多清晰度视频播放 + */ +@property (nonatomic, strong) NSArray *multiVideoURLs; + +/// 指定多码率情况下,默认播放的连接Index +@property (nonatomic, assign) NSUInteger defaultPlayIndex; + +// ------------------------------------------------------------------ +// FileId 播放方式 +// ------------------------------------------------------------------ + +/// 腾讯云点播 File ID 播放参数 +@property SuperPlayerVideoId *videoId; + +/// 用于兼容旧版本(V2)腾讯云点播 File ID 播放参数(即将废弃,不推荐使用) +@property SuperPlayerVideoIdV2 *videoIdV2; + +@end + + + +/// 腾讯云点播 File ID 播放参数 +@interface SuperPlayerVideoId : NSObject + +/// 云点播 File ID +@property NSString *fileId; + +/** + * 防盗链签名 + * (使用 fileId 播放时填写) + */ +@property NSString *psign; + +@end + + + +/** + * V2 版本播放协议播放参数(即将废弃,不推荐使用) + * 如果需要使用 timeout, exper, us, sign 进行播放。 + * 请使用这个对象初始化并传入包括fileId的各项参数,并传入SuperPlayerModel中的videoIdV2属性 + */ +@interface SuperPlayerVideoIdV2 : NSObject + +/** + * 云点播 File Id + */ +@property NSString *fileId; +/** + * 加密链接超时时间戳,转换为16进制小写字符串,腾讯云 CDN 服务器会根据该时间判断该链接是否有效。可选 + * (使用 fileId 播放时填写) + */ +@property NSString *timeout; +/** + * 试看时长,单位:秒。可选 + * (使用 fileId 播放时填写) + */ +@property int exper; +/** + * 唯一标识请求,增加链接唯一性 + * (使用 fileId 播放时填写) + */ +@property NSString *us; +/** + * 防盗链签名 + * (使用 fileId 播放时填写) + */ +@property NSString *sign; + +@end + diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerModel.m b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerModel.m new file mode 100644 index 0000000..55cd367 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerModel.m @@ -0,0 +1,184 @@ +#import "SuperPlayerModel.h" +#import "SuperPlayer.h" +#import "AFNetworking/AFNetworking.h" +#import "J2Obj.h" +#import "AdaptiveStream.h" +#import "SPPlayCGIParser.h" +#import "SuperPlayerModelInternal.h" + +const NSString *kPlayCGIHostname = @"playvideo.qcloud.com"; +NSString * const kErrorDomain = @"SuperPlayerCGI"; +const NSInteger kInvalidResponseErrorCode = -100; + +@implementation SuperPlayerVideoId +@end + +@implementation SuperPlayerModel { +} + +- (instancetype)init { + self = [super init]; + if (self) { + _sessionManager = [AFHTTPSessionManager manager]; + _defaultPlayIndex = NSUIntegerMax; + } + return self; +} + +- (void)dealloc { +// [_sessionManager invalidateSessionCancelingTasks:YES]; + [_sessionManager invalidateSessionCancelingTasks:YES resetSession:NO]; +} + +- (NSString *)playingDefinitionUrl +{ + NSString *url; + for (int i = 0; i < self.multiVideoURLs.count; i++) { + if ([self.multiVideoURLs[i].title isEqualToString:self.playingDefinition]) { + url = self.multiVideoURLs[i].url; + } + } + if (url == nil) + url = self.videoURL; + if (url == nil) { + if (self.multiVideoURLs.count > 0) + url = self.multiVideoURLs.firstObject.url; + } + return url; +} + +- (void)setDefaultPlayIndex:(NSUInteger)defaultPlayIndex { + _defaultPlayIndex = defaultPlayIndex; + if (defaultPlayIndex < _multiVideoURLs.count) { + self.playingDefinition = _multiVideoURLs[defaultPlayIndex].title; + } +} + +- (void)setMultiVideoURLs:(NSArray *)multiVideoURLs { + _multiVideoURLs = multiVideoURLs; + if (_defaultPlayIndex < multiVideoURLs.count) { + self.playingDefinition = _multiVideoURLs[_defaultPlayIndex].title; + } +} + +- (NSArray *)playDefinitions +{ + NSMutableArray *array = @[].mutableCopy; + for (int i = 0; i < self.multiVideoURLs.count; i++) { + [array addObject:self.multiVideoURLs[i].title ?: @""]; + } + return array; +} + +- (NSInteger)playingDefinitionIndex +{ + for (int i = 0; i < self.multiVideoURLs.count; i++) { + if ([self.multiVideoURLs[i].title isEqualToString:self.playingDefinition]) { + return i; + } + } + return 0; +} + +- (NSURLSessionTask *)requestWithCompletion: + (void(^)(NSError *, SuperPlayerModel *model))completion +{ + AFHTTPSessionManager *manager = self.sessionManager; + int ver = self.videoId ? 4 : 2; + NSString *url = [NSString stringWithFormat:@"https://%@/getplayinfo/v%d/%ld/%@", + kPlayCGIHostname, ver, self.appId, self.videoId ? self.videoId.fileId : self.videoIdV2.fileId]; + + // 防盗链参数 + NSDictionary *params = [self _buildParams]; + + __weak SuperPlayerModel *weakSelf = self; + + return [manager GET:url parameters:params headers:nil progress:nil + success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { + __strong SuperPlayerModel *self = weakSelf; +#if DEBUG + NSLog(@"%@", responseObject); +#endif + NSInteger code = [responseObject[@"code"] integerValue]; + if (code != 0) { + NSString *msg = responseObject[@"message"]; + NSString *requestID = responseObject[@"requestId"]; + NSString *warning = responseObject[@"warning"]; + NSError *error = [NSError errorWithDomain:kErrorDomain + code:code + userInfo:@{NSLocalizedDescriptionKey: msg, + @"requestID": requestID ?: @"", + @"warning": warning ?: @"" + }]; + if (completion) { + completion(error, self); + return; + } + return; + } + + NSInteger responseVersion = [responseObject[@"version"] integerValue]; + if (responseVersion == 0) { + responseVersion = 2; + } + Class parser = [SPPlayCGIParser parserOfVersion:responseVersion]; + SPPlayCGIParseResult *result = [parser parseResponse:responseObject]; + if (result == nil) { + if (completion) { + NSError *error = [NSError errorWithDomain:kErrorDomain + code:kInvalidResponseErrorCode + userInfo:@{NSLocalizedDescriptionKey:@"Invalid response."}]; + completion(error, self); + } + return; + } + self.drmType = result.drmType; + self.videoURL = result.url; + self.multiVideoURLs = result.multiVideoURLs; + self.keyFrameDescList = result.keyFrameDescList; + self.imageSprite = result.imageSprite; + + if (responseVersion == 4) { + self.drmToken = result.drmToken; + self.originalDuration = result.originalDuration; + } + if (completion) { + completion(nil, self); + } + + } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { + if (error.code != NSURLErrorCancelled) { + if (completion) { + completion(error, self); + } + } + }]; +} + +- (NSDictionary *)_buildParams { + NSMutableDictionary *params = [NSMutableDictionary new]; + if (self.videoId) { + if (self.videoId.psign) { + params[@"psign"] = self.videoId.psign; + } + } else if (self.videoIdV2) { + if (self.videoIdV2.timeout) { + params[@"t"] = self.videoIdV2.timeout; + } + if (self.videoIdV2.us) { + params[@"us"] = self.videoIdV2.us; + } + if (self.videoIdV2.sign) { + params[@"sign"] = self.videoIdV2.sign; + } + if (self.videoIdV2.exper >= 0) { + params[@"exper"] = @(self.videoIdV2.exper); + } + } + return params; +} + +@end + +@implementation SuperPlayerVideoIdV2 +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerModelInternal.h b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerModelInternal.h new file mode 100644 index 0000000..049517b --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerModelInternal.h @@ -0,0 +1,58 @@ +// +// SuperPlayerModelInternal.h +// SuperPlayer +// +// Created by Steven Choi on 2020/2/12. +// Copyright © 2020 annidy. All rights reserved. +// + +#import "SuperPlayerModel.h" +#import "AFNetworking/AFNetworking.h" +#import "SPVideoFrameDescription.h" +#import "SPPlayCGIParseResult.h" + +@class TXImageSprite; + +NS_ASSUME_NONNULL_BEGIN + +@interface SuperPlayerModel() +/// 播放配置, 为 nil 时为 "default" +@property (copy, nonatomic) NSString *pcfg; + +@property (strong, nonatomic) AFHTTPSessionManager *sessionManager; +// 以下为 PlayCGI V4 协议解析结果 + +/// 正在播放的清晰度 +@property (nonatomic) NSString *playingDefinition; + +/// 正在播放的清晰度URL +@property (readonly) NSString *playingDefinitionUrl; + +/// 正在播放的清晰度索引 +@property (readonly) NSInteger playingDefinitionIndex; + +/// 清晰度列表 +@property (readonly) NSArray *playDefinitions; + +/// 打点信息 +@property (strong, nonatomic) NSArray *keyFrameDescList; + +/// 视频雪碧图 +@property (strong, nonatomic) TXImageSprite *imageSprite; + +/// 视频原时长(用于试看时返回完整视频时长) +@property (assign, nonatomic) NSTimeInterval originalDuration; + +/// 加载播放信息 +- (NSURLSessionTask *)requestWithCompletion: + (void(^)(NSError *err, SuperPlayerModel *model))completion; + +/// DRM Token +@property (strong, nonatomic) NSString *drmToken; + +/// DRM Type +@property (nonatomic, assign) SPDrmType drmType; + +@end + +NS_ASSUME_NONNULL_END diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerView.h b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerView.h new file mode 100644 index 0000000..ea70d3c --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerView.h @@ -0,0 +1,140 @@ +#import +#import "SuperPlayer.h" +#import "SuperPlayerModel.h" +#import "SuperPlayerViewConfig.h" +#import "SPVideoFrameDescription.h" + +@class SuperPlayerControlView; +@class SuperPlayerView; +@class TXImageSprite; +@protocol SuperPlayerDelegate +@optional +/// 返回事件 +- (void)superPlayerBackAction:(SuperPlayerView *)player; +/// 全屏改变通知 +- (void)superPlayerFullScreenChanged:(SuperPlayerView *)player; +/// 播放开始通知 +- (void)superPlayerDidStart:(SuperPlayerView *)player; +/// 播放结束通知 +- (void)superPlayerDidEnd:(SuperPlayerView *)player; +/// 播放错误通知 +- (void)superPlayerError:(SuperPlayerView *)player errCode:(int)code errMessage:(NSString *)why; +/// 播放状态发生变化 +- (void)onPlayStateChange:(int) playState; +/// 播放进度发生变化 +- (void)onPlayProgressChange:(int) current duration:(int) duration; +// 需要通知到父view的事件在此添加 +@end + +/// 播放器的状态 +typedef NS_ENUM(NSInteger, SuperPlayerState) { + StateFailed = 5, // 播放失败 + StateBuffering = 3, // 缓冲中 + StatePlaying = 1, // 播放中 + StateStopped = 4, // 停止播放 + StatePause = 2, // 暂停播放 +}; + + +/// 播放器布局样式 +typedef NS_ENUM(NSInteger, SuperPlayerLayoutStyle) { + SuperPlayerLayoutStyleCompact, ///< 精简模式 + SuperPlayerLayoutStyleFullScreen ///< 全屏模式 +}; + +@interface SuperPlayerView : UIView + +/** 设置代理 */ +@property (nonatomic, weak) id delegate; + +@property (nonatomic, assign) SuperPlayerLayoutStyle layoutStyle; + +/// 设置播放器的父view。播放过程中调用可实现播放窗口转移 +@property (nonatomic, weak) UIView *fatherView; + +/// 播放器的状态 +@property (nonatomic, assign) SuperPlayerState state; +/// 是否全屏 +@property (nonatomic, assign, setter=setFullScreen:) BOOL isFullScreen; +/// 是否锁定旋转 +@property (nonatomic, assign) BOOL isLockScreen; +/// 是否是直播流 +@property (readonly) BOOL isLive; +/// 超级播放器控制层 +@property (nonatomic) SuperPlayerControlView *controlView; +/// 是否允许竖屏手势 +@property (nonatomic) BOOL disableGesture; +/// 是否在手势中 +@property (readonly) BOOL isDragging; +/// 是否加载成功 +@property (readonly) BOOL isLoaded; +/// 设置封面图片 +@property (nonatomic) UIImageView *coverImageView; +/// 遮罩层 +@property (nonatomic, strong) UIView *maskView; +/// 重播按钮 +@property (nonatomic, strong) UIButton *repeatBtn; +/// 全屏退出 +@property (nonatomic, strong) UIButton *repeatBackBtn; +/// 是否自动播放(在playWithModel前设置) +@property BOOL autoPlay; +/// 视频总时长 +@property (nonatomic) CGFloat playDuration; +/// 原始视频总时长,主要用于试看场景下显示总时长 +@property (nonatomic) NSTimeInterval originalDuration; +/// 视频当前播放时间 +@property (nonatomic) CGFloat playCurrentTime; +/// 起始播放时间,用于从上次位置开播 +@property CGFloat startTime; +/// 播放的视频Model +@property (readonly) SuperPlayerModel *playerModel; +/// 播放器配置 +@property SuperPlayerViewConfig *playerConfig; +/// 循环播放 +@property (nonatomic) BOOL loop; +/** + * 视频雪碧图 + */ +@property TXImageSprite *imageSprite; +/** + * 打点信息 + */ +@property NSArray *keyFrameDescList; +/** + * 播放model + */ +- (void)playWithModel:(SuperPlayerModel *)playerModel; + +/** + * 重置player + */ +- (void)resetPlayer; + +/** + * 播放 + */ +- (void)resume; + +/** + * 暂停 + * @warn isLoaded == NO 时暂停无效 + */ +- (void)pause; + +/** + * 从xx秒开始播放视频跳转 + * + * @param dragedSeconds 视频跳转的秒数 + */ +- (void)seekToTime:(NSInteger)dragedSeconds; + +/** + * 设置播放速率 + */ +- (void)setPlayRate:(CGFloat)playRate; + +- (void)uiHideDanmu; + +- (void)uiHideReplay; + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerView.m b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerView.m new file mode 100644 index 0000000..823598f --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerView.m @@ -0,0 +1,1889 @@ +#import "SuperPlayerView.h" +#import +#import "SuperPlayer.h" +#import "SuperPlayerControlViewDelegate.h" +#import "J2Obj.h" +#import "SuperPlayerView+Private.h" +#import "DataReport.h" +#import "TXCUrl.h" +#import "StrUtils.h" +#import "UIView+Fade.h" +#import "TXBitrateItemHelper.h" +#import "UIView+MMLayout.h" +#import "SPDefaultControlView.h" +#import "SuperPlayerModelInternal.h" +#import "NSString+URL.h" +// TODO: 处理头部引用 +#import "TXAudioCustomProcessDelegate.h" +#import "TXAudioRawDataDelegate.h" +#import "TXBitrateItem.h" +#import "TXImageSprite.h" +#import "TXLiteAVCode.h" +#import "TXLiveAudioSessionDelegate.h" +#import "TXLiveBase.h" +#import "TXLivePlayConfig.h" +#import "TXLivePlayListener.h" +#import "TXLivePlayer.h" +#import "TXLiveRecordListener.h" +#import "TXLiveRecordTypeDef.h" +#import "TXLiveSDKEventDef.h" +#import "TXLiveSDKTypeDef.h" +#import "TXPlayerAuthParams.h" +#ifdef ENABLE_UGC +#import "TXUGCBase.h" +#import "TXUGCPartsManager.h" +#import "TXUGCRecord.h" +#import "TXUGCRecordListener.h" +#import "TXUGCRecordTypeDef.h" +#endif +#import "TXVideoCustomProcessDelegate.h" +#import "TXVodPlayConfig.h" +#import "TXVodPlayListener.h" +#import "TXVodPlayer.h" + +static UISlider * _volumeSlider; + +#define CellPlayerFatherViewTag 200 +#define SUPPORT_PARAM_MAJOR_VERSION (8) +#define SUPPORT_PARAM_MINOR_VERSION (2) + +//忽略编译器的警告 +#pragma clang diagnostic push +#pragma clang diagnostic ignored"-Wdeprecated-declarations" + + + + +@implementation SuperPlayerView { + UIView *_fullScreenBlackView; + SuperPlayerControlView *_controlView; + NSURLSessionTask *_currentLoadingTask; +} + + +#pragma mark - life Cycle + +/** + * 代码初始化调用此方法 + */ +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { [self initializeThePlayer]; } + return self; +} + +/** + * storyboard、xib加载playerView会调用此方法 + */ +- (void)awakeFromNib { + [super awakeFromNib]; + [self initializeThePlayer]; +} + +/** + * 初始化player + */ +- (void)initializeThePlayer { + LOG_ME; + self.netWatcher = [[NetWatcher alloc] init]; + + CGRect frame = CGRectMake(0, -100, 10, 0); + self.volumeView = [[MPVolumeView alloc] initWithFrame:frame]; + [self.volumeView sizeToFit]; + for (UIWindow *window in [[UIApplication sharedApplication] windows]) { + if (!window.isHidden) { + [window addSubview:self.volumeView]; + break; + } + } + + _fullScreenBlackView = [UIView new]; + _fullScreenBlackView.backgroundColor = [UIColor blackColor]; + + // 单例slider + _volumeSlider = nil; + for (UIView *view in [self.volumeView subviews]){ + if ([view.class.description isEqualToString:@"MPVolumeSlider"]){ + _volumeSlider = (UISlider *)view; + break; + } + } + + _playerConfig = [[SuperPlayerViewConfig alloc] init]; + // 添加通知 + [self addNotifications]; + // 添加手势 + [self createGesture]; + + self.autoPlay = YES; +} + +- (void)dealloc { + LOG_ME; + // 移除通知 + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications]; + + [self reportPlay]; + [self.netWatcher stopWatch]; + [self.volumeView removeFromSuperview]; +} + +#pragma mark - 观察者、通知 + +/** + * 添加观察者、通知 + */ +- (void)addNotifications { + // app退到后台 + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterBackground:) name:UIApplicationWillResignActiveNotification object:nil]; + // app进入前台 + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterPlayground:) name:UIApplicationDidBecomeActiveNotification object:nil]; + + // 监测设备方向 + [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(onDeviceOrientationChange) + name:UIDeviceOrientationDidChangeNotification + object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(onStatusBarOrientationChange) + name:UIApplicationDidChangeStatusBarOrientationNotification + object:nil]; +} + +#pragma mark - layoutSubviews + +- (void)layoutSubviews { + [super layoutSubviews]; + if (self.subviews.count > 0) { + UIView *innerView = self.subviews[0]; + if ([innerView isKindOfClass:NSClassFromString(@"TXIJKSDLGLView")] || + [innerView isKindOfClass:NSClassFromString(@"TXCAVPlayerView")]) { + innerView.frame = self.bounds; + } + } +} + +#pragma mark - Public Method + +- (void)playWithModel:(SuperPlayerModel *)playerModel { + LOG_ME; + self.isShiftPlayback = NO; + self.imageSprite = nil; + self.originalDuration = 0; + [self reportPlay]; + self.reportTime = [NSDate date]; + [self _removeOldPlayer]; + [self _playWithModel:playerModel]; + self.coverImageView.alpha = 1; + self.maskView.hidden = YES; + self.repeatBtn.hidden = YES; + self.repeatBackBtn.hidden = YES; + // 播放时添加监听 + [self addNotifications]; +} + +- (void)reloadModel { + SuperPlayerModel *model = _playerModel; + if (model) { + [self resetPlayer]; + [self _playWithModel:_playerModel]; + [self addNotifications]; + } +} + +- (void)_playWithModel:(SuperPlayerModel *)playerModel { + [_currentLoadingTask cancel]; + _currentLoadingTask = nil; + + _playerModel = playerModel; + + [self pause]; + + NSString *videoURL = playerModel.playingDefinitionUrl; + if (videoURL != nil) { + [self configTXPlayer]; + } else if (playerModel.videoId || playerModel.videoIdV2) { + self.isLive = NO; + __weak __typeof(self) weakSelf = self; + _currentLoadingTask = [_playerModel requestWithCompletion: + ^(NSError *error,SuperPlayerModel *model) { + if (error) { + [weakSelf _onModelLoadFailed:model error:error]; + } else { + weakSelf.imageSprite = model.imageSprite; + weakSelf.keyFrameDescList = model.keyFrameDescList; + weakSelf.originalDuration = model.originalDuration; + [weakSelf _onModelLoadSucceed:model]; + } + }]; + return; + } else { + NSLog(@"无播放地址"); + return; + } +} + +- (void)_onModelLoadSucceed:(SuperPlayerModel *)model { + if (model == _playerModel) { + [self _playWithModel:_playerModel]; + } +} + +- (void)_onModelLoadFailed:(SuperPlayerModel *)model error:(NSError *)error { + if (model != _playerModel) { + return; + } + // error 错误信息 + [self showMiddleBtnMsg:kStrLoadFaildRetry withAction:ActionRetry]; + [self.spinner stopAnimating]; + NSLog(@"Load play model failed. fileID: %@, error: %@", + _playerModel.videoId.fileId, error); + if ([self.delegate respondsToSelector:@selector(superPlayerError:errCode:errMessage:)]) { + NSString *message = [NSString stringWithFormat:@"网络请求失败 %d %@", + (int)error.code, error.localizedDescription]; + [self.delegate superPlayerError:self + errCode:(int)error.code + errMessage:message]; + } + return; +} + +/** + * player添加到fatherView上 + */ +- (void)addPlayerToFatherView:(UIView *)view { + [self removeFromSuperview]; + if (view) { + [view addSubview:self]; + [self mas_remakeConstraints:^(MASConstraintMaker *make) { + make.edges.mas_offset(UIEdgeInsetsZero); + }]; + } +} + +- (void)setFatherView:(UIView *)fatherView { + if (fatherView != _fatherView) { + [self addPlayerToFatherView:fatherView]; + } + _fatherView = fatherView; +} + +/** + * 重置player + */ +- (void)resetPlayer { + LOG_ME; + // 移除通知 + [[NSNotificationCenter defaultCenter] removeObserver:self]; + // 暂停 + [self pause]; + + [self.vodPlayer stopPlay]; + [self.vodPlayer removeVideoWidget]; + self.vodPlayer = nil; + + [self.livePlayer stopPlay]; + [self.livePlayer removeVideoWidget]; + self.livePlayer = nil; + + [self reportPlay]; + + self.state = StateStopped; +} + +/** + * 播放 + */ +- (void)resume { + LOG_ME; + [self.controlView setPlayState:YES]; + self.isPauseByUser = NO; + self.state = StatePlaying; + if (self.isLive) { + [_livePlayer resume]; + } else { + [_vodPlayer resume]; + } +} + +/** + * 暂停 + */ +- (void)pause { + LOG_ME; + if (!self.isLoaded) + return; + [self.controlView setPlayState:NO]; + self.isPauseByUser = YES; + self.state = StatePause; + if (self.isLive) { + [_livePlayer pause]; + } else { + [_vodPlayer pause]; + } +} +#pragma mark - Control View Configuration +- (void)resetControlViewWithLive:(BOOL)isLive + shiftPlayback:(BOOL)isShiftPlayback + isPlaying:(BOOL)isPlaying +{ + [_controlView resetWithResolutionNames:self.playerModel.playDefinitions + currentResolutionIndex:self.playerModel.playingDefinitionIndex + isLive:isLive + isTimeShifting:isShiftPlayback + isPlaying:isPlaying]; +} + +#pragma mark - Private Method +- (BOOL)isSupportAppendParam { + NSString* version = [TXLiveBase getSDKVersionStr]; + NSArray* vers = [version componentsSeparatedByString:@"."]; + if (vers.count <= 1) { + return NO; + } + NSInteger majorVer = [vers[0] integerValue]?:0; + NSInteger minorVer = [vers[1] integerValue]?:0; + return majorVer >= SUPPORT_PARAM_MAJOR_VERSION && minorVer >= SUPPORT_PARAM_MINOR_VERSION; +} + +/** + * 设置Player相关参数 + */ +- (void)configTXPlayer { + LOG_ME; + self.backgroundColor = [UIColor blackColor]; + + if (_playerConfig.enableLog) { + [TXLiveBase setLogLevel:LOGLEVEL_DEBUG]; + [TXLiveBase sharedInstance].delegate = self; + [TXLiveBase setConsoleEnabled:YES]; + } + + [self.vodPlayer stopPlay]; + [self.vodPlayer removeVideoWidget]; + [self.livePlayer stopPlay]; + [self.livePlayer removeVideoWidget]; + + self.liveProgressTime = self.maxLiveProgressTime = 0; + + int liveType = [self livePlayerType]; + if (liveType >= 0) { + self.isLive = YES; + } else { + self.isLive = NO; + } + self.isLoaded = NO; + + self.netWatcher.playerModel = self.playerModel; + //时移播放需要原始分辨率播放流地址 +// if (self.playerModel.playingDefinition == nil) { +// self.playerModel.playingDefinition = self.netWatcher.adviseDefinition; +// } + NSString *videoURL = self.playerModel.playingDefinitionUrl; + // 时移 + [TXLiveBase setAppID:[NSString stringWithFormat:@"%ld", _playerModel.appId]]; + if (self.isLive) { + if (!self.livePlayer) { + self.livePlayer = [[TXLivePlayer alloc] init]; + self.livePlayer.delegate = self; + } + TXLivePlayConfig *config = [[TXLivePlayConfig alloc] init]; + config.bAutoAdjustCacheTime = NO; + config.maxAutoAdjustCacheTime = 5.0f; + config.minAutoAdjustCacheTime = 5.0f; + config.headers = self.playerConfig.headers; + [self.livePlayer setConfig:config]; + + + self.livePlayer.enableHWAcceleration = self.playerConfig.hwAcceleration; + [self.livePlayer startPlay:videoURL type:liveType]; + TXCUrl *curl = [[TXCUrl alloc] initWithString:videoURL]; + [self.livePlayer prepareLiveSeek:self.playerConfig.playShiftDomain bizId:curl.bizid]; + [self.livePlayer setMute:self.playerConfig.mute]; + [self.livePlayer setRenderMode:self.playerConfig.renderMode]; + } else { + if (!self.vodPlayer) { + self.vodPlayer = [[TXVodPlayer alloc] init]; + self.vodPlayer.vodDelegate = self; + } + + TXVodPlayConfig *config = [[TXVodPlayConfig alloc] init]; + config.smoothSwitchBitrate = YES; + if (self.playerConfig.maxCacheItem) { + // https://github.com/tencentyun/SuperPlayer_iOS/issues/64 + config.cacheFolderPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingString:@"/TXCache"]; + config.maxCacheItems = (int)self.playerConfig.maxCacheItem; + } + config.progressInterval = 0.02; + self.vodPlayer.token = self.playerModel.drmToken; +// if (_playerModel.videoId.version == FileIdV3) { +// if ([_playerModel.drmType isEqualToString:kDrmType_FairPlay]) { +// config.certificate = self.playerModel.certificate; +// self.vodPlayer.token = self.playerModel.token; +// NSLog(@"FairPlay播放"); +// } else if ([_playerModel.drmType isEqualToString:kDrmType_SimpleAES]) { +// self.vodPlayer.token = self.playerModel.token; +// NSLog(@"SimpleAES播放"); +// } else { +// // 降级播放 +// self.vodPlayer.token = nil; +// } +// } else if (_playerModel.token) { +// if (self.playerModel.certificate) { +// config.certificate = self.playerModel.certificate; +// } +// self.vodPlayer.token = self.playerModel.token; +// } else { +// self.vodPlayer.token = nil; +// } + + config.headers = self.playerConfig.headers; + + [self.vodPlayer setConfig:config]; + + self.vodPlayer.enableHWAcceleration = self.playerConfig.hwAcceleration; + [self.vodPlayer setStartTime:self.startTime]; self.startTime = 0; + + NSString *appParameter = [NSString stringWithFormat:@"spappid=%ld",self.playerModel.appId]; + NSString *fileidParameter = [NSString stringWithFormat:@"spfileid=%@",self.playerModel.videoId.fileId]; + NSString *drmtypeParameter = [NSString stringWithFormat:@"spdrmtype=%@", + self.playerModel.drmType == SPDrmTypeSimpleAES ? @"SimpleAES" : @"plain"]; + NSString *vodParamVideoUrl = [NSString appendParameter:appParameter WithOriginUrl:videoURL]; + vodParamVideoUrl = [NSString appendParameter:fileidParameter WithOriginUrl:vodParamVideoUrl]; + vodParamVideoUrl = [NSString appendParameter:drmtypeParameter WithOriginUrl:vodParamVideoUrl]; + + [self.vodPlayer startPlay:([self isSupportAppendParam] ? vodParamVideoUrl : videoURL)]; + [self.vodPlayer setBitrateIndex:self.playerModel.playingDefinitionIndex]; + + [self.vodPlayer setRate:self.playerConfig.playRate]; + [self.vodPlayer setMirror:self.playerConfig.mirror]; + [self.vodPlayer setMute:self.playerConfig.mute]; + [self.vodPlayer setRenderMode:self.playerConfig.renderMode]; + [self.vodPlayer setLoop:self.loop]; + } + [self.netWatcher startWatch]; + __weak SuperPlayerView *weakSelf = self; + [self.netWatcher setNotifyTipsBlock:^(NSString *msg) { + SuperPlayerView *strongSelf = weakSelf; + if (strongSelf) { + [strongSelf showMiddleBtnMsg:msg withAction:ActionSwitch]; + [strongSelf.middleBlackBtn fadeOut:2]; + } + }]; + self.state = StateBuffering; + self.isPauseByUser = NO; + [self resetControlViewWithLive:self.isLive + shiftPlayback:self.isShiftPlayback + isPlaying:self.autoPlay]; + self.controlView.playerConfig = self.playerConfig; + self.maskView.hidden = YES; + self.repeatBtn.hidden = YES; + self.repeatBackBtn.hidden = YES; + self.playDidEnd = NO; + [self.middleBlackBtn fadeOut:0.1]; +} + +/** + * 创建手势 + */ +- (void)createGesture { + // 单击 + self.singleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(singleTapAction:)]; + self.singleTap.delegate = self; + self.singleTap.numberOfTouchesRequired = 1; //手指数 + self.singleTap.numberOfTapsRequired = 1; + [self addGestureRecognizer:self.singleTap]; + + // 双击(播放/暂停) + self.doubleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(doubleTapAction:)]; + self.doubleTap.delegate = self; + self.doubleTap.numberOfTouchesRequired = 1; //手指数 + self.doubleTap.numberOfTapsRequired = 2; + [self addGestureRecognizer:self.doubleTap]; + + // 解决点击当前view时候响应其他控件事件 + [self.singleTap setDelaysTouchesBegan:YES]; + [self.doubleTap setDelaysTouchesBegan:YES]; + // 双击失败响应单击事件 + [self.singleTap requireGestureRecognizerToFail:self.doubleTap]; + + // 加载完成后,再添加平移手势 + // 添加平移手势,用来控制音量、亮度、快进快退 + UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panDirection:)]; + panRecognizer.delegate = self; + [panRecognizer setMaximumNumberOfTouches:1]; + [panRecognizer setDelaysTouchesBegan:YES]; + [panRecognizer setDelaysTouchesEnded:YES]; + [panRecognizer setCancelsTouchesInView:YES]; + [self addGestureRecognizer:panRecognizer]; +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { + +} + +#pragma mark - KVO + +/** + * 设置横屏的约束 + */ +- (void)setOrientationLandscapeConstraint:(UIInterfaceOrientation)orientation { + _isFullScreen = YES; +// [self _switchToLayoutStyle:orientation]; +} + +/** + * 设置竖屏的约束 + */ +- (void)setOrientationPortraitConstraint { + + [self addPlayerToFatherView:self.fatherView]; + _isFullScreen = NO; +// [self _switchToLayoutStyle:UIInterfaceOrientationPortrait]; +} + +- (UIDeviceOrientation)_orientationForFullScreen:(BOOL)fullScreen { + UIDeviceOrientation targetOrientation = [UIDevice currentDevice].orientation; + if (fullScreen) { + if (!UIDeviceOrientationIsLandscape(targetOrientation)) { + targetOrientation = UIDeviceOrientationLandscapeLeft; + } + } else { + if (!UIDeviceOrientationIsPortrait(targetOrientation)) { + targetOrientation = UIDeviceOrientationPortrait; + } + // targetOrientation = (UIDeviceOrientation)[UIApplication sharedApplication].statusBarOrientation; + } + return targetOrientation; +} + +- (void)_switchToFullScreen:(BOOL)fullScreen { + if (_isFullScreen == fullScreen) { + return; + } + _isFullScreen = fullScreen; + [self.fatherView.viewController setNeedsStatusBarAppearanceUpdate]; + + UIDeviceOrientation targetOrientation = [self _orientationForFullScreen:fullScreen];// [UIDevice currentDevice].orientation; + + if (fullScreen) { + [self removeFromSuperview]; + [[UIApplication sharedApplication].keyWindow addSubview:_fullScreenBlackView]; + [_fullScreenBlackView mas_makeConstraints:^(MASConstraintMaker *make) { + make.width.equalTo(@(ScreenHeight)); + make.height.equalTo(@(ScreenWidth)); + make.center.equalTo([UIApplication sharedApplication].keyWindow); + }]; + + [[UIApplication sharedApplication].keyWindow addSubview:self]; + [self mas_remakeConstraints:^(MASConstraintMaker *make) { + if (IsIPhoneX) { + make.width.equalTo(@(ScreenHeight - self.mm_safeAreaTopGap * 2)); + } else { + make.width.equalTo(@(ScreenHeight)); + } + make.height.equalTo(@(ScreenWidth)); + make.center.equalTo([UIApplication sharedApplication].keyWindow); + }]; + [self.superview setNeedsLayout]; + } else { + [_fullScreenBlackView removeFromSuperview]; + [self addPlayerToFatherView:self.fatherView]; + } +} + +- (void)_switchToLayoutStyle:(SuperPlayerLayoutStyle)style { + // 获取到当前状态条的方向 + +// UIInterfaceOrientation currentOrientation = [UIDevice currentDevice].orientation; + // 判断如果当前方向和要旋转的方向一致,那么不做任何操作 +// if (currentOrientation == orientation) { return; } + + // 根据要旋转的方向,使用Masonry重新修改限制 + if (style == SuperPlayerLayoutStyleFullScreen) {// + // 这个地方加判断是为了从全屏的一侧,直接到全屏的另一侧不用修改限制,否则会出错; + if (_layoutStyle != SuperPlayerLayoutStyleFullScreen) { //UIInterfaceOrientationIsPortrait(currentOrientation)) { + [self removeFromSuperview]; + [[UIApplication sharedApplication].keyWindow addSubview:_fullScreenBlackView]; + [_fullScreenBlackView mas_remakeConstraints:^(MASConstraintMaker *make) { + make.width.equalTo(@(ScreenHeight)); + make.height.equalTo(@(ScreenWidth)); + make.center.equalTo([UIApplication sharedApplication].keyWindow); + }]; + + [[UIApplication sharedApplication].keyWindow addSubview:self]; + [self mas_remakeConstraints:^(MASConstraintMaker *make) { + if (IsIPhoneX) { + make.width.equalTo(@(ScreenHeight - self.mm_safeAreaTopGap * 2)); + } else { + make.width.equalTo(@(ScreenHeight)); + } + make.height.equalTo(@(ScreenWidth)); + make.center.equalTo([UIApplication sharedApplication].keyWindow); + }]; + } + } else { + [_fullScreenBlackView removeFromSuperview]; + } + self.controlView.compact = style == SuperPlayerLayoutStyleCompact; + + [[UIApplication sharedApplication].keyWindow layoutIfNeeded]; + + + // iOS6.0之后,设置状态条的方法能使用的前提是shouldAutorotate为NO,也就是说这个视图控制器内,旋转要关掉; + // 也就是说在实现这个方法的时候-(BOOL)shouldAutorotate返回值要为NO + /* + [[UIApplication sharedApplication] setStatusBarOrientation:orientation animated:NO]; + [UIView beginAnimations:nil context:nil]; + [UIView setAnimationDuration:0.3]; + // 更改了状态条的方向,但是设备方向UIInterfaceOrientation还是正方向的,这就要设置给你播放视频的视图的方向设置旋转 + // 给你的播放视频的view视图设置旋转 + self.transform = CGAffineTransformIdentity; + self.transform = [self getTransformRotationAngleOfOrientation:[UIDevice currentDevice].orientation]; + + _fullScreenContainerView.transform = self.transform; + // 开始旋转 + [UIView commitAnimations]; + + [self.fatherView.mm_viewController setNeedsStatusBarAppearanceUpdate]; + _layoutStyle = style; + */ +} + +- (void)_adjustTransform:(UIDeviceOrientation)orientation { + + [UIView beginAnimations:nil context:nil]; + [UIView setAnimationDuration:0.3]; + + self.transform = [self getTransformRotationAngleOfOrientation:orientation]; + _fullScreenBlackView.transform = self.transform; + [UIView commitAnimations]; +} + +/** + * 获取变换的旋转角度 + * + * @return 变换矩阵 + */ +- (CGAffineTransform)getTransformRotationAngleOfOrientation:(UIDeviceOrientation)orientation { + // 状态条的方向已经设置过,所以这个就是你想要旋转的方向 + UIInterfaceOrientation interfaceOrientation = [UIApplication sharedApplication].statusBarOrientation; + if (interfaceOrientation == (UIInterfaceOrientation)orientation) { + return CGAffineTransformIdentity; + } + // 根据要进行旋转的方向来计算旋转的角度 + if (orientation == UIInterfaceOrientationPortrait) { + return CGAffineTransformIdentity; + } else if (orientation == UIInterfaceOrientationLandscapeLeft){ + return CGAffineTransformMakeRotation(-M_PI_2); + } else if(orientation == UIInterfaceOrientationLandscapeRight){ + return CGAffineTransformMakeRotation(M_PI_2); + } + return CGAffineTransformIdentity; +} + +#pragma mark 屏幕转屏相关 + +/** + * 屏幕转屏 + * + * @param orientation 屏幕方向 + */ +- (void)interfaceOrientation:(UIInterfaceOrientation)orientation { + if (orientation == UIInterfaceOrientationLandscapeRight || orientation == UIInterfaceOrientationLandscapeLeft) { + // 设置横屏 + [self setOrientationLandscapeConstraint:orientation]; + } else if (orientation == UIInterfaceOrientationPortrait) { + // 设置竖屏 + [self setOrientationPortraitConstraint]; + } +} + +- (SuperPlayerLayoutStyle)defaultStyleForDeviceOrientation:(UIDeviceOrientation)orientation { + if (UIDeviceOrientationIsPortrait(orientation)) { + return SuperPlayerLayoutStyleCompact; + } else { + return SuperPlayerLayoutStyleFullScreen; + } +} + +#pragma mark - Action + +/** + * 轻拍方法 + * + * @param gesture UITapGestureRecognizer + */ +- (void)singleTapAction:(UIGestureRecognizer *)gesture { + if (gesture.state == UIGestureRecognizerStateRecognized) { + + if (self.playDidEnd) { + return; + } + if (SuperPlayerWindowShared.isShowing) + return; + + if (self.controlView.hidden) { + [[self.controlView fadeShow] fadeOut:5]; + } else { + [self.controlView fadeOut:0.2]; + } + } +} + +/** + * 双击播放/暂停 + * + * @param gesture UITapGestureRecognizer + */ +- (void)doubleTapAction:(UIGestureRecognizer *)gesture { + if (self.playDidEnd) { return; } + // 显示控制层 + [self.controlView fadeShow]; + if (self.isPauseByUser) { + [self resume]; + } else { + [self pause]; + } +} + + + +/** 全屏 */ +- (void)setFullScreen:(BOOL)fullScreen { + + if (_isFullScreen != fullScreen) { + [self _adjustTransform:[self _orientationForFullScreen:fullScreen]]; + [self _switchToFullScreen:fullScreen]; + [self _switchToLayoutStyle:fullScreen ? SuperPlayerLayoutStyleFullScreen : SuperPlayerLayoutStyleCompact]; + } + _isFullScreen = fullScreen; + /* + self.controlView.compact = !_isFullScreen; + if (fullScreen) { + UIDeviceOrientation orientation = [UIDevice currentDevice].orientation; + if (orientation == UIDeviceOrientationLandscapeRight) { + [self interfaceOrientation:UIInterfaceOrientationLandscapeLeft]; + } else { + [self interfaceOrientation:UIInterfaceOrientationLandscapeRight]; + } + } else { + [self setOrientationPortraitConstraint]; + [self interfaceOrientation:UIInterfaceOrientationPortrait]; + } + */ +} + +/** + * 播放完了 + * + */ +- (void)moviePlayDidEnd { + self.state = StateStopped; + self.playDidEnd = YES; + // 播放结束隐藏 + if (SuperPlayerWindowShared.isShowing) { + [SuperPlayerWindowShared hide]; + [self resetPlayer]; + } + [self.controlView fadeOut:0.2]; + [self fastViewUnavaliable]; + [self.netWatcher stopWatch]; + self.maskView.hidden = NO; + self.repeatBtn.hidden = NO; + self.repeatBackBtn.hidden = NO; + if ([self.delegate respondsToSelector:@selector(superPlayerDidEnd:)]) { + [self.delegate superPlayerDidEnd:self]; + } +} + +#pragma mark - UIKit Notifications + +/** + * 应用退到后台 + */ +- (void)appDidEnterBackground:(NSNotification *)notify { + NSLog(@"appDidEnterBackground"); + self.didEnterBackground = YES; + if (self.isLive) { + return; + } + if (!self.isPauseByUser && (self.state != StateStopped && self.state != StateFailed)) { + [_vodPlayer pause]; + self.state = StatePause; + } +} + +/** + * 应用进入前台 + */ +- (void)appDidEnterPlayground:(NSNotification *)notify { + NSLog(@"appDidEnterPlayground"); + self.didEnterBackground = NO; + if (self.isLive) { + return; + } + if (!self.isPauseByUser && (self.state != StateStopped && self.state != StateFailed)) { + self.state = StatePlaying; + [_vodPlayer resume]; + } +} + +// 状态条变化通知(在前台播放才去处理) +- (void)onStatusBarOrientationChange { + [self onDeviceOrientationChange]; + return; + if (!self.didEnterBackground) { + UIInterfaceOrientation orientation = (UIInterfaceOrientation)[UIDevice currentDevice].orientation; + SuperPlayerLayoutStyle style = [self defaultStyleForDeviceOrientation:orientation]; +// [[UIApplication sharedApplication] setStatusBarOrientation:orientation animated:NO]; + if ([UIApplication sharedApplication].statusBarOrientation != orientation) { + [self _adjustTransform:(UIInterfaceOrientation)[UIDevice currentDevice].orientation]; + } + [self _switchToFullScreen:style == SuperPlayerLayoutStyleFullScreen]; + [self _switchToLayoutStyle:style]; + /* // 获取到当前状态条的方向 + UIInterfaceOrientation currentOrientation = [UIApplication sharedApplication].statusBarOrientation; + if (currentOrientation == UIInterfaceOrientationPortrait) { + [self setOrientationPortraitConstraint]; + } else { + [self _switchToLayoutStyle:style]; + + if (currentOrientation == UIInterfaceOrientationLandscapeRight) { + [self _switchToLayoutStyle:style]; + } else if (currentOrientation == UIDeviceOrientationLandscapeLeft){ + [self _switchToLayoutStyle:UIInterfaceOrientationLandscapeLeft]; + } + } + */ + } +} + +/** + * 屏幕方向发生变化会调用这里 + */ +- (void)onDeviceOrientationChange { + if (!self.isLoaded) { return; } + if (self.isLockScreen) { return; } + if (self.didEnterBackground) { return; }; + if (SuperPlayerWindowShared.isShowing) { return; } + UIDeviceOrientation orientation = [UIDevice currentDevice].orientation; + if (orientation == UIDeviceOrientationFaceUp || orientation == UIDeviceOrientationFaceDown) { + return; + } + SuperPlayerLayoutStyle style = [self defaultStyleForDeviceOrientation:[UIDevice currentDevice].orientation]; + + BOOL shouldFullScreen = UIDeviceOrientationIsLandscape(orientation); + [self _switchToFullScreen:shouldFullScreen]; + [self _adjustTransform:[self _orientationForFullScreen:shouldFullScreen]]; + [self _switchToLayoutStyle:style]; +} + +#pragma mark - +- (void)seekToTime:(NSInteger)dragedSeconds { + if (!self.isLoaded || self.state == StateStopped) { + return; + } + if (self.isLive) { + [DataReport report:@"timeshift" param:nil]; + int ret = [self.livePlayer seek:dragedSeconds]; + if (ret != 0) { + [self showMiddleBtnMsg:kStrTimeShiftFailed withAction:ActionNone]; + [self.middleBlackBtn fadeOut:2]; + [self resetControlViewWithLive:self.isLive + shiftPlayback:self.isShiftPlayback + isPlaying:YES]; + } else { + if (!self.isShiftPlayback) + self.isLoaded = NO; + self.isShiftPlayback = YES; + self.state = StateBuffering; + [self resetControlViewWithLive:YES + shiftPlayback:self.isShiftPlayback + isPlaying:YES]; //时移播放不能切码率 + } + } else { + [self.vodPlayer resume]; + [self.vodPlayer seek:dragedSeconds]; + [self.controlView setPlayState:YES]; + } +} + +- (void)setPlayRate:(CGFloat)playRate { + if (self.isLive) { + return; + } + self.playerConfig.playRate = playRate; + [self.vodPlayer setRate:playRate]; +} + +#pragma mark - UIPanGestureRecognizer手势方法 +- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer +{ + if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) { + if (((UITapGestureRecognizer*)gestureRecognizer).numberOfTapsRequired == 2) { + if (self.isLockScreen == YES) + return NO; + } + return YES; + } + + if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) { + if (!self.isLoaded) { return NO; } + if (self.isLockScreen) { return NO; } + if (SuperPlayerWindowShared.isShowing) { return NO; } + + if (self.disableGesture) { + if (!self.isFullScreen) { + return NO; + } + } + return YES; + } + + return NO; +} +/** + * pan手势事件 + * + * @param pan UIPanGestureRecognizer + */ +- (void)panDirection:(UIPanGestureRecognizer *)pan { + + //根据在view上Pan的位置,确定是调音量还是亮度 + CGPoint locationPoint = [pan locationInView:self]; + + // 我们要响应水平移动和垂直移动 + // 根据上次和本次移动的位置,算出一个速率的point + CGPoint veloctyPoint = [pan velocityInView:self]; + + if (self.state == StateStopped) + return; + + // 判断是垂直移动还是水平移动 + switch (pan.state) { + case UIGestureRecognizerStateBegan:{ // 开始移动 + // 使用绝对值来判断移动的方向 + CGFloat x = fabs(veloctyPoint.x); + CGFloat y = fabs(veloctyPoint.y); + if (x > y) { // 水平移动 + // 取消隐藏 + self.panDirection = PanDirectionHorizontalMoved; + self.sumTime = [self playCurrentTime]; + } + else if (x < y){ // 垂直移动 + self.panDirection = PanDirectionVerticalMoved; + // 开始滑动的时候,状态改为正在控制音量 + if (locationPoint.x > self.bounds.size.width / 2) { + self.isVolume = YES; + }else { // 状态改为显示亮度调节 + self.isVolume = NO; + } + } + self.isDragging = YES; + [self.controlView fadeOut:0.2]; + break; + } + case UIGestureRecognizerStateChanged:{ // 正在移动 + switch (self.panDirection) { + case PanDirectionHorizontalMoved:{ + [self horizontalMoved:veloctyPoint.x]; // 水平移动的方法只要x方向的值 + break; + } + case PanDirectionVerticalMoved:{ + [self verticalMoved:veloctyPoint.y]; // 垂直移动方法只要y方向的值 + break; + } + default: + break; + } + self.isDragging = YES; + break; + } + case UIGestureRecognizerStateEnded:{ // 移动停止 + // 移动结束也需要判断垂直或者平移 + // 比如水平移动结束时,要快进到指定位置,如果这里没有判断,当我们调节音量完之后,会出现屏幕跳动的bug + switch (self.panDirection) { + case PanDirectionHorizontalMoved:{ + self.isPauseByUser = NO; + [self seekToTime:self.sumTime]; + // 把sumTime滞空,不然会越加越多 + self.sumTime = 0; + break; + } + case PanDirectionVerticalMoved:{ + // 垂直移动结束后,把状态改为不再控制音量 + self.isVolume = NO; + break; + } + default: + break; + } + [self fastViewUnavaliable]; + self.isDragging = NO; + break; + } + case UIGestureRecognizerStateCancelled: { + self.sumTime = 0; + self.isVolume = NO; + [self fastViewUnavaliable]; + self.isDragging = NO; + } + default: + break; + } +} + +/** + * pan垂直移动的方法 + * + * @param value void + */ +- (void)verticalMoved:(CGFloat)value { + + self.isVolume ? ([[self class] volumeViewSlider].value -= value / 10000) : ([UIScreen mainScreen].brightness -= value / 10000); + + if (self.isVolume) { + [self fastViewImageAvaliable:SuperPlayerImage(@"sound_max") progress:[[self class] volumeViewSlider].value]; + } else { + [self fastViewImageAvaliable:SuperPlayerImage(@"light_max") progress:[UIScreen mainScreen].brightness]; + } +} + +/** + * pan水平移动的方法 + * + * @param value void + */ +- (void)horizontalMoved:(CGFloat)value { + // 每次滑动需要叠加时间 + CGFloat totalMovieDuration = [self playDuration]; + self.sumTime += value / 10000 * totalMovieDuration; + + if (self.sumTime > totalMovieDuration) { self.sumTime = totalMovieDuration;} + if (self.sumTime < 0) { self.sumTime = 0; } + + [self fastViewProgressAvaliable:self.sumTime]; +} + +- (void)volumeChanged:(NSNotification *)notification +{ + if (self.isDragging) + return; // 正在拖动,不响应音量事件 + + if (![[[notification userInfo] objectForKey:@"AVSystemController_AudioVolumeChangeReasonNotificationParameter"] isEqualToString:@"ExplicitVolumeChange"]) { + return; + } + float volume = [[[notification userInfo] objectForKey:@"AVSystemController_AudioVolumeNotificationParameter"] floatValue]; + [self fastViewImageAvaliable:SuperPlayerImage(@"sound_max") progress:volume]; + [self.fastView fadeOut:1]; +} + +- (SuperPlayerFastView *)fastView +{ + if (_fastView == nil) { + _fastView = [[SuperPlayerFastView alloc] init]; + [self addSubview:_fastView]; + [_fastView mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.mas_equalTo(UIEdgeInsetsZero); + }]; + } + return _fastView; +} + +- (void)fastViewImageAvaliable:(UIImage *)image progress:(CGFloat)draggedValue { + if (self.controlView.isShowSecondView) + return; + [self.fastView showImg:image withProgress:draggedValue]; + [self.fastView fadeShow]; +} + +- (void)fastViewProgressAvaliable:(NSInteger)draggedTime +{ + NSInteger totalTime = 0; + if (self.originalDuration > 0) { + totalTime = self.originalDuration; + } else { + totalTime = [self playDuration]; + } + NSString *currentTimeStr = [StrUtils timeFormat:draggedTime]; + NSString *totalTimeStr = [StrUtils timeFormat:totalTime]; + NSString *timeStr = [NSString stringWithFormat:@"%@ / %@", currentTimeStr, totalTimeStr]; + if (self.isLive) { + timeStr = [NSString stringWithFormat:@"%@", currentTimeStr]; + } + + UIImage *thumbnail; + if (self.isFullScreen) { + thumbnail = [self.imageSprite getThumbnail:draggedTime]; + } + if (thumbnail) { + self.fastView.videoRatio = self.videoRatio; + [self.fastView showThumbnail:thumbnail withText:timeStr]; + } else { + CGFloat sliderValue = 1; + if (totalTime > 0) { + sliderValue = (CGFloat)draggedTime/totalTime; + } + if (self.isLive && totalTime > MAX_SHIFT_TIME) { + CGFloat base = totalTime - MAX_SHIFT_TIME; + if (self.sumTime < base) + self.sumTime = base; + sliderValue = (self.sumTime - base) / MAX_SHIFT_TIME; + NSLog(@"%f",sliderValue); + } + [self.fastView showText:timeStr withText:sliderValue]; + } + [self.fastView fadeShow]; +} + +- (void)fastViewUnavaliable +{ + [self.fastView fadeOut:0.1]; +} + +#pragma mark - UIGestureRecognizerDelegate + +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { + + + if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) { + if (self.playDidEnd){ + return NO; + } + } + + if ([touch.view isKindOfClass:[UISlider class]] || [touch.view.superview isKindOfClass:[UISlider class]]) { + return NO; + } + + if (SuperPlayerWindowShared.isShowing) + return NO; + + return YES; +} + +#pragma mark - Setter + + +/** + * 设置播放的状态 + * + * @param state SuperPlayerState + */ +- (void)setState:(SuperPlayerState)state { + if (_state != state) { + [self.delegate onPlayStateChange:state]; + } + + _state = state; + // 控制菊花显示、隐藏 + if (state == StateBuffering) { + [self.spinner startAnimating]; + } else { + [self.spinner stopAnimating]; + } + if (state == StatePlaying) { + + [[NSNotificationCenter defaultCenter] removeObserver:self + name:@"AVSystemController_SystemVolumeDidChangeNotification" + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(volumeChanged:) + name:@"AVSystemController_SystemVolumeDidChangeNotification" + object:nil]; + + if (self.coverImageView.alpha == 1) { + [UIView animateWithDuration:0.2 animations:^{ + self.coverImageView.alpha = 0; + }]; + } + } else if (state == StateFailed) { + + } else if (state == StateStopped) { + + [[NSNotificationCenter defaultCenter] removeObserver:self + name:@"AVSystemController_SystemVolumeDidChangeNotification" + object:nil]; + + self.coverImageView.alpha = 1; + + } else if (state == StatePause) { + + } +} + +- (void)setControlView:(SuperPlayerControlView *)controlView { + if (_controlView == controlView) { + return; + } + [_controlView removeFromSuperview]; + + _controlView = controlView; + controlView.delegate = self; + [self addSubview:controlView]; + [controlView mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.mas_equalTo(UIEdgeInsetsZero); + }]; + [self resetControlViewWithLive:self.isLive + shiftPlayback:self.isShiftPlayback + isPlaying:self.autoPlay]; + [controlView setTitle:_controlView.title]; + [controlView setPointArray:_controlView.pointArray]; +} + +- (SuperPlayerControlView *)controlView +{ + if (_controlView == nil) { + self.controlView = [[SPDefaultControlView alloc] initWithFrame:CGRectZero]; + } + return _controlView; +} + +- (void)setDragging:(BOOL)dragging +{ + _isDragging = dragging; + if (dragging) { + [[NSNotificationCenter defaultCenter] + removeObserver:self name:@"AVSystemController_SystemVolumeDidChangeNotification" + object:nil]; + } else { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(volumeChanged:) + name:@"AVSystemController_SystemVolumeDidChangeNotification" + object:nil]; + }); + } +} + +- (void)setLoop:(BOOL)loop +{ + _loop = loop; + if (self.vodPlayer) { + self.vodPlayer.loop = loop; + } +} + +#pragma mark - Getter + +- (CGFloat)playDuration { + if (self.isLive) { + return self.maxLiveProgressTime; + } + return self.vodPlayer.duration; +} + +- (CGFloat)playCurrentTime { + if (self.isLive) { + if (self.isShiftPlayback) { + return self.liveProgressTime; + } + return self.maxLiveProgressTime; + } + + return _playCurrentTime; +} + ++ (UISlider *)volumeViewSlider { + return _volumeSlider; +} +#pragma mark - SuperPlayerControlViewDelegate + +- (void)controlViewPlay:(SuperPlayerControlView *)controlView +{ + [self resume]; + if (self.state == StatePause) { self.state = StatePlaying; } +} + +- (void)controlViewPause:(SuperPlayerControlView *)controlView +{ + [self pause]; + if (self.state == StatePlaying) { self.state = StatePause;} +} + +- (void)controlViewBack:(SuperPlayerControlView *)controlView { + [self controlViewBackAction:controlView]; +} + +- (void)controlViewBackAction:(id)sender { + if (self.isFullScreen) { + self.isFullScreen = NO; + return; + } + if ([self.delegate respondsToSelector:@selector(superPlayerBackAction:)]) { + [self.delegate superPlayerBackAction:self]; + } +} + +- (void)controlViewChangeScreen:(SuperPlayerControlView *)controlView withFullScreen:(BOOL)isFullScreen { + self.isFullScreen = isFullScreen; +} + +- (void)controlViewDidChangeScreen:(UIView *)controlView +{ + if ([self.delegate respondsToSelector:@selector(superPlayerFullScreenChanged:)]) { + [self.delegate superPlayerFullScreenChanged:self]; + } +} + +- (void)controlViewLockScreen:(SuperPlayerControlView *)controlView withLock:(BOOL)isLock { + self.isLockScreen = isLock; +} + +- (void)controlViewSwitch:(SuperPlayerControlView *)controlView withDefinition:(NSString *)definition { + if ([self.playerModel.playingDefinition isEqualToString:definition]) + return; + + self.playerModel.playingDefinition = definition; + NSString *url = self.playerModel.playingDefinitionUrl; + if (self.isLive) { + [self.livePlayer switchStream:url]; + [self showMiddleBtnMsg:[NSString stringWithFormat:@"正在切换到%@...", definition] withAction:ActionNone]; + } else { + if ([self.vodPlayer supportedBitrates].count > 1) { + [self.vodPlayer setBitrateIndex:self.playerModel.playingDefinitionIndex]; + } else { + CGFloat startTime = [self.vodPlayer currentPlaybackTime]; + [self.vodPlayer setStartTime:startTime]; + [self.vodPlayer startPlay:url]; + } + } +} + +- (void)controlViewConfigUpdate:(SuperPlayerView *)controlView withReload:(BOOL)reload { + if (self.isLive) { + [self.livePlayer setMute:self.playerConfig.mute]; + [self.livePlayer setRenderMode:self.playerConfig.renderMode]; + } else { + [self.vodPlayer setRate:self.playerConfig.playRate]; + [self.vodPlayer setMirror:self.playerConfig.mirror]; + [self.vodPlayer setMute:self.playerConfig.mute]; + [self.vodPlayer setRenderMode:self.playerConfig.renderMode]; + } + if (reload) { + if (!self.isLive) + self.startTime = [self.vodPlayer currentPlaybackTime]; + self.isShiftPlayback = NO; + [self configTXPlayer]; // 软硬解需要重启 + } +} + + +- (void)controlViewReload:(UIView *)controlView { + if (self.isLive) { + self.isShiftPlayback = NO; + self.isLoaded = NO; + [self.livePlayer resumeLive]; + [self resetControlViewWithLive:self.isLive + shiftPlayback:self.isShiftPlayback + isPlaying:YES]; + } else { + self.startTime = [self.vodPlayer currentPlaybackTime]; + [self configTXPlayer]; + } +} + +- (void)controlViewSnapshot:(SuperPlayerControlView *)controlView { + + void (^block)(UIImage *img) = ^(UIImage *img) { + [self.fastView showSnapshot:img]; + + if ([self.fastView.snapshotView gestureRecognizers].count == 0) { + UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(openPhotos)]; + singleTap.numberOfTapsRequired = 1; + [self.fastView.snapshotView setUserInteractionEnabled:YES]; + [self.fastView.snapshotView addGestureRecognizer:singleTap]; + } + [self.fastView fadeShow]; + [self.fastView fadeOut:2]; + UIImageWriteToSavedPhotosAlbum(img, nil, nil, nil); + }; + + if (_isLive) { + [_livePlayer snapshot:block]; + } else { + [_vodPlayer snapshot:block]; + } +} + +-(void)openPhotos { + [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"photos-redirect://"]]; +} + +- (CGFloat)sliderPosToTime:(CGFloat)pos +{ + // 视频总时间长度 + CGFloat totalTime = 0; + if (self.originalDuration > 0) { + totalTime = self.originalDuration; + } else { + totalTime = [self playDuration]; + } + + //计算出拖动的当前秒数 + CGFloat dragedSeconds = floorf(totalTime * pos); + if (self.isLive && totalTime > MAX_SHIFT_TIME) { + CGFloat base = totalTime - MAX_SHIFT_TIME; + dragedSeconds = floor(MAX_SHIFT_TIME * pos) + base; + } + return dragedSeconds; +} + +- (void)controlViewSeek:(SuperPlayerControlView *)controlView where:(CGFloat)pos { + CGFloat dragedSeconds = [self sliderPosToTime:pos]; + [self seekToTime:dragedSeconds]; + [self fastViewUnavaliable]; +} + +- (void)controlViewPreview:(SuperPlayerControlView *)controlView where:(CGFloat)pos { + CGFloat dragedSeconds = [self sliderPosToTime:pos]; + if ([self playDuration] > 0) { // 当总时长 > 0时候才能拖动slider + [self fastViewProgressAvaliable:dragedSeconds]; + } +} + +#pragma clang diagnostic pop +#pragma mark - 点播回调 + +- (void)_removeOldPlayer +{ + for (UIView *w in [self subviews]) { + if ([w isKindOfClass:NSClassFromString(@"TXCRenderView")]) + [w removeFromSuperview]; + if ([w isKindOfClass:NSClassFromString(@"TXIJKSDLGLView")]) + [w removeFromSuperview]; + if ([w isKindOfClass:NSClassFromString(@"TXCAVPlayerView")]) + [w removeFromSuperview]; + } +} +/* +- (NSString *)_getResolutionName:(long)minEdge { + for (NSInteger i = 0; i < self.resolutions.count; ++ i) { + SPResolutionDefination *resDef = self.resolutions[i]; + if (minEdge <= resDef.minEdge) { + return resDef.name; + } + } + return self.resolutions.lastObject.name; +} + +- (NSArray*)_getHLSDefinations:(NSArray *)supportedBitrates { + NSMutableArray *definations = [NSMutableArray arrayWithCapacity:3]; + [supportedBitrates enumerateObjectsUsingBlock:^(TXBitrateItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + SuperPlayerUrl *url = [[SuperPlayerUrl alloc] init]; + url.title = [self _getResolutionName:MIN(obj.width, obj.height)]; + [definations addObject:url]; + }]; + return definations; +} +*/ + +-(void) onPlayEvent:(TXVodPlayer *)player event:(int)EvtID withParam:(NSDictionary*)param +{ + dispatch_async(dispatch_get_main_queue(), ^{ + if (EvtID != PLAY_EVT_PLAY_PROGRESS) { + NSString *desc = [param description]; + NSLog(@"%@", [NSString stringWithCString:[desc cStringUsingEncoding:NSUTF8StringEncoding] encoding:NSNonLossyASCIIStringEncoding]); + } + + float duration = 0; + if (self.originalDuration > 0) { + duration = self.originalDuration; + } else { + duration = player.duration; + } + + if (EvtID == PLAY_EVT_PLAY_BEGIN || EvtID == PLAY_EVT_RCV_FIRST_I_FRAME) { + [self setNeedsLayout]; + [self layoutIfNeeded]; + self.isLoaded = YES; + [self _removeOldPlayer]; + [self.vodPlayer setupVideoWidget:self insertIndex:0]; + [self layoutSubviews]; // 防止横屏状态下添加view显示不全 + self.state = StatePlaying; + +// if (self.playerModel.playDefinitions.count == 0) { + [self updateBitrates:player.supportedBitrates]; +// } + for (SPVideoFrameDescription *p in self.keyFrameDescList) { + if (player.duration > 0) + p.where = p.time/duration; + } + self.controlView.pointArray = self.keyFrameDescList; + + // 不使用vodPlayer.autoPlay的原因是暂停的时候会黑屏,影响体验 + if (!self.autoPlay) { + self.autoPlay = YES; // 下次用户设置自动播放失效 + [self pause]; + } + + if (self.originalDuration > 0) { + // 当前是试看 + self.controlView.maxPlayableRatio = player.duration / self.originalDuration; + } + } + if (EvtID == PLAY_EVT_VOD_PLAY_PREPARED) { + // 防止暂停导致加载进度不消失 + if (self.isPauseByUser) + [self.spinner stopAnimating]; + + if ([self.delegate respondsToSelector:@selector(superPlayerDidStart:)]) { + [self.delegate superPlayerDidStart:self]; + } + } + if (EvtID == PLAY_EVT_PLAY_PROGRESS) { + if (self.state == StateStopped) + return; + if ((int) self.playCurrentTime != (int) player.currentPlaybackTime) { + [self.delegate onPlayProgressChange:(int) player.currentPlaybackTime + duration:(int) duration]; + } + + self.playCurrentTime = player.currentPlaybackTime; + CGFloat totalTime = duration; + CGFloat value = player.currentPlaybackTime / duration; + + [self.controlView setProgressTime:self.playCurrentTime + totalTime:totalTime + progressValue:value + playableValue:player.playableDuration / duration]; + } else if (EvtID == PLAY_EVT_PLAY_END) { + [self.controlView setProgressTime:[self playDuration] + totalTime:[self playDuration] + progressValue:player.duration/duration + playableValue:player.duration/duration]; + [self moviePlayDidEnd]; + } else if (EvtID == PLAY_ERR_NET_DISCONNECT || EvtID == PLAY_ERR_FILE_NOT_FOUND || EvtID == PLAY_ERR_HLS_KEY /*|| EvtID == PLAY_ERR_VOD_LOAD_LICENSE_FAIL*/) { + // DRM视频播放失败自动降级 +// if ([self.playerModel.drmType isEqualToString:kDrmType_FairPlay]) { +// if ([self.playerModel canSetDrmType:kDrmType_SimpleAES]) { +// self.playerModel.drmType = kDrmType_SimpleAES; +// NSLog(@"降级SimpleAES"); +// } else { +// NSLog(@"降级无加密"); +// self.playerModel.drmType = nil; +// } +// [self configTXPlayer]; +// return; +// } else if ([self.playerModel.drmType isEqualToString:kDrmType_SimpleAES]) { +// NSLog(@"降级无加密"); +// self.playerModel.drmType = nil; +// [self configTXPlayer]; +// return; +// } + + if (EvtID == PLAY_ERR_NET_DISCONNECT) { + [self showMiddleBtnMsg:kStrBadNetRetry withAction:ActionContinueReplay]; + } else { + [self showMiddleBtnMsg:kStrLoadFaildRetry withAction:ActionRetry]; + } + self.state = StateFailed; + [player stopPlay]; + if ([self.delegate respondsToSelector:@selector(superPlayerError:errCode:errMessage:)]) { + [self.delegate superPlayerError:self errCode:EvtID errMessage:param[EVT_MSG]]; + } + } else if (EvtID == PLAY_EVT_PLAY_LOADING){ + // 当缓冲是空的时候 + self.state = StateBuffering; + } else if (EvtID == PLAY_EVT_VOD_LOADING_END) { + [self.spinner stopAnimating]; + } else if (EvtID == PLAY_EVT_CHANGE_RESOLUTION) { + if (player.height != 0) { + self.videoRatio = (GLfloat)player.width / player.height; + } + } + }); +} + +// 更新当前播放的视频信息,包括清晰度、码率等 +- (void)updateBitrates:(NSArray *)bitrates; +{ + if (bitrates.count > 0) { + if (self.resolutions) { + if (_playerModel.multiVideoURLs == nil) { + NSMutableArray *urlDefs = [[NSMutableArray alloc] initWithCapacity:self.resolutions.count]; + for (SPSubStreamInfo *info in self.resolutions) { + SuperPlayerUrl *url = [[SuperPlayerUrl alloc] init]; + url.title = info.resolutionName; + [urlDefs addObject:url]; + } + _playerModel.playingDefinition = _playerModel.multiVideoURLs.firstObject.title; + } + } else { + NSArray *titles = [TXBitrateItemHelper sortWithBitrate:bitrates]; + _playerModel.multiVideoURLs = titles; + self.netWatcher.playerModel = _playerModel; + if (_playerModel.playingDefinition == nil) + _playerModel.playingDefinition = self.netWatcher.adviseDefinition; + } + [self resetControlViewWithLive:self.isLive + shiftPlayback:self.isShiftPlayback + isPlaying:self.autoPlay]; + [self.vodPlayer setBitrateIndex:_playerModel.playingDefinitionIndex]; + + } +} + + +#pragma mark - 直播回调 + +- (void)onPlayEvent:(int)EvtID withParam:(NSDictionary *)param { + NSDictionary* dict = param; + + dispatch_async(dispatch_get_main_queue(), ^{ + if (EvtID != PLAY_EVT_PLAY_PROGRESS) { + NSString *desc = [param description]; + NSLog(@"%@", [NSString stringWithCString:[desc cStringUsingEncoding:NSUTF8StringEncoding] encoding:NSNonLossyASCIIStringEncoding]); + } + + if (EvtID == PLAY_EVT_PLAY_BEGIN || EvtID == PLAY_EVT_RCV_FIRST_I_FRAME) { + if (!self.isLoaded) { + [self setNeedsLayout]; + [self layoutIfNeeded]; + self.isLoaded = YES; + [self _removeOldPlayer]; + [self.livePlayer setupVideoWidget:CGRectZero containView:self insertIndex:0]; + [self layoutSubviews]; // 防止横屏状态下添加view显示不全 + self.state = StatePlaying; + + if ([self.delegate respondsToSelector:@selector(superPlayerDidStart:)]) { + [self.delegate superPlayerDidStart:self]; + } + } + + if (self.state == StateBuffering) + self.state = StatePlaying; + [self.netWatcher loadingEndEvent]; + } else if (EvtID == PLAY_EVT_PLAY_END) { + [self moviePlayDidEnd]; + } else if (EvtID == PLAY_ERR_NET_DISCONNECT) { + if (self.isShiftPlayback) { + [self controlViewReload:self.controlView]; + [self showMiddleBtnMsg:kStrTimeShiftFailed withAction:ActionRetry]; + [self.middleBlackBtn fadeOut:2]; + } else { + [self showMiddleBtnMsg:kStrBadNetRetry withAction:ActionRetry]; + self.state = StateFailed; + } + if ([self.delegate respondsToSelector:@selector(superPlayerError:errCode:errMessage:)]) { + [self.delegate superPlayerError:self errCode:EvtID errMessage:param[EVT_MSG]]; + } + } else if (EvtID == PLAY_EVT_PLAY_LOADING){ + // 当缓冲是空的时候 + self.state = StateBuffering; + if (!self.isShiftPlayback) { + [self.netWatcher loadingEvent]; + } + } else if (EvtID == PLAY_EVT_STREAM_SWITCH_SUCC) { + [self showMiddleBtnMsg:[@"已切换为" stringByAppendingString:self.playerModel.playingDefinition] withAction:ActionNone]; + [self.middleBlackBtn fadeOut:1]; + } else if (EvtID == PLAY_ERR_STREAM_SWITCH_FAIL) { + [self showMiddleBtnMsg:kStrHDSwitchFailed withAction:ActionRetry]; + self.state = StateFailed; + } else if (EvtID == PLAY_EVT_PLAY_PROGRESS) { + if (self.state == StateStopped) + return; + NSInteger progress = [dict[EVT_PLAY_PROGRESS] intValue]; + self.liveProgressTime = progress; + self.maxLiveProgressTime = MAX(self.maxLiveProgressTime, self.liveProgressTime); + + if (self.isShiftPlayback) { + CGFloat sv = 0; + if (self.maxLiveProgressTime > MAX_SHIFT_TIME) { + CGFloat base = self.maxLiveProgressTime - MAX_SHIFT_TIME; + sv = (self.liveProgressTime - base) / MAX_SHIFT_TIME; + } else { + sv = self.liveProgressTime / (self.maxLiveProgressTime + 1); + } + [self.controlView setProgressTime:self.liveProgressTime totalTime:-1 progressValue:sv playableValue:0]; + } else { + [self.controlView setProgressTime:self.maxLiveProgressTime totalTime:-1 progressValue:1 playableValue:0]; + } + } + }); +} + +// 日志回调 +-(void) onLog:(NSString*)log LogLevel:(int)level WhichModule:(NSString*)module +{ + NSLog(@"%@:%@", module, log); +} + +- (int)livePlayerType { + int playType = -1; + NSString *videoURL = self.playerModel.playingDefinitionUrl; + NSURLComponents *components = [NSURLComponents componentsWithString:videoURL]; + NSString *scheme = [[components scheme] lowercaseString]; + if ([scheme isEqualToString:@"rtmp"]) { + playType = PLAY_TYPE_LIVE_RTMP; + } else if ([scheme hasPrefix:@"http"] + && [[components path].lowercaseString hasSuffix:@".flv"]) { + playType = PLAY_TYPE_LIVE_FLV; + } + return playType; +} + +- (void)reportPlay { + if (self.reportTime == nil) + return; + int usedtime = -[self.reportTime timeIntervalSinceNow]; + if (self.isLive) { + [DataReport report:@"superlive" param:@{@"usedtime":@(usedtime)}]; + } else { + [DataReport report:@"supervod" param:@{@"usedtime":@(usedtime), @"fileid":@(self.playerModel.videoId.fileId?1:0)}]; + } + if (self.imageSprite) { + [DataReport report:@"image_sprite" param:nil]; + } + self.reportTime = nil; +} + +#pragma mark - middle btn + +- (UIButton *)middleBlackBtn +{ + if (_middleBlackBtn == nil) { + _middleBlackBtn = [UIButton buttonWithType:UIButtonTypeSystem]; + [_middleBlackBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; + _middleBlackBtn.titleLabel.font = [UIFont systemFontOfSize:14.0]; + _middleBlackBtn.backgroundColor = RGBA(0, 0, 0, 0.7); + [_middleBlackBtn addTarget:self action:@selector(middleBlackBtnClick:) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:_middleBlackBtn]; + [_middleBlackBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.center.equalTo(self); + make.height.mas_equalTo(33); + }]; + } + return _middleBlackBtn; +} + +- (void)showMiddleBtnMsg:(NSString *)msg withAction:(ButtonAction)action { + [self.middleBlackBtn setTitle:msg forState:UIControlStateNormal]; + self.middleBlackBtn.titleLabel.text = msg; + self.middleBlackBtnAction = action; + CGFloat width = self.middleBlackBtn.titleLabel.attributedText.size.width; + + [self.middleBlackBtn mas_updateConstraints:^(MASConstraintMaker *make) { + make.width.equalTo(@(width+10)); + }]; + [self.middleBlackBtn fadeShow]; +} + +- (void)middleBlackBtnClick:(UIButton *)btn +{ + switch (self.middleBlackBtnAction) { + case ActionNone: + break; + case ActionContinueReplay: { + if (!self.isLive) { + self.startTime = self.playCurrentTime; + } + [self configTXPlayer]; + } + break; + case ActionRetry: + [self reloadModel]; + break; + case ActionSwitch: + [self controlViewSwitch:self.controlView withDefinition:self.netWatcher.adviseDefinition]; + [self resetControlViewWithLive:self.isLive + shiftPlayback:self.isShiftPlayback + isPlaying:YES]; + break; + case ActionIgnore: + return; + default: + break; + } + [btn fadeOut:0.2]; +} + +- (UIView *)maskView { + if (!_maskView) { + _maskView = [[UIView alloc] initWithFrame:CGRectZero]; + [_maskView setBackgroundColor:[[UIColor blackColor]colorWithAlphaComponent:0.3]]; + [self addSubview:_maskView]; + [_maskView mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.mas_equalTo(UIEdgeInsetsZero); + }]; + } + return _maskView; +} + +- (UIButton *)repeatBtn { + if (!_repeatBtn) { + _repeatBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + [_repeatBtn setImage:SuperPlayerImage(@"repeat_video") forState:UIControlStateNormal]; + [_repeatBtn addTarget:self action:@selector(repeatBtnClick:) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:_repeatBtn]; + [_repeatBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.center.equalTo(self); + }]; + } + return _repeatBtn; +} + +- (UIButton *)repeatBackBtn { + if (!_repeatBackBtn) { + _repeatBackBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + [_repeatBackBtn setImage:SuperPlayerImage(@"back_full") forState:UIControlStateNormal]; + [_repeatBackBtn addTarget:self action:@selector(controlViewBackAction:) forControlEvents:UIControlEventTouchUpInside]; + [self addSubview:_repeatBackBtn]; + [_repeatBackBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self).offset(15); + make.top.equalTo(self).offset(15); + make.width.mas_equalTo(@30); + }]; + } + return _repeatBackBtn; +} + +- (void)repeatBtnClick:(UIButton *)sender { + [self configTXPlayer]; +} + +- (MMMaterialDesignSpinner *)spinner { + if (!_spinner) { + _spinner = [[MMMaterialDesignSpinner alloc] init]; + _spinner.lineWidth = 1; + _spinner.duration = 1; + _spinner.hidden = YES; + _spinner.hidesWhenStopped = YES; + _spinner.tintColor = [[UIColor whiteColor] colorWithAlphaComponent:0.9]; + [self addSubview:_spinner]; + [_spinner mas_makeConstraints:^(MASConstraintMaker *make) { + make.center.equalTo(self); + make.width.with.height.mas_equalTo(45); + }]; + } + return _spinner; +} + +- (UIImageView *)coverImageView { + if (!_coverImageView) { + _coverImageView = [[UIImageView alloc] init]; + _coverImageView.userInteractionEnabled = YES; + _coverImageView.contentMode = UIViewContentModeScaleAspectFit; + _coverImageView.alpha = 0; + [self insertSubview:_coverImageView belowSubview:self.controlView]; + [_coverImageView mas_makeConstraints:^(MASConstraintMaker *make) { + make.edges.mas_equalTo(UIEdgeInsetsZero); + }]; + } + return _coverImageView; +} + +- (void)uiHideDanmu { + [_controlView hideDanmu]; +} + +- (void)uiHideReplay { + [_controlView hideReplay]; +} + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerViewConfig.h b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerViewConfig.h new file mode 100644 index 0000000..d022755 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerViewConfig.h @@ -0,0 +1,30 @@ +// +// SuperPlayerViewConfig.h +// SuperPlayer +// +// Created by annidyfeng on 2018/10/18. +// + +#import +#import + +@interface SuperPlayerViewConfig : NSObject +/// 是否镜像,默认NO +@property BOOL mirror; +/// 是否硬件加速,默认YES +@property BOOL hwAcceleration; +/// 播放速度,默认1.0 +@property CGFloat playRate; +/// 是否静音,默认NO +@property BOOL mute; +/// 填充模式,默认铺满。 参见 TXLiveSDKTypeDef.h +@property NSInteger renderMode; +/// http头,跟进情况自行设置 +@property NSDictionary *headers; +/// 播放器最大缓存个数 +@property (nonatomic) NSInteger maxCacheItem; +/// 时移域名,默认为playtimeshift.live.myqcloud.com +@property NSString *playShiftDomain; +/// log打印 +@property BOOL enableLog; +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerViewConfig.m b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerViewConfig.m new file mode 100644 index 0000000..6283c3a --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerViewConfig.m @@ -0,0 +1,35 @@ +// +// SuperPlayerViewConfig.m +// SuperPlayer +// +// Created by annidyfeng on 2018/10/18. +// + +#import "SuperPlayerViewConfig.h" +#import "SuperPlayer.h" +#import "TXLiveSDKTypeDef.h" + +@implementation SuperPlayerViewConfig + +- (instancetype)init { + self = [super init]; + self.hwAcceleration = 1; + self.playRate = 1; + self.renderMode = RENDER_MODE_FILL_EDGE; + self.maxCacheItem = 5; + self.playShiftDomain = @"playtimeshift.live.myqcloud.com"; + self.enableLog = YES; + return self; +} + +- (BOOL)hwAcceleration +{ +#if TARGET_OS_SIMULATOR + return NO; +#else + return _hwAcceleration; +#endif +} + + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerWindow.h b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerWindow.h new file mode 100644 index 0000000..cb4f1dc --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerWindow.h @@ -0,0 +1,36 @@ +// +// SuperPlayerWindow.h +// TXLiteAVDemo +// +// Created by annidyfeng on 2018/6/26. +// Copyright © 2018年 Tencent. All rights reserved. +// + +#import + +@class SuperPlayerView; + +typedef void(^SuperPlayerWindowEventHandler)(void); + +/// 播放器小窗Window +@interface SuperPlayerWindow : UIWindow + +/// 显示小窗 +- (void)show; +/// 隐藏小窗 +- (void)hide; +/// 单例 ++ (instancetype)sharedInstance; + +@property (nonatomic,copy) SuperPlayerWindowEventHandler backHandler; +@property (nonatomic,copy) SuperPlayerWindowEventHandler closeHandler; // 默认关闭 +/// 小窗播放器 +@property (nonatomic,weak) SuperPlayerView *superPlayer; +/// 小窗主view +@property (readonly) UIView *rootView; +/// 点击小窗返回的controller +@property UIViewController *backController; +/// 小窗是否显示 +@property (readonly) BOOL isShowing; // + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerWindow.m b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerWindow.m new file mode 100644 index 0000000..f2bd3de --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/SuperPlayerWindow.m @@ -0,0 +1,221 @@ +// +// SuperPlayerWindow.m +// TXLiteAVDemo +// +// Created by annidyfeng on 2018/6/26. +// Copyright © 2018年 Tencent. All rights reserved. +// + +#import "SuperPlayerWindow.h" +#import "SuperPlayer.h" +#import "SuperPlayerView+Private.h" +#import "UIView+MMLayout.h" +#import "DataReport.h" +#import "UIView+Fade.h" +#import "TXVodPlayListener.h" + +#define FLOAT_VIEW_WIDTH 200 +#define FLOAT_VIEW_HEIGHT 112 + +@interface SuperPlayerWindow() +@property (weak) UIView *origFatherView; +@property CGRect floatViewRect; +@end + +@implementation SuperPlayerWindow { + UIView *_rootView; + UIButton *_closeBtn; + UIButton *_backBtn; +} + ++ (instancetype)sharedInstance { + static SuperPlayerWindow *instance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[SuperPlayerWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + }); + return instance; +} + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + + self.windowLevel = UIWindowLevelStatusBar - 1; + self.rootViewController = [UIViewController new]; + self.rootViewController.view.backgroundColor = [UIColor clearColor]; + self.rootViewController.view.userInteractionEnabled = NO; + + _rootView = [[UIView alloc] initWithFrame:CGRectZero]; + _rootView.backgroundColor = [UIColor blackColor]; + + UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureRecognizer:)]; + [_rootView addGestureRecognizer:panGesture]; + + UIButton *closeBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + closeBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + [closeBtn setImage:SuperPlayerImage(@"close") forState:UIControlStateNormal]; + [closeBtn addTarget:self action:@selector(closeBtnClick:) forControlEvents:UIControlEventTouchUpInside]; + [_rootView addSubview:closeBtn]; + [closeBtn sizeToFit]; + _closeBtn = closeBtn; + + CGRect rect = CGRectMake(ScreenWidth-FLOAT_VIEW_WIDTH, ScreenHeight-FLOAT_VIEW_HEIGHT, FLOAT_VIEW_WIDTH, FLOAT_VIEW_HEIGHT); + + if (IsIPhoneX) { + rect.origin.y -= 44; + } + self.floatViewRect = rect; + + self.hidden = YES; + + return self; +} + + +- (void)show { + _rootView.frame = self.floatViewRect; + [self addSubview:_rootView]; + self.hidden = NO; + + self.origFatherView = self.superPlayer.fatherView; + if (self.origFatherView != _rootView) { + self.superPlayer.fatherView = _rootView; + } + + [self.superPlayer.controlView fadeOut:0.01]; + + [_rootView bringSubviewToFront:_backBtn]; + [_rootView bringSubviewToFront:_closeBtn]; + + _closeBtn.m_width(42).m_height(42).m_top(0).m_right(0); + + _isShowing = YES; + + [DataReport report:@"floatmode" param:nil]; +} + +- (void)hide { + self.floatViewRect = _rootView.frame; + + [_rootView removeFromSuperview]; + self.hidden = YES; + + self.superPlayer.fatherView = self.origFatherView; + + _isShowing = NO; +} + +- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event { + + if (CGRectContainsPoint(_rootView.bounds, + [_rootView convertPoint:point fromView:self])) { + return [super pointInside:point withEvent:event]; + } + + return NO; +} + +- (void)closeBtnClick:(id)sender +{ + if (self.closeHandler) { + self.closeHandler(); + } else { + [self hide]; + [_superPlayer resetPlayer]; + self.backController = nil; + } +} + +- (void)backBtnClick:(id)sender +{ + if (self.backHandler) { + self.backHandler(); + } else { + [self hide]; + [self.topNavigationController pushViewController:self.backController animated:YES]; + self.backController = nil; + } +} + +- (UINavigationController *)topNavigationController { + UIWindow *window = [[UIApplication sharedApplication].delegate window]; + UIViewController *topViewController = [window rootViewController]; + while (true) { + if (topViewController.presentedViewController) { + topViewController = topViewController.presentedViewController; + } else if ([topViewController isKindOfClass:[UINavigationController class]] && [(UINavigationController*)topViewController topViewController]) { + topViewController = [(UINavigationController *)topViewController topViewController]; + } else if ([topViewController isKindOfClass:[UITabBarController class]]) { + UITabBarController *tab = (UITabBarController *)topViewController; + topViewController = tab.selectedViewController; + } else { + break; + } + } + return topViewController.navigationController; +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { + [self backBtnClick:nil]; +} +#pragma mark - GestureRecognizer + +// 手势处理 +- (void)panGestureRecognizer:(UIPanGestureRecognizer *)panGesture { + if (UIGestureRecognizerStateBegan == panGesture.state) { + } + else if (UIGestureRecognizerStateChanged == panGesture.state) { + CGPoint translation = [panGesture translationInView:self]; + + CGPoint center = _rootView.center; + center.x += translation.x; + center.y += translation.y; + _rootView.center = center; + + UIEdgeInsets effectiveEdgeInsets = UIEdgeInsetsZero; // 边距可以自己调 + + CGFloat leftMinX = 0.0f + effectiveEdgeInsets.left; + CGFloat topMinY = 0.0f + effectiveEdgeInsets.top; + CGFloat rightMaxX = self.bounds.size.width - _rootView.bounds.size.width + effectiveEdgeInsets.right; + CGFloat bottomMaxY = self.bounds.size.height - _rootView.bounds.size.height + effectiveEdgeInsets.bottom; + + CGRect frame = _rootView.frame; + frame.origin.x = frame.origin.x > rightMaxX ? rightMaxX : frame.origin.x; + frame.origin.x = frame.origin.x < leftMinX ? leftMinX : frame.origin.x; + frame.origin.y = frame.origin.y > bottomMaxY ? bottomMaxY : frame.origin.y; + frame.origin.y = frame.origin.y < topMinY ? topMinY : frame.origin.y; + _rootView.frame = frame; + + // zero + [panGesture setTranslation:CGPointZero inView:self]; + } + else if (UIGestureRecognizerStateEnded == panGesture.state) { + + } +} + +/** + * 点播事件通知 + * + * @param player 点播对象 + * @param EvtID 参见TXLiveSDKTypeDef.h + * @param param 参见TXLiveSDKTypeDef.h + */ +-(void) onPlayEvent:(TXVodPlayer *)player event:(int)EvtID withParam:(NSDictionary*)param +{ + +} + +/** + * 网络状态通知 + * + * @param player 点播对象 + * @param param 参见TXLiveSDKTypeDef.h + */ +-(void) onNetStatus:(TXVodPlayer *)player withParam:(NSDictionary*)param +{ + +} + + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/DataReport.h b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/DataReport.h new file mode 100644 index 0000000..6dfc287 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/DataReport.h @@ -0,0 +1,15 @@ +// +// DataReport.h +// TXLiteAVDemo +// +// Created by annidyfeng on 2018/7/10. +// Copyright © 2018年 Tencent. All rights reserved. +// + +#import + +@interface DataReport : NSObject + ++ (void)report:(NSString *)action param:(NSDictionary *)param; + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/DataReport.m b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/DataReport.m new file mode 100644 index 0000000..c849f81 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/DataReport.m @@ -0,0 +1,127 @@ +// +// DataReport.m +// TXLiteAVDemo +// +// Created by annidyfeng on 2018/7/10. +// Copyright © 2018年 Tencent. All rights reserved. +// + +#import "DataReport.h" + +//错误码 +#define kError_InvalidParam -10001 +#define kError_ConvertJsonFailed -10002 +#define kError_HttpError -10003 + +//数据上报 +#define DEFAULT_ELK_HOST @"https://ilivelog.qcloud.com" +#define kHttpTimeout 30 +@implementation DataReport + ++ (void)report:(NSString *)action param:(NSDictionary *)param +{ + NSMutableDictionary *dict = @{}.mutableCopy; + if (param) { + [dict addEntriesFromDictionary:param]; + } + [dict setObject:@"superplayer" forKey:@"bussiness"]; + [dict setObject:@"ios" forKey:@"platform"]; + [dict setObject:@"log" forKey:@"type"]; + [dict setObject:action?:@"" forKey:@"action"]; + [dict setObject:[[NSBundle mainBundle] bundleIdentifier] forKey:@"appidentifier"]; + [dict setObject:[DataReport getPackageName] forKey:@"appname"]; + + [self report:dict handler:^(int resultCode, NSString *message) { + + }]; +} + ++ (NSString *)getPackageName { + static NSString *packname = nil; + if (packname) + return packname; + + NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary]; + packname = [infoDict objectForKey:@"CFBundleDisplayName"]; + if (packname == nil || [packname isEqual:@""]) { + packname = [infoDict objectForKey:@"CFBundleIdentifier"]; + } + return packname; +} + + ++ (void)report:(NSMutableDictionary *)param handler:(void (^)(int resultCode, NSString *message))handler; +{ + dispatch_async(dispatch_get_global_queue(0, 0), ^{ + NSData* data = [self dictionary2JsonData:param]; + if (data == nil) + { + dispatch_async(dispatch_get_main_queue(), ^{ + if (handler) handler(kError_ConvertJsonFailed, nil); + }); + return; + } + + NSMutableString *strUrl = [[NSMutableString alloc] initWithString:DEFAULT_ELK_HOST]; + + NSURL *URL = [NSURL URLWithString:strUrl]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL]; + + if (data) + { + [request setValue:[NSString stringWithFormat:@"%ld",(long)[data length]] forHTTPHeaderField:@"Content-Length"]; + [request setHTTPMethod:@"POST"]; + [request setValue:@"application/json; charset=UTF-8" forHTTPHeaderField:@"Content-Type"]; + [request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"]; + + [request setHTTPBody:data]; + } + + [request setTimeoutInterval:kHttpTimeout]; + + + NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + if (error != nil) + { + NSLog(@"internalSendRequest failed,NSURLSessionDataTask return error code:%ld, des:%@", (long)[error code], [error description]); + dispatch_async(dispatch_get_main_queue(), ^{ + if (handler) handler(kError_HttpError, nil); + }); + } + else + { + NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + dispatch_async(dispatch_get_main_queue(), ^{ + if ([responseString isEqualToString:@"ok"]) { + if (handler) handler(0, responseString); + }else{ + if (handler) handler(-1, responseString); + } + }); + } + }]; + + [task resume]; + }); +} + ++ (NSData *)dictionary2JsonData:(NSDictionary *)dict +{ + // 转成Json数据 + if ([NSJSONSerialization isValidJSONObject:dict]) + { + NSError *error = nil; + NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:0 error:&error]; + if(error) + { + NSLog(@"[%@] Post Json Error", [self class]); + } + return data; + } + else + { + NSLog(@"[%@] Post Json is not valid", [self class]); + } + return nil; +} +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/J2Obj.h b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/J2Obj.h new file mode 100644 index 0000000..715216f --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/J2Obj.h @@ -0,0 +1,19 @@ +// +// J2Obj.h +// TXLiteAVDemo +// +// Created by annidyfeng on 2018/6/25. +// Copyright © 2018年 Tencent. All rights reserved. +// + +#ifndef J2Obj_h +#define J2Obj_h + + +NSArray *J2Array(id value); + +NSString *J2Str(id value); + +NSNumber *J2Num(id value); + +#endif /* J2Obj_h */ diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/J2Obj.m b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/J2Obj.m new file mode 100644 index 0000000..166c965 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/J2Obj.m @@ -0,0 +1,61 @@ +// +// J2Obj.m +// TXLiteAVDemo +// +// Created by annidyfeng on 2018/6/25. +// Copyright © 2018年 Tencent. All rights reserved. +// + +#import +#import "J2Obj.h" + + +NSArray *J2Array(id value) { + id obj = value; + id ret; + if ([obj isKindOfClass:[NSArray class]]) + { + ret = obj; + } + else + { + ret = @[]; + } + return ret; +} + +NSString * J2Str(id value) { + id obj = value; + id ret; + if ([obj isKindOfClass:[NSString class]]) + { + ret = obj; + } + else if ([obj isKindOfClass:[NSNumber class]]) + { + ret = [obj stringValue]; + } + else + { + ret = @""; + } + return ret; +} + +NSNumber *J2Num(id value) { + id obj = value; + id ret; + if ([obj isKindOfClass:[NSString class]]) + { + ret = [NSNumber numberWithDouble:[obj doubleValue]]; + } + else if ([obj isKindOfClass:[NSNumber class]]) + { + ret = obj; + } + else + { + ret = @0; + } + return ret; +} diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/NetWatcher.h b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/NetWatcher.h new file mode 100644 index 0000000..2063545 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/NetWatcher.h @@ -0,0 +1,26 @@ +// +// NetWatcher.h +// TXLiteAVDemo +// +// Created by annidyfeng on 2018/7/31. +// Copyright © 2018年 Tencent. All rights reserved. +// + +#import +#import "SuperPlayerModel.h" + +@interface NetWatcher : NSObject + +@property (copy) void (^notifyTipsBlock)(NSString *); + +@property (nonatomic) SuperPlayerModel *playerModel; + +- (void)startWatch; +- (void)stopWatch; + +- (void)loadingEvent; +- (void)loadingEndEvent; + +@property NSString *adviseDefinition; + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/NetWatcher.m b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/NetWatcher.m new file mode 100644 index 0000000..53fe871 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/NetWatcher.m @@ -0,0 +1,169 @@ +// +// NetWatcher.m +// TXLiteAVDemo +// +// Created by annidyfeng on 2018/7/31. +// Copyright © 2018年 Tencent. All rights reserved. +// + +#import "NetWatcher.h" +#import "AFNetworking/AFNetworking.h" +#import "SuperPlayerModelInternal.h" + +@interface NetWatcher() +@property NSArray *definitions; +@end + +@implementation NetWatcher { + NSDate *_startTime; + int _loadingCount; + dispatch_source_t _timer1; + BOOL _onFire; +} + +- (void)setPlayerModel:(SuperPlayerModel *)playerModel +{ + _playerModel = playerModel; + + self.definitions = [self.playerModel.playDefinitions sortedArrayUsingComparator:^NSComparisonResult(NSString *obj1, NSString *obj2) { + return [NetWatcher weightOfDefinition:obj1] < [NetWatcher weightOfDefinition:obj2]; + }]; + + if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusReachableViaWWAN) { + self.adviseDefinition = self.definitions.lastObject; + } else { + self.adviseDefinition = self.definitions.firstObject; + } +} + +- (void)startWatch +{ + [self stopWatch]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkChanged:) name:AFNetworkingReachabilityDidChangeNotification object:nil]; + + if (self.definitions.count <= 1) { + return; + } + + _startTime = [NSDate date]; + _loadingCount = 0; + + NSLog(@"NetWatcher: startWatch"); +} + +- (void)stopWatch +{ + _startTime = nil; + if (_timer1) { + if (!_onFire) { + dispatch_resume(_timer1); + } + dispatch_source_cancel(_timer1); + _timer1 = nil; + } + _onFire = NO; + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)loadingEvent +{ + if (!_startTime) + return; + NSLog(@"NetWatcher: loadingEvent"); + if (_timer1 == nil) { + _timer1 = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); + dispatch_source_set_timer(_timer1, DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC, 0 * NSEC_PER_SEC); + __weak NetWatcher *weakSelf = self; + dispatch_source_set_event_handler(_timer1, ^{ + NetWatcher *strongSelf = weakSelf; + NSLog(@"NetWatcher: time out"); + if (strongSelf) { + strongSelf->_loadingCount++; + [strongSelf testNotify]; + } + }); + } + if (!_onFire) { + dispatch_resume(_timer1); + } + _onFire = YES; +} + +- (void)loadingEndEvent +{ + if (_onFire) { + dispatch_suspend(_timer1); + _onFire = NO; + _loadingCount++; + } + NSLog(@"NetWatcher: loadingEndEvent"); +} + +- (BOOL)testNotify +{ + if (-[_startTime timeIntervalSinceNow] > 30) { + [self stopWatch]; // 超过30秒不检测了 + return NO; + } + + // 暂定30秒缓冲次数超过2次,为网络不好 + if (_loadingCount >= 2) { + + NSUInteger i = [self.definitions indexOfObject:self.adviseDefinition]; + if (i < self.definitions.count-1) { + self.adviseDefinition = self.definitions[i+1]; + } + + if (self.notifyTipsBlock) { + self.notifyTipsBlock(@"检测到你的网络较差,建议切换清晰度"); + } + _loadingCount = 0; + [self stopWatch]; + return YES; + } + + return NO; +} + +- (void)networkChanged:(NSNotification *)noti +{ + if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusReachableViaWWAN) { + self.adviseDefinition = self.definitions.lastObject; + if (self.adviseDefinition && ![self.playerModel.playingDefinition isEqualToString:self.adviseDefinition]) { + self.notifyTipsBlock([@"当前网络为4G,建议切换到" stringByAppendingString:self.adviseDefinition]); + } + } +} + ++(int)weightOfDefinition:(NSString *)def +{ + if ([def isEqualToString:@"流畅"]) { + return 10; + } + if ([def isEqualToString:@"标清"]) { + return 15; + } + if ([def isEqualToString:@"高清"]) { + return 20; + } + if ([def isEqualToString:@"全高清"]) { + return 40; + } + if ([def isEqualToString:@"超清"]) { + return 50; + } + if ([def isEqualToString:@"原画"]) { + return 60; + } + if ([def isEqualToString:@"2K"]) { + return 70; + } + if ([def isEqualToString:@"4K"]) { + return 80; + } + return 10000; +} + + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/StrUtils.h b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/StrUtils.h new file mode 100644 index 0000000..ca94131 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/StrUtils.h @@ -0,0 +1,19 @@ +// +// StrUtils.h +// Pods +// +// Created by annidyfeng on 2018/9/28. +// + +#import + +@interface StrUtils : NSObject + ++ (NSString *)timeFormat:(NSInteger)totalTime; +@end + +extern NSString *kStrLoadFaildRetry; +extern NSString *kStrBadNetRetry; +extern NSString *kStrTimeShiftFailed; +extern NSString *kStrHDSwitchFailed; +extern NSString *kStrWeakNet; diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/StrUtils.m b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/StrUtils.m new file mode 100644 index 0000000..d08d82a --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/StrUtils.m @@ -0,0 +1,33 @@ +// +// StrUtils.m +// Pods +// +// Created by annidyfeng on 2018/9/28. +// + +#import "StrUtils.h" + +NSString *kStrLoadFaildRetry = @"加载失败,点击重试"; +NSString *kStrBadNetRetry = @"网络不给力,点击重试"; +NSString *kStrTimeShiftFailed = @"时移失败,返回直播"; +NSString *kStrHDSwitchFailed = @"清晰度切换失败"; +NSString *kStrWeakNet = @"检测到你的网络较差,建议切换清晰度"; + +@implementation StrUtils + ++ (NSString *)timeFormat:(NSInteger)totalTime { + if (totalTime < 0) { + return @""; + } + NSInteger durHour = totalTime / 3600; + NSInteger durMin = (totalTime / 60) % 60; + NSInteger durSec = totalTime % 60; + + if (durHour > 0) { + return [NSString stringWithFormat:@"%zd:%02zd:%02zd", durHour, durMin, durSec]; + } else { + return [NSString stringWithFormat:@"%02zd:%02zd", durMin, durSec]; + } +} + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/TXBitrateItemHelper.h b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/TXBitrateItemHelper.h new file mode 100644 index 0000000..9805d70 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/TXBitrateItemHelper.h @@ -0,0 +1,21 @@ +// +// TXBitrateItemHelper.h +// SuperPlayer +// +// Created by annidyfeng on 2018/9/28. +// + +#import +#import "SuperPlayerModel.h" +#import "SuperPlayer.h" + +@class TXBitrateItem; +@interface TXBitrateItemHelper : NSObject +@property NSInteger bitrate; +@property NSString *title; +@property int index; + ++ (NSArray *)sortWithBitrate:(NSArray *)bitrates; + + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/TXBitrateItemHelper.m b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/TXBitrateItemHelper.m new file mode 100644 index 0000000..99dcdcc --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/TXBitrateItemHelper.m @@ -0,0 +1,36 @@ +// +// TXBitrateItemHelper.m +// SuperPlayer +// +// Created by annidyfeng on 2018/9/28. +// + +#import "TXBitrateItemHelper.h" +#import "TXBitrateItem.h" + +@implementation TXBitrateItemHelper + ++ (NSArray *)sortWithBitrate:(NSArray *)bitrates { + NSMutableArray *origin = [NSMutableArray new]; + NSArray *titles = @[@"流畅",@"高清",@"超清",@"原画",@"2K",@"4K"]; + NSMutableArray *retArray = [[NSMutableArray alloc] initWithCapacity:bitrates.count]; + + for (int i = 0; i < bitrates.count; i++) { + TXBitrateItemHelper *h = [TXBitrateItemHelper new]; + h.bitrate = bitrates[i].bitrate; + h.index = i; + [origin addObject:h]; + [retArray addObject:[NSNull null]]; + } + + NSArray *sorted = [origin sortedArrayUsingDescriptors:@[[[NSSortDescriptor alloc] initWithKey:@"bitrate" ascending:YES]]]; + + [sorted enumerateObjectsUsingBlock:^(TXBitrateItemHelper *h, NSUInteger idx, BOOL *stop) { + SuperPlayerUrl *sub = [SuperPlayerUrl new]; + sub.title = titles[idx]; + retArray[h.index] = sub; + }]; + return retArray; +} + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/TXCUrl.h b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/TXCUrl.h new file mode 100644 index 0000000..c2913dd --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/TXCUrl.h @@ -0,0 +1,17 @@ +// +// TXCUrl.h +// SuperPlayer +// +// Created by annidyfeng on 2018/9/17. +// Copyright © 2018年 annidy. All rights reserved. +// + +#import + +@interface TXCUrl : NSObject + +- (instancetype)initWithString:(NSString *)url; + +- (NSInteger)bizid; + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/TXCUrl.m b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/TXCUrl.m new file mode 100644 index 0000000..8e54016 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/TXCUrl.m @@ -0,0 +1,46 @@ +// +// TXCUrl.m +// SuperPlayer +// +// Created by annidyfeng on 2018/9/17. +// Copyright © 2018年 annidy. All rights reserved. +// + +#import "TXCUrl.h" + +@implementation TXCUrl { + NSURL *_url; +} + +- (instancetype)initWithString:(NSString *)url +{ + self = [super init]; + + _url = [NSURL URLWithString:url]; + + return self; +} + +- (NSInteger)bizid +{ + NSString *bizId = nil; + for (NSString *param in [_url.query componentsSeparatedByString:@"&"]) { + NSArray *elts = [param componentsSeparatedByString:@"="]; + if([elts count] < 2) continue; + if ([[elts firstObject] isEqualToString:@"bizid"]) { + bizId = [elts lastObject]; + break; + } + } + if (bizId == nil) { + bizId = [[_url host] componentsSeparatedByString:@"."].firstObject; + } + + NSInteger bizIdNum = [bizId integerValue]; + if (bizIdNum <= 0) { + bizIdNum = -1; + } + return bizIdNum; +} + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/UIView+Fade.h b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/UIView+Fade.h new file mode 100644 index 0000000..f1240b1 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/UIView+Fade.h @@ -0,0 +1,15 @@ +// +// UIView+Fade.h +// SuperPlayer +// +// Created by annidyfeng on 2018/9/28. +// + +#import + +@interface UIView (Fade) + +- (UIView *)fadeShow; +- (void)fadeOut:(NSTimeInterval)delay; +- (void)cancelFadeOut; +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/UIView+Fade.m b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/UIView+Fade.m new file mode 100644 index 0000000..8ee4e8a --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Utils/UIView+Fade.m @@ -0,0 +1,53 @@ +// +// UIView+Fade.m +// SuperPlayer +// +// Created by annidyfeng on 2018/9/28. +// + +#import "UIView+Fade.h" +#import + + +@implementation UIView (Fade) + +- (NSNumber *)fadeSeeds{ + return objc_getAssociatedObject(self, @selector(fadeSeeds)); +} + +- (void)setFadeSeeds:(NSNumber *)seeds{ + objc_setAssociatedObject(self, @selector(fadeSeeds), seeds, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (void)cancelFadeOut +{ + [self setFadeSeeds:@(arc4random_uniform(1000))]; +} + +- (UIView *)fadeShow +{ + [self cancelFadeOut]; + [UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{ + self.hidden = NO; + } completion:^(BOOL finished) { + + }]; + return self; +} + +- (void)fadeOut:(NSTimeInterval)delay +{ + int seeds = [self.fadeSeeds intValue]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + if (seeds == [self.fadeSeeds intValue]) { + [UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ + self.hidden = YES; + } completion:^(BOOL finished) { + + }]; + [self cancelFadeOut]; + } + }); +} + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Views/MMMaterialDesignSpinner.h b/lib/my_flutter_superplayer/ios/SuperPlayer/Views/MMMaterialDesignSpinner.h new file mode 100644 index 0000000..6b92c16 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Views/MMMaterialDesignSpinner.h @@ -0,0 +1,58 @@ +// +// MMMaterialDesignSpinner.h +// Pods +// +// Created by Michael Maxwell on 12/28/14. +// +// + +#import + +//! Project version number for MMMaterialDesignSpinner. +FOUNDATION_EXPORT double MMMaterialDesignSpinnerVersionNumber; + +//! Project version string for MMMaterialDesignSpinner. +FOUNDATION_EXPORT const unsigned char MMMaterialDesignSpinnerVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + +/** + * A control similar to iOS' UIActivityIndicatorView modeled after Google's Material Design Activity spinner. + */ +@interface MMMaterialDesignSpinner : UIView + +/** Sets the line width of the spinner's circle. */ +@property (nonatomic) CGFloat lineWidth; + +/** Sets whether the view is hidden when not animating. */ +@property (nonatomic) BOOL hidesWhenStopped; + +/** Specifies the timing function to use for the control's animation. Defaults to kCAMediaTimingFunctionEaseInEaseOut */ +@property (nonatomic, strong) CAMediaTimingFunction *timingFunction; + +/** Property indicating whether the view is currently animating. */ +@property (nonatomic, readonly) BOOL isAnimating; + +/** Property indicating the duration of the animation, default is 1.5s. Should be set prior to -[startAnimating] */ +@property (nonatomic, readwrite) NSTimeInterval duration; + +/** + * Convenience function for starting & stopping animation with a boolean variable instead of explicit + * method calls. + * + * @param animate true to start animating, false to stop animating. + @note This method simply calls the startAnimating or stopAnimating methods based on the value of the animate parameter. + */ +- (void)setAnimating:(BOOL)animate; + +/** + * Starts animation of the spinner. + */ +- (void)startAnimating; + +/** + * Stops animation of the spinnner. + */ +- (void)stopAnimating; + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Views/MMMaterialDesignSpinner.m b/lib/my_flutter_superplayer/ios/SuperPlayer/Views/MMMaterialDesignSpinner.m new file mode 100644 index 0000000..a7262b0 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Views/MMMaterialDesignSpinner.m @@ -0,0 +1,201 @@ +// +// MMMaterialDesignSpinner.m +// Pods +// +// Created by Michael Maxwell on 12/28/14. +// +// + +#import "MMMaterialDesignSpinner.h" + +static NSString *kMMRingStrokeAnimationKey = @"mmmaterialdesignspinner.stroke"; +static NSString *kMMRingRotationAnimationKey = @"mmmaterialdesignspinner.rotation"; + +@interface MMMaterialDesignSpinner () +@property (nonatomic, readonly) CAShapeLayer *progressLayer; +@property (nonatomic, readwrite) BOOL isAnimating; +@end + +@implementation MMMaterialDesignSpinner + +@synthesize progressLayer=_progressLayer; + +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + [self initialize]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + if (self = [super initWithCoder:aDecoder]) { + [self initialize]; + } + return self; +} + +- (void)awakeFromNib +{ + [super awakeFromNib]; + [self initialize]; +} + +- (void)initialize { + self.duration = 1.5f; + _timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + + [self.layer addSublayer:self.progressLayer]; + + // See comment in resetAnimations on why this notification is used. + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resetAnimations) name:UIApplicationDidBecomeActiveNotification object:nil]; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + self.progressLayer.frame = CGRectMake(0, 0, CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds)); + [self updatePath]; +} + +- (void)tintColorDidChange { + [super tintColorDidChange]; + + self.progressLayer.strokeColor = self.tintColor.CGColor; +} + +- (void)resetAnimations { + // If the app goes to the background, returning it to the foreground causes the animation to stop (even though it's not explicitly stopped by our code). Resetting the animation seems to kick it back into gear. + if (self.isAnimating) { + [self stopAnimating]; + [self startAnimating]; + } +} + +- (void)setAnimating:(BOOL)animate { + (animate ? [self startAnimating] : [self stopAnimating]); +} + + + +- (void)startAnimating { + if (self.isAnimating) + return; + + CABasicAnimation *animation = [CABasicAnimation animation]; + animation.keyPath = @"transform.rotation"; + animation.duration = self.duration / 0.375f; + animation.fromValue = @(0.f); + animation.toValue = @(2 * M_PI); + animation.repeatCount = INFINITY; + animation.removedOnCompletion = NO; + [self.progressLayer addAnimation:animation forKey:kMMRingRotationAnimationKey]; + + CABasicAnimation *headAnimation = [CABasicAnimation animation]; + headAnimation.keyPath = @"strokeStart"; + headAnimation.duration = self.duration / 1.5f; + headAnimation.fromValue = @(0.f); + headAnimation.toValue = @(0.25f); + headAnimation.timingFunction = self.timingFunction; + + CABasicAnimation *tailAnimation = [CABasicAnimation animation]; + tailAnimation.keyPath = @"strokeEnd"; + tailAnimation.duration = self.duration / 1.5f; + tailAnimation.fromValue = @(0.f); + tailAnimation.toValue = @(1.f); + tailAnimation.timingFunction = self.timingFunction; + + + CABasicAnimation *endHeadAnimation = [CABasicAnimation animation]; + endHeadAnimation.keyPath = @"strokeStart"; + endHeadAnimation.beginTime = self.duration / 1.5f; + endHeadAnimation.duration = self.duration / 3.0f; + endHeadAnimation.fromValue = @(0.25f); + endHeadAnimation.toValue = @(1.f); + endHeadAnimation.timingFunction = self.timingFunction; + + CABasicAnimation *endTailAnimation = [CABasicAnimation animation]; + endTailAnimation.keyPath = @"strokeEnd"; + endTailAnimation.beginTime = self.duration / 1.5f; + endTailAnimation.duration = self.duration / 3.0f; + endTailAnimation.fromValue = @(1.f); + endTailAnimation.toValue = @(1.f); + endTailAnimation.timingFunction = self.timingFunction; + + CAAnimationGroup *animations = [CAAnimationGroup animation]; + [animations setDuration:self.duration]; + [animations setAnimations:@[headAnimation, tailAnimation, endHeadAnimation, endTailAnimation]]; + animations.repeatCount = INFINITY; + animations.removedOnCompletion = NO; + [self.progressLayer addAnimation:animations forKey:kMMRingStrokeAnimationKey]; + + + self.isAnimating = true; + + if (self.hidesWhenStopped) { + self.hidden = NO; + } +} + +- (void)stopAnimating { + if (!self.isAnimating) + return; + + [self.progressLayer removeAnimationForKey:kMMRingRotationAnimationKey]; + [self.progressLayer removeAnimationForKey:kMMRingStrokeAnimationKey]; + self.isAnimating = false; + + if (self.hidesWhenStopped) { + self.hidden = YES; + } +} + +#pragma mark - Private + +- (void)updatePath { + CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); + CGFloat radius = MIN(CGRectGetWidth(self.bounds) / 2, CGRectGetHeight(self.bounds) / 2) - self.progressLayer.lineWidth / 2; + CGFloat startAngle = (CGFloat)(0); + CGFloat endAngle = (CGFloat)(2*M_PI); + UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES]; + self.progressLayer.path = path.CGPath; + + self.progressLayer.strokeStart = 0.f; + self.progressLayer.strokeEnd = 0.f; +} + +#pragma mark - Properties + +- (CAShapeLayer *)progressLayer { + if (!_progressLayer) { + _progressLayer = [CAShapeLayer layer]; + _progressLayer.strokeColor = self.tintColor.CGColor; + _progressLayer.fillColor = nil; + _progressLayer.lineWidth = 1.5f; + } + return _progressLayer; +} + +- (BOOL)isAnimating { + return _isAnimating; +} + +- (CGFloat)lineWidth { + return self.progressLayer.lineWidth; +} + +- (void)setLineWidth:(CGFloat)lineWidth { + self.progressLayer.lineWidth = lineWidth; + [self updatePath]; +} + +- (void)setHidesWhenStopped:(BOOL)hidesWhenStopped { + _hidesWhenStopped = hidesWhenStopped; + self.hidden = !self.isAnimating && hidesWhenStopped; +} + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Views/PlayerSlider.h b/lib/my_flutter_superplayer/ios/SuperPlayer/Views/PlayerSlider.h new file mode 100644 index 0000000..54a5869 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Views/PlayerSlider.h @@ -0,0 +1,31 @@ +// +// PlayerSlider.h +// Slider +// +// Created by annidyfeng on 2018/8/27. +// Copyright © 2018年 annidy. All rights reserved. +// + +#import + +@interface PlayerPoint : NSObject +@property GLfloat where; +@property UIControl *holder; +@property NSString *content; +@property NSInteger timeOffset; +@end + +@protocol PlayerSliderDelegate +- (void)onPlayerPointSelected:(PlayerPoint *)point; +@end + +@interface PlayerSlider : UISlider + +@property NSMutableArray *pointArray; +@property UIProgressView *progressView; +@property (weak) id delegate; +@property (nonatomic) BOOL hiddenPoints; + +- (PlayerPoint *)addPoint:(GLfloat)where; + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Views/PlayerSlider.m b/lib/my_flutter_superplayer/ios/SuperPlayer/Views/PlayerSlider.m new file mode 100644 index 0000000..0f2c5a0 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Views/PlayerSlider.m @@ -0,0 +1,143 @@ +// +// PlayerSlider.m +// Slider +// +// Created by annidyfeng on 2018/8/27. +// Copyright © 2018年 annidy. All rights reserved. +// + +#import "PlayerSlider.h" +#import +#import "UIView+MMLayout.h" + +@implementation PlayerPoint +- (instancetype)init { + self = [super init]; + + self.holder = [[UIControl alloc] initWithFrame:CGRectMake(0, 0, 30, 30)]; + UIView *inter = [[UIView alloc] initWithFrame:CGRectMake(14, 14, 2, 2)]; + inter.backgroundColor = [UIColor whiteColor]; + [self.holder addSubview:inter]; + self.holder.userInteractionEnabled = YES; + + return self; +} +@end + +@interface PlayerSlider() +@property UIImageView *tracker; +@end + +@implementation PlayerSlider + +/* + // Only override drawRect: if you perform custom drawing. + // An empty implementation adversely affects performance during animation. + - (void)drawRect:(CGRect)rect { + // Drawing code + } + */ + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + [self initUI]; + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super initWithCoder:aDecoder]; + [self initUI]; + return self; +} + +- (void)initUI { + _progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault]; + _progressView.progressTintColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.3]; + _progressView.trackTintColor = [UIColor clearColor]; + + [self addSubview:_progressView]; + + self.pointArray = [NSMutableArray new]; + + self.maximumValue = 1; + self.maximumTrackTintColor = [UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:0.5]; + + [_progressView mas_makeConstraints:^(MASConstraintMaker *make) { + make.left.equalTo(self); + make.right.equalTo(self); + make.centerY.equalTo(self).mas_offset(0.5); + make.height.mas_equalTo(2); + }]; + _progressView.layer.masksToBounds = YES; + _progressView.layer.cornerRadius = 1; + self.progressView.backgroundColor = [UIColor.blackColor colorWithAlphaComponent:0.8]; + [self sendSubviewToBack:self.progressView]; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + self.tracker = self.subviews.lastObject; + for (PlayerPoint *point in self.pointArray) { + point.holder.center = [self holderCenter:point.where]; + if (@available(iOS 14.0, *)) { + [self addSubview:point.holder]; + } else { + // Fallback on earlier versions + [self insertSubview:point.holder belowSubview:self.tracker]; + } + + } +} + +- (PlayerPoint *)addPoint:(GLfloat)where +{ + for (PlayerPoint *pp in self.pointArray) { + if (fabsf(pp.where - where) < 0.0001) + return pp; + } + PlayerPoint *point = [PlayerPoint new]; + point.where = where; + point.holder.center = [self holderCenter:where]; + point.holder.hidden = _hiddenPoints; + [self.pointArray addObject:point]; + [point.holder addTarget:self action:@selector(onClickHolder:) forControlEvents:UIControlEventTouchUpInside]; + [self setNeedsLayout]; + return point; +} + +- (CGPoint)holderCenter:(GLfloat)where { + return CGPointMake(self.frame.size.width * where, self.progressView.mm_centerY); +} + +- (void)onClickHolder:(UIControl *)sender { + NSLog(@"clokc"); + for (PlayerPoint *point in self.pointArray) { + if (point.holder == sender) { + if ([self.delegate respondsToSelector:@selector(onPlayerPointSelected:)]) + [self.delegate onPlayerPointSelected:point]; + } + } +} + +- (void)setHiddenPoints:(BOOL)hiddenPoints +{ + for (PlayerPoint *point in self.pointArray) { + point.holder.hidden = hiddenPoints; + } + _hiddenPoints = hiddenPoints; +} + +//- (CGRect)thumbRectForBounds:(CGRect)bounds trackRect:(CGRect)rect value:(float)value { +// +// rect.origin.x = rect.origin.x - 10 ; +// +// rect.size.width = rect.size.width +20; +// +// return CGRectInset ([super thumbRectForBounds:bounds +// trackRect:rect +// value:value], +// 10 , +// 10); +// +//} +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Views/SuperPlayerFastView.h b/lib/my_flutter_superplayer/ios/SuperPlayer/Views/SuperPlayerFastView.h new file mode 100644 index 0000000..335bf44 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Views/SuperPlayerFastView.h @@ -0,0 +1,43 @@ +// +// SuperPlayerFastView.h +// TXLiteAVDemo +// +// Created by annidyfeng on 2018/8/24. +// Copyright © 2018年 Tencent. All rights reserved. +// + +#import + +typedef enum : NSUInteger { + ImgWithProgress, // 图片+进度,比如声音滑动 + TextWithProgress, // 文字+进度,比如进度滑动 + ImgWithText, // 图片+文字,比如缩略图滑动 + SnapshotImg, +} FastViewStyle; + +@interface SuperPlayerFastView : UIView +/** 快进快退进度progress*/ +@property (nonatomic, strong) UIProgressView *progressView; +/** 快进快退时间*/ +@property (nonatomic, strong) UILabel *textLabel; +/** 快进快退亮度图片*/ +@property (nonatomic, strong) UIImageView *imgView; + +@property (nonatomic, strong) UIImageView *thumbView; + +@property (nonatomic, strong) UIImageView *snapshotView; + +@property CGFloat videoRatio; + + +@property (nonatomic) FastViewStyle style; + +/// 亮度等 +- (void)showImg:(UIImage *)img withProgress:(GLfloat)progress; +/// 快进 +- (void)showThumbnail:(UIImage *)img withText:(NSString *)text; +- (void)showText:(NSString *)text withText:(GLfloat)progress; +/// 截图 +- (void)showSnapshot:(UIImage *)img; + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Views/SuperPlayerFastView.m b/lib/my_flutter_superplayer/ios/SuperPlayer/Views/SuperPlayerFastView.m new file mode 100644 index 0000000..b9a1505 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Views/SuperPlayerFastView.m @@ -0,0 +1,190 @@ +// +// SuperPlayerFastView.m +// TXLiteAVDemo +// +// Created by annidyfeng on 2018/8/24. +// Copyright © 2018年 Tencent. All rights reserved. +// + +#import "SuperPlayerFastView.h" +#import "SuperPlayer.h" +#import "SuperPlayerView+Private.h" +#import "UIView+MMLayout.h" + +#define THUMB_VIEW_WIDTH 142 +#define THUMB_VIEW_HEIGHT (142/(16/9.0)) + +@implementation SuperPlayerFastView + +- (instancetype)init { + self = [super init]; + + self.backgroundColor = RGBA(0, 0, 0, 0.2); + + _videoRatio = 1; + _style = -1; + + return self; +} + +- (UILabel *)textLabel { + if (!_textLabel) { + _textLabel = [[UILabel alloc] init]; + _textLabel.textColor = [UIColor whiteColor]; + _textLabel.textAlignment = NSTextAlignmentCenter; + _textLabel.font = [UIFont systemFontOfSize:18.0]; + [self addSubview:_textLabel]; + } + return _textLabel; +} + +- (UIImageView *)imgView { + if (!_imgView) { + _imgView = [[UIImageView alloc] init]; + [self addSubview:_imgView]; + } + return _imgView; +} + +- (UIImageView *)thumbView { + if (!_thumbView) { + _thumbView = [[UIImageView alloc] init]; + _thumbView.contentMode = UIViewContentModeScaleAspectFit; + _thumbView.backgroundColor = [UIColor blackColor]; + [self addSubview:_thumbView]; + } + return _thumbView; +} + +- (UIImageView *)snapshotView { + if (!_snapshotView) { + _snapshotView = [[UIImageView alloc] init]; + _snapshotView.contentMode = UIViewContentModeScaleAspectFit; + _snapshotView.backgroundColor = [UIColor blackColor]; + [self addSubview:_snapshotView]; + + [_snapshotView mas_remakeConstraints:^(MASConstraintMaker *make) { + make.width.mas_equalTo(THUMB_VIEW_WIDTH); + make.height.mas_equalTo(THUMB_VIEW_HEIGHT); + make.center.equalTo(self); + }]; + } + return _snapshotView; +} + +- (UIProgressView *)progressView { + if (!_progressView) { + _progressView = [[UIProgressView alloc] init]; + _progressView.progressTintColor = [UIColor whiteColor]; + _progressView.trackTintColor = [[UIColor lightGrayColor] colorWithAlphaComponent:0.4]; + [self addSubview:_progressView]; + } + return _progressView; +} + +- (void)setStyle:(FastViewStyle)style { + if (_style == style) + return; + + switch (style) { + case ImgWithProgress: { + self.imgView.hidden = self.progressView.hidden = NO; + self.textLabel.hidden = self.thumbView.hidden = self.snapshotView.hidden = YES; + self.imgView.contentMode = UIViewContentModeScaleAspectFit; + + [self.imgView mas_remakeConstraints:^(MASConstraintMaker *make) { + make.center.equalTo(self); + }]; + [self.progressView mas_remakeConstraints:^(MASConstraintMaker *make) { + make.centerX.mas_equalTo(self); + make.top.equalTo(self.imgView.mas_bottom).offset(10); + make.width.mas_equalTo(120); + }]; + } + break; + case ImgWithText: { + self.thumbView.hidden = self.textLabel.hidden = NO; + self.progressView.hidden = self.imgView.hidden = self.snapshotView.hidden = YES; + + [self.thumbView mas_remakeConstraints:^(MASConstraintMaker *make) { + make.centerX.mas_equalTo(self); + make.width.mas_equalTo(THUMB_VIEW_WIDTH); + make.height.mas_equalTo(THUMB_VIEW_HEIGHT); + make.bottom.equalTo(self.mas_centerY).offset(20); + }]; + + [self.textLabel mas_remakeConstraints:^(MASConstraintMaker *make) { + make.centerX.mas_equalTo(self); + make.top.equalTo(self.thumbView.mas_bottom).offset(10); + }]; + } + break; + case TextWithProgress: { + self.progressView.hidden = self.textLabel.hidden = NO; + self.imgView.hidden = self.thumbView.hidden = self.snapshotView.hidden = YES; + + [self.textLabel mas_remakeConstraints:^(MASConstraintMaker *make) { + make.center.equalTo(self); + }]; + [self.progressView mas_remakeConstraints:^(MASConstraintMaker *make) { + make.centerX.mas_equalTo(self); + make.top.equalTo(self.textLabel.mas_bottom).offset(10); + make.width.mas_equalTo(120); + }]; + } + break; + case SnapshotImg: { + self.progressView.hidden = self.textLabel.hidden = self.imgView.hidden = self.thumbView.hidden = YES; + self.snapshotView.hidden = NO; + } + break; + default: + break; + } + _style = style; +} + +- (void)showImg:(UIImage *)img withProgress:(GLfloat)progress +{ + self.imgView.image = img; + self.progressView.progress = progress; + self.style = ImgWithProgress; +} + +- (void)showThumbnail:(UIImage *)img withText:(NSString *)text +{ + self.thumbView.image = [self imageWithImage:img]; + self.textLabel.text = text; + [self.textLabel sizeToFit]; + self.style = ImgWithText; +} +- (void)showText:(NSString *)text withText:(GLfloat)progress +{ + self.textLabel.text = text; + [self.textLabel sizeToFit]; + self.progressView.progress = progress; + self.style = TextWithProgress; +} + +- (void)showSnapshot:(UIImage *)img +{ + self.snapshotView.image = img; + self.style = SnapshotImg; +} + +- (UIImage *)imageWithImage:(UIImage *)image { + //UIGraphicsBeginImageContext(newSize); + // In next line, pass 0.0 to use the current device's pixel scaling factor (and thus account for Retina resolution). + // Pass 1.0 to force exact pixel size. + CGSize newSize = CGSizeMake(THUMB_VIEW_WIDTH, THUMB_VIEW_HEIGHT); + UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0); + + CGRect rect = AVMakeRectWithAspectRatioInsideRect(CGSizeMake(image.size.width, image.size.width/self.videoRatio), CGRectMake(0, 0, THUMB_VIEW_WIDTH, THUMB_VIEW_HEIGHT)); + + [image drawInRect:rect]; + + UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return newImage; +} +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Views/SuperPlayerSettingsView.h b/lib/my_flutter_superplayer/ios/SuperPlayer/Views/SuperPlayerSettingsView.h new file mode 100644 index 0000000..a882757 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Views/SuperPlayerSettingsView.h @@ -0,0 +1,34 @@ +// +// SuperPlayerSettingsView.h +// TXLiteAVDemo +// +// Created by annidyfeng on 2018/7/4. +// Copyright © 2018年 Tencent. All rights reserved. +// + +#import +#import "SuperPlayerViewConfig.h" + +#define MoreViewWidth 330 + +@class SuperPlayerControlView; + +@interface SuperPlayerSettingsView : UIView + +@property (weak) SuperPlayerControlView *controlView; + +@property UISlider *soundSlider; + +@property UISlider *lightSlider; + +/** + * 是否显示播放速度和镜像 + * + * 目前仅点播放支持修改播放速度与设置画面镜像 + */ +@property (nonatomic) BOOL enableSpeedAndMirrorControl; + +@property SuperPlayerViewConfig *playerConfig; +- (void)update; + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Views/SuperPlayerSettingsView.m b/lib/my_flutter_superplayer/ios/SuperPlayer/Views/SuperPlayerSettingsView.m new file mode 100644 index 0000000..afe9772 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Views/SuperPlayerSettingsView.m @@ -0,0 +1,384 @@ +// +// SuperPlayerSettingsView.m +// TXLiteAVDemo +// +// Created by annidyfeng on 2018/7/4. +// Copyright © 2018年 Tencent. All rights reserved. +// + +#import "SuperPlayerSettingsView.h" +#import "UIView+MMLayout.h" +#import "SuperPlayer.h" +#import +#import +#import "SuperPlayerControlView.h" +#import "SuperPlayerView+Private.h" +#import "DataReport.h" + +#define TAG_1_SPEED 1001 +#define TAG_2_SPEED 1002 +#define TAG_3_SPEED 1003 +#define TAG_4_SPEED 1004 + +@interface SuperPlayerSettingsView() +@property (nonatomic) UIView *soundCell; +@property (nonatomic) UIView *ligthCell; +@property (nonatomic) UIView *speedCell; +@property (nonatomic) UIView *mirrorCell; +@property (nonatomic) UIView *hwCell; +@property BOOL isVolume; +@property NSDate *volumeEndTime; +@end + +@implementation SuperPlayerSettingsView { + NSInteger _contentHeight; + NSInteger _speedTag; + + UISwitch *_mirrorSwitch; + UISwitch *_hwSwitch; +} + +- (instancetype)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + + self.mm_h = ScreenHeight; + self.mm_w = MoreViewWidth; + + [self addSubview:[self soundCell]]; + [self addSubview:[self lightCell]]; + [self addSubview:[self speedCell]]; + [self addSubview:[self mirrorCell]]; + [self addSubview:[self hwCell]]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(volumeChanged:) name:@"AVSystemController_SystemVolumeDidChangeNotification" + object:nil]; + + return self; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)volumeChanged:(NSNotification *)notify +{ + if (!self.isVolume) { + if (self.volumeEndTime != nil && -[self.volumeEndTime timeIntervalSinceNow] < 2.f) + return; + float volume = [[[notify userInfo] objectForKey:@"AVSystemController_AudioVolumeNotificationParameter"] floatValue]; + self.soundSlider.value = volume; + } +} + +- (void)sizeToFit +{ + _contentHeight = 20; + + _soundCell.m_top(_contentHeight); + _contentHeight += _soundCell.mm_h; + + _ligthCell.m_top(_contentHeight); + _contentHeight += _ligthCell.mm_h; + + + if (self.enableSpeedAndMirrorControl) { + _speedCell.m_top(_contentHeight); + _contentHeight += _speedCell.mm_h; + + _mirrorCell.m_top(_contentHeight); + _contentHeight += _mirrorCell.mm_h; + + _speedCell.hidden = NO; + _mirrorCell.hidden = NO; + } else { + _speedCell.hidden = YES; + _mirrorCell.hidden = YES; + } + + _hwCell.m_top(_contentHeight); + _contentHeight += _hwCell.mm_h; +} + +- (UIView *)soundCell +{ + if (_soundCell == nil) { + _soundCell = [[UIView alloc] initWithFrame:CGRectZero]; + _soundCell.m_width(MoreViewWidth).m_height(50).m_left(10); + + // 声音 + UILabel *sound = [UILabel new]; + sound.text = @"声音"; + sound.textColor = [UIColor whiteColor]; + [sound sizeToFit]; + [_soundCell addSubview:sound]; + sound.m_centerY(); + + + UIImageView *soundImage1 = [[UIImageView alloc] initWithImage:SuperPlayerImage(@"sound_min")]; + [_soundCell addSubview:soundImage1]; + soundImage1.m_left(sound.mm_maxX+10).m_centerY(); + + UIImageView *soundImage2 = [[UIImageView alloc] initWithImage:SuperPlayerImage(@"sound_max")]; + [_soundCell addSubview:soundImage2]; + soundImage2.m_right(50).m_centerY(); + + + UISlider *soundSlider = [[UISlider alloc] init]; + [soundSlider setThumbImage:SuperPlayerImage(@"slider_thumb") forState:UIControlStateNormal]; + + soundSlider.maximumValue = 1; + soundSlider.minimumTrackTintColor = TintColor; + soundSlider.maximumTrackTintColor = [UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:0.5]; + + // slider开始滑动事件 + [soundSlider addTarget:self action:@selector(soundSliderTouchBegan:) forControlEvents:UIControlEventTouchDown]; + // slider滑动中事件 + [soundSlider addTarget:self action:@selector(soundSliderValueChanged:) forControlEvents:UIControlEventValueChanged]; + // slider结束滑动事件 + [soundSlider addTarget:self action:@selector(soundSliderTouchEnded:) forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchCancel | UIControlEventTouchUpOutside]; + [_soundCell addSubview:soundSlider]; + soundSlider.m_centerY().m_left(soundImage1.mm_maxX).m_width(soundImage2.mm_minX-soundImage1.mm_maxX); + + self.soundSlider = soundSlider; + } + return _soundCell; +} + +- (UIView *)lightCell +{ + if (_ligthCell == nil) { + _ligthCell = [[UIView alloc] initWithFrame:CGRectZero]; + _ligthCell.m_width(MoreViewWidth).m_height(50).m_left(10); + + // 亮度 + UILabel *ligth = [UILabel new]; + ligth.text = @"亮度"; + ligth.textColor = [UIColor whiteColor]; + [ligth sizeToFit]; + [_ligthCell addSubview:ligth]; + ligth.m_centerY(); + + UIImageView *ligthImage1 = [[UIImageView alloc] initWithImage:SuperPlayerImage(@"light_min")]; + [_ligthCell addSubview:ligthImage1]; + ligthImage1.m_left(ligth.mm_maxX+10).m_centerY(); + + UIImageView *ligthImage2 = [[UIImageView alloc] initWithImage:SuperPlayerImage(@"light_max")]; + [_ligthCell addSubview:ligthImage2]; + ligthImage2.m_right(50).m_centerY(); + + + UISlider *lightSlider = [[UISlider alloc] init]; + + [lightSlider setThumbImage:SuperPlayerImage(@"slider_thumb") forState:UIControlStateNormal]; + + lightSlider.maximumValue = 1; + lightSlider.minimumTrackTintColor = TintColor; + lightSlider.maximumTrackTintColor = [UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:0.5]; + + // slider开始滑动事件 + [lightSlider addTarget:self action:@selector(lightSliderTouchBegan:) forControlEvents:UIControlEventTouchDown]; + // slider滑动中事件 + [lightSlider addTarget:self action:@selector(lightSliderValueChanged:) forControlEvents:UIControlEventValueChanged]; + // slider结束滑动事件 + [lightSlider addTarget:self action:@selector(lightSliderTouchEnded:) forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchCancel | UIControlEventTouchUpOutside]; + + [_ligthCell addSubview:lightSlider]; + lightSlider.m_centerY().m_left(ligthImage1.mm_maxX).m_width(ligthImage2.mm_minX-ligthImage1.mm_maxX); + + self.lightSlider = lightSlider; + } + + + return _ligthCell; +} + +- (UIView *)speedCell { + if (!_speedCell) { + _speedCell = [UIView new]; + _speedCell.m_width(MoreViewWidth).m_height(50).m_left(10); + + // 倍速 + UILabel *speed = [UILabel new]; + speed.text = @"倍速播放"; + speed.textColor = [UIColor whiteColor]; + [speed sizeToFit]; + [_speedCell addSubview:speed]; + speed.m_centerY(); + + UIButton *speed1 = [UIButton buttonWithType:UIButtonTypeCustom]; + [speed1 setTitle:@"1.0X" forState:UIControlStateNormal]; + [speed1 setTitleColor:TintColor forState:UIControlStateSelected]; + speed1.selected = YES; + speed1.tag = TAG_1_SPEED; + [speed1 sizeToFit]; + [_speedCell addSubview:speed1]; + [speed1 addTarget:self action:@selector(changeSpeed:) forControlEvents:UIControlEventTouchUpInside]; + + speed1.m_left(speed.mm_maxX+10).m_centerY(); + + + UIButton *speed2 = [UIButton buttonWithType:UIButtonTypeCustom]; + [speed2 setTitle:@"1.25X" forState:UIControlStateNormal]; + [speed2 setTitleColor:TintColor forState:UIControlStateSelected]; + speed2.tag = TAG_2_SPEED; + [speed2 sizeToFit]; + [_speedCell addSubview:speed2]; + [speed2 addTarget:self action:@selector(changeSpeed:) forControlEvents:UIControlEventTouchUpInside]; + + speed2.m_left(speed1.mm_maxX+12).m_centerY(); + + + UIButton *speed3 = [UIButton buttonWithType:UIButtonTypeCustom]; + [speed3 setTitle:@"1.5X" forState:UIControlStateNormal]; + [speed3 setTitleColor:TintColor forState:UIControlStateSelected]; + speed3.tag = TAG_3_SPEED; + [speed3 sizeToFit]; + [_speedCell addSubview:speed3]; + [speed3 addTarget:self action:@selector(changeSpeed:) forControlEvents:UIControlEventTouchUpInside]; + + speed3.m_left(speed2.mm_maxX+12).m_centerY(); + + UIButton *speed4 = [UIButton buttonWithType:UIButtonTypeCustom]; + [speed4 setTitle:@"2.0X" forState:UIControlStateNormal]; + [speed4 setTitleColor:TintColor forState:UIControlStateSelected]; + speed4.tag = TAG_4_SPEED; + [speed4 sizeToFit]; + [_speedCell addSubview:speed4]; + [speed4 addTarget:self action:@selector(changeSpeed:) forControlEvents:UIControlEventTouchUpInside]; + speed4.m_left(speed3.mm_maxX+12).m_centerY(); + } + return _speedCell; +} + +- (UIView *)mirrorCell { + if (!_mirrorCell) { + _mirrorCell = [UIView new]; + _mirrorCell.m_width(MoreViewWidth).m_height(50).m_left(10); + + + UILabel *mirror = [UILabel new]; + mirror.text = @"镜像"; + mirror.textColor = [UIColor whiteColor]; + [mirror sizeToFit]; + [_mirrorCell addSubview:mirror]; + mirror.m_centerY(); + + UISwitch *switcher = [UISwitch new]; + _mirrorSwitch = switcher; + [switcher addTarget:self action:@selector(changeMirror:) forControlEvents:UIControlEventValueChanged]; + [_mirrorCell addSubview:switcher]; + switcher.m_right(30).m_centerY(); + } + return _mirrorCell; +} + +- (UIView *)hwCell { + if (!_hwCell) { + _hwCell = [UIView new]; + _hwCell.m_width(MoreViewWidth).m_height(50).m_left(10); + + + UILabel *hd = [UILabel new]; + hd.text = @"硬件加速"; + + hd.textColor = [UIColor whiteColor]; + [hd sizeToFit]; + [_hwCell addSubview:hd]; + hd.m_centerY(); + + UISwitch *switcher = [UISwitch new]; + _hwSwitch = switcher; + [switcher addTarget:self action:@selector(changeHW:) forControlEvents:UIControlEventValueChanged]; + [_hwCell addSubview:switcher]; + switcher.m_right(30).m_centerY(); + } + return _hwCell; +} + +- (void)soundSliderTouchBegan:(UISlider *)sender { + self.isVolume = YES; +} + +- (void)soundSliderValueChanged:(UISlider *)sender { + if (self.isVolume) + [SuperPlayerView volumeViewSlider].value = sender.value; +} + +- (void)soundSliderTouchEnded:(UISlider *)sender { + self.isVolume = NO; + self.volumeEndTime = [NSDate date]; +} + +- (void)lightSliderTouchBegan:(UISlider *)sender { + +} + +- (void)lightSliderValueChanged:(UISlider *)sender { + [UIScreen mainScreen].brightness = sender.value; +} + +- (void)lightSliderTouchEnded:(UISlider *)sender { + +} + +- (void)changeSpeed:(UIButton *)sender { + + for (int i = TAG_1_SPEED; i <= TAG_4_SPEED; i++) { + UIButton *b = [_speedCell viewWithTag:i]; + if (b.isSelected && b != sender) + b.selected = NO; + } + sender.selected = YES; + self.playerConfig.playRate = [sender.titleLabel.text floatValue]; + [self.controlView.delegate controlViewConfigUpdate:self.controlView withReload:NO]; + [DataReport report:@"change_speed" param:nil]; +} + +- (void)changeMirror:(UISwitch *)sender { + self.playerConfig.mirror = sender.on; + [self.controlView.delegate controlViewConfigUpdate:self.controlView withReload:NO]; + if (sender.on) { + [DataReport report:@"mirror" param:nil]; + } +} + +- (void)changeHW:(UISwitch *)sender { + self.playerConfig.hwAcceleration = sender.on; + [self.controlView.delegate controlViewConfigUpdate:self.controlView withReload:YES]; + [DataReport report:sender.on?@"hw_decode":@"soft_decode" param:nil]; +} + +- (void)update +{ + self.soundSlider.value = [SuperPlayerView volumeViewSlider].value; + self.lightSlider.value = [UIScreen mainScreen].brightness; + + CGFloat rate = self.playerConfig.playRate; + + for (int i = TAG_1_SPEED; i <= TAG_4_SPEED; i++) { + UIButton *b = [_speedCell viewWithTag:i]; + b.selected = NO; + } + + if (rate == 1.0) { + [[_speedCell viewWithTag:TAG_1_SPEED] setSelected:YES]; + } + if (rate == 1.25) { + [[_speedCell viewWithTag:TAG_2_SPEED] setSelected:YES]; + } + if (rate == 1.5) { + [[_speedCell viewWithTag:TAG_3_SPEED] setSelected:YES]; + } + if (rate == 2.0) { + [[_speedCell viewWithTag:TAG_4_SPEED] setSelected:YES]; + } + + _mirrorSwitch.on = self.playerConfig.mirror; + _hwSwitch.on = self.playerConfig.hwAcceleration; + + [self sizeToFit]; +} + +@end diff --git a/lib/my_flutter_superplayer/ios/SuperPlayer/Views/SuperPlayerView+Private.h b/lib/my_flutter_superplayer/ios/SuperPlayer/Views/SuperPlayerView+Private.h new file mode 100644 index 0000000..797721d --- /dev/null +++ b/lib/my_flutter_superplayer/ios/SuperPlayer/Views/SuperPlayerView+Private.h @@ -0,0 +1,122 @@ +// +// SuperPlayerView+Private.h +// TXLiteAVDemo +// +// Created by annidyfeng on 2018/7/9. +// Copyright © 2018年 Tencent. All rights reserved. +// + +#ifndef SuperPlayerView_Private_h +#define SuperPlayerView_Private_h +#import "SuperPlayer.h" + +#import "SuperPlayerControlViewDelegate.h" +#import "NetWatcher.h" +#import + +#import "Masonry/Masonry.h" +#import "AFNetworking/AFNetworking.h" +//#import "SPResolutionDefination.h" +#import "SPSubStreamInfo.h" +#import + +// 枚举值,包含水平移动方向和垂直移动方向 +typedef NS_ENUM(NSInteger, PanDirection){ + PanDirectionHorizontalMoved, // 横向移动 + PanDirectionVerticalMoved // 纵向移动 +}; + +typedef NS_ENUM(NSInteger, ButtonAction) { + ActionNone, + ActionRetry, + ActionSwitch, + ActionIgnore, + ActionContinueReplay, +}; + +@class TXVodPlayer, TXLivePlayer; +@interface SuperPlayerView () + + +/** 用来保存快进的总时长 */ +@property (nonatomic, assign) CGFloat sumTime; +@property (nonatomic, assign) CGFloat startVeloctyPoint; + +/** 定义一个实例变量,保存枚举值 */ +@property (nonatomic, assign) PanDirection panDirection; +/** 是否在调节音量*/ +@property (nonatomic, assign) BOOL isVolume; +/** 是否被用户暂停 */ +@property (nonatomic, assign) BOOL isPauseByUser; +/** 播放完了*/ +@property (nonatomic, assign) BOOL playDidEnd; +/** 进入后台*/ +@property (nonatomic, assign) BOOL didEnterBackground; +/** 单击 */ +@property (nonatomic, strong) UITapGestureRecognizer *singleTap; +/** 双击 */ +@property (nonatomic, strong) UITapGestureRecognizer *doubleTap; +/** 快进快退、View*/ +@property (nonatomic, strong) SuperPlayerFastView *fastView; + +@property (nonatomic, setter=setDragging:) BOOL isDragging; +/// 中间的提示按钮 +@property (nonatomic, strong) UIButton *middleBlackBtn; +@property ButtonAction middleBlackBtnAction; + +/** 系统菊花 */ +@property (nonatomic, strong) MMMaterialDesignSpinner *spinner; + +@property (nonatomic, strong) UIButton *lockTipsBtn; + +@property (nonatomic, strong) SuperPlayerModel *playerModel; + +@property (class, readonly) UISlider *volumeViewSlider; + +@property MPVolumeView *volumeView; + +// add for txvodplayer +@property BOOL isLoaded; + +@property (nonatomic) BOOL isShiftPlayback; + +@property CGFloat maxLiveProgressTime; // 直播最大进度/总时间 +@property CGFloat liveProgressTime; // 直播播放器回调过来的时间 +@property CGFloat liveProgressBase; // 直播播放器超出时移的最大时间 +#define MAX_SHIFT_TIME (2*60*60) +/** 是否是直播流 */ +@property BOOL isLive; + +/** 腾讯点播播放器 */ +@property (nonatomic, strong) TXVodPlayer *vodPlayer; +/** 腾讯直播播放器 */ +@property (nonatomic, strong) TXLivePlayer *livePlayer; + +@property NSDate *reportTime; + +@property NetWatcher *netWatcher; + +@property (nonatomic) CGFloat videoRatio; + +/// 由协议解析出分辨率定义表 +@property (strong, nonatomic) NSArray *resolutions; +/// 当前可用的分辨率列表 +//@property (strong, nonatomic) NSArray *currentResolutionNames; +@end + + +// --------------------------------------------------------------- + +@class AdaptiveStream; + +@interface SuperPlayerModel() + +//@property (nonatomic, strong) NSString *drmType; +@property NSMutableArray *streams; + +//- (BOOL)canSetDrmType:(NSString *)drmType; + +@end + +#endif /* SuperPlayerView_Private_h */ diff --git a/lib/my_flutter_superplayer/ios/flutter_superplayer.podspec b/lib/my_flutter_superplayer/ios/flutter_superplayer.podspec new file mode 100644 index 0000000..8f5fb74 --- /dev/null +++ b/lib/my_flutter_superplayer/ios/flutter_superplayer.podspec @@ -0,0 +1,36 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint flutter_superplayer.podspec' to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'flutter_superplayer' + s.version = '0.0.1' + s.summary = 'A new flutter plugin project.' + s.description = <<-DESC +A new flutter plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.public_header_files = 'Classes/**/*.h' + s.dependency 'Flutter' + s.platform = :ios, '9.0' + + s.default_subspec = 'SuperPlayer_Professional' + + s.subspec "SuperPlayer_Professional" do |ss| + ss.dependency 'AFNetworking', '~> 4.0' + ss.dependency 'Masonry' + ss.dependency 'TXLiteAVSDK_Professional' + ss.source_files = 'SuperPlayer/**/*.{h,m}' + ss.private_header_files = 'SuperPlayer/Utils/TXBitrateItemHelper.h', 'SuperPlayer/Views/SuperPlayerView+Private.h' + ss.resource = 'SuperPlayer/Resource/*' + end + + # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } + s.static_framework = true +end + diff --git a/lib/my_flutter_superplayer/lib/flutter_superplayer.dart b/lib/my_flutter_superplayer/lib/flutter_superplayer.dart new file mode 100644 index 0000000..0d13f93 --- /dev/null +++ b/lib/my_flutter_superplayer/lib/flutter_superplayer.dart @@ -0,0 +1,22 @@ +import 'dart:async'; + +import 'package:flutter/services.dart'; + +export './src/superplayer_const.dart'; +export './src/superplayer_control_view.dart'; +export './src/superplayer_controller.dart'; +export './src/superplayer_listener.dart'; +export './src/superplayer_model.dart'; +export './src/superplayer_video_id.dart'; +export './src/superplayer_video_id_v2.dart'; +export './src/superplayer_view.dart'; + +class FlutterSuperPlayer { + static const MethodChannel _channel = + const MethodChannel('flutter_superplayer'); + + static Future get sdkVersion async { + final String version = await _channel.invokeMethod('getSDKVersion'); + return version; + } +} diff --git a/lib/my_flutter_superplayer/lib/src/constants.dart b/lib/my_flutter_superplayer/lib/src/constants.dart new file mode 100644 index 0000000..d2c07fe --- /dev/null +++ b/lib/my_flutter_superplayer/lib/src/constants.dart @@ -0,0 +1,3 @@ +const kSuperPlayerViewViewType = "leanflutter.org/superplayer_view"; +const kSuperPlayerViewChannelName = "leanflutter.org/superplayer_view/channel"; +const kSuperPlayerViewEventChannelName = "leanflutter.org/superplayer_view/event_channel"; \ No newline at end of file diff --git a/lib/my_flutter_superplayer/lib/src/superplayer_const.dart b/lib/my_flutter_superplayer/lib/src/superplayer_const.dart new file mode 100644 index 0000000..e7c10da --- /dev/null +++ b/lib/my_flutter_superplayer/lib/src/superplayer_const.dart @@ -0,0 +1,25 @@ +/// 超级播放器常量 +class SuperPlayerConst { + // 播放模式 + static final int PLAYMODE_WINDOW = 1; // 窗口 + static final int PLAYMODE_FULLSCREEN = 2; // 全屏 + static final int PLAYMODE_FLOAT = 3; // 悬浮窗 + + // 播放状态 + static final int PLAYSTATE_PLAYING = 1; + static final int PLAYSTATE_PAUSE = 2; + static final int PLAYSTATE_LOADING = 3; + static final int PLAYSTATE_END = 4; + static final int PLAYSTATE_FAILED = 5; + + // 屏幕方向 + static final int ORIENTATION_LANDSCAPE = 1; // 横屏 + static final int ORIENTATION_PORTRAIT = 2; // 竖屏 + + // 播放视频类型 + static final int PLAYTYPE_VOD = 1; // 点播 + static final int PLAYTYPE_LIVE = 2; // 直播 + static final int PLAYTYPE_LIVE_SHIFT = 3; // 直播回看 + + static final int MAX_SHIFT_TIME = 7200; // demo演示直播时移是MAX_SHIFT_TIMEs,即2小时 +} diff --git a/lib/my_flutter_superplayer/lib/src/superplayer_control_view.dart b/lib/my_flutter_superplayer/lib/src/superplayer_control_view.dart new file mode 100644 index 0000000..d7d1a95 --- /dev/null +++ b/lib/my_flutter_superplayer/lib/src/superplayer_control_view.dart @@ -0,0 +1,11 @@ +import 'package:flutter/material.dart'; + +const kControlViewTypeDefault = 'default'; +const kControlViewTypeWithout = 'without'; + +class SuperPlayerControlView extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container(); + } +} diff --git a/lib/my_flutter_superplayer/lib/src/superplayer_controller.dart b/lib/my_flutter_superplayer/lib/src/superplayer_controller.dart new file mode 100644 index 0000000..fbe2b97 --- /dev/null +++ b/lib/my_flutter_superplayer/lib/src/superplayer_controller.dart @@ -0,0 +1,172 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import './constants.dart'; +import './superplayer_const.dart'; +import './superplayer_listener.dart'; +import './superplayer_model.dart'; +// import './superplayer_view.dart'; + +class SuperPlayerController { + ObserverList _listeners = ObserverList(); + MethodChannel _channel; + EventChannel _eventChannel; + + void _onEvent(dynamic event) { + String listener = '${event['listener']}'; + String method = '${event['method']}'; + + switch (listener) { + case 'SuperPlayerListener': + notifyListeners(method, event['data']); + break; + } + } + + bool _debugAssertNotDisposed() { + assert(() { + if (_listeners == null) { + throw FlutterError('A $runtimeType was used after being disposed.\n' + 'Once you have called dispose() on a $runtimeType, it can no longer be used.'); + } + return true; + }()); + return true; + } + + bool get hasListeners { + assert(_debugAssertNotDisposed()); + return _listeners.isNotEmpty; + } + + void addListener(SuperPlayerListener listener) { + assert(_debugAssertNotDisposed()); + _listeners.add(listener); + } + + void removeListener(SuperPlayerListener listener) { + assert(_debugAssertNotDisposed()); + _listeners.remove(listener); + } + + void dispose() { + assert(_debugAssertNotDisposed()); + _listeners = null; + } + + void notifyListeners(String method, dynamic data) { + assert(_debugAssertNotDisposed()); + if (_listeners != null) { + final List localListeners = List.from(_listeners); + for (final SuperPlayerListener listener in localListeners) { + try { + if (_listeners.contains(listener)) { + switch (method) { + case 'onFullScreenChange': + listener.onFullScreenChange(data['isFullScreen']); + break; + case 'onClickFloatCloseBtn': + listener.onClickFloatCloseBtn(); + break; + case 'onClickSmallReturnBtn': + listener.onClickSmallReturnBtn(); + break; + case 'onStartFloatWindowPlay': + listener.onStartFloatWindowPlay(); + break; + case 'onPlayStateChange': + listener.onPlayStateChange(data['playState']); + break; + case 'onPlayProgressChange': + listener.onPlayProgressChange( + data['current'], + data['duration'], + ); + break; + } + } + } catch (exception) {} + } + } + } + + void initWithViewId(int viewId) { + _channel = MethodChannel('${kSuperPlayerViewChannelName}_$viewId'); + _eventChannel = EventChannel('${kSuperPlayerViewEventChannelName}_$viewId'); + + _eventChannel.receiveBroadcastStream().listen(_onEvent); + } + + Future getPlayMode() async { + return await _channel.invokeMethod('getPlayMode', {}); + } + + Future getPlayState() async { + return await _channel.invokeMethod('getPlayState', {}); + } + + Future getPlayRate() async { + return await _channel.invokeMethod('getPlayRate', {}); + } + + void setControlViewType(String controlViewType) { + _channel.invokeMethod('setControlViewType', { + 'controlViewType': controlViewType, + }); + } + + void setPlayRate(num playRate) { + _channel.invokeMethod('setPlayRate', {'playRate': playRate}); + } + + void resetPlayer() { + _channel.invokeMethod('resetPlayer'); + } + + void toFullScreen() { + _channel.invokeMethod('toFullScreen'); + } + + void requestPlayMode(int playMode) { + _channel.invokeMethod('requestPlayMode', { + 'playMode': playMode, + }); + } + + void playWithModel(final SuperPlayerModel model) { + _channel.invokeMethod('playWithModel', model.toJson()); + } + + void pause() { + _channel.invokeMethod('pause'); + } + + void resume() { + _channel.invokeMethod('resume'); + } + + void release() { + _channel.invokeMethod('release'); + } + + void seekTo(int time) { + _channel.invokeMethod('seekTo', { + 'time': time, + }); + } + + void setLoop(bool isLoop) { + _channel.invokeMethod('setLoop', { + 'isLoop': isLoop, + }); + } + + void uiHideDanmu() { + _channel.invokeMethod('uiHideDanmu'); + } + + void uiHideReplay() { + _channel.invokeMethod('uiHideReplay'); + } +} diff --git a/lib/my_flutter_superplayer/lib/src/superplayer_listener.dart b/lib/my_flutter_superplayer/lib/src/superplayer_listener.dart new file mode 100644 index 0000000..9eef36f --- /dev/null +++ b/lib/my_flutter_superplayer/lib/src/superplayer_listener.dart @@ -0,0 +1,19 @@ +abstract class SuperPlayerListener { + /// 全屏切换 + void onFullScreenChange(bool isFullScreen); + + /// 点击悬浮窗模式下的x按钮 + void onClickFloatCloseBtn(); + + /// 点击小播放模式的返回按钮 + void onClickSmallReturnBtn(); + + /// 开始悬浮窗播放 + void onStartFloatWindowPlay(); + + /// 播放状态发生变化 + void onPlayStateChange(int playState); + + /// 播放进度发生变化 + void onPlayProgressChange(int current, int duration); +} diff --git a/lib/my_flutter_superplayer/lib/src/superplayer_model.dart b/lib/my_flutter_superplayer/lib/src/superplayer_model.dart new file mode 100644 index 0000000..b70052c --- /dev/null +++ b/lib/my_flutter_superplayer/lib/src/superplayer_model.dart @@ -0,0 +1,76 @@ +import './superplayer_video_id.dart'; +import './superplayer_video_id_v2.dart'; + +class SuperPlayerModel { + /// AppId 用于腾讯云点播 File ID 播放及腾讯云直播时移功能 + int appId; + + /// 直接使用URL播放 + ///

+ /// 支持 RTMP、FLV、MP4、HLS 封装格式 + /// 使用腾讯云直播时移功能则需要填写appId + String url = ""; // 视频URL + + /// 多码率视频 URL + ///

+ /// 用于拥有多个播放地址的多清晰度视频播放 + List multiURLs; + + /// 指定多码率情况下,默认播放的连接Index + int playDefaultIndex; + + /// 腾讯云点播 File ID 播放参数 + SuperPlayerVideoId videoId; + + /// 用于兼容旧版本(V2)腾讯云点播 File ID 播放参数(即将废弃,不推荐使用) + @deprecated + SuperPlayerVideoIdV2 videoIdV2; + + /// 视频文件名 (用于显示在UI层);使用file id播放,若未指定title,则使用FileId返回的Title;使用url播放需要指定title,否则title显示为空 + String title = ""; + + SuperPlayerModel({ + this.appId, + this.url, + this.multiURLs, + this.playDefaultIndex, + this.videoId, + this.videoIdV2, + this.title, + }); + + Map toJson() { + Map jsonObject = Map(); + if (appId != null) jsonObject.putIfAbsent("appId", () => appId); + if (url != null) jsonObject.putIfAbsent("url", () => url); + if (multiURLs != null) jsonObject.putIfAbsent("multiURLs", () => multiURLs); + if (playDefaultIndex != null) + jsonObject.putIfAbsent("playDefaultIndex", () => playDefaultIndex); + if (videoId != null) + jsonObject.putIfAbsent("videoId", () => videoId.toJson()); + if (videoIdV2 != null) + jsonObject.putIfAbsent("videoIdV2", () => videoIdV2.toJson()); + if (title != null) jsonObject.putIfAbsent("title", () => title); + + return jsonObject; + } +} + +class SuperPlayerURL { + /// 清晰度名称(用于显示在UI层) + String qualityName = "原画"; + + /// 该清晰度对应的地址 + String url = ""; + + SuperPlayerURL({this.url, this.qualityName}); + + Map toJson() { + Map jsonObject = Map(); + if (qualityName != null) + jsonObject.putIfAbsent("qualityName", () => qualityName); + if (url != null) jsonObject.putIfAbsent("url", () => url); + + return jsonObject; + } +} diff --git a/lib/my_flutter_superplayer/lib/src/superplayer_video_id.dart b/lib/my_flutter_superplayer/lib/src/superplayer_video_id.dart new file mode 100644 index 0000000..b86e334 --- /dev/null +++ b/lib/my_flutter_superplayer/lib/src/superplayer_video_id.dart @@ -0,0 +1,15 @@ +/// 使用腾讯云fileId播放 +class SuperPlayerVideoId { + String fileId; // 腾讯云视频fileId + String pSign; // v4 开启防盗链必填 + + SuperPlayerVideoId({this.fileId, this.pSign}); + + Map toJson() { + Map jsonObject = Map(); + if (fileId != null) jsonObject.putIfAbsent("fileId", () => fileId); + if (pSign != null) jsonObject.putIfAbsent("pSign", () => pSign); + + return jsonObject; + } +} diff --git a/lib/my_flutter_superplayer/lib/src/superplayer_video_id_v2.dart b/lib/my_flutter_superplayer/lib/src/superplayer_video_id_v2.dart new file mode 100644 index 0000000..1433e3f --- /dev/null +++ b/lib/my_flutter_superplayer/lib/src/superplayer_video_id_v2.dart @@ -0,0 +1,19 @@ +class SuperPlayerVideoIdV2 { + String fileId; // 腾讯云视频fileId + String timeout; // 【可选】加密链接超时时间戳,转换为16进制小写字符串,腾讯云 CDN 服务器会根据该时间判断该链接是否有效。 + String us; // 【可选】唯一标识请求,增加链接唯一性 + String sign; // 【可选】防盗链签名 + + int exper = -1; // 【V2可选】试看时长,单位:秒。可选 + + Map toJson() { + Map jsonObject = Map(); + if (fileId != null) jsonObject.putIfAbsent("fileId", () => fileId); + if (timeout != null) jsonObject.putIfAbsent("timeout", () => timeout); + if (us != null) jsonObject.putIfAbsent("us", () => us); + if (sign != null) jsonObject.putIfAbsent("sign", () => sign); + if (exper != null) jsonObject.putIfAbsent("exper", () => exper); + + return jsonObject; + } +} diff --git a/lib/my_flutter_superplayer/lib/src/superplayer_view.dart b/lib/my_flutter_superplayer/lib/src/superplayer_view.dart new file mode 100644 index 0000000..1743e31 --- /dev/null +++ b/lib/my_flutter_superplayer/lib/src/superplayer_view.dart @@ -0,0 +1,71 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +import './constants.dart'; +import './superplayer_control_view.dart'; +import './superplayer_controller.dart'; + +class SuperPlayerView extends StatefulWidget { + Function(SuperPlayerController controller) onSuperPlayerViewCreated; + SuperPlayerController controller; + String controlViewType; + + SuperPlayerView({ + Key key, + this.onSuperPlayerViewCreated, + this.controller, + this.controlViewType = kControlViewTypeDefault, + }) : super(key: key); + + @override + _SuperPlayerViewState createState() => _SuperPlayerViewState(); +} + +class _SuperPlayerViewState extends State { + void _onPlatformViewCreated(int viewId) { + if (widget.controller == null) { + widget.controller = SuperPlayerController(); + } + if (widget.controller != null) { + widget.controller.initWithViewId(viewId); + } + if (widget.onSuperPlayerViewCreated != null) { + widget.onSuperPlayerViewCreated(widget.controller); + } + } + + @override + void didUpdateWidget(covariant SuperPlayerView oldWidget) { + if (oldWidget.controlViewType != widget.controlViewType) { + widget.controller.setControlViewType(widget.controlViewType); + } + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + Map creationParams = { + 'controlViewType': widget.controlViewType, + }; + + if (Platform.isAndroid) { + return AndroidView( + viewType: kSuperPlayerViewViewType, + onPlatformViewCreated: _onPlatformViewCreated, + creationParams: creationParams, + creationParamsCodec: const StandardMessageCodec(), + ); + } else if (Platform.isIOS) { + return UiKitView( + viewType: kSuperPlayerViewViewType, + onPlatformViewCreated: _onPlatformViewCreated, + creationParams: creationParams, + creationParamsCodec: const StandardMessageCodec(), + ); + } + return Container(); + } +} diff --git a/lib/my_flutter_superplayer/pubspec.lock b/lib/my_flutter_superplayer/pubspec.lock new file mode 100644 index 0000000..500e067 --- /dev/null +++ b/lib/my_flutter_superplayer/pubspec.lock @@ -0,0 +1,147 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.5.0-nullsafety.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.0-nullsafety.1" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0-nullsafety.3" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.0-nullsafety.1" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0-nullsafety.1" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.15.0-nullsafety.3" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.0-nullsafety.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.12.10-nullsafety.1" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.0-nullsafety.3" + path: + dependency: transitive + description: + name: path + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.8.0-nullsafety.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.8.0-nullsafety.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.10.0-nullsafety.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.0-nullsafety.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0-nullsafety.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.0-nullsafety.1" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.19-nullsafety.2" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.0-nullsafety.3" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.0-nullsafety.3" +sdks: + dart: ">=2.10.0 <2.11.0" + flutter: ">=1.20.0 <2.0.0" diff --git a/lib/my_flutter_superplayer/pubspec.yaml b/lib/my_flutter_superplayer/pubspec.yaml new file mode 100644 index 0000000..f057302 --- /dev/null +++ b/lib/my_flutter_superplayer/pubspec.yaml @@ -0,0 +1,65 @@ +name: flutter_superplayer +description: 适用于 Flutter 的腾讯云超级播放器插件 +version: 0.0.2 +author: LiJianying +homepage: https://github.com/leanflutter/flutter_superplayer + +environment: + sdk: ">=2.10.0 <3.0.0" + flutter: ">=1.20.0 <2.0.0" + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' and Android 'package' identifiers should not ordinarily + # be modified. They are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + platforms: + android: + package: org.leanflutter.plugins.flutter_superplayer + pluginClass: FlutterSuperplayerPlugin + ios: + pluginClass: FlutterSuperplayerPlugin + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # To add custom fonts to your plugin package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/lib/my_flutter_superplayer/test/flutter_superplayer_test.dart b/lib/my_flutter_superplayer/test/flutter_superplayer_test.dart new file mode 100644 index 0000000..ab133f5 --- /dev/null +++ b/lib/my_flutter_superplayer/test/flutter_superplayer_test.dart @@ -0,0 +1,23 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_superplayer/flutter_superplayer.dart'; + +void main() { + const MethodChannel channel = MethodChannel('flutter_superplayer'); + + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + return '42'; + }); + }); + + tearDown(() { + channel.setMockMethodCallHandler(null); + }); + + // test('getPlatformVersion', () async { + // expect(await FlutterSuperplayer.platformVersion, '42'); + // }); +} diff --git a/lib/pages/Address/AddressAdd.dart b/lib/pages/Address/AddressAdd.dart new file mode 100644 index 0000000..cbf5605 --- /dev/null +++ b/lib/pages/Address/AddressAdd.dart @@ -0,0 +1,144 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import '../../widget/JdText.dart'; + +import '../../widget/JdButton.dart'; + +import 'package:city_pickers/city_pickers.dart'; + +import '../../services/UserServices.dart'; +import '../../services/SignServices.dart'; + +import '../../config/Config.dart'; +import 'package:dio/dio.dart'; + +import '../../services/EventBus.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class AddressAddPage extends StatefulWidget { + AddressAddPage({Key key}) : super(key: key); + + _AddressAddPageState createState() => _AddressAddPageState(); +} + +class _AddressAddPageState extends State { + + String area=''; + String name=''; + String phone=''; + String address=''; + + //监听页面销毁的事件 + dispose(){ + super.dispose(); + eventBus.fire(new AddressEvent('增加成功...')); + eventBus.fire(new CheckOutEvent('改收货地址成功...')); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("增加收货地址"), + ), + body: Container( + padding: EdgeInsets.all(10), + child: ListView( + children: [ + SizedBox(height: 20), + JdText( + text: "收货人姓名", + onChanged: (value){ + this.name=value; + }, + ), + SizedBox(height: 10), + JdText( + text: "收货人电话", + onChanged: (value){ + this.phone=value; + }, + ), + SizedBox(height: 10), + Container( + padding: EdgeInsets.only(left: 5), + height: ScreenUtil().setHeight(68), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(width: 1, color: Colors.black12))), + child: InkWell( + child: Row( + children: [ + Icon(Icons.add_location), + this.area.length>0?Text('${this.area}', style: TextStyle(color: Colors.black54)):Text('省/市/区', style: TextStyle(color: Colors.black54)) + ], + ), + onTap: () async{ + Result result = await CityPickers.showCityPicker( + context: context, + cancelWidget: + Text("取消", style: TextStyle(color: Colors.blue)), + confirmWidget: + Text("确定", style: TextStyle(color: Colors.blue)) + ); + + print(result); + setState(() { + this.area= "${result.provinceName}/${result.cityName}/${result.areaName}"; + }); + }, + ), + ), + SizedBox(height: 10), + JdText( + text: "详细地址", + maxLines: 4, + height: 200, + onChanged: (value){ + this.address="${this.area} ${value}"; + }, + ), + SizedBox(height: 10), + SizedBox(height: 40), + JdButton(text: "增加", color: Colors.red,onTop: () async{ + + List userinfo=await UserServices.getUserInfo(); + + print(userinfo); + + + // print('1234'); + var tempJson={ + "uid":userinfo[0]["_id"], + "name":this.name, + "phone":this.phone, + "address":this.address, + "salt":userinfo[0]["salt"] + }; + + var sign=SignServices.getSign(tempJson); + // print(sign); + + var api = '${Config.domain}api/addAddress'; + var result = await Dio().post(api,data:{ + "uid":userinfo[0]["_id"], + "name":this.name, + "phone":this.phone, + "address":this.address, + "sign":sign + }); + + // if(result.data["success"]){ + + // } + Navigator.pop(context); + + + + }) + ], + ), + )); + } +} diff --git a/lib/pages/Address/AddressEdit.dart b/lib/pages/Address/AddressEdit.dart new file mode 100644 index 0000000..29c5586 --- /dev/null +++ b/lib/pages/Address/AddressEdit.dart @@ -0,0 +1,155 @@ +import 'package:flutter/material.dart'; + +import '../../widget/JdText.dart'; + +import '../../widget/JdButton.dart'; + +import 'package:city_pickers/city_pickers.dart'; + +import '../../services/UserServices.dart'; +import '../../services/SignServices.dart'; + +import '../../config/Config.dart'; +import 'package:dio/dio.dart'; + +import '../../services/EventBus.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class AddressEditPage extends StatefulWidget { + Map arguments; + AddressEditPage({Key key,this.arguments}) : super(key: key); + + _AddressEditPageState createState() => _AddressEditPageState(); +} + +class _AddressEditPageState extends State { + + String area=''; + TextEditingController nameController=new TextEditingController(); + TextEditingController phoneController=new TextEditingController(); + TextEditingController addressController=new TextEditingController(); + + @override + void initState() { + // TODO: implement initState + super.initState(); + + // print(widget.arguments); + + nameController.text=widget.arguments['name']; + phoneController.text=widget.arguments['phone']; + addressController.text=widget.arguments['address']; + } + //监听页面销毁的事件 + dispose(){ + super.dispose(); + eventBus.fire(new AddressEvent('增加成功...')); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("修改收货地址"), + ), + body: Container( + padding: EdgeInsets.all(10), + child: ListView( + children: [ + SizedBox(height: 20), + JdText( + controller: nameController, + text: "收货人姓名", + onChanged: (value){ + nameController.text=value; + }, + ), + SizedBox(height: 10), + JdText( + controller: phoneController, + text: "收货人电话", + onChanged: (value){ + phoneController.text=value; + }, + ), + SizedBox(height: 10), + Container( + padding: EdgeInsets.only(left: 5), + height: ScreenUtil().setHeight(68), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(width: 1, color: Colors.black12))), + child: InkWell( + child: Row( + children: [ + Icon(Icons.add_location), + this.area.length>0?Text('${this.area}', style: TextStyle(color: Colors.black54)):Text('省/市/区', style: TextStyle(color: Colors.black54)) + ], + ), + onTap: () async{ + Result result = await CityPickers.showCityPicker( + context: context, + locationCode: "130102", + cancelWidget: + Text("取消", style: TextStyle(color: Colors.blue)), + confirmWidget: + Text("确定", style: TextStyle(color: Colors.blue)) + ); + + // print(result); + setState(() { + this.area= "${result.provinceName}/${result.cityName}/${result.areaName}"; + }); + }, + ), + ), + SizedBox(height: 10), + JdText( + controller: addressController, + text: "详细地址", + maxLines: 4, + height: 200, + onChanged: (value){ + addressController.text=value; + }, + ), + SizedBox(height: 10), + SizedBox(height: 40), + JdButton(text: "修改", color: Colors.red,onTop: () async{ + + List userinfo=await UserServices.getUserInfo(); + + + var tempJson={ + "uid":userinfo[0]["_id"], + "id":widget.arguments["id"], + "name": nameController.text, + "phone":phoneController.text, + "address":addressController.text, + "salt":userinfo[0]["salt"] + }; + + var sign=SignServices.getSign(tempJson); + // print(sign); + + var api = '${Config.domain}api/editAddress'; + var response = await Dio().post(api,data:{ + "uid":userinfo[0]["_id"], + "id":widget.arguments["id"], + "name": nameController.text, + "phone":phoneController.text, + "address":addressController.text, + "sign":sign + }); + + print(response); + Navigator.pop(context); + + + }) + ], + ), + ) + ); + } +} \ No newline at end of file diff --git a/lib/pages/Address/AddressList.dart b/lib/pages/Address/AddressList.dart new file mode 100644 index 0000000..d45c293 --- /dev/null +++ b/lib/pages/Address/AddressList.dart @@ -0,0 +1,256 @@ +import 'package:flutter/material.dart'; + +import '../../services/UserServices.dart'; +import '../../services/SignServices.dart'; + +import '../../config/Config.dart'; +import 'package:dio/dio.dart'; + +import '../../services/EventBus.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + + +class AddressListPage extends StatefulWidget { + AddressListPage({Key key}) : super(key: key); + + _AddressListPageState createState() => _AddressListPageState(); +} + +class _AddressListPageState extends State { + List addressList = []; + + @override + void initState() { + super.initState(); + this._getAddressList(); + + //监听增加收货地址的广播 + eventBus.on().listen((event) { + // print(event.str); + this._getAddressList(); + }); + } + //监听页面销毁的事件 + dispose(){ + super.dispose(); + eventBus.fire(new CheckOutEvent('改收货地址成功...')); + } + + //获取收货地址列表 + _getAddressList() async { + //请求接口 + List userinfo = await UserServices.getUserInfo(); + + var tempJson = {"uid": userinfo[0]['_id'], "salt": userinfo[0]["salt"]}; + + var sign = SignServices.getSign(tempJson); + + var api = + '${Config.domain}api/addressList?uid=${userinfo[0]['_id']}&sign=${sign}'; + + var response = await Dio().get(api); + // print(response.data["result"]); + + setState(() { + this.addressList = response.data["result"]; + }); + } + + //修改默认收货地址 + _changeDefaultAddress(id) async{ + + List userinfo = await UserServices.getUserInfo(); + + var tempJson = {"uid": userinfo[0]['_id'], "id":id,"salt": userinfo[0]["salt"]}; + + var sign = SignServices.getSign(tempJson); + + var api = + '${Config.domain}api/changeDefaultAddress'; + var response = await Dio().post(api,data:{ + "uid": userinfo[0]['_id'], + "id":id, + "sign":sign + }); + Navigator.pop(context); + + } + + //删除收货地址 + + _delAddress(id) async{ + + List userinfo=await UserServices.getUserInfo(); + var tempJson={ + "uid":userinfo[0]["_id"], + "id":id, + "salt":userinfo[0]["salt"] + }; + + var sign=SignServices.getSign(tempJson); + + var api = '${Config.domain}api/deleteAddress'; + var response = await Dio().post(api,data:{ + "uid":userinfo[0]["_id"], + "id":id, + "sign":sign + }); + this._getAddressList(); //删除收货地址完成后重新获取列表 + + } + + //弹出框 + _showDelAlertDialog(id) async{ + + var result= await showDialog( + barrierDismissible:false, //表示点击灰色背景的时候是否消失弹出框 + context:context, + builder: (context){ + return AlertDialog( + title: Text("提示信息!"), + content:Text("您确定要删除吗?") , + actions: [ + FlatButton( + child: Text("取消"), + onPressed: (){ + Navigator.pop(context); + }, + ), + FlatButton( + child: Text("确定"), + onPressed: () async{ + //执行删除操作 + this._delAddress(id); + Navigator.pop(context); + + }, + ) + ], + + ); + } + ); + } + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("收货地址列表"), + ), + body: Container( + child: Stack( + children: [ + ListView.builder( + itemCount: this.addressList.length, + itemBuilder: (context, index) { + if (this.addressList[index]["default_address"] == 1) { + return Column( + children: [ + SizedBox(height: 20), + ListTile( + leading: Icon(Icons.check, color: Colors.red), + title: InkWell( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${this.addressList[index]["name"]} ${this.addressList[index]["phone"]}"), + SizedBox(height: 10), + Text("${this.addressList[index]["address"]}"), + ]), + + onTap: (){ + + this._changeDefaultAddress(this.addressList[index]["_id"]); + }, + onLongPress: (){ + this._showDelAlertDialog(this.addressList[index]["_id"]); + }, + + ), + trailing: IconButton( + icon:Icon(Icons.edit, color: Colors.blue), + onPressed: (){ + Navigator.pushNamed(context, '/addressEdit',arguments: { + "id":this.addressList[index]["_id"], + "name":this.addressList[index]["name"], + "phone":this.addressList[index]["phone"], + "address":this.addressList[index]["address"], + }); + }, + ), + ), + Divider(height: 20), + ], + ); + } else { + return Column( + children: [ + SizedBox(height: 20), + ListTile( + title:InkWell( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${this.addressList[index]["name"]} ${this.addressList[index]["phone"]}"), + SizedBox(height: 10), + Text("${this.addressList[index]["address"]}"), + ]), + onTap: (){ + this._changeDefaultAddress(this.addressList[index]["_id"]); + + }, + onLongPress: (){ + this._showDelAlertDialog(this.addressList[index]["_id"]); + }, + ), + trailing: IconButton( + icon:Icon(Icons.edit, color: Colors.blue), + onPressed: (){ + Navigator.pushNamed(context, '/addressEdit',arguments: { + "id":this.addressList[index]["_id"], + "name":this.addressList[index]["name"], + "phone":this.addressList[index]["phone"], + "address":this.addressList[index]["address"], + }); + }, + ), + ), + Divider(height: 20), + ], + ); + } + }, + ), + Positioned( + bottom: 0, + width: ScreenUtil().setWidth(750), + height: ScreenUtil().setHeight(88), + child: Container( + padding: EdgeInsets.all(5), + width: ScreenUtil().setWidth(750), + height: ScreenUtil().setHeight(88), + decoration: BoxDecoration( + color: Colors.red, + border: Border( + top: BorderSide(width: 1, color: Colors.black26))), + child: InkWell( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.add, color: Colors.white), + Text("增加收货地址", style: TextStyle(color: Colors.white)) + ], + ), + onTap: () { + Navigator.pushNamed(context, '/addressAdd'); + }, + ), + ), + ) + ], + ), + )); + } +} diff --git a/lib/pages/Login/FaceLogin.dart b/lib/pages/Login/FaceLogin.dart new file mode 100644 index 0000000..deaa97e --- /dev/null +++ b/lib/pages/Login/FaceLogin.dart @@ -0,0 +1,151 @@ +import 'package:flutter/material.dart'; +import '../../services/EventBus.dart'; +import 'package:camera/camera.dart'; +import '../../components/commonFun.dart'; +import 'dart:io'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + + +class FaceLogin extends StatefulWidget { + FaceLogin({this.arguments, Key key}) : super(key: key); + var arguments; + + _LoginPageState createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + Future camerasInit() async { + try { + WidgetsFlutterBinding.ensureInitialized(); + cameras = await availableCameras(); + print(cameras.toString()); + } on CameraException catch (e) { + //logError(e.code, e.description); + } + } + + @override + void initState() { + super.initState(); + camerasInit(); + } + + //监听登录页面销毁的事件 + dispose() { + super.dispose(); + eventBus.fire(new UserEvent('登录成功...')); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + // appBar: AppBar( + // centerTitle: true, + // title: Text('人脸验证登录'), + // ), + body: Container( + padding: EdgeInsets.all(ScreenUtil().setWidth(20)), + child: ListView( + children: [ + Center( + child: Container( + margin: EdgeInsets.only(top: 30), + height: ScreenUtil().setWidth(160), + width: ScreenUtil().setWidth(160), + child: Image.asset('assets/images/ybsthbj.png', fit: BoxFit.fitHeight), + ), + ), + //SizedBox(height: 30), + Center( + child: Container( + //margin: EdgeInsets.only(top: 30), + height: ScreenUtil().setWidth(800), + width: ScreenUtil().setWidth(800), + child: FlatButton( + onPressed: () { + Navigator.pushNamed(context, '/faceLogin_take_pictuer', arguments: 'FaceLogin'); + }, + //已经为图片header1.png添加圆形边框 + child: Stack( + children: [ + Align( + alignment: Alignment.center, + child: Container( + child: Image.asset('assets/images/header1.png', fit: BoxFit.cover), + ), + ), + Align( + alignment: Alignment.center, + child: Container( + alignment: Alignment(0, 0), + //192像素的图片显示出来为202像素,200 = 192 + 8 + height: 200, + width: 200, + decoration: BoxDecoration( + //color: Colors.black, + shape: BoxShape.circle, + border: Border.all(color: Color.fromRGBO(88, 126, 211, 1), width: 5.0), + // borderRadius: BorderRadius.all( + // Radius.circular(400), + // ), + ), + ), + ), + ], + ), + ), + ), + ), + SizedBox( + height: 20, + ), + // Container( + // alignment: Alignment(0, 0), + // width: 200, + // height: 230, + // decoration: BoxDecoration( + // border: Border.all(color: Colors.orange, width: 1.0), + // ), + // child: _getImage(widget.arguments), + // ), + ], + ), + ), + ); + } + + //定义一个组件显示图片 + Widget _getImage(String filePath) { + if (null == filePath) { + return Text("请选择图片..."); + } + File _image = File(filePath); + //return Image.file(_image); + + int imageWidth; + int imageHeight; + // 预先获取图片信息 + Image image = Image.file(File.fromUri(Uri.parse(filePath))); + image.image + .resolve(new ImageConfiguration()) + .addListener(new ImageStreamListener((ImageInfo info, bool _) { + imageWidth = info.image.width; + imageHeight = info.image.height; + })); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + //480*720 + Text('宽高:${imageWidth}x${imageHeight}', style: TextStyle(fontSize: 17.0)), + Container( + width: imageWidth / 4, + height: imageHeight / 4, + decoration: BoxDecoration( + image: DecorationImage(image: AssetImage(filePath), fit: BoxFit.cover), + ), + ) + ], + ); + } +} diff --git a/lib/pages/Login/FaceLogin2.dart b/lib/pages/Login/FaceLogin2.dart new file mode 100644 index 0000000..0b69e8a --- /dev/null +++ b/lib/pages/Login/FaceLogin2.dart @@ -0,0 +1,239 @@ +import 'package:flutter/material.dart'; +import 'package:hyzp_ybqx/pages/Works/TJXX/tj_data.dart'; +import 'package:hyzp_ybqx/widget/JdButton.dart'; +import '../../services/EventBus.dart'; +import 'package:camera/camera.dart'; +import '../../components/commonFun.dart'; +import 'dart:io'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class FaceLogin2 extends StatefulWidget { + FaceLogin2({this.arguments, Key key}) : super(key: key); + var arguments; + + _LoginPageState createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + //try_setState(); //避免如下异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + Future camerasInit() async { + try { + WidgetsFlutterBinding.ensureInitialized(); + cameras = await availableCameras(); + print(cameras.toString()); + } on CameraException catch (e) { + //logError(e.code, e.description); + } + } + + @override + void initState() { + super.initState(); + camerasInit(); + + //监听统计数据改变事件 + eventBus.on().listen((event) { + print(event.str); + updateMayLogin(); + }); + } + + //处理延迟登录 + updateMayLogin() { + //判断从网络获取三种统计数据是否完成 + // if (listZptjStatisAlone.length >= dwSum && + // listTodayShtj.length >= dwSum && + // listClltjStatisAlone.length >= dwSum) { + // bMayLogin = true; + // } + if (listAllStatisData.length >= dwSum) { + bMayLogin = true; + try_setState(); + } + } + + //监听登录页面销毁的事件 + dispose() { + super.dispose(); + eventBus.fire(new UserEvent('登录成功...')); + } + + double _heigth = 340; + + @override + Widget build(BuildContext context) { + return Scaffold( + // appBar: AppBar( + // centerTitle: true, + // title: Text('人脸验证登录'), + // ), + body: Container( + padding: EdgeInsets.only( + left: ScreenUtil().setWidth(60), + right: ScreenUtil().setWidth(60), + top: ScreenUtil().setWidth(58)), + child: ListView( + children: [ + InkWell( + child: Center( + child: Container( + //margin: EdgeInsets.only(top: ScreenUtil().setHeight(40)), + height: ScreenUtil().setHeight(_heigth), //这样在不同的手机上会变形 + width: ScreenUtil().setWidth(_heigth), + // height: _heigth, + // width: _heigth, + color: Colors.black12, + child: Stack( + children: [ + Align( + alignment: Alignment.center, + child: Image.asset('assets/images/图层 5.png', fit: BoxFit.cover), + ), + Align( + alignment: Alignment.center, + child: Column( + children: [ + SizedBox(height: ScreenUtil().setHeight(_heigth / 3)), + Container( + height: ScreenUtil().setHeight(6), + color: Color.fromRGBO(53, 136, 240, 1), + ), + ], + ), + ), + Align( + alignment: Alignment.bottomCenter, + child: Container( + height: ScreenUtil().setHeight(_heigth - _heigth / 3 - 6), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color.fromRGBO(222, 237, 255, 1), + Color.fromRGBO(255, 255, 255, 0), + ], + ), + ), + )), + ], + ), + ), + ), + onTap: () { + if (!bMayLogin) { + return; + } + Navigator.pushNamed(context, '/faceLogin_take_pictuer', arguments: 'FaceLogin'); + }, + ), + SizedBox(height: ScreenUtil().setHeight(40)), + Container( + height: ScreenUtil().setWidth(130), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _getImageWidget('assets/images/形状 811.png', '正对手机'), + _getImageWidget('assets/images/形状 810.png', '光线充足'), + _getImageWidget('assets/images/形状 809.png', '放慢动作'), + ], + ), + ), + SizedBox(height: ScreenUtil().setHeight(20)), + InkWell( + onTap: () { + if (!bMayLogin) { + return; + } + Navigator.pushNamed(context, '/faceLogin_take_pictuer', arguments: 'FaceLogin'); + }, + child: Container( + alignment: Alignment(0, 0), + margin: EdgeInsets.all(5), + padding: EdgeInsets.all(5), + width: ScreenUtil().setWidth(999), + height: ScreenUtil().setHeight(126), + decoration: BoxDecoration( + color: Color.fromRGBO(23, 176, 91, 1), borderRadius: BorderRadius.circular(10)), + child: Text( + bMayLogin ? "开始检测" : "加载中 . . .", + style: TextStyle(color: Colors.white, fontSize: 18), + ), + ), + ), + // JdButton( + // height: 126, + // //JdText中已经使用ScreenUtil().setHeight(126),此处不能传 ScreenUtil().setHeight(126) ,否则严重错位 + // width: 999, + // text: "开始检测", + // color: Color.fromRGBO(23, 176, 91, 1), + // onTop: () { + // if (!bMayLogin) { + // return; + // } + // Navigator.pushNamed(context, '/faceLogin_take_pictuer', arguments: 'FaceLogin'); + // }, + // ), + ], + ), + ), + ); + } + + // 定义一个图文组件 + Widget _getImageWidget(String imagePath, String text) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + height: ScreenUtil().setHeight(60), + child: Image.asset(imagePath, fit: BoxFit.cover), + ), + Text(text, style: TextStyle(fontSize: 14)), + ], + ); + } + + //定义一个组件显示图片 + Widget _getImage(String filePath) { + if (null == filePath) { + return Text("请选择图片..."); + } + File _image = File(filePath); + //return Image.file(_image); + + int imageWidth; + int imageHeight; + // 预先获取图片信息 + Image image = Image.file(File.fromUri(Uri.parse(filePath))); + image.image + .resolve(new ImageConfiguration()) + .addListener(new ImageStreamListener((ImageInfo info, bool _) { + imageWidth = info.image.width; + imageHeight = info.image.height; + })); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + //480*720 + Text('宽高:${imageWidth}x${imageHeight}', style: TextStyle(fontSize: 17.0)), + Container( + width: imageWidth / 4, + height: imageHeight / 4, + decoration: BoxDecoration( + image: DecorationImage(image: AssetImage(filePath), fit: BoxFit.cover), + ), + ) + ], + ); + } +} diff --git a/lib/pages/Login/FaceReg.dart b/lib/pages/Login/FaceReg.dart new file mode 100644 index 0000000..fd0c06d --- /dev/null +++ b/lib/pages/Login/FaceReg.dart @@ -0,0 +1,399 @@ +import 'dart:io'; + +import 'package:camera/camera.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_drag_scale/core/drag_scale_widget.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hyzp_ybqx/components/customDialogFaceReg.dart'; +import 'package:hyzp_ybqx/components/dioFun.dart'; +import 'package:image_picker/image_picker.dart'; + +import '../../components/commonFun.dart'; +import '../../services/EventBus.dart'; + +class FaceReg extends StatefulWidget { + FaceReg({this.arguments, Key key}) : super(key: key); + var arguments; + + _LoginPageState createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + //try_setState(); //避免异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + Future camerasInit() async { + try { + WidgetsFlutterBinding.ensureInitialized(); + cameras = await availableCameras(); + print(cameras.toString()); + } on CameraException catch (e) { + //logError(e.code, e.description); + } + } + + String imagePath = ''; + Size imageSize; + String _username; + //人脸注册页面,这里建议自动填入当前登录用户名 + TextEditingController _controller = TextEditingController(text: g_userInfo.username); + + @override + void initState() { + camerasInit(); + + //监听人脸注册数据更新事件 + eventBus.on().listen((event) async { + print(event.str); + try_setState(); + }); + + super.initState(); + } + + //监听登录页面销毁的事件 + dispose() { + super.dispose(); + eventBus.fire(new UserEvent('登录成功...')); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: PreferredSize( + preferredSize: Size.fromHeight(ScreenUtil().setHeight(173)), // 设置appBar高度 + // 设置appBar高度 + child: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + titleSpacing: 0.0, + //设置title的左边距 + flexibleSpace: Container( + //SizedBox(height: ScreenUtil().statusBarHeight), //显示顶部状态栏 + // SizedBox(height: ScreenUtil().setHeight(10)), //显示顶部状态栏 + padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight), //留出顶部状态栏高度 + child: Container( + //height: ScreenUtil().setHeight(173), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + // decoration: BoxDecoration( + // gradient: LinearGradient(colors: [ + // Color(0xFF0018EB), + // Color(0xFF01C1D9), + // ], begin: Alignment.bottomCenter, end: Alignment.topCenter), + // ), + ), + ), + title: Padding( + padding: EdgeInsets.only(left: 0, right: 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + getIconAndTextButton( + iconColor: Colors.white, + iconData: Icons.chevron_left_outlined, + onPress: () { + Navigator.pop(context); + }, + ), + Expanded( + child: Text("人脸注册", + style: TextStyle(color: Colors.white, fontSize: 20), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis), + ), + SizedBox(width: 50), + ], + ), + ), + ), + ), + body: Container( + padding: EdgeInsets.all(ScreenUtil().setWidth(20)), + child: ListView( + children: [ + //解决 Flutter ListView 子元素 无限宽度 的问题 + Container( + alignment: Alignment.topCenter, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Center( + child: Container( + margin: EdgeInsets.only(top: 20, bottom: 5, left: 0, right: 0), + height: ScreenUtil().setHeight(200), + width: ScreenUtil().setWidth(260), + child: Image.asset('assets/images/ybsthbj.png', fit: BoxFit.fitHeight), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(width: 10), + Container( + padding: EdgeInsets.only(top: 16), + child: Text('用户名: ', style: TextStyle(fontSize: 18)), + ), + Container( + height: ScreenUtil().setHeight(160), + width: ScreenUtil().setWidth(500), + child: TextField( + //textAlign: TextAlign.right, + textAlignVertical: TextAlignVertical.center, + maxLines: 1, + style: TextStyle(fontSize: 18), + decoration: InputDecoration( + hintText: '請輸入用户名', + //border: InputBorder.none, //TextField去掉下划线 + contentPadding: EdgeInsets.only(right: 0), + enabledBorder: new UnderlineInputBorder( + borderSide: BorderSide(color: Colors.blue)), + focusedBorder: new UnderlineInputBorder( + borderSide: BorderSide(color: Colors.blue)), + ), + controller: _controller, + //利用控制器初始化文本 + onChanged: (value) { + _username = value; + }, + ), + ), + SizedBox(width: 10), + Container( + width: ScreenUtil().setWidth(220), + padding: EdgeInsets.only(top: 16), + child: FlatButton( + color: Colors.black12, + child: Text('本人', style: TextStyle(color: Colors.blue, fontSize: 18)), + onPressed: () { + _controller.text = g_userInfo.username; + }, + ), + ), + ], + ), + //SizedBox(height: 30), + SizedBox(height: 10), + //480*720 + Text(null == imageSize ? '' : '宽高:${imageSize.width}x${imageSize.height}', + style: TextStyle(fontSize: 18)), + SizedBox(height: 5), + Container( + alignment: Alignment(0, 0), + width: ScreenUtil().setWidth(980), + height: ScreenUtil().setHeight(760), + decoration: BoxDecoration( + border: Border.all(color: Colors.orange, width: 1.0), + borderRadius: BorderRadius.circular(5), + ), + child: _getImage(imagePath), + ), + SizedBox(height: 25), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + getBtnSizeX( + width: 90, + text: "拍照", + onPressedFun: () { + //Navigator.pushNamed(context, '/faceReg_take_pictuer', arguments: 'FaceLogin'); + _username = _controller.text; + Navigator.pushNamed(context, '/faceReg_take_pictuer', + arguments: 'FaceReg') + .then((_imagePath) { + _controller.text = _username; + imagePath = _imagePath as String; + if (imagePath.isNotEmpty) { + print('imagePath = $imagePath'); + //imageSize = Size(480, 720); + getImageSize(imagePath); + //eventBus.fire(FaceRegUpdateEvent('人脸注册数据已更新')); + } + }); + }), + getBtnSizeX( + width: 90, + text: "选择图片", + onPressedFun: () { + //Navigator.pushNamed(context, '/faceReg_take_pictuer', arguments: 'FaceLogin'); + _getImageGallery(); + }), + getBtnSizeX( + width: 90, + text: "人脸注册", + onPressedFun: (_controller.text.isEmpty && imagePath.isEmpty) + ? null + : () { + //人脸注册,username 用户名,filePath 人脸图片路径 + if (_controller.text.isNotEmpty && imagePath.isNotEmpty) { + print('等待人脸注册或更新确认'); + Navigator.of(context) + .push( + PageRouteBuilder( + opaque: false, + pageBuilder: (context, animation, secondaryAnimation) => + CustomDialogFaceReg( + title: '人脸注册或更新确认', + username: _username, + imagePath: imagePath, + imageSize: imageSize, + ), + ), + ) + .then((ret) async { + print('value = $ret'); + if (ret) { + print('用户已确认,开始处理人脸注册或更新!'); + faceRegFun( + username: _username, + filePath: imagePath, + context: context); + } else { + print('用户取消了人脸注册或更新'); + } + }); + } else if (_controller.text.isEmpty) { + Fluttertoast.showToast( + msg: "用户名不能为空!", + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } else if (imagePath.isEmpty) { + Fluttertoast.showToast( + msg: "请选择用户人脸图片!", + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + }), + ], + ), + // RaisedButton( + // child: Text('人脸登录'), + // onPressed: () { + // Navigator.pushNamed(context, '/faceReg_take_pictuer', arguments: 'FaceLogin'); + // }), + SizedBox(height: 10), + ], + ), + ), + ], + )), + ); + } + + //从相册选取图片 + Future _getImageGallery() async { + final imagePicker = ImagePicker(); + _username = _controller.text; + //final pickedFile = await imagePicker.getImage(source: ImageSource.gallery, maxWidth: 400); + imagePicker.getImage(source: ImageSource.gallery, maxWidth: 480).then((pickedFile) { + if (pickedFile != null) { + print('pickedFile = ${pickedFile.path}'); + imagePath = pickedFile.path; + getImageSize(imagePath); + } else { + print('No image selected.'); + } + _controller.text = _username; + }); + } + + //定义一个组件显示图片 + _getImage(String filePath) { + print('filePath = $filePath'); + if (null == imageSize || filePath.isEmpty) { + return Text("注意:\n1、请输入“已注册”的用户名;\n2、请选择包含“用户人脸”的图片;\n3、两样都选好后再进行注册,不然可能失败!"); + } + + double _width = ScreenUtil().setWidth(980); + double _heigth = _width * (imageSize.height / imageSize.width); + + final _image = Image.file(File(filePath)); + + // 预先获取图片信息 + return SingleChildScrollView( + //滑动的方向 Axis.vertical为垂直方向滑动,Axis.horizontal 为水平方向 + scrollDirection: Axis.vertical, + //true 滑动到底部 + reverse: false, + padding: EdgeInsets.all(0.0), + //滑动到底部回弹效果 + physics: BouncingScrollPhysics(), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: _width, + height: _heigth, + // child: PinchZoomImage( //不好用 + // //image: Image.network('https://i.imgur.com/tKg0XEb.jpg'), + // image: _image, + // zoomedBackgroundColor: Color.fromRGBO(240, 240, 240, 1.0), + // hideStatusBarWhileZooming: true, + // onZoomStart: () { + // print('Zoom started'); + // }, + // onZoomEnd: () { + // print('Zoom finished'); + // }, + // ), + + //DragScaleContainer 插件只能放大,不能缩小到比原始尺寸小 + child: DragScaleContainer(doubleTapStillScale: true, child: _image + // child: Image( + // image: NetworkImage( + // 'http://h.hiphotos.baidu.com/zhidao/wh%3D450%2C600/sign=0d023672312ac65c67506e77cec29e27/9f2f070828381f30dea167bbad014c086e06f06c.jpg'), + // ), + ), + //child: _image, + //一个大坑:用 AssetImage(filePath) 方式,首次加载拍照返回的照片,始终报错,刷新后则能够正常加载。 + // 用 Container 的 child 方式解决 + // decoration: BoxDecoration( + // image: DecorationImage(image: AssetImage(filePath), fit: BoxFit.cover), + // ), + ) + ], + ), + ); + } + + // 预先获取图片信息 + Future getImageSize(String filePath) async { + Image image = Image.file(File.fromUri(Uri.parse(filePath))); + image.image + .resolve(new ImageConfiguration()) + .addListener(new ImageStreamListener((ImageInfo info, bool _) { + imageSize = Size( + info.image.width.toDouble(), + info.image.height.toDouble(), + ); + print('imageSize = $imageSize'); + //必须延迟刷新,否则启动App后,第一次进行拍照返回会抛异常,无法显示返回的照片 + //启动App后,第一次进行拍照返回,在 AS Terminal 按 R 刷新可以显示图片。 + //暂存,后续解决 + try_setState(); + // Future.delayed(const Duration(milliseconds: 3000), () { + // //try_setState(); + //eventBus.fire(FaceRegUpdateEvent('人脸注册数据已更新')); //这样刷新有效 + // }); + })); + } +} diff --git a/lib/pages/Login/ForgotPassword.dart b/lib/pages/Login/ForgotPassword.dart new file mode 100644 index 0000000..a0b6ea3 --- /dev/null +++ b/lib/pages/Login/ForgotPassword.dart @@ -0,0 +1,153 @@ +import 'package:flutter/material.dart'; +import '../../components/commonFun.dart'; +import '../../widget/JdText.dart'; +import '../../widget/JdButton.dart'; + +import '../../config/Config.dart'; +import 'package:dio/dio.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import '../../services/Storage.dart'; +import 'dart:convert'; +import '../../components/EncryptUtil.dart'; + +import '../../services/EventBus.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + + +class ForgotPassword extends StatefulWidget { + ForgotPassword({Key key}) : super(key: key); + + _LoginPageState createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + //监听登录页面销毁的事件 + dispose() { + super.dispose(); + eventBus.fire(new UserEvent('登录成功...')); + } + + doLogin() async { + //临时跳转 + Navigator.pushNamed(context, '/', arguments: g_iIndex); + return; + + RegExp reg = new RegExp(r"^1\d{10}$"); + if (!reg.hasMatch(g_userInfo.username)) { + Fluttertoast.showToast( + msg: '手机号格式不对', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } else if (g_userInfo.password.length < 6) { + Fluttertoast.showToast( + msg: '密码不正确', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } else { + var api = '${Config.domain}api/doLogin'; + var response = await Dio().post(api, data: { + "username": g_userInfo.username, + "password": g_userInfo.password + }); + if (response.data["success"]) { + print(response.data); + //保存用户信息 + Storage.setString('userInfo', json.encode(response.data["userinfo"])); + + Navigator.pop(context); + } else { + Fluttertoast.showToast( + msg: '${response.data["message"]}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + } + } + + @override + Widget build(BuildContext context) { + + return Scaffold( + appBar: AppBar( + title: Text("找回密码"), + centerTitle: true, + ), + body: Container( + padding: EdgeInsets.only(top: 20, bottom: 20, left: 20, right: 20), + child: ListView( + children: [ + Center( + child: Container( + margin: EdgeInsets.only(top: 30), + height: ScreenUtil().setWidth(160), + width: ScreenUtil().setWidth(160), + //child: Image.asset('assets/images/user.png', fit: BoxFit.cover), + child: Image.asset('assets/images/ybsthbj.png', fit: BoxFit.fitHeight), + // child: Image.network( + // 'https://www.itying.com/images/flutter/list5.jpg', + // fit: BoxFit.cover), + ), + ), + SizedBox(height: 30), + JdText( + height: ScreenUtil().setHeight(300), + title: '账号:', + text: "请输入账号(手机号)", + onChanged: (String value) { + // print(value); + g_userInfo.username = value; + }, + endBtn: 'ClearBtn', + ), + SizedBox(height: 10), + JdText( + height: ScreenUtil().setHeight(300), + title: '验证码:', + text: "请输入短信验证码", + password: true, + onChanged: (String value) { + // print(value); + g_userInfo.password = value; + }, + endBtn: 'OutlineButton', + ), + SizedBox(height: 10), + JdText( + height: ScreenUtil().setHeight(300), + title: '新密码:', + text: "请输入6-12位新密码", + password: true, + onChanged: (String value) { + // print(value); + g_userInfo.password = value; + }, + endBtn: 'ShowHiddenBtn', + ), + SizedBox(height: 10), + JdText( + height: ScreenUtil().setHeight(300), + title: '新密码:', + text: "请再次输入6-12位新密码", + password: true, + onChanged: (String value) { + // print(value); + g_userInfo.password = value; + }, + endBtn: 'ShowHiddenBtn', + ), + SizedBox(height: 40), + JdButton( + height: ScreenUtil().setHeight(350), + text: "确认", + color: Colors.blueAccent, + onTop: doLogin, + ) + ], + ), + ), + ); + } +} diff --git a/lib/pages/Login/LoginByName.dart b/lib/pages/Login/LoginByName.dart new file mode 100644 index 0000000..15ec4f6 --- /dev/null +++ b/lib/pages/Login/LoginByName.dart @@ -0,0 +1,279 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:hyzp_ybqx/components/UserAuthority.dart'; +import '../../components/commonFun.dart'; +import '../../widget/JdText.dart'; +import '../../widget/JdButton.dart'; + +import '../../config/Config.dart'; +import 'package:dio/dio.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import '../../services/Storage.dart'; +import 'dart:convert'; +import '../../components/EncryptUtil.dart'; + +import '../../services/EventBus.dart'; +import '../../config/service_url.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + + +class LoginByName extends StatefulWidget { + LoginByName({Key key}) : super(key: key); + + _LoginPageState createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + //监听登录页面销毁的事件 + dispose() { + super.dispose(); + eventBus.fire(new UserEvent('登录成功...')); + } + + bool bRemmberPW = false; + + @override + void initState() { + super.initState(); + // g_userInfo.username = 'the_user_03'; + // g_userInfo.password = '123456'; + // g_userInfo.password = 'ybhb1234'; + g_userInfo.username = ''; + g_userInfo.password = ''; + doInit(); + } + + doInit() async { + bRemmberPW = await Storage.getBool('bRemmberPW'); + bRemmberPW = (null == bRemmberPW) ? false : bRemmberPW; + print('bRemmberPW = $bRemmberPW'); + + if (bRemmberPW) { + //取出后需解密 + g_userInfo.username = await Storage.getString('username'); + g_userInfo.username = EncryptUtil.aesDecode(g_userInfo.username); + g_userInfo.password = await Storage.getString('password'); + g_userInfo.password = EncryptUtil.aesDecode(g_userInfo.password); + } + setState(() {}); + } + + doLogin() async { + //测试用,临时绕过登录处理 + // Navigator.pushNamed(context, '/tabs', arguments: g_iIndex); + // return; + + if (bRemmberPW) { + Storage.setBool('bRemmberPW', bRemmberPW); + //加密保存 + Storage.setString('username', EncryptUtil.aesEncode(g_userInfo.username)); + Storage.setString('password', EncryptUtil.aesEncode(g_userInfo.password)); + } else { + Storage.remove('username'); + Storage.remove('password'); + } + + var api = ServicePath.loginUrl; + print(api); + try { + print('开始处理登录请求...'); + print('username = ${g_userInfo.username}'); + print('password = ${g_userInfo.password}'); + Response response; + Dio dio = Dio(); + + String random = RandomBit(6); //flutter (dart)生成N位随机数 + response = await dio.post(api, data: { + "username": g_userInfo.username, + "password": g_userInfo.password, + "sign": GenerateMd5(APPkey + random), + "random": random, + }); + print('response = ${response.toString()}'); + //I/flutter ( 5242): {"ret":200,"data":{"is_login":true,"user_id":3,"token":"32EE57A0109A3D1D6590CFD3DEBA71820F77AB654093C1DE750347C88D1A41CF"},"msg":""} + if (response.statusCode == 200) { + // Storage.setString('userInfo', json.encode(response.data["userinfo"])); + // //Navigator.pop(context); + // Navigator.pushNamed(context, '/tabs', arguments: g_iIndex); + + //print('response.data["data"]["is_login"] = ${response.data["data"]["is_login"]}'); + //I/flutter ( 5242): response.data["data"]["is_login"] = true + + //{ + // "ret": 200, + // "data": { + // "is_login": true, + // "user_id": 1, + // "token": "B93EC91FA2FE293B7077162D4527FC4BB228CD6C0A4F24A882B9A8BBE6C3FB47" + // }, + // "msg": "" + // } + + print('response = ${response}'); + //response = {"ret":406,"data":{},"msg":"非法请求:签名错误"} + if (true == response.data["data"]["is_login"]) { + print('登录成功'); + print('response.data = ${response.data}'); + //保存用户信息 + Storage.setString('userInfo', json.encode(response.data["data"])); + g_userInfo.setUserInfo(theMapUserInfoRet: await getMapFromJson(response.data)); + //获取用户所属分组和相应权限 + getUserGroupAll().then((value) { + //Navigator.pop(context); + Navigator.pushNamed(context, '/tabs', arguments: g_iIndex); + }); + } else { + print('登录失败:${response.data["data"]}'); + Fluttertoast.showToast( + msg: '登录失败:用户名或密码不正确。}', + toastLength: Toast.LENGTH_LONG, + gravity: ToastGravity.BOTTOM, + ); + print('登录失败:${response.data["data"]}'); + } + print('登录过程正常完成'); + } else { + throw Exception('后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('登录过程异常...'); + print('ERROR:======>${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + + return; + } + + @override + Widget build(BuildContext context) { + + + //FocusScope.of(context).requestFocus(FocusNode()); + //FocusScope.of(context).unfocus(); + + return Scaffold( + body: Container( + padding: EdgeInsets.only(top: 20, bottom: 20, left: 20, right: 20), + child: ListView( + children: [ + Center( + child: Container( + margin: EdgeInsets.only(top: 30), + height: ScreenUtil().setWidth(160), + width: ScreenUtil().setWidth(160), + //child: Image.asset('assets/images/user.png', fit: BoxFit.cover), + child: Image.asset('assets/images/ybsthbj.png', fit: BoxFit.fitHeight), + // child: Image.network( + // 'https://www.itying.com/images/flutter/list5.jpg', + // fit: BoxFit.cover), + ), + ), + SizedBox(height: 30), + JdText( + height: ScreenUtil().setHeight(300), + title: '账号:', + text: "请输入账号", + onChanged: (String value) { + // print(value); + g_userInfo.username = value; + }, + endBtn: 'ClearBtn', + controller: TextEditingController(text: g_userInfo.username), + ), + SizedBox(height: 10), + JdText( + height: ScreenUtil().setHeight(300), + title: '密码:', + text: "请输入密码", + password: true, + onChanged: (String value) { + // print(value); + g_userInfo.password = value; + }, + endBtn: 'ShowHiddenBtn', + controller: TextEditingController(text: g_userInfo.password), + ), + SizedBox(height: 10), + Container( + padding: EdgeInsets.all(ScreenUtil().setWidth(20)), + child: Stack( + children: [ + // Align( + // alignment: Alignment.centerLeft, + // child: InkWell( + // onTap: () { + // Navigator.pushNamed(context, '/forgotPassword'); + // }, + // child: Text('忘记密码'), + // ), + // ), + Align( + alignment: Alignment.topRight, + child: InkWell( + onTap: () { + Navigator.pushNamed(context, '/registerFirst'); + }, + //child: Text('记住密码'), + child: Container( + alignment: Alignment(1, -1), + width: 150, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('记住密码:'), + Container( + alignment: Alignment(1, -1), + height: 22, + width: 22, + child: Checkbox( + value: bRemmberPW, + activeColor: Colors.blue, + onChanged: (bool val) { + this.setState(() { + bRemmberPW = !bRemmberPW; + }); + Storage.setBool('bRemmberPW', bRemmberPW); + }, + ), + ), + ], + ), + ), + ), + ), + // Align( + // alignment: Alignment.centerRight, + // child: InkWell( + // onTap: () { + // Navigator.pushNamed(context, '/registerFirst'); + // }, + // child: Text( + // '新用户注册', + // style: TextStyle( + // // 创建 paint 对象,设置 color 属性为想要的颜色 + // background: Paint()..color = Color.fromRGBO(218, 218, 218, 1)), + // ), + // ), + // ), + ], + ), + ), + SizedBox(height: 20), + JdButton( + height: ScreenUtil().setHeight(350), + text: "登录", + color: Colors.blueAccent, + onTop: doLogin, + ) + ], + ), + ), + ); + } +} diff --git a/lib/pages/Login/LoginByName2.dart b/lib/pages/Login/LoginByName2.dart new file mode 100644 index 0000000..4dc1e4c --- /dev/null +++ b/lib/pages/Login/LoginByName2.dart @@ -0,0 +1,328 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hyzp_ybqx/components/UserAuthority.dart'; +import 'package:hyzp_ybqx/pages/Works/TJXX/tj_data.dart'; + +import '../../components/EncryptUtil.dart'; +import '../../components/commonFun.dart'; +import '../../config/Config.dart'; +import '../../config/service_url.dart'; +import '../../services/EventBus.dart'; +import '../../services/Storage.dart'; +import '../../widget/JdButton.dart'; +import '../../widget/JdText.dart'; + +class LoginByName2 extends StatefulWidget { + LoginByName2({Key key, this.height}) : super(key: key); + double height; + + _LoginPageState createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + //try_setState(); //避免如下异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + //监听登录页面销毁的事件 + dispose() { + super.dispose(); + eventBus.fire(new UserEvent('登录成功...')); + } + + bool bRemmberPW = false; + + @override + void initState() { + super.initState(); + // g_userInfo.username = 'the_user_03'; + // g_userInfo.password = '123456'; + // g_userInfo.password = 'ybhb1234'; + g_userInfo.username = ''; + g_userInfo.password = ''; + doInit(); + + //监听统计数据改变事件 + eventBus.on().listen((event) { + print(event.str); + updateMayLogin(); + }); + } + + //处理延迟登录 + updateMayLogin() { + //判断从网络获取三种统计数据是否完成 + // if (listZptjStatisAlone.length >= dwSum && + // listTodayShtj.length >= dwSum && + // listClltjStatisAlone.length >= dwSum) { + // bMayLogin = true; + // } + if (listAllStatisData.length >= dwSum) { + bMayLogin = true; + } + + if (bMayLogin && bLoginVerify) { + //重新初始化处理延时登录的变量 + // bMayLogin = false; + // bPreLoading = false; + // bLoginVerify = false; //处理延时登录,判断用户名登录是否验证通过 + + Navigator.pushNamed(context, '/tabs', arguments: g_iIndex); + // Future.delayed(const Duration(milliseconds: 1000), () { + // }); + } + } + + doInit() async { + bRemmberPW = await Storage.getBool('bRemmberPW'); + bRemmberPW = (null == bRemmberPW) ? false : bRemmberPW; + print('bRemmberPW = $bRemmberPW'); + + if (bRemmberPW) { + //取出后需解密 + g_userInfo.username = await Storage.getString('username'); + g_userInfo.username = EncryptUtil.aesDecode(g_userInfo.username); + g_userInfo.password = await Storage.getString('password'); + g_userInfo.password = EncryptUtil.aesDecode(g_userInfo.password); + } + try_setState(); + } + + doLogin() async { + //测试用,临时绕过登录处理 + // Navigator.pushNamed(context, '/tabs', arguments: g_iIndex); + // return; + + if (!bMayLogin) { + bPreLoading = true; + try_setState(); + } + + if (bRemmberPW) { + Storage.setBool('bRemmberPW', bRemmberPW); + //加密保存 + Storage.setString('username', EncryptUtil.aesEncode(g_userInfo.username)); + Storage.setString('password', EncryptUtil.aesEncode(g_userInfo.password)); + } else { + Storage.remove('username'); + Storage.remove('password'); + } + + var api = ServicePath.loginUrl; + print(api); + try { + print('开始处理登录请求...'); + print('username = ${g_userInfo.username}'); + print('password = ${g_userInfo.password}'); + Response response; + Dio dio = Dio(); + + String random = RandomBit(6); //flutter (dart)生成N位随机数 + response = await dio.post(api, data: { + "username": g_userInfo.username, + "password": g_userInfo.password, + "sign": GenerateMd5(APPkey + random), + "random": random, + }); + print('response = ${response.toString()}'); + //I/flutter ( 5242): {"ret":200,"data":{"is_login":true,"user_id":3,"token":"32EE57A0109A3D1D6590CFD3DEBA71820F77AB654093C1DE750347C88D1A41CF"},"msg":""} + if (response.statusCode == 200) { + // Storage.setString('userInfo', json.encode(response.data["userinfo"])); + // //Navigator.pop(context); + // Navigator.pushNamed(context, '/tabs', arguments: g_iIndex); + + //print('response.data["data"]["is_login"] = ${response.data["data"]["is_login"]}'); + //I/flutter ( 5242): response.data["data"]["is_login"] = true + + //{ + // "ret": 200, + // "data": { + // "is_login": true, + // "user_id": 1, + // "token": "B93EC91FA2FE293B7077162D4527FC4BB228CD6C0A4F24A882B9A8BBE6C3FB47" + // }, + // "msg": "" + // } + + print('response = ${response}'); + //response = {"ret":406,"data":{},"msg":"非法请求:签名错误"} + if (true == response.data["data"]["is_login"]) { + print('登录成功'); + print('response.data = ${response.data}'); + //保存用户信息 + Storage.setString('userInfo', json.encode(response.data["data"])); + g_userInfo.setUserInfo(theMapUserInfoRet: await getMapFromJson(response.data)); + //获取用户所属分组和相应权限 + getUserGroupAll().then((value) { + bLoginVerify = true; //处理延时登录,判断用户名登录是否验证通过 + if (bMayLogin) { + // bMayLogin = false; + // bPreLoading = false; + // bLoginVerify = false; //处理延时登录,判断用户名登录是否验证通过 + Navigator.pushNamed(context, '/tabs', arguments: g_iIndex); + } + }); + } else { + print('登录失败:${response.data["data"]}'); + bLoginVerify = false; //处理延时登录,判断用户名登录是否验证通过 + bPreLoading = false; + Fluttertoast.showToast( + msg: '登录失败:用户名或密码不正确。', + toastLength: Toast.LENGTH_LONG, + gravity: ToastGravity.BOTTOM, + ); + print('登录失败:${response.data["data"]}'); + } + print('登录过程正常完成'); + } else { + throw Exception('后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('登录过程异常...'); + print('ERROR:======>${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + + return; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + //resizeToAvoidBottomPadding: false, //解决输入法键盘弹出越界问题-无效 + backgroundColor: Colors.transparent, + body: Container( + height: widget.height, + child: Column( + children: [ + Container(color: Colors.transparent, height: ScreenUtil().setHeight(45)), + Container( + color: Colors.white, + width: double.infinity, + height: ScreenUtil().setHeight(380), + padding: EdgeInsets.only(top: 10, bottom: 20, left: 20, right: 20), + child: Column( + children: [ + JdText( + height: 126, + //JdText中已经使用ScreenUtil().setHeight(126),此处不能传 ScreenUtil().setHeight(126) ,否则严重错位 + title: '用户名:', + text: "请输入用户名", + onChanged: (String value) { + // print(value); + g_userInfo.username = value; + }, + endBtn: 'ClearBtn', + controller: TextEditingController(text: g_userInfo.username), + ), + Container(color: Colors.transparent, height: ScreenUtil().setHeight(30)), + JdText( + height: 126, + //JdText中已经使用ScreenUtil().setHeight(126),此处不能传 ScreenUtil().setHeight(126) ,否则严重错位 + title: '密 码:', + text: "请输入密码", + password: true, + onChanged: (String value) { + g_userInfo.password = value; + }, + endBtn: 'ShowHiddenBtn', + controller: TextEditingController(text: g_userInfo.password), + ), + ], + ), + ), + Container(color: Colors.transparent, height: ScreenUtil().setHeight(30)), + Container( + //color: Colors.transparent, + padding: EdgeInsets.all(ScreenUtil().setWidth(10)), + child: Row( + children: [ + SizedBox(width: ScreenUtil().setWidth(45)), + InkWell( + onTap: () { + //Navigator.pushNamed(context, '/registerFirst'); + this.setState(() { + bRemmberPW = !bRemmberPW; + }); + Storage.setBool('bRemmberPW', bRemmberPW); + }, + child: Container( + alignment: Alignment(1, -1), + //width: 150, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + alignment: Alignment(0, 0), + height: 22, + width: 22, + padding: EdgeInsets.only(top: ScreenUtil().setHeight(3)), + // child: Checkbox( + // value: bRemmberPW, + // activeColor: Colors.blue, + // onChanged: (bool val) { + // this.setState(() { + // bRemmberPW = !bRemmberPW; + // }); + // Storage.setBool('bRemmberPW', bRemmberPW); + // }, + // ), + child: bRemmberPW + ? Icon(Icons.check_box, color: Colors.blue) + : Icon(Icons.check_box_outline_blank, color: Colors.white), + ), + SizedBox(width: ScreenUtil().setWidth(15)), + Text('记住密码', style: TextStyle(fontSize: 17, color: Colors.white)), + ], + ), + ), + ), + ], + ), + ), + SizedBox(height: ScreenUtil().setHeight(48)), + InkWell( + onTap: doLogin, + child: Container( + alignment: Alignment(0, 0), + margin: EdgeInsets.all(5), + padding: EdgeInsets.all(5), + width: ScreenUtil().setWidth(999), + height: ScreenUtil().setHeight(126), + decoration: BoxDecoration( + color: Color.fromRGBO(23, 176, 91, 1), borderRadius: BorderRadius.circular(10)), + child: Text( + bPreLoading ? "加载中 . . ." : "登 录", + style: TextStyle(color: Colors.white, fontSize: 18), + ), + ), + ), + // JdButton( + // height: 126, + // //JdText中已经使用ScreenUtil().setHeight(126),此处不能传 ScreenUtil().setHeight(126) ,否则严重错位 + // width: 999, + // text: "登 录", + // color: Color.fromRGBO(23, 176, 91, 1), + // onTop: doLogin, + // ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/Login/LoginTabs.dart b/lib/pages/Login/LoginTabs.dart new file mode 100644 index 0000000..62d4ef0 --- /dev/null +++ b/lib/pages/Login/LoginTabs.dart @@ -0,0 +1,105 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import '../../components/commonFun.dart'; +import '../../services/Storage.dart'; +import 'FaceLogin.dart'; +import 'LoginByName2.dart'; + +// class LoginTabs extends StatelessWidget { +// // This widget is the root of your application. +// @override +// Widget build(BuildContext context) { +// //Flutter 强制竖屏 +// SystemChrome.setPreferredOrientations([ +// DeviceOrientation.portraitUp, //只能纵向 +// DeviceOrientation.portraitDown, //只能纵向 +// ]); +// +// return MaterialApp( +// debugShowCheckedModeBanner: false, +// title: 'Flutter Demo', +// theme: ThemeData( +// primarySwatch: Colors.blue, +// visualDensity: VisualDensity.adaptivePlatformDensity, +// ), +// home: MyHomePage(title: 'Flutter Demo Home Page'), +// ); +// } +// } + +class LoginTabs extends StatefulWidget { + LoginTabs({Key key, this.title}) : super(key: key); + final String title; + + @override + _MyHomePageState createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + @override + void initState() { + super.initState(); + + //若下面文件不存在, + // /data/data/com.flutter.hyzp_ybqx/shared_prefs/FlutterSharedPreferences.xml + // value为null, 会抛出异常: + // [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: Invalid argument(s): The source must not be null + // int.parse (dart:core-patch/integers_patch.dart:51:25) + Storage.getString("tabs_index").then((value) { + g_iIndex = (null == value) ? 0 : int.parse(value); + }); + } + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("assets/images/背景图.png"), + fit: BoxFit.cover, + ), + ), + child: Container( + width: double.infinity, + height: 400, + child: Container( + constraints: BoxConstraints( + minWidth: double.infinity, //最小宽度尽量取最大 + maxHeight: 400 //最小高度为80 + ), + child: DefaultTabController( + length: 2, + child: Scaffold( + //backgroundColor: Colors.transparent, + appBar: AppBar( + //backgroundColor: Colors.transparent, + title: Text("宜宾市黑烟车电子抓拍系统"), + leading: IconButton( + icon: Icon(Icons.close), + onPressed: () { + SystemNavigator.pop(); + }, + ), + centerTitle: true, + bottom: TabBar( + labelColor: Colors.blueAccent, + unselectedLabelColor: Colors.black26, + tabs: [Tab(text: "密码登录"), Tab(text: "刷脸登录")], + ), + ), + body: TabBarView( + //flutter tabbar禁止手势滑动-OK + physics: new NeverScrollableScrollPhysics(), + children: [ + LoginByName2(), + FaceLogin(), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/Login/LoginTabs2.dart b/lib/pages/Login/LoginTabs2.dart new file mode 100644 index 0000000..4511194 --- /dev/null +++ b/lib/pages/Login/LoginTabs2.dart @@ -0,0 +1,174 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import '../../components/commonFun.dart'; +import 'LoginByName2.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +import 'LoginTabsWidget.dart'; + +const pageData = { + "discountStatus": 2, + "subscribeStatus": "0", + "title": "限时免费", + "subTitle": "活动时间9月1日-9月30日", + "packageList": [ + {"id": 23, "desc": "月度订阅", "dealPrice": 10, "originPrice": 50, "recommand": 1}, + {"id": 33, "desc": "半年订阅", "dealPrice": 56, "originPrice": 280, "recommand": 0}, + {"id": 56, "desc": "年度订阅", "dealPrice": 108, "originPrice": 540, "recommand": 0} + ] +}; + +class LoginTabs2 extends StatefulWidget { + @override + createState() => new LoginTabs2State(); +} + +// ScreenUtil().statusBarHeight + ScreenUtil().setHeight(144) + ScreenUtil().setHeight(348) +// + ScreenUtil().setHeight(36) + height: ScreenUtil().setHeight(166) + ScreenUtil().setHeight(17) +// + ScreenUtil().setHeight(826) +class LoginTabs2State extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: PreferredSize(child: AppBar(), preferredSize: Size.fromHeight(0)), + //使用 SingleChildScrollView 包装一下,否则键盘弹出时会报错空间溢出 + body: SingleChildScrollView( + child: Column( + children: [ + Container( + height: ScreenUtil().screenHeight - + ScreenUtil().statusBarHeight - + ScreenUtil().bottomBarHeight, + width: ScreenUtil().screenWidth, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage("assets/images/背景图.png"), fit: BoxFit.cover)), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + // alignment: WrapAlignment.center, + // crossAxisAlignment: WrapCrossAlignment.center, + // runSpacing: 9.0, + children: [ + Container( + height: ScreenUtil().setHeight(144), + //padding: EdgeInsets.only(top: ScreenUtil().setHeight(10)), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + IconButton( + onPressed: () { + //Navigator.pop(context); + SystemNavigator.pop(); //退出App + }, + icon: const Icon(Icons.close, color: Colors.white), + ) + ], + ), + ), + Center( + child: Container( + margin: EdgeInsets.only(top: ScreenUtil().setHeight(0)), + width: ScreenUtil().setWidth(490), //单位px, + height: ScreenUtil().setHeight(348), + //child: Image.asset('assets/images/user.png', fit: BoxFit.cover), + child: Image.asset('assets/images/图层 2.png', fit: BoxFit.fitHeight), + ), + ), + SizedBox(height: ScreenUtil().setHeight(36)), + Container( + height: ScreenUtil().setHeight(166), + margin: EdgeInsets.all(0), + child: RichText( + maxLines: 2, + textAlign: TextAlign.center, + text: TextSpan(children: [ + TextSpan( + text: '宜宾黑烟车', + style: TextStyle( + fontSize: 26.0, color: Colors.white, fontWeight: FontWeight.bold)), + TextSpan( + text: '抓拍系统', + style: TextStyle( + fontSize: 26.0, + color: Color.fromRGBO(49, 216, 123, 1), + fontWeight: FontWeight.bold)), + TextSpan( + text: '\nYIBIN BLACK SMOKE CAR CAPTURE SYSTEM', + style: + TextStyle(fontSize: 11.0, color: Color.fromRGBO(101, 117, 142, 1))), + ]), + ), + ), + SizedBox(height: ScreenUtil().setHeight(17)), + Container( + height: ScreenUtil().setHeight(945), + child: LoginTabsWidget(), + ), + Container(color: Colors.transparent, height: ScreenUtil().setHeight(30)), + Container( + color: Colors.transparent, + height: ScreenUtil().setHeight(130), //不能超过133,否则有些手机会越界 + child: Text( + //'© 宜宾市生态环境局\n© 四川省踏石科技有限公司 版权所有 \n服务热线:187-8467-8300', + //'© 宜宾市生态环境局\n© 四川省踏石科技有限公司', + '© 宜宾市生态环境局 四川省踏石科技 版权所有\n服务热线:187-8467-8300', + maxLines: 2, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16.0, + color: Color.fromRGBO(106, 144, 204, 1), + fontWeight: FontWeight.bold), + ), + ), + + //144 + // 384 + // 36 + // 166 + // 17 + // 1026 + // 1573 + + // 1768 + //S7采用5.1英寸的Super AMOLED屏幕,分辨率为2560 ×1440(Quad HD),设置为1920*1080 + // Wrap( + // runSpacing: 9.0, + // alignment: WrapAlignment.center, + // children: [ + // Row( + // crossAxisAlignment: CrossAxisAlignment.center, + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // Text('${pageData['title']}', + // style: TextStyle(fontSize: 38.0, color: Color.fromRGBO(234, 200, 134, 1))) + // ], + // ), + // //自定义圆角 + // ClipRRect( + // borderRadius: BorderRadius.circular(12.5), + // child: Container( + // height: 25.0, + // width: 190.0, + // color: Color.fromRGBO(234, 200, 134, 1), + // child: Row( + // crossAxisAlignment: CrossAxisAlignment.center, + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // Text( + // '${pageData['subTitle']}', + // textAlign: TextAlign.center, + // style: TextStyle(color: Color.fromRGBO(113, 80, 24, 1)), + // ) + // ]))) + // ], + // ) + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/Login/LoginTabsWidget.dart b/lib/pages/Login/LoginTabsWidget.dart new file mode 100644 index 0000000..2bb5ae4 --- /dev/null +++ b/lib/pages/Login/LoginTabsWidget.dart @@ -0,0 +1,190 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; //import 'package:flustars/flustars.dart' as flustars; //该组件中有ScreenUtil,// 获取网络图片尺寸flustars.WidgetUtil +import 'package:hyzp_ybqx/pages/Works/TJXX/tj_data.dart'; +import 'package:hyzp_ybqx/services/EventBus.dart'; +import 'package:hyzp_ybqx/widget/my_Tabs.dart' as my_Tabs; + +import '../../components/commonFun.dart'; +import '../../services/Storage.dart'; +import 'FaceLogin2.dart'; +import 'LoginByName2.dart'; + +// class LoginTabsWidget extends StatelessWidget { +// // This widget is the root of your application. +// @override +// Widget build(BuildContext context) { +// //Flutter 强制竖屏 +// SystemChrome.setPreferredOrientations([ +// DeviceOrientation.portraitUp, //只能纵向 +// DeviceOrientation.portraitDown, //只能纵向 +// ]); +// +// return MaterialApp( +// debugShowCheckedModeBanner: false, +// title: 'Flutter Demo', +// theme: ThemeData( +// primarySwatch: Colors.blue, +// visualDensity: VisualDensity.adaptivePlatformDensity, +// ), +// home: MyHomePage(title: 'Flutter Demo Home Page'), +// ); +// } +// } + +class LoginTabsWidget extends StatefulWidget { + LoginTabsWidget({Key key, this.title}) : super(key: key); + final String title; + + @override + _MyHomePageState createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State with SingleTickerProviderStateMixin { + int loginTabs_index = 0; + + //用TabController实现顶部tab切换 + TabController _tabController; + + //try_setState(); //避免如下异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + @override + void initState() { + super.initState(); + + //若下面文件不存在, + // /data/data/com.flutter.hyzp_ybqx/shared_prefs/FlutterSharedPreferences.xml + // value为null, 会抛出异常: + // [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: Invalid argument(s): The source must not be null + // int.parse (dart:core-patch/integers_patch.dart:51:25) + Storage.getString("tabs_index").then((value) { + g_iIndex = (null == value) ? 0 : int.parse(value); + }); + + Storage.getString("LoginTabs_index").then((value) { + loginTabs_index = (null == value) ? 0 : int.parse(value); + print('loginTabs_index = ${loginTabs_index}'); + //用TabController实现顶部tab切换,并设置默认 Tab。initialIndex: loginTabs_index + _tabController = TabController(vsync: this, length: 2, initialIndex: loginTabs_index); + + //监听 _tabController 切换事件 + _tabController.addListener(() { + Storage.setString("LoginTabs_index", _tabController.index.toString()); + print('_tabController.index = ${_tabController.index}'); + }); + + //Flutter DefaultTabController 获取/设置当前 Tab - OK + DefaultTabController.of(_scaffoldKey.currentContext).animateTo(loginTabs_index); + try_setState(); + }); + + //监听统计数据改变事件 + eventBus.on().listen((event) { + print(event.str); + updateMayLogin(); + }); + } + + //处理延迟登录 + updateMayLogin() { + //判断从网络获取三种统计数据是否完成 + // if (listZptjStatisAlone.length >= dwSum && + // listTodayShtj.length >= dwSum && + // listClltjStatisAlone.length >= dwSum) { + // bMayLogin = true; + // } + if (listAllStatisData.length >= dwSum) { + bMayLogin = true; + } else { + bMayLogin = false; + } + try_setState(); + } + + dispose() { + _tabController?.dispose(); + super.dispose(); + } + + double _height = ScreenUtil().setHeight(1026); + final GlobalKey _scaffoldKey = new GlobalKey(); + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + height: _height, + child: DefaultTabController( + length: 2, + child: Scaffold( + key: _scaffoldKey, + backgroundColor: Colors.transparent, + appBar: PreferredSize( + preferredSize: Size.fromHeight(50), // here the desired height + child: AppBar( + backgroundColor: Colors.transparent, + bottom: TabBar( + controller: _tabController, + indicatorSize: TabBarIndicatorSize.label, + labelColor: Color.fromRGBO(49, 216, 123, 1), + unselectedLabelColor: Color.fromRGBO(118, 135, 162, 1), + //tabs: [Tab(text: " 密码登录 "), Tab(text: " 刷脸登录 ")], + tabs: [ + my_Tabs.MyTab( + text: " 密码登录 ", + style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold)), + my_Tabs.MyTab( + text: " 刷脸登录 ", + style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold)) + ], + // tabs: [ + // Text(" 密码登录 ", style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold)), + // Text(" 刷脸登录 ", style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold)), + // ], + ), + ), + ), + body: Stack( + children: [ + TabBarView( + controller: _tabController, + //flutter tabbar禁止手势滑动-OK + physics: new NeverScrollableScrollPhysics(), + children: [ + LoginByName2(height: _height), + FaceLogin2(), + ], + ), + // bMayLogin + // ? SizedBox.shrink() + // : Positioned( + // left: ScreenUtil().setWidth(0), + // right: ScreenUtil().setWidth(0), + // top: ScreenUtil().setHeight(297), + // child: Container( + // height: 200, + // width: 400, + // child: getMoreWidget2( + // text: '正在获取网络数据 ...', + // color: Colors.orangeAccent, + // size: 25.0, + // strokeWidth: 3.0, + // fontWeight: FontWeight.w600, + // edge: 0, + // height: 60, + // ), //显示加载中的圈圈, + // ), + // ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/Login/ModifyPassword.dart b/lib/pages/Login/ModifyPassword.dart new file mode 100644 index 0000000..caec543 --- /dev/null +++ b/lib/pages/Login/ModifyPassword.dart @@ -0,0 +1,283 @@ +import 'package:flutter/material.dart'; +import '../../components/commonFun.dart'; +import '../../widget/JdText.dart'; +import '../../widget/JdButton.dart'; + +import '../../config/Config.dart'; +import 'package:dio/dio.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import '../../services/Storage.dart'; +import 'dart:convert'; +import '../../components/EncryptUtil.dart'; + +import '../../services/EventBus.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import '../../config/service_url.dart'; + +class ModifyPassword extends StatefulWidget { + ModifyPassword({Key key}) : super(key: key); + + _LoginPageState createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + //监听登录页面销毁的事件 + dispose() { + super.dispose(); + eventBus.fire(new UserEvent('登录成功...')); + } + + String oldPassword = ''; + String newPassword1 = ''; + String newPassword2 = ''; + + doModifyPw() async { + oldPassword = oldPassword.trim(); + newPassword1 = newPassword1.trim(); + newPassword2 = newPassword2.trim(); + + RegExp regNewPw = RegExp(r"^[a-zA-Z0-9\?_!@#\$\%\^&]+$"); + if (g_userInfo.password.isNotEmpty && oldPassword != g_userInfo.password) { + Fluttertoast.showToast( + msg: '输入的旧密码错误!', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } else if (newPassword1 != newPassword2) { + Fluttertoast.showToast( + msg: '两次输入的新密码不一致!', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } else if (oldPassword == newPassword1) { + Fluttertoast.showToast( + msg: '新旧密码不能完全一样!', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } else if (newPassword1.length < 6 || newPassword1.length > 12) { + Fluttertoast.showToast( + msg: '新密码位数不对,应在 6-12 位之间!', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } else if (!regNewPw.hasMatch(newPassword1)) { + Fluttertoast.showToast( + msg: '新密码格式不对,应该由 6-12 位英文字母、数字和特殊符号组成!', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } else { + doModifyPwDio().then((value) { + if (value) { + print(value); + //密码修改成,保存新密码 + g_userInfo.password = newPassword1; + //加密保存新密码 + Storage.setString('password', EncryptUtil.aesEncode(g_userInfo.password)); + + Fluttertoast.showToast( + msg: '密码修改成功!', + toastLength: Toast.LENGTH_LONG, + gravity: ToastGravity.CENTER, + ); + + Navigator.pop(context); // 返回上一个页面 + } + }); + } + } + + Future doModifyPwDio() async { + var api = ServicePath.modifyPwUrl; + print(api); + bool ret = false; + + try { + print('开始处理修改密码请求...'); + print('username = ${g_userInfo.username}'); + print('password = ${g_userInfo.password}'); + Response response; + Dio dio = Dio(); + + String random = RandomBit(6); //flutter (dart)生成N位随机数 + response = await dio.post(api, data: { + "username": g_userInfo.username, + "oldpassword": oldPassword, + "newpassword": newPassword1, + "sign": GenerateMd5(APPkey + random), + "random": random, + }); + print('response = ${response.toString()}'); + //response = {"ret":200,"data":"密码修改成功","msg":""} + //response = {"ret":200,"data":"原密码错误","msg":""} + + if (response.statusCode == 200) { + if (response.data["data"].indexOf('成功') > 0) { + ret = true; + print('密码修改成功'); + print('response.data["data"] = ${response.data["data"]}'); + } else { + print('密码修改失败:${response.data["data"]}!'); + Fluttertoast.showToast( + msg: '密码修改失败:${response.data["data"]}!', + toastLength: Toast.LENGTH_LONG, + gravity: ToastGravity.CENTER, + ); + } + print('密码修改过程正常完成'); + } else { + throw Exception('后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('密码修改过程异常...'); + print('ERROR:======>${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + + return ret; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: PreferredSize( + preferredSize: Size.fromHeight(ScreenUtil().setHeight(173)), // 设置appBar高度 + // 设置appBar高度 + child: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + titleSpacing: 0.0, + //设置title的左边距 + flexibleSpace: Container( + //SizedBox(height: ScreenUtil().statusBarHeight), //显示顶部状态栏 + // SizedBox(height: ScreenUtil().setHeight(10)), //显示顶部状态栏 + padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight), //留出顶部状态栏高度 + child: Container( + //height: ScreenUtil().setHeight(173), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + // decoration: BoxDecoration( + // gradient: LinearGradient(colors: [ + // Color(0xFF0018EB), + // Color(0xFF01C1D9), + // ], begin: Alignment.bottomCenter, end: Alignment.topCenter), + // ), + ), + ), + title: Padding( + padding: EdgeInsets.only(left: 0, right: 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + getIconAndTextButton( + iconColor: Colors.white, + iconData: Icons.chevron_left_outlined, + onPress: () { + Navigator.pop(context); + }, + ), + Expanded( + child: Text("修改密码", + style: TextStyle(color: Colors.white, fontSize: 20), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis), + ), + SizedBox(width: 50), + ], + ), + ), + ), + ), + body: Container( + padding: EdgeInsets.only(top: 30, bottom: 20, left: 20, right: 20), + child: ListView( + children: [ + Center( + child: Container( + margin: EdgeInsets.only(top: 30), + height: ScreenUtil().setWidth(160), + width: ScreenUtil().setWidth(160), + //child: Image.asset('assets/images/user.png', fit: BoxFit.cover), + child: Image.asset('assets/images/ybsthbj.png', fit: BoxFit.fitHeight), + // child: Image.network( + // 'https://www.itying.com/images/flutter/list5.jpg', + // fit: BoxFit.cover), + ), + ), + SizedBox(height: 50), + JdText( + height: ScreenUtil().setHeight(300), + title: '旧密码:', + text: "请输入旧密码", + password: true, + onChanged: (String value) { + // print(value); + oldPassword = value; + }, + endBtn: 'ShowHiddenBtn', + ), + SizedBox(height: 20), + JdText( + height: ScreenUtil().setHeight(300), + title: '新密码:', + text: "请输入6-12位新密码", + password: true, + onChanged: (String value) { + // print(value); + newPassword1 = value; + }, + endBtn: 'ShowHiddenBtn', + ), + SizedBox(height: 20), + JdText( + height: ScreenUtil().setHeight(300), + title: '新密码:', + text: "请再次输入6-12位新密码", + password: true, + onChanged: (String value) { + // print(value); + newPassword2 = value; + }, + endBtn: 'ShowHiddenBtn', + ), + SizedBox(height: 20), + Container( + alignment: Alignment(0, 0), + height: ScreenUtil().setHeight(222), + //width: ScreenUtil().setWidth(142), + padding: EdgeInsets.only( + left: ScreenUtil().setWidth(25), right: ScreenUtil().setWidth(25)), + decoration: new BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(4.0)), + border: new Border.all(width: 1, color: Colors.grey), + ), + child: Text('新密码需要6-12位,可以由大小写字母、阿拉伯数字,以及英文 ?、_、!、@、#、\$、%、^、& 等字符组成。', + style: TextStyle(fontSize: 15)), + ), + SizedBox(height: 60), + JdButton( + height: ScreenUtil().setHeight(382), + //height: 126, + text: "确认", + color: Colors.blueAccent, + onTop: doModifyPw, + ) + ], + ), + ), + ); + } +} diff --git a/lib/pages/Login/TakePictuer.dart b/lib/pages/Login/TakePictuer.dart new file mode 100644 index 0000000..9fd7351 --- /dev/null +++ b/lib/pages/Login/TakePictuer.dart @@ -0,0 +1,273 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'dart:async'; +import 'dart:io'; +import 'package:camera/camera.dart'; +import 'package:flutter_screenutil/screen_util.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:audioplayers/audio_cache.dart'; +import '../../components/commonFun.dart'; +import '../../components/dioFun.dart'; + +class TakePictuer extends StatefulWidget { + TakePictuer({this.arguments, Key key}) : super(key: key); + var arguments; + + @override + _TakePictuerState createState() { + return _TakePictuerState(); + } +} + +class _TakePictuerState extends State { + final GlobalKey _scaffoldKey = GlobalKey(); + + String timestamp() => DateTime.now().millisecondsSinceEpoch.toString(); + + Image image; + String imagePath; + CameraController _cameraController; + + @override + void initState() { + super.initState(); + //controller = CameraController(cameras[0], ResolutionPreset.medium); //后置相机 + _cameraController = CameraController(cameras[1], ResolutionPreset.medium); //前置相机 + _cameraController.initialize().then((_) { + //初始化相机 + if (!mounted) { + return; + } + _onCamera(); //开始拍照 + setState(() {}); + }); + } + + @override + void dispose() { + _cameraController?.dispose(); + super.dispose(); + } + + //开始拍照 + Future _onCamera() async { + await Future.delayed(Duration(milliseconds: 1000), () { + print('开始拍照...'); + AudioCache().play(File('audio/yinxiao1064.mp3').path); //播放咔嚓声 + onTakePictureButtonPressed(); + }); + } + + @override + Widget build(BuildContext context) { + if (!_cameraController.value.isInitialized) { + return Container(); + } + + return Scaffold( + key: _scaffoldKey, + // appBar: AppBar( + // automaticallyImplyLeading: false, + // title: Container( + // //获取 appBar 高度 kToolbarHeight,R:\Flutter\FlutterSDK\flutter\packages\flutter\lib\src\material\constants.dart + // height: kToolbarHeight, + // //width: ScreenUtil().screenWidth, + // width: double.infinity, + // child: Text('请拿起手机,眨眨眼 ...'), + // decoration: BoxDecoration( + // gradient: LinearGradient( + // begin: Alignment.centerLeft, + // end: Alignment.centerRight, + // colors: [ + // Color.fromRGBO(12, 186, 156, 1), + // Color.fromRGBO(39, 127, 235, 1), + // ], + // ), + // ), + // ), + // ), + body: Container( + decoration: new BoxDecoration( + //color: Colors.black, + color: Colors.transparent, + ), + child: Column( + children: [ + SizedBox(height: ScreenUtil().statusBarHeight), //显示顶部状态栏 + Container( + alignment: Alignment(0, 0), + child: Text( + '请拿起手机,眨眨眼 ...', + style: TextStyle(fontSize: 18.0, color: Colors.white), + textAlign: TextAlign.center, + ), + height: ScreenUtil().setHeight(173), + //越界 + //height: kToolbarHeight, + //width: ScreenUtil().screenWidth, + width: double.infinity, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + ), + Stack( + children: [ + Align( + child: _cameraPreviewWidget(), + ), + Align( + child: _getImage(), + ), + Positioned( + left: ScreenUtil().screenWidth / 3, + top: ScreenUtil().screenHeight / 4, + child: image == null ? getMoreWidget() : _getSuccess(), + ), + // Align( + // alignment: Alignment.bottomCenter, + // child: Center( + // child: image == null ? getMoreWidget() : _getSuccess(), + // ), + // ), + ], + ), + Expanded(child: Container(color: Colors.black)) + ], + ), + ), + ); + } + + //mounted 是 bool 类型,表示当前 State 是否加载到树⾥。 + // 常用于判断页面是否释放。比如在程序中有些异步的处理,当处理结束时直接调用setState方法会直接报错, + // 因为页面已经释放(dispose)了,此时无法渲染页面,这时就可以使用mounted来进行判断页面是否被释放,如果释放了就不进行渲染。 + // if(mounted){ + // setState((){}) + // } + void onTakePictureButtonPressed() async { + image = null; + takePicture().then((String filePath) async { + if (mounted) { + setState(() { + imagePath = filePath; + }); + + if (filePath != null) { + image = Image.file(File(filePath)); + print('Picture saved to $filePath'); + } + + await Future.delayed(Duration(milliseconds: 1000), () { + if (filePath != null) { + if ('FaceLogin' == widget.arguments) { + //人脸验证,直接调用 uploadImage 进行验证登录 + faceLoginFun(filePath: filePath, context: context); + } else if ('FaceReg' == widget.arguments) { + //返回获得的人脸图片路径 filePath,等待管理员确认注册 + Navigator.pop(context, filePath); + //人脸注册,username 用户名,filePath 人脸图片路径 + //faceRegFun(username: 'admin', filePath: filePath); + } + } + }); + } + }); + } + + Future takePicture() async { + if (!_cameraController.value.isInitialized) { + print('Error: select a camera first.'); + return null; + } + final Directory extDir = await getApplicationDocumentsDirectory(); + final String dirPath = '${extDir.path}/Pictures/flutter_test'; + await Directory(dirPath).create(recursive: true); + final String filePath = '$dirPath/${timestamp()}.jpg'; + + if (_cameraController.value.isTakingPicture) { + // A capture is already pending, do nothing. + return null; + } + + try { + await _cameraController.takePicture(filePath); + } on CameraException catch (e) { + print('e = ${e.toString()}'); + return null; + } + return filePath; + } + + //拍照成功 + Widget _getSuccess({String text = '拍照成功!'}) { + if (image == null || _cameraController == null || !_cameraController.value.isInitialized) { + return Container(); //不能放回null,否则Stack会报错 + } else { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Icons.check, + size: 60, + color: Colors.white, + ), + SizedBox( + height: 10, + ), + Text( + text, + style: TextStyle( + fontSize: 30.0, // 文字大小 + color: Colors.white, // 文字颜色 + ), + ), + ], + ), + ); + } + } + + //加载照片 + Widget _getImage() { + if (image == null || _cameraController == null || !_cameraController.value.isInitialized) { + return Container(); //不能放回null,否则Stack会报错 + } else { + return Center( + child: AspectRatio( + aspectRatio: _cameraController.value.aspectRatio, + child: image, + ), + ); + } + } + + Widget _cameraPreviewWidget() { + if (_cameraController == null || !_cameraController.value.isInitialized) { + return const Text( + '正在启动相机...', + style: TextStyle( + color: Colors.white, + fontSize: 24.0, + fontWeight: FontWeight.w900, + ), + ); + } else { + return AspectRatio( + aspectRatio: _cameraController.value.aspectRatio, + child: CameraPreview(_cameraController), + ); + } + } +} diff --git a/lib/pages/MyMsics/01_messages/message_content.dart b/lib/pages/MyMsics/01_messages/message_content.dart new file mode 100644 index 0000000..9cd639e --- /dev/null +++ b/lib/pages/MyMsics/01_messages/message_content.dart @@ -0,0 +1,368 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'dart:convert'; + +//import 'messages_data.dart'; +import '../../../components/doJSON.dart'; +import '../../../components/hyxx_data_handle.dart'; + +class listMessagesData { + List listMessages = []; + int listIndex = 0; +} + +class MessagesView extends StatefulWidget { + //MessagesView({Key key, this.title, this.mapData}) : super(key: key); + MessagesView({ + @required this.title, + this.listIndex, + Key key, + }) : super(key: key); + String title; + int listIndex = 0; + List listMessages; + Map mapListMessagesData; + + _LoginPageState createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + @override + void initState() { + // TODO: implement initState + super.initState(); + setInit(); + } + + String strContent = ''; + + setInit() async { + widget.mapListMessagesData = Map(); + widget.mapListMessagesData['收到的消息'] = listMessagesData(); + widget.mapListMessagesData['发送的消息'] = listMessagesData(); + + widget.mapListMessagesData['收到的消息'].listMessages = listMessagesInbox2; + widget.mapListMessagesData['发送的消息'].listMessages = listMessagesOutbox2; + selectValue = widget.title; + switch (selectValue) { + case '收到的消息': + //第一次调用时widget.listIndex是由外面传入的,不能覆盖widget.listIndex, + // 所以单独抽离出一个不会覆盖widget.listIndex的函数 + onRadioBtnInboxSet(); + break; + case '发送的消息': + onRadioBtnOutboxSet(); + break; + } + widget.listMessages = widget.mapListMessagesData[selectValue].listMessages; + getContent(); + } + + getPreBtn_NextBtn() { + preBtn = getBtnSizeX( + text: "上一条", + onPressedFun: null, + ); + nextBtn = getBtnSizeX( + text: "下一条", + onPressedFun: null, + ); + + if (widget.listIndex > 0 && widget.listMessages.length > 0) { + preBtn = getBtnSizeX( + text: "上一条", + onPressedFun: () async { + if (widget.listIndex > 0) { + widget.listIndex--; + getContent(); + } + }, + ); + } + + if (widget.listIndex < (widget.listMessages.length - 1) && widget.listMessages.length > 0) { + nextBtn = getBtnSizeX( + text: "下一条", + onPressedFun: () async { + if (widget.listIndex < widget.listMessages.length - 1) { + widget.listIndex++; + getContent(); + } + }, + ); + } + } + + getContent() async { + if (widget.listMessages.isEmpty) { + strContent = ''; + widget.mapListMessagesData[selectValue].listIndex = widget.listIndex = 0; + } else { + widget.mapListMessagesData[selectValue].listIndex = widget.listIndex; + strContent = "第 ${widget.listIndex + 1} 条(共 ${widget.listMessages.length} 条)" + + "\n" + + "时间:" + + widget.listMessages[widget.listIndex]['date'] + + ", " + + widget.listMessages[widget.listIndex]['time'] + + "\n\n" + + "内容:" + + widget.listMessages[widget.listIndex]['content']; + } + getPreBtn_NextBtn(); + setState(() {}); + } + + var selectValue; + + //解决第一次进入报错问题。因为getPreBtn_NextBtn()还未执行,preBtn和nextBtn为空 + Widget preBtn = Container( + color: Colors.white12, //onPressedFun为null时无效 + width: 70.0, + height: 35.0, + child: RaisedButton( + padding: EdgeInsets.all(0), + textColor: Colors.black, + child: Text('上一条'), + onPressed: null, + ), + ); + + Widget nextBtn = Container( + color: Colors.white12, //onPressedFun为null时无效 + width: 70.0, + height: 35.0, + child: RaisedButton( + padding: EdgeInsets.all(0), + textColor: Colors.black, + child: Text('下一条'), + onPressed: null, + ), + ); + + Widget getBtnSizeColor( + {@required title, + width = 70.0, + height = 35.0, + //colorBK = Colors.white12, + txtColor = Colors.black, + fontSize = 16.0, + bottomBorder = false, + onPressedFun}) { + return Container( + //Failed assertion: line 285 pos 15: 'color == null || decoration == null': + // Cannot provide both a color and a decoration + //color: colorBK, //onPressedFun为null时无效 + width: width, + height: height, + decoration: BoxDecoration( + border: bottomBorder ? Border(bottom: BorderSide(width: 1, color: Colors.blue)) : null, + ), + child: FlatButton( + padding: EdgeInsets.all(0), + textColor: txtColor, + child: Text(title, style: TextStyle(fontSize: fontSize)), + onPressed: onPressedFun, + ), + ); + } + + Widget radioBtnInbox; + Widget radioBtnOutbox; + + //第一次调用时widget.listIndex是由外面传入的,不能覆盖widget.listIndex, + // 所以单独抽离出一个不会覆盖widget.listIndex的函数 + onRadioBtnInboxSet() { + radioBtnInbox = getBtnSizeColor( + title: '收到的消息', + width: 100.0, + onPressedFun: onRadioBtnInbox, + //colorBK: Colors.white, + txtColor: Colors.blue, + bottomBorder: true); + radioBtnOutbox = getBtnSizeColor( + title: '发送的消息', width: 100.0, onPressedFun: onRadioBtnOutbox, txtColor: Colors.black38); + } + + onRadioBtnInbox() { + setState(() { + selectValue = '收到的消息'; + onRadioBtnInboxSet(); + widget.listMessages = widget.mapListMessagesData[selectValue].listMessages; + widget.listIndex = widget.mapListMessagesData[selectValue].listIndex; + getContent(); + }); + } + + onRadioBtnOutboxSet() { + radioBtnInbox = getBtnSizeColor( + title: '收到的消息', width: 100.0, onPressedFun: onRadioBtnInbox, txtColor: Colors.black38); + radioBtnOutbox = getBtnSizeColor( + title: '发送的消息', + width: 100.0, + onPressedFun: onRadioBtnOutbox, + //colorBK: Colors.white, + txtColor: Colors.blue, + bottomBorder: true); + } + + onRadioBtnOutbox() { + setState(() { + selectValue = '发送的消息'; + onRadioBtnOutboxSet(); + widget.listMessages = widget.mapListMessagesData[selectValue].listMessages; + widget.listIndex = widget.mapListMessagesData[selectValue].listIndex; + getContent(); + }); + } + + Future alertDialog(String title, String content, var onPresse) async { + return await showDialog( + barrierDismissible: false, //表示点击灰色背景的时候是否消失弹出框 + context: context, + builder: (context) { + return AlertDialog( + title: Text(title), + content: Text(content), + actions: [ + FlatButton( + child: Text("确定"), + onPressed: onPresse, + ), + FlatButton( + child: Text("取消"), + onPressed: () { + Navigator.pop(context, false); + }, + ), + ], + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + Size mediaSize = MediaQuery.of(context).size; + + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () { + //SystemNavigator.pop(); //退出App + Navigator.pop(context); //返回 + }, + ), + centerTitle: true, + elevation: 0, + backgroundColor: Colors.white, + title: Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, // 主轴元素的排序方式(水平布局中,X轴是主轴) + crossAxisAlignment: CrossAxisAlignment.end, // 次轴元素的排序方式 + children: [ + radioBtnInbox, + radioBtnOutbox, + ], + ), + ), + actions: [ + IconButton( + icon: Icon(Icons.close), + onPressed: () async { + Navigator.pop(context); //关闭弹框,播放输入视频地址 + }, + ), + SizedBox( + width: 10, + ), + ], + ), + body: Container( + alignment: Alignment(0, 0), + child: Container( + child: Column( + children: [ + Container( + padding: EdgeInsets.fromLTRB(20, 10, 20, 10), + width: double.infinity, + height: mediaSize.height * 0.75, + child: SingleChildScrollView( + child: Container( + child: Text(strContent), + ), + ), + ), + Divider( + color: Colors.blue, + ), + SizedBox( + height: 6, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + getBtnSizeX( + text: "复制", + onPressedFun: () { + // Flutter 复制文本到剪贴板 + Clipboard.setData(ClipboardData( + text: ' ' + + selectValue + + '\n\n' + + widget.listMessages[widget.listIndex]['content'])); + //showToast('帮助信息已复制到剪贴板', textAlign: TextAlign.left); + Fluttertoast.showToast(msg: '帮助信息已复制到剪贴板', gravity: ToastGravity.CENTER); + //Navigator.pop(context, ret); + }), + getBtnSizeX( + text: "删除", + onPressedFun: () async { + //Navigator.pop(context); //关闭弹框,播放输入视频地址 + bool ret = await alertDialog('删除确认', '是否确定要删除当前项目?', () { + if (0 == selectValue.compareTo('收到的消息')) { + listMessagesInbox2.removeAt(widget.listIndex); + widget.mapListMessagesData['收到的消息'].listMessages = listMessagesInbox2; + writeJSON(json.encode(listMessagesInbox2), 'listMessagesInbox02.json'); + } else if (0 == selectValue.compareTo('发送的消息')) { + listMessagesOutbox2.removeAt(widget.listIndex); + widget.mapListMessagesData['发送的消息'].listMessages = listMessagesOutbox2; + writeJSON( + json.encode(listMessagesOutbox2), 'listMessagesOutbox02.json'); + } + if (widget.listIndex > 0) { + widget.listIndex--; + } + widget.listMessages = + widget.mapListMessagesData[selectValue].listMessages; + getContent(); + Navigator.pop(context); + }); + }), + preBtn, + nextBtn, + ], + ), + ], + ), + ), + ), + ); + } + + Widget getBtnSizeX({@required text, width = 70.0, height = 35.0, onPressedFun}) { + return Container( + color: Colors.white12, //onPressedFun为null时无效 + width: width, + height: height, + child: RaisedButton( + padding: EdgeInsets.all(0), + textColor: Colors.black, + child: Text(text), + onPressed: onPressedFun, + ), + ); + } +} diff --git a/lib/pages/MyMsics/01_messages/messages_data.dart b/lib/pages/MyMsics/01_messages/messages_data.dart new file mode 100644 index 0000000..5680bbd --- /dev/null +++ b/lib/pages/MyMsics/01_messages/messages_data.dart @@ -0,0 +1,229 @@ +List listMessagesOutbox = [ + { + "title": '工作汇报', + "date": '20200325', + "time": '09:19', + "content": "已经处理了7个黑烟事件的初审和推送工作" + }, + { + "title": "数据统计", + "date": '20200608', + "time": '13:55', + "content": "已经完成2020年4月份的黑烟事件数据统计工作", + }, + { + "title": '工作汇报', + "date": '20200325', + "time": '09:19', + "content": "已经处理了7个黑烟事件的初审和推送工作" + }, + { + "title": "数据统计", + "date": '20200608', + "time": '13:55', + "content": "已经完成2020年4月份的黑烟事件数据统计工作", + }, + { + "title": '工作汇报', + "date": '20200325', + "time": '09:19', + "content": "已经处理了7个黑烟事件的初审和推送工作" + }, + { + "title": "数据统计", + "date": '20200608', + "time": '13:55', + "content": "已经完成2020年4月份的黑烟事件数据统计工作", + }, + { + "title": '工作汇报', + "date": '20200325', + "time": '09:19', + "content": "已经处理了7个黑烟事件的初审和推送工作" + }, + { + "title": "数据统计", + "date": '20200608', + "time": '13:55', + "content": "已经完成2020年4月份的黑烟事件数据统计工作", + }, + { + "title": '工作汇报', + "date": '20200325', + "time": '09:19', + "content": "已经处理了7个黑烟事件的初审和推送工作" + }, + { + "title": "数据统计", + "date": '20200608', + "time": '13:55', + "content": "已经完成2020年4月份的黑烟事件数据统计工作", + }, + { + "title": '工作汇报', + "date": '20200325', + "time": '09:19', + "content": "已经处理了7个黑烟事件的初审和推送工作" + }, + { + "title": "数据统计", + "date": '20200608', + "time": '13:55', + "content": "已经完成2020年4月份的黑烟事件数据统计工作", + }, + { + "title": '工作汇报', + "date": '20200325', + "time": '09:19', + "content": "已经处理了7个黑烟事件的初审和推送工作" + }, + { + "title": "数据统计", + "date": '20200608', + "time": '13:55', + "content": "已经完成2020年4月份的黑烟事件数据统计工作", + }, + { + "title": '工作汇报', + "date": '20200325', + "time": '09:19', + "content": "已经处理了7个黑烟事件的初审和推送工作" + }, + { + "title": "数据统计", + "date": '20200608', + "time": '13:55', + "content": "已经完成2020年4月份的黑烟事件数据统计工作", + }, +]; + +List listMessagesInbox = [ + { + "title": '三千多人预约办事每天只放47个号 这县被国办通报!', + "date": '20200311', + "time": '10:23', + "content": "2020年10月,按照国务院第七次大督查的统一部署,14个国务院督查组分赴14个省(区、市)" + "和新疆生产建设兵团开展实地督查。督查发现,部分地方和部门仍存在违规设置行政审批环节," + "擅自构筑市场准入壁垒等问题,推进“一网通办”、“只进一扇门”、“最多跑一次”还有堵点障碍。", + }, + { + "title": "阿根廷举行马拉多纳遗体告别仪式:棺椁覆盖10号球衣", + "date": '20200317', + "time": '16:02', + "content": "【马拉多纳遗体告别仪式:棺椁覆盖10号球衣】阿根廷传奇球星马拉多纳的告别仪式正在举行中," + "阿根廷TN电视台直播的画面显示,马拉多纳的棺材上覆盖着阿根廷国旗和10号球衣," + "球迷排队进入总统府玫瑰宫向这位传奇球星告别。(中国日报网)", + }, + { + "title": '三千多人预约办事每天只放47个号 这县被国办通报!', + "date": '20200311', + "time": '10:23', + "content": "2020年10月,按照国务院第七次大督查的统一部署,14个国务院督查组分赴14个省(区、市)" + "和新疆生产建设兵团开展实地督查。督查发现,部分地方和部门仍存在违规设置行政审批环节," + "擅自构筑市场准入壁垒等问题,推进“一网通办”、“只进一扇门”、“最多跑一次”还有堵点障碍。", + }, + { + "title": "阿根廷举行马拉多纳遗体告别仪式:棺椁覆盖10号球衣", + "date": '20200317', + "time": '16:02', + "content": "【马拉多纳遗体告别仪式:棺椁覆盖10号球衣】阿根廷传奇球星马拉多纳的告别仪式正在举行中," + "阿根廷TN电视台直播的画面显示,马拉多纳的棺材上覆盖着阿根廷国旗和10号球衣," + "球迷排队进入总统府玫瑰宫向这位传奇球星告别。(中国日报网)", + }, + { + "title": '三千多人预约办事每天只放47个号 这县被国办通报!', + "date": '20200311', + "time": '10:23', + "content": "2020年10月,按照国务院第七次大督查的统一部署,14个国务院督查组分赴14个省(区、市)" + "和新疆生产建设兵团开展实地督查。督查发现,部分地方和部门仍存在违规设置行政审批环节," + "擅自构筑市场准入壁垒等问题,推进“一网通办”、“只进一扇门”、“最多跑一次”还有堵点障碍。", + }, + { + "title": "阿根廷举行马拉多纳遗体告别仪式:棺椁覆盖10号球衣", + "date": '20200317', + "time": '16:02', + "content": "【马拉多纳遗体告别仪式:棺椁覆盖10号球衣】阿根廷传奇球星马拉多纳的告别仪式正在举行中," + "阿根廷TN电视台直播的画面显示,马拉多纳的棺材上覆盖着阿根廷国旗和10号球衣," + "球迷排队进入总统府玫瑰宫向这位传奇球星告别。(中国日报网)", + }, + { + "title": '三千多人预约办事每天只放47个号 这县被国办通报!', + "date": '20200311', + "time": '10:23', + "content": "2020年10月,按照国务院第七次大督查的统一部署,14个国务院督查组分赴14个省(区、市)" + "和新疆生产建设兵团开展实地督查。督查发现,部分地方和部门仍存在违规设置行政审批环节," + "擅自构筑市场准入壁垒等问题,推进“一网通办”、“只进一扇门”、“最多跑一次”还有堵点障碍。", + }, + { + "title": "阿根廷举行马拉多纳遗体告别仪式:棺椁覆盖10号球衣", + "date": '20200317', + "time": '16:02', + "content": "【马拉多纳遗体告别仪式:棺椁覆盖10号球衣】阿根廷传奇球星马拉多纳的告别仪式正在举行中," + "阿根廷TN电视台直播的画面显示,马拉多纳的棺材上覆盖着阿根廷国旗和10号球衣," + "球迷排队进入总统府玫瑰宫向这位传奇球星告别。(中国日报网)", + }, + { + "title": '三千多人预约办事每天只放47个号 这县被国办通报!', + "date": '20200311', + "time": '10:23', + "content": "2020年10月,按照国务院第七次大督查的统一部署,14个国务院督查组分赴14个省(区、市)" + "和新疆生产建设兵团开展实地督查。督查发现,部分地方和部门仍存在违规设置行政审批环节," + "擅自构筑市场准入壁垒等问题,推进“一网通办”、“只进一扇门”、“最多跑一次”还有堵点障碍。", + }, + { + "title": "阿根廷举行马拉多纳遗体告别仪式:棺椁覆盖10号球衣", + "date": '20200317', + "time": '16:02', + "content": "【马拉多纳遗体告别仪式:棺椁覆盖10号球衣】阿根廷传奇球星马拉多纳的告别仪式正在举行中," + "阿根廷TN电视台直播的画面显示,马拉多纳的棺材上覆盖着阿根廷国旗和10号球衣," + "球迷排队进入总统府玫瑰宫向这位传奇球星告别。(中国日报网)", + }, + { + "title": '三千多人预约办事每天只放47个号 这县被国办通报!', + "date": '20200311', + "time": '10:23', + "content": "2020年10月,按照国务院第七次大督查的统一部署,14个国务院督查组分赴14个省(区、市)" + "和新疆生产建设兵团开展实地督查。督查发现,部分地方和部门仍存在违规设置行政审批环节," + "擅自构筑市场准入壁垒等问题,推进“一网通办”、“只进一扇门”、“最多跑一次”还有堵点障碍。", + }, + { + "title": "阿根廷举行马拉多纳遗体告别仪式:棺椁覆盖10号球衣", + "date": '20200317', + "time": '16:02', + "content": "【马拉多纳遗体告别仪式:棺椁覆盖10号球衣】阿根廷传奇球星马拉多纳的告别仪式正在举行中," + "阿根廷TN电视台直播的画面显示,马拉多纳的棺材上覆盖着阿根廷国旗和10号球衣," + "球迷排队进入总统府玫瑰宫向这位传奇球星告别。(中国日报网)", + }, + { + "title": '三千多人预约办事每天只放47个号 这县被国办通报!', + "date": '20200311', + "time": '10:23', + "content": "2020年10月,按照国务院第七次大督查的统一部署,14个国务院督查组分赴14个省(区、市)" + "和新疆生产建设兵团开展实地督查。督查发现,部分地方和部门仍存在违规设置行政审批环节," + "擅自构筑市场准入壁垒等问题,推进“一网通办”、“只进一扇门”、“最多跑一次”还有堵点障碍。", + }, + { + "title": "阿根廷举行马拉多纳遗体告别仪式:棺椁覆盖10号球衣", + "date": '20200317', + "time": '16:02', + "content": "【马拉多纳遗体告别仪式:棺椁覆盖10号球衣】阿根廷传奇球星马拉多纳的告别仪式正在举行中," + "阿根廷TN电视台直播的画面显示,马拉多纳的棺材上覆盖着阿根廷国旗和10号球衣," + "球迷排队进入总统府玫瑰宫向这位传奇球星告别。(中国日报网)", + }, + { + "title": '三千多人预约办事每天只放47个号 这县被国办通报!', + "date": '20200311', + "time": '10:23', + "content": "2020年10月,按照国务院第七次大督查的统一部署,14个国务院督查组分赴14个省(区、市)" + "和新疆生产建设兵团开展实地督查。督查发现,部分地方和部门仍存在违规设置行政审批环节," + "擅自构筑市场准入壁垒等问题,推进“一网通办”、“只进一扇门”、“最多跑一次”还有堵点障碍。", + }, + { + "title": "阿根廷举行马拉多纳遗体告别仪式:棺椁覆盖10号球衣", + "date": '20200317', + "time": '16:02', + "content": "【马拉多纳遗体告别仪式:棺椁覆盖10号球衣】阿根廷传奇球星马拉多纳的告别仪式正在举行中," + "阿根廷TN电视台直播的画面显示,马拉多纳的棺材上覆盖着阿根廷国旗和10号球衣," + "球迷排队进入总统府玫瑰宫向这位传奇球星告别。(中国日报网)", + }, +]; diff --git a/lib/pages/MyMsics/01_messages/messages_inbox.dart b/lib/pages/MyMsics/01_messages/messages_inbox.dart new file mode 100644 index 0000000..f934677 --- /dev/null +++ b/lib/pages/MyMsics/01_messages/messages_inbox.dart @@ -0,0 +1,113 @@ +import 'package:flutter/material.dart'; +import 'message_content.dart'; +//import 'messages_data.dart'; +import '../../../components/doJSON.dart'; +import '../../../components/hyxx_data_handle.dart'; +import '../../../components/customDialogG.dart'; + +class MessagesInbox extends StatefulWidget { + MessagesInbox({Key key}) : super(key: key); + + _LoginPageState createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + Widget _getListTile(BuildContext context, index) { + return Column( + children: [ + ListTile( + //leading: new Icon(Icons.phone), + title: new Text(listMessagesInbox2[index]['date'], style: TextStyle(fontSize: 10)), + subtitle: Text(listMessagesInbox2[index]['time'], style: TextStyle(fontSize: 10)), + trailing: Container( + width: 260, + child: Text( + listMessagesInbox2[index]['title'], + maxLines: 1, + overflow: TextOverflow.ellipsis, + //textAlign: TextAlign.right, + style: TextStyle(fontSize: 16), + ), + ), + contentPadding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 0), + enabled: true, + onTap: () { + // Navigator.of(context).push(MaterialPageRoute( + // builder: (context) => MessagesView( + // title: '收到的消息', mapData: listMessagesInbox2[index]))); + + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => MessagesView(title: '收到的消息', listIndex: index), + ), + ); + + // Navigator.push(context, MaterialPageRoute(builder: (context) { + // return MessagesOutbox( + // title: "PeakPlayer帮助信息", mapData: listMessagesInbox2[index]); + // })); + }, + onLongPress: () async { + bFlash = false; + // Navigator.of(context).push( + // MaterialPageRoute( + // builder: (context) => customDialogG( + // title: '收到的消息', + // index: index), + // ), + // ); + Navigator.of(context).push( + PageRouteBuilder( + opaque: false, + pageBuilder: (context, animation, secondaryAnimation) => + customDialogG(title: '收到的消息', index: index), + ), + // pageBuilder: (context, animation, secondaryAnimation) { + // return Scaffold( + // backgroundColor: Colors.transparent, + // body: SafeArea( + // child: Stack( + // children: [ + // Text('text'), + // //... + // ], + // ), + // ), + // ); + // }, + ).then((value) { + print('Page2_Contacts bFlash = $bFlash'); + if (bFlash) { + setState(() {}); + } + }); + }, + ), + Divider( + height: 1.0, + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + + + return Scaffold( + body: Container( + child: Column( + children: [ + Expanded( + //Flutter Column套ListView不显示,可将ListView用Expanded包裹起来。 + child: ListView.builder( + itemCount: listMessagesInbox2.length, + itemBuilder: _getListTile, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/MyMsics/01_messages/messages_manage.dart b/lib/pages/MyMsics/01_messages/messages_manage.dart new file mode 100644 index 0000000..1d30384 --- /dev/null +++ b/lib/pages/MyMsics/01_messages/messages_manage.dart @@ -0,0 +1,42 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'messages_inbox.dart'; +import 'messages_outbox.dart'; + +class MessagesManagePage extends StatefulWidget { + MessagesManagePage({Key key}) : super(key: key); + + @override + _MessagesManagePageState createState() => _MessagesManagePageState(); +} + +class _MessagesManagePageState extends State { + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: 2, + child: Scaffold( + appBar: AppBar( + title: Text('消息管理'), + centerTitle: true, + bottom: TabBar( + labelColor: Colors.blueAccent, + unselectedLabelColor: Colors.black26, + tabs: [Tab(text: "收件箱"), Tab(text: "已发送")], + ), + ), + body: TabBarView( + //flutter tabbar禁止手势滑动-OK + physics: new NeverScrollableScrollPhysics(), + children: [ + MessagesInbox(), + MessagesOutbox(), + ], + ), + ), + // theme: ThemeData( + // primarySwatch: Colors.yellow + // ), + ); + } +} diff --git a/lib/pages/MyMsics/01_messages/messages_outbox.dart b/lib/pages/MyMsics/01_messages/messages_outbox.dart new file mode 100644 index 0000000..238efdf --- /dev/null +++ b/lib/pages/MyMsics/01_messages/messages_outbox.dart @@ -0,0 +1,95 @@ +import 'package:flutter/material.dart'; +import 'message_content.dart'; +import '../../../components/hyxx_data_handle.dart'; +import '../../../components/customDialogG.dart'; + +class MessagesOutbox extends StatefulWidget { + MessagesOutbox({Key key}) : super(key: key); + + _LoginPageState createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + Widget _getListTile(BuildContext context, index) { + return Column( + children: [ + ListTile( + //leading: new Icon(Icons.phone), + title: new Text(listMessagesOutbox2[index]['date'], + style: TextStyle(fontSize: 10)), + subtitle: Text(listMessagesOutbox2[index]['time'], + style: TextStyle(fontSize: 10)), + trailing: Container( + width: 260, + child: Text( + listMessagesOutbox2[index]['title'], + maxLines: 1, + overflow: TextOverflow.ellipsis, + //textAlign: TextAlign.right, + style: TextStyle(fontSize: 16), + ), + ), + contentPadding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 0), + enabled: true, + onTap: () { + // Navigator.of(context).push(MaterialPageRoute( + // builder: (context) => MessagesView( + // title: '发送的消息', mapData: listMessagesOutbox2[index]))); + + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => MessagesView( + title: '发送的消息', + listIndex: index), + ), + ); + + // Navigator.push(context, MaterialPageRoute(builder: (context) { + // return MessagesOutbox( + // title: "PeakPlayer帮助信息", mapData: listMessagesOutbox2[index]); + // })); + }, + onLongPress: () { + bFlash = false; + Navigator.of(context).push( + PageRouteBuilder( + opaque: false, + pageBuilder: (context, animation, secondaryAnimation) => + customDialogG(title: '发送的消息', index: index), + ), + ).then((value) { + print('Page2_Contacts bFlash = $bFlash'); + if (bFlash) { + setState(() {}); + } + }); + }, + ), + Divider( + height: 1.0, + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + + + return Scaffold( + body: Container( + child: Column( + children: [ + Expanded( + //Flutter Column套ListView不显示,可将ListView用Expanded包裹起来。 + child: ListView.builder( + itemCount: listMessagesOutbox2.length, + itemBuilder: _getListTile, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/MyMsics/03_personal/ContactAdd.dart b/lib/pages/MyMsics/03_personal/ContactAdd.dart new file mode 100644 index 0000000..72d9923 --- /dev/null +++ b/lib/pages/MyMsics/03_personal/ContactAdd.dart @@ -0,0 +1,198 @@ +import 'package:flutter/material.dart'; + +import '../../../widget/JdButton.dart'; +import '../../../services/EventBus.dart'; +import '../01_messages/messages_manage.dart'; +import '../../../components/doJSON.dart'; +import '../../../components/hyxx_data_handle.dart'; +import 'dart:convert'; +import '../../../res/listContacts.dart'; + +class ContactAdd extends StatefulWidget { + ContactAdd({@required this.contactIndex, Key key}) : super(key: key); + int contactIndex; + + _LoginPageState createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + //List listController; + bool changed = false; + + void initState() { + // TODO: implement initState + super.initState(); + getListFlields(); + } + + getListFlields() { + //在listContacts2末尾添加一条记录,这种方式会导致添加后,同时修改被拷贝的记录 + // listContacts2.add(listContacts2[widget.contactIndex]); + // widget.contactIndex = listContacts2.length - 1; + // + // listController = List.generate(listContacts2[widget.contactIndex].length, (index) { + // String key = listContacts2[widget.contactIndex].keys.elementAt(index); + // listContacts2[widget.contactIndex][key] = ''; //清空内容 + // return TextEditingController(); + // }); + + //在listContacts2末尾添加一条记录 + //添加硬数据方式不好,若listContacts2的字段修改后,便可能导致问题 + // var item = { + // "姓名": '张三', + // "登录名称": 'ZhangSan', + // "登录密码": '**********', + // "部门": '办公室', + // "职务": '主任', + // "手机": '133xxxxxxxx', + // "办公电话": '0831xxxxxxx', + // "邮箱": '1234@qq.com', + // "权限": '管理员', + // "备注": '示例用户', + // }; + // //map遍历 + // //usrMap.forEach((k,v) => print('${k}: ${v}')); + // item.forEach((key, value) { + // item[key] = ''; //清空内容 + // }); + + //在listContacts2末尾添加一条记录,动态生成item中的元素 + Map item = {}; + listContacts2[widget.contactIndex].forEach((key, value) { + item[key] = ''; //为item添加元素 + }); + + listContacts2.add(item); + widget.contactIndex = listContacts2.length - 1; + + //setState(() {}); + } + + //监听登录页面销毁的事件 + dispose() { + super.dispose(); + if (changed) { + print("writeJSON()"); + writeJSON(json.encode(listContacts2), 'listContacts02.json'); + } else { + print("removeLast()"); + listContacts2.removeLast(); //删除添加的末尾元素 + } + eventBus.fire(new UserEvent('登录成功...')); + } + + doLogin() async { + Navigator.pop(context); //返回 + return; + } + + OnTap_messages_manage() { + Navigator.of(context).push(MaterialPageRoute(builder: (context) => MessagesManagePage())); + } + + //自定义方法 + static onNullFun() {} + + Widget getTrail(String key, int index, double widthTrail) { + return Container( + alignment: Alignment(1, 0), + //widthTrail = 400报错,360刚能显示,300换行,260 + width: widthTrail, + child: TextField( + //textAlign: TextAlign.right, + style: TextStyle(fontSize: 16), + decoration: InputDecoration( + hintText: '請輸入字段信息', + //border: InputBorder.none, //TextField去掉下划线 + contentPadding: EdgeInsets.only(right: 0), + ), + //controller: listController[index], + //利用控制器初始化文本 + onChanged: (value) { + listContacts2[widget.contactIndex][key] = value; + bFlash = changed = true; + print("ContactAdd bFlash = $bFlash"); + }, + ), + ); + } + + Widget _getListTile(String key, int index, double widthTrail, + {onTapFun = onNullFun, onLongPressFun = onNullFun, size = 16.0}) { + return ListTile( + //leading: new Icon(Icons.phone), + title: Text('${mapUserInfoText[key]} :', style: TextStyle(fontSize: 16)), + trailing: getTrail(key, index, widthTrail), + contentPadding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 0), + enabled: true, + onTap: () {}, + onLongPress: () {}, + ); + } + + @override + Widget build(BuildContext context) { + + return Scaffold( + appBar: AppBar( + title: Text("修改联系人"), + centerTitle: true, + ), + body: Container( + child: Column( + children: [ + Expanded( + //Flutter Column套ListView不显示,可将ListView用Expanded包裹起来。 + // child: ListView.builder( + // itemCount: listFlields.length, + // itemBuilder: _getListTileFields, + // ), + + //https://www.it1352.com/2028416.html + //用Map而不是List的Flutter ListView(Flutter listview with Map instead of List) + //listContacts2[widget.contactIndex] + child: ListView.builder( + itemCount: listContacts2[widget.contactIndex].length, + itemBuilder: (BuildContext context, index) { + String key = listContacts2[widget.contactIndex].keys.elementAt(index); + return Column( + children: [ + _getListTile(key, index, 220.0), + Divider( + height: 1.0, + ), + ], + ); + }, + ), + ), + ], + ), + ), + ); + } + + //https://www.it1352.com/2028416.html + //用Map而不是List的ListView.builder + //Its a little late but You could also try this. Map values = snapshot.data; + getMap() { + Map values = listContacts2[widget.contactIndex]; + return new ListView.builder( + itemCount: values.length, + itemBuilder: (BuildContext context, int index) { + String key = values.keys.elementAt(index); + return new Column( + children: [ + new ListTile( + title: new Text("$key"), + subtitle: new Text("${values[key]}"), + ), + new Divider( + height: 2.0, + ), + ], + ); + }, + ); + } +} diff --git a/lib/pages/MyMsics/03_personal/ContactModify.dart b/lib/pages/MyMsics/03_personal/ContactModify.dart new file mode 100644 index 0000000..5603fb1 --- /dev/null +++ b/lib/pages/MyMsics/03_personal/ContactModify.dart @@ -0,0 +1,162 @@ +import 'package:flutter/material.dart'; + +import '../../../services/EventBus.dart'; +import '../01_messages/messages_manage.dart'; +import '../../../components/doJSON.dart'; +import '../../../components/hyxx_data_handle.dart'; +import 'dart:convert'; +import '../../../res/listContacts.dart'; + +class ContactModify extends StatefulWidget { + ContactModify({@required this.contactIndex, Key key}) : super(key: key); + int contactIndex; + + _LoginPageState createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + List listController; + bool changed = false; + + void initState() { + // TODO: implement initState + super.initState(); + getListFlields(); + } + + String getUserText3(int index, String key) { + String str = (listContacts2[index][key] is String) ? listContacts2[index][key] : ''; + return str; + } + + getListFlields() { + listController = List.generate(listContacts2[widget.contactIndex].length, (index) { + String key = listContacts2[widget.contactIndex].keys.elementAt(index); + //return TextEditingController(text: listContacts2[widget.contactIndex][key]); + return TextEditingController(text: getUserText3(widget.contactIndex, key)); + }); + } + + //监听登录页面销毁的事件 + dispose() { + super.dispose(); + if (changed) { + writeJSON(json.encode(listContacts2), 'listContacts02.json'); + } + eventBus.fire(new UserEvent('登录成功...')); + } + + doLogin() async { + Navigator.pop(context); //返回 + return; + } + + OnTap_messages_manage() { + Navigator.of(context).push(MaterialPageRoute(builder: (context) => MessagesManagePage())); + } + + //自定义方法 + static onNullFun() {} + + Widget getTrail(String key, int index, double widthTrail) { + return Container( + alignment: Alignment(1, 0), + //widthTrail = 400报错,360刚能显示,300换行,260 + width: widthTrail, + child: TextField( + //textAlign: TextAlign.right, + style: TextStyle(fontSize: 16), + decoration: InputDecoration( + hintText: '請輸入字段信息', + //border: InputBorder.none, //TextField去掉下划线 + contentPadding: EdgeInsets.only(right: 0), + ), + controller: listController[index], + enabled: mapUserInfoModifyable[key], + //利用控制器初始化文本 + onChanged: (value) { + listContacts2[widget.contactIndex][key] = value; + bFlash = changed = true; + print("ContactAdd bFlash = $bFlash"); + }, + ), + ); + } + + Widget _getListTile(String key, int index, double widthTrail, + {onTapFun = onNullFun, onLongPressFun = onNullFun, size = 16.0}) { + return ListTile( + //leading: new Icon(Icons.phone), + title: Text('${mapUserInfoText[key]} :', style: TextStyle(fontSize: 16)), + trailing: getTrail(key, index, widthTrail), + contentPadding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 0), + enabled: true, + onTap: () {}, + onLongPress: () {}, + ); + } + + @override + Widget build(BuildContext context) { + + return Scaffold( + appBar: AppBar( + title: Text("修改联系人"), + centerTitle: true, + ), + body: Container( + child: Column( + children: [ + Expanded( + //Flutter Column套ListView不显示,可将ListView用Expanded包裹起来。 + // child: ListView.builder( + // itemCount: listFlields.length, + // itemBuilder: _getListTileFields, + // ), + + //listContacts2[widget.contactIndex] + child: ListView.builder( + itemCount: listContacts2[widget.contactIndex].length, + itemBuilder: (BuildContext context, index) { + String key = listContacts2[widget.contactIndex].keys.elementAt(index); + return Column( + children: [ + _getListTile(key, index, 220.0), + Divider( + height: 1.0, + ), + ], + ); + }, + ), + ), + ], + ), + ), + ); + } + + //https://www.it1352.com/2028416.html + //用Map而不是List的ListView.builder + //Its a little late but You could also try this. Map values = snapshot.data; + getMap() { + Map values = listContacts2[widget.contactIndex]; + return new ListView.builder( + itemCount: values.length, + itemBuilder: (BuildContext context, int index) { + String key = values.keys.elementAt(index); + return new Column( + children: [ + new ListTile( + title: new Text("$key"), + subtitle: new Text("${values[key]}"), + ), + new Divider( + height: 2.0, + ), + ], + ); + }, + ); + } +} diff --git a/lib/pages/MyMsics/03_personal/PersonalData.dart b/lib/pages/MyMsics/03_personal/PersonalData.dart new file mode 100644 index 0000000..cf4279e --- /dev/null +++ b/lib/pages/MyMsics/03_personal/PersonalData.dart @@ -0,0 +1,274 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hyzp_ybqx/components/commonFun.dart'; +import 'package:hyzp_ybqx/components/dioFun.dart'; + +import '../../../components/customDialogH.dart'; +import '../../../components/hyxx_data_handle.dart'; +import '../../../res/listContacts.dart'; +import '../../../services/EventBus.dart'; +import '../../../services/Storage.dart'; +import '../../../widget/JdButton.dart'; + +class PersonalData extends StatefulWidget { + PersonalData({Key key}) : super(key: key); + + _LoginPageState createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + List listController = []; + String imagePath = ''; + Image _image; + + void initState() { + // TODO: implement initState + getListFlields(); + super.initState(); + } + + //监听登录页面销毁的事件 + dispose() { + super.dispose(); + eventBus.fire(new UserEvent('登录成功...')); + } + + getListFlields() async { + Storage.getString('userAvatarPath').then((value) { + if (null != value) { + imagePath = value; + } + }); + print('imagePath = $imagePath'); + + //mapUserInfo = mapUserInfoRet['data']['profile']; + await getMyUserinfo(); + // print('mapUserInfoRet = $mapUserInfoRet'); + // print('mapUserInfo = $mapUserInfo'); + + listController = List.generate(mapUserInfo.length, (index) { + String key = mapUserInfo.keys.elementAt(index); + if ("reg_time" == key) { + var strtime = DateTime.fromMillisecondsSinceEpoch(mapUserInfo[key]); //将拿到的时间戳转化为日期 + print('时间戳:${mapUserInfo[key]}'); + print('转换为日期时间:${strtime.toLocal().toString()}'); + // I/flutter (25364): 时间戳:1606653977 + // I/flutter (25364): 转换为日期时间:1970-01-19 14:17:33.977 + return TextEditingController(text: strtime.toLocal().toString()); + } else { + var controller = TextEditingController(text: mapUserInfo[key].toString()); + controller.selection = TextSelection.fromPosition( + TextPosition(affinity: TextAffinity.downstream, offset: '${controller.text}'.length), + ); + return controller; + } + }); + setState(() {}); + } + + Widget getTrail(String key, int index, double widthTrail) { + if (0 == listController.length) { + return Container(); + } + + //print('key = $key'); + if ('avatar' == key) { + if (imagePath.isEmpty) { + imagePath = listController[index].text; + } + return getAvatar(width: widthTrail); + } else { + return Container( + alignment: Alignment(1, 0), + //widthTrail = 400报错,360刚能显示,300换行,260 + width: widthTrail, + child: TextField( + //textAlign: TextAlign.right, + style: TextStyle(fontSize: 16), + decoration: InputDecoration( + //hintText: '請輸入字段信息', + border: InputBorder.none, //TextField去掉下划线 + contentPadding: EdgeInsets.only(right: 0), + ), + controller: listController[index], + enabled: mapUserInfoModifyable[key], + //利用控制器初始化文本 + onChanged: (value) { + mapUserInfo[key] = value; + }, + ), + ); + } + } + + Future doContacts() async { + bFlash = false; + return showDialog( + context: context, + builder: (BuildContext context) { + return customDialogH( + title: "请选择头像修改操作", + content: "头像修改", + index: 0, + ); + }, + ).then((value) { + if (null == value) { + return; + } + imagePath = value; + print('Page2_Contacts bFlash = $bFlash'); + if (imagePath?.isNotEmpty) { + Storage.setString('userAvatarPath', imagePath); + _image = Image.file(File(imagePath), fit: BoxFit.cover); + setState(() {}); + } + }); + } + + Widget getAvatar({double width = 260.0}) { + if (imagePath.isEmpty) { + _image = Image.asset('assets/images/user.png', fit: BoxFit.cover); + } else { + String head = imagePath.substring(0, 4).trim().toLowerCase(); + if ('http' == head) { + _image = Image.network(imagePath, fit: BoxFit.cover); + } else { + _image = Image.file(File(imagePath), fit: BoxFit.cover); + } + } + return Container( + alignment: Alignment(-1, 0), + width: width, + child: InkWell( + onTap: () async { + //doContacts(); + }, + child: Container( + width: 40, + child: _image, + ), + ), + ); + } + + static onNullFun() {} + + Widget _getListTile(String key, int index, double widthTrail, + {onTapFun = onNullFun, onLongPressFun = onNullFun, size = 16.0}) { + return ListTile( + //leading: new Icon(Icons.phone), + title: Text(mapUserInfoText.containsKey(key) ? '${mapUserInfoText[key]} :' : '$key :', + style: TextStyle(fontSize: 16)), + trailing: getTrail(key, index, widthTrail), + contentPadding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 0), + enabled: true, + onTap: () async { + // if ('avatar' == key) { + // print('选择图片或拍照'); + // await doContacts(); + // } + }, + onLongPress: () {}, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: PreferredSize( + preferredSize: Size.fromHeight(ScreenUtil().setHeight(173)), // 设置appBar高度 + // 设置appBar高度 + child: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + titleSpacing: 0.0, + //设置title的左边距 + flexibleSpace: Container( + //SizedBox(height: ScreenUtil().statusBarHeight), //显示顶部状态栏 + // SizedBox(height: ScreenUtil().setHeight(10)), //显示顶部状态栏 + padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight), //留出顶部状态栏高度 + child: Container( + //height: ScreenUtil().setHeight(173), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + // decoration: BoxDecoration( + // gradient: LinearGradient(colors: [ + // Color(0xFF0018EB), + // Color(0xFF01C1D9), + // ], begin: Alignment.bottomCenter, end: Alignment.topCenter), + // ), + ), + ), + title: Padding( + padding: EdgeInsets.only(left: 0, right: 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + getIconAndTextButton( + iconColor: Colors.white, + iconData: Icons.chevron_left_outlined, + onPress: () { + Navigator.pop(context); + }, + ), + Expanded( + child: Text("个人资料", + style: TextStyle(color: Colors.white, fontSize: 20), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis), + ), + SizedBox(width: 50), + ], + ), + ), + ), + ), + body: Container( + child: Column( + children: [ + Expanded( + //https://www.it1352.com/2028416.html + //用Map而不是List的Flutter ListView.builder(Flutter listview with Map instead of List) + child: ListView.builder( + itemCount: mapUserInfo.length, + itemBuilder: (BuildContext context, index) { + String key = mapUserInfo.keys.elementAt(index); + return Column( + children: [ + _getListTile(key, index, 220.0), + Divider( + height: 1.0, + ), + ], + ); + }, + ), + ), + SizedBox(height: 40), + JdButton( + height: 126, + width: 899, + text: "确认", + color: Colors.blueAccent, + onTop: () async { + Navigator.pop(context); + }, + ), + SizedBox(height: 40), + ], + ), + ), + ); + } +} diff --git a/lib/pages/MyMsics/04_myFeedback/MyFeedback.dart b/lib/pages/MyMsics/04_myFeedback/MyFeedback.dart new file mode 100644 index 0000000..a68bdf1 --- /dev/null +++ b/lib/pages/MyMsics/04_myFeedback/MyFeedback.dart @@ -0,0 +1,221 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hyzp_ybqx/components/commonFun.dart'; + +import '../../../widget/JdButton.dart'; + +class MyFeedback extends StatefulWidget { + MyFeedback({Key key}) : super(key: key); + + _LoginPageState createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + void initState() { + // TODO: implement initState + super.initState(); + } + + doOK() { + Navigator.pop(context); //返回 + } + + int radioSelect = 0; + String opinionText = ''; + + //监听登录页面销毁的事件 + dispose() { + super.dispose(); + } + + Widget getTextField() { + return Container( + decoration: new BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(4.0)), + border: new Border.all(width: 1, color: Colors.blue), + ), + constraints: BoxConstraints( + maxHeight: 240.0, + maxWidth: MediaQuery.of(context).size.width, + minHeight: 240.0, + minWidth: MediaQuery.of(context).size.width, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(width: 10), + Expanded( + child: TextField( + style: TextStyle(fontSize: 16), + keyboardType: TextInputType.multiline, + maxLines: null, + //不限制行数 + decoration: InputDecoration( + hintText: '請輸入反馈问题的详细描述内容', + border: InputBorder.none, //TextField去掉下划线 + //border: OutlineInputBorder(), + ), + onChanged: (val) { + opinionText = val; + }, + ), + ), + SizedBox(width: 10), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: PreferredSize( + preferredSize: Size.fromHeight(ScreenUtil().setHeight(173)), // 设置appBar高度 + // 设置appBar高度 + child: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + titleSpacing: 0.0, + //设置title的左边距 + flexibleSpace: Container( + //SizedBox(height: ScreenUtil().statusBarHeight), //显示顶部状态栏 + // SizedBox(height: ScreenUtil().setHeight(10)), //显示顶部状态栏 + padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight), //留出顶部状态栏高度 + child: Container( + //height: ScreenUtil().setHeight(173), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + // decoration: BoxDecoration( + // gradient: LinearGradient(colors: [ + // Color(0xFF0018EB), + // Color(0xFF01C1D9), + // ], begin: Alignment.bottomCenter, end: Alignment.topCenter), + // ), + ), + ), + title: Padding( + padding: EdgeInsets.only(left: 0, right: 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + getIconAndTextButton( + iconColor: Colors.white, + iconData: Icons.chevron_left_outlined, + onPress: () { + Navigator.pop(context); + }, + ), + Expanded( + child: Text("意见反馈", + style: TextStyle(color: Colors.white, fontSize: 20), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis), + ), + SizedBox(width: 50), + ], + ), + ), + ), + ), + body: Container( + padding: EdgeInsets.only(top: 20, bottom: 20, left: 20, right: 20), + child: ListView( + children: [ + Center( + child: Container( + margin: EdgeInsets.only(top: 10), + height: ScreenUtil().setWidth(160), + width: ScreenUtil().setWidth(160), + //child: Image.asset('assets/images/user.png', fit: BoxFit.cover), + child: Image.asset('assets/images/ybsthbj.png', fit: BoxFit.fitHeight), + ), + ), + SizedBox(height: 10), + Text('请选择问题类型'), + getRadioRow(), + Text('请輸入问题描述'), + SizedBox(height: 10), + getTextField(), + SizedBox(height: 40), + JdButton( + height: 126, + text: "确认", + color: Colors.blueAccent, + onTop: doOK, + ) + ], + ), + ), + ); + } + + Widget getRadio(int index) { + return Container( + alignment: Alignment(1, -1), + height: 30, + width: 30, + child: Radio( + value: index, + onChanged: (v) { + setState(() { + radioSelect = v; + }); + }, + groupValue: radioSelect, + ), + ); + } + + Widget getRadioRow() { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "投诉", + style: TextStyle( + color: (0 == radioSelect) ? Colors.blue : null, + fontWeight: (0 == radioSelect) ? FontWeight.bold : null, + ), + ), + getRadio(0), + SizedBox(width: 25), + Text( + "故障", + style: TextStyle( + color: (1 == radioSelect) ? Colors.blue : null, + fontWeight: (1 == radioSelect) ? FontWeight.bold : null, + ), + ), + getRadio(1), + SizedBox(width: 25), + Text( + "建议", + style: TextStyle( + color: (2 == radioSelect) ? Colors.blue : null, + fontWeight: (2 == radioSelect) ? FontWeight.bold : null, + ), + ), + getRadio(2), + SizedBox(width: 25), + Text( + "其他", + style: TextStyle( + color: (3 == radioSelect) ? Colors.blue : null, + fontWeight: (3 == radioSelect) ? FontWeight.bold : null, + ), + ), + getRadio(3), + ], + ); + } +} diff --git a/lib/pages/MyMsics/05_updated/MyUpdated.dart b/lib/pages/MyMsics/05_updated/MyUpdated.dart new file mode 100644 index 0000000..ff04f47 --- /dev/null +++ b/lib/pages/MyMsics/05_updated/MyUpdated.dart @@ -0,0 +1,389 @@ +import 'dart:async'; +import 'dart:io'; + +///https://blog.csdn.net/zcylyzhi4/article/details/108002879 +///1、导入相关包 +import 'dart:isolate'; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter_downloader/flutter_downloader.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hyzp_ybqx/components/dioFun.dart'; +import 'package:hyzp_ybqx/components/doJSON.dart'; +import 'package:open_file/open_file.dart'; +import 'package:package_info/package_info.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:progress_dialog/progress_dialog.dart'; + +import '../../../components/commonFun.dart'; +import '../../../widget/JdButton.dart'; + +///版本更新頁面 +class MyUpdated extends StatefulWidget { + MyUpdated({Key key, this.ver, this.date, this.theContext}) : super(key: key); + String ver = '1.0.0'; + String date = ''; + BuildContext theContext; + + _MyUpdatedState createState() => _MyUpdatedState(); +} + +class _MyUpdatedState extends State { + //2、声明变量 + int _stampAppCompile = -1; + String _dateNewver = ''; + Map _mapVer = {}; + String serviceVersionCode = ''; + String appId = ''; + ProgressDialog pr; + String apkName = 'app-release.apk'; + String appPath = ''; + ReceivePort _port = ReceivePort(); + + void initState() { + // 0、初始化FlutterDownLoader。版本更新初始化,放在这里会报错,初始化失败 + // WidgetsFlutterBinding.ensureInitialized(); + if (!bFlutterDownloader_initialize) { + FlutterDownloader.initialize(debug: true).then((value) { + //3、在initState中初始化 + IsolateNameServer.registerPortWithName(_port.sendPort, 'downloader_send_port'); + _port.listen(_updateDownLoadInfo); + FlutterDownloader.registerCallback(_downLoadCallback); + getNewverUrl().then((value) { + _mapVer = value; + print('_mapVer = ${_mapVer}'); + }); + bFlutterDownloader_initialize = true; + }); + } else { + //3、在initState中初始化 + IsolateNameServer.registerPortWithName(_port.sendPort, 'downloader_send_port'); + _port.listen(_updateDownLoadInfo); + FlutterDownloader.registerCallback(_downLoadCallback); + getNewverUrl().then((value) { + _mapVer = value; + print('_mapVer = ${_mapVer}'); + //I/flutter (12498): _mapVer = {id: 1, ver: 1.0.0, miaos: 版本说明, + // downurl: http://www.sctastech.com/download/hyzp_20210425.apk, updatetime: 1620632231} + + print('oldVer = ${widget.ver}'); + _mapVer['ver'] = '1.3.1'; + print('newVer = ${_mapVer['ver']}'); + // I/flutter ( 1872): oldVer = 1.3.0 + // I/flutter ( 1872): newVer = 1.3.1 + + ///从字符串时间获取秒时间戳:int getStampFromString(String strTime) + // _stampAppCompile = getStampFromString(widget.date.replaceAll('.', '-')); + // //timeStamp可以是int类型或String类型的时间戳(秒):String getFtpdir_YYYYMMDD(var timeStamp) + // _dateNewver = getFtpdir_YYYYMMDD(_mapVer['updatetime'], sep: '.'); + // print('_stampAppCompile = $_stampAppCompile'); + // print('_mapVer[\'updatetime\'] = ${_mapVer['updatetime']}'); + // print('widget.date = ${widget.date}'); + // print('_dateNewver = $_dateNewver'); + // I/flutter (30820): _stampAppCompile = 1620403200 + // I/flutter (30820): _mapVer['updatetime'] = 1620632231 + // I/flutter (30820): widget.date = 2021.05.08 + // I/flutter (30820): _dateNewver = 2021.05.10 + }); + } + + super.initState(); + } + + //4、判断,自动更新 + + // 版本比较 + Future verCompare({String newVer, String oldVer}) async { + List listNewVer = await tran2int(newVer.split('.')); + List listOldVer = await tran2int(oldVer.split('.')); + + int len = listNewVer.length; + for (int i = 0; i < len; i++) { + if (listNewVer[i] > listOldVer[i]) { + return true; + } + } + return false; + } + + Future tran2int(List _list) async { + List listRet = []; + int len = _list.length; + for (int i = 0; i < len; i++) { + listRet.add(int.parse(_list[i].trim())); + } + return listRet; + } + + @override + Future afterFirstLayout(BuildContext context) async { + // 如果是android,则执行热更新 + if (Platform.isAndroid) { + verCompare(newVer: _mapVer['ver'], oldVer: widget.ver).then((value) { + if (value) { + print('value = $value'); + //_getNewVersionAPP(context); + serviceVersionCode = _mapVer['ver']; + //appId = res.data['id']; + //_checkVersionCode(); + _showNewVersionAppDialog(); + } + }); + } + } + + //监听登录页面销毁的事件 + dispose() { + super.dispose(); + } + + doLogin() async { + //临时跳转 + //Navigator.pushNamed(context, '/tabs', arguments: g_iIndex); + afterFirstLayout(context).then((value) {}); + //Navigator.pop(context); //返回 + return; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: PreferredSize( + preferredSize: Size.fromHeight(ScreenUtil().setHeight(173)), + // 设置appBar高度 + // 设置appBar高度 + child: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + titleSpacing: 0.0, + //设置title的左边距 + flexibleSpace: Container( + //SizedBox(height: ScreenUtil().statusBarHeight), //显示顶部状态栏 + // SizedBox(height: ScreenUtil().setHeight(10)), //显示顶部状态栏 + padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight), + //留出顶部状态栏高度 + child: Container( + //height: ScreenUtil().setHeight(173), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + // decoration: BoxDecoration( + // gradient: LinearGradient(colors: [ + // Color(0xFF0018EB), + // Color(0xFF01C1D9), + // ], begin: Alignment.bottomCenter, end: Alignment.topCenter), + // ), + ), + ), + title: Padding( + padding: EdgeInsets.only(left: 0, right: 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + getIconAndTextButton( + iconColor: Colors.white, + iconData: Icons.chevron_left_outlined, + onPress: () { + Navigator.pop(context); + }, + ), + Expanded( + child: Text("版本更新", + style: TextStyle(color: Colors.white, fontSize: 20), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis), + ), + SizedBox(width: 50), + ], + ), + ), + ), + ), + body: Container( + padding: EdgeInsets.only(top: 20, bottom: 20, left: 5, right: 5), + child: ListView( + children: [ + Center( + child: Container( + margin: EdgeInsets.only(top: 30), + height: ScreenUtil().setWidth(160), + width: ScreenUtil().setWidth(160), + //child: Image.asset('assets/images/user.png', fit: BoxFit.cover), + child: Image.asset('assets/images/ybsthbj.png', fit: BoxFit.fitHeight), + // child: Image.network( + // 'https://www.itying.com/images/flutter/list5.jpg', + // fit: BoxFit.cover), + ), + ), + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(height: 60), + Text('宜宾市黑烟车电子抓拍系统', style: TextStyle(fontSize: 20)), + Text('v${widget.ver}(${widget.date})', style: TextStyle(fontSize: 20)), + SizedBox(height: 60), + Text('© 宜宾市生态环境局 四川省踏石科技 版权所有\n服务热线:187-8467-8300', + maxLines: 2, style: TextStyle(fontSize: 16), textAlign: TextAlign.center), + ], + ), + ), + SizedBox(height: 100), + Container( + padding: EdgeInsets.only(top: 20, bottom: 20, left: 20, right: 20), + child: JdButton( + height: 126, + text: "确认", + color: Colors.blueAccent, + onTop: doLogin, + ), + ), + ], + ), + ), + ); + } + + ///https://blog.csdn.net/zcylyzhi4/article/details/108002879 + //5、自动更新代码 + /// 执行版本更新的网络请求 + _getNewVersionAPP(context) async { + // HttpUtils.send( + // context, + // 'http://update.rwworks.com:8088/appManager/monitor/app/version/check/flutterTempldate', + // ).then((res) { + // serviceVersionCode = res.data["versionNo"]; + // appId = res.data['id']; + // _checkVersionCode(); + // }); + } + + /// 检查当前版本是否为最新,若不是,则更新 + void _checkVersionCode() { + PackageInfo.fromPlatform().then((PackageInfo packageInfo) { + var currentVersionCode = packageInfo.version; + if (double.parse(serviceVersionCode.substring(0, 3)) > + double.parse(currentVersionCode.substring(0, 3))) { + _showNewVersionAppDialog(); + } + }); + } + + /// 版本更新提示对话框 + Future _showNewVersionAppDialog() async { + return showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: new Row( + children: [ + new Padding( + padding: const EdgeInsets.fromLTRB(30.0, 0.0, 10.0, 0.0), + child: new Text("发现新版本")) + ], + ), + content: new Text(serviceVersionCode), + actions: [ + new FlatButton( + child: new Text('下次再说'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + new FlatButton( + child: new Text('立即更新'), + onPressed: () { + _doUpdate(context); + }, + ) + ], + ); + }); + } + + /// 执行更新操作 + _doUpdate(BuildContext context) async { + Navigator.pop(context); + _executeDownload(context); + } + + /// 下载最新apk包 + Future _executeDownload(BuildContext context) async { + pr = new ProgressDialog( + context, + type: ProgressDialogType.Download, + isDismissible: true, + showLogs: true, + ); + pr.style(message: '准备下载...'); + if (!pr.isShowing()) { + pr.show(); + } + + final path = await _apkLocalPath; + await FlutterDownloader.enqueue( + //url: 'http://update.rwworks.com:8088/appManager/monitor/app/appload/' + appId + '', + url: _mapVer['downurl'], + savedDir: path, + fileName: apkName, + showNotification: true, + openFileFromNotification: true); + } + + /// 下载进度回调函数 + static void _downLoadCallback(String id, DownloadTaskStatus status, int progress) { + final SendPort send = IsolateNameServer.lookupPortByName('downloader_send_port'); + send.send([id, status, progress]); + } + + /// 更新下载进度框 + _updateDownLoadInfo(dynamic data) { + DownloadTaskStatus status = data[1]; + int progress = data[2]; + if (status == DownloadTaskStatus.running) { + pr.update(progress: double.parse(progress.toString()), message: "下载中,请稍后…"); + } + if (status == DownloadTaskStatus.failed) { + if (pr.isShowing()) { + pr.hide(); + } + } + + if (status == DownloadTaskStatus.complete) { + if (pr.isShowing()) { + pr.hide(); + } + _installApk(); + } + } + + /// 安装apk + Future _installApk() async { + await OpenFile.open(appPath + '/' + apkName); + } + + /// 获取apk存储位置 + Future get _apkLocalPath async { + final directory = await getExternalStorageDirectory(); + String path = directory.path + Platform.pathSeparator + 'Download'; + ; + final savedDir = Directory(path); + bool hasExisted = await savedDir.exists(); + if (!hasExisted) { + await savedDir.create(); + } + this.setState(() { + appPath = path; + }); + return path; + } +} diff --git a/lib/pages/MyMsics/05_updated/MyUpdatedNew.dart b/lib/pages/MyMsics/05_updated/MyUpdatedNew.dart new file mode 100644 index 0000000..96e08e1 --- /dev/null +++ b/lib/pages/MyMsics/05_updated/MyUpdatedNew.dart @@ -0,0 +1,386 @@ +import 'dart:async'; +import 'dart:io'; + +///https://blog.csdn.net/zcylyzhi4/article/details/108002879 +///1、导入相关包 +import 'dart:isolate'; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter_downloader/flutter_downloader.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hyzp_ybqx/components/dioFun.dart'; +import 'package:open_file/open_file.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:progress_dialog/progress_dialog.dart'; + +import '../../../components/commonFun.dart'; + +///版本更新类 +class MyUpdatedNew { + MyUpdatedNew( + {this.ver, + this.date, + this.theContext, + this.bStartUpdated = false, + this.bShowNoNewVersion = false}) { + initState(); + } + + String ver = '1.0.0'; + String date = ''; + BuildContext theContext; + bool bStartUpdated; + bool bShowNoNewVersion; + + //2、声明变量 + int _stampAppCompile = -1; + String _dateNewver = ''; + Map _mapVer = {}; + String serviceVersionCode = ''; + String appId = ''; + ProgressDialog pr; + String apkName = ''; + String appPath = ''; + ReceivePort _port = ReceivePort(); + + void initState() { + // 0、初始化FlutterDownLoader。版本更新初始化,放在这里会报错,初始化失败 + // WidgetsFlutterBinding.ensureInitialized(); + if (!bFlutterDownloader_initialize) { + FlutterDownloader.initialize(debug: true).then((value) { + registerCallback(first: true); + }); + } else { + registerCallback(); + } + } + + registerCallback({bool first = false}) { + //3、在initState中初始化 + /* + D/DownloadWorker( 4745): Update too frequently!!!!, this should be dropped + E/flutter ( 4745): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: NoSuchMethodError: The method 'update' was called on null. + E/flutter ( 4745): Receiver: null + E/flutter ( 4745): Tried calling: update(message: "下载中,请稍后…", progress: 0.0) + E/flutter ( 4745): #0 Object.noSuchMethod (dart:core-patch/object_patch.dart:51:5) + E/flutter ( 4745): #1 MyUpdatedNew._updateDownLoadInfo (package:hyzp_ybqx/pages/MyMsics/05_updated/MyUpdatedNew.dart:251:10) + E/flutter ( 4745): #2 _rootRunUnary (dart:async/zone.dart:1206:13) + E/flutter ( 4745): #3 _CustomZone.runUnary (dart:async/zone.dart:1100:19) + E/flutter ( 4745): #4 _CustomZone.runUnaryGuarded (dart:async/zone.dart:1005:7) + E/flutter ( 4745): #5 _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:357:11) + E/flutter ( 4745): #6 _BufferingStreamSubscription._add (dart:async/stream_impl.dart:285:7) + E/flutter ( 4745): #7 _SyncStreamControllerDispatch._sendData (dart:async/stream_controller.dart:808:19) + E/flutter ( 4745): #8 _StreamController._add (dart:async/stream_controller.dart:682:7) + E/flutter ( 4745): #9 _StreamController.add (dart:async/stream_controller.dart:624:5) + E/flutter ( 4745): #10 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:168:12) + E/flutter ( 4745): + */ + + if (!first) { + // 解决2次进入报错无法下载的问题-OK + // 如果以前注册过必须先移除,否则报错无法下载:DownloadWorker( 4745): Update too frequently!!!!, this should be dropped + IsolateNameServer.removePortNameMapping('downloader_send_port'); + // FlutterDownloader.cancelAll(); + // FlutterDownloader.remove(taskId: null); + } + IsolateNameServer.registerPortWithName(_port.sendPort, 'downloader_send_port'); + _port.listen(_updateDownLoadInfo); + FlutterDownloader.registerCallback(_downLoadCallback); + getNewverUrl().then((value) { + _mapVer = value; + print('_mapVer = ${_mapVer}'); + //I/flutter (12498): _mapVer = {id: 1, ver: 1.0.0, miaos: 版本说明, + // downurl: http://www.sctastech.com/download/hyzp_20210425.apk, updatetime: 1620632231} + + print('oldVer = ${ver}'); + //_mapVer['ver'] = '1.3.1'; + print('newVer = ${_mapVer['ver']}'); + // I/flutter ( 1872): oldVer = 1.3.0 + // I/flutter ( 1872): newVer = 1.3.1 + + if (first) { + bFlutterDownloader_initialize = true; + } + + startUpdated(); + }); + } + + ///开始更新过程 + Future startUpdated() async { + // 如果是android,则执行热更新 + if (Platform.isAndroid) { + verCompare(newVer: _mapVer['ver'], oldVer: ver).then((value) { + if (value) { + print('value = $value'); + //_getNewVersionAPP(context); + //appId = res.data['id']; + //_checkVersionCode(); + bNewVer = true; //发现新版本 + if (bStartUpdated) { + serviceVersionCode = _mapVer['ver']; + _showNewVersionAppDialog(); + } + } else if (bShowNoNewVersion) { + // 没有发现新版本 + _showNoNewVersionAppDialog(); + } + }); + } + } + + /// 没有发现新版本 + Future _showNoNewVersionAppDialog() async { + return showDialog( + context: theContext, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [Text("没有发现新版本")], + ), + content: Container( + //padding: EdgeInsets.only(bottom: ScreenUtil().setHeight(18)), + height: ScreenUtil().setHeight(230), + decoration: BoxDecoration( + border: Border.all(color: Colors.blueAccent, width: 1.0), + borderRadius: BorderRadius.circular(5), + ), + alignment: Alignment.center, + child: RichText( + textAlign: TextAlign.center, + text: TextSpan(children: [ + TextSpan(text: 'NewVer: ', style: TextStyle(fontSize: 17, color: Colors.black)), + TextSpan( + text: '暂无新版本', + style: TextStyle( + fontSize: 17, color: Colors.black, fontWeight: FontWeight.bold)), + ]), + ), + ), + actions: [ + new FlatButton( + child: new Text('确定'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }); + } + + /// 版本更新提示对话框 + Future _showNewVersionAppDialog() async { + String content1 = "注意:"; + String content2 = "新版本下载成功后,将弹出“安装未知应用程序”界面,请"; + String content3 = "授权“允许此来源”"; + String content4 = ";然后"; + String content5 = "稍等几秒钟"; + String content6 = ",再"; + String content7 = "点击“返回”按钮(一般位于左上角)"; + String content8 = ",按照提示即可完成升级过程。若"; + String content9 = "升级过程意外中断"; + String content10 = ",可重启App再次升级即可。"; + + return showDialog( + context: theContext, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + insetPadding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 24.0), + buttonPadding: EdgeInsets.only(bottom: 15, right: 20), + title: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [Text("发现新版本")], + ), + content: Container( + padding: EdgeInsets.only( + left: ScreenUtil().setHeight(25), right: ScreenUtil().setHeight(10)), + height: ScreenUtil().setHeight(620), + decoration: BoxDecoration( + border: Border.all(color: Colors.blueAccent, width: 1.0), + borderRadius: BorderRadius.circular(5), + ), + alignment: Alignment.center, + child: Column( + children: [ + RichText( + textAlign: TextAlign.center, + text: TextSpan(children: [ + TextSpan( + text: '\nNewVer: ', + style: TextStyle(fontSize: 17, color: Colors.redAccent)), + TextSpan( + text: serviceVersionCode, + style: TextStyle( + fontSize: 17, color: Colors.redAccent, fontWeight: FontWeight.bold)), + ]), + ), + RichText( + textAlign: TextAlign.justify, + text: TextSpan(children: [ + getTextSpan('\n' + content1, color: Colors.blueAccent), + getTextSpan(content2), + getTextSpan(content3, color: Colors.redAccent), + getTextSpan(content4), + getTextSpan(content5, color: Colors.redAccent), + getTextSpan(content6), + getTextSpan(content7, color: Colors.redAccent), + getTextSpan(content8), + getTextSpan(content9, color: Colors.blueAccent), + getTextSpan(content10), + ]), + ), + ], + ), + ), + actions: [ + new FlatButton( + child: new Text('立即更新'), + onPressed: () { + /// 执行更新操作 + Navigator.pop(context); + _executeDownload(context); + }, + ), + new FlatButton( + child: new Text('下次再说'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }); + } + + TextSpan getTextSpan(String _text, {Color color = Colors.black, double fontSize = 15}) { + return TextSpan(text: _text, style: TextStyle(fontSize: fontSize, color: color)); + } + + /// 下载最新apk包 + Future _executeDownload(BuildContext context) async { + pr = new ProgressDialog( + context, + type: ProgressDialogType.Download, + isDismissible: true, + showLogs: true, + ); + pr.style(message: '准备下载...'); + if (!pr.isShowing()) { + pr.show(); + } + + final path = await _apkLocalPath; + apkName = getFileName(_mapVer['downurl']); + await FlutterDownloader.enqueue( + //url: 'http://update.rwworks.com:8088/appManager/monitor/app/appload/' + appId + '', + url: _mapVer['downurl'], + savedDir: path, + fileName: apkName, + showNotification: true, + openFileFromNotification: true); + } + + /// 下载进度回调函数 + static void _downLoadCallback(String id, DownloadTaskStatus status, int progress) { + final SendPort send = IsolateNameServer.lookupPortByName('downloader_send_port'); + send.send([id, status, progress]); + } + + /// 更新下载进度框 + _updateDownLoadInfo(dynamic data) { + DownloadTaskStatus status = data[1]; + int progress = data[2]; + if (status == DownloadTaskStatus.running) { + pr.update(progress: double.parse(progress.toString()), message: "下载中,请稍后…"); + } + if (status == DownloadTaskStatus.failed) { + if (pr.isShowing()) { + pr.hide(); + } + } + + if (status == DownloadTaskStatus.complete) { + if (pr.isShowing()) { + pr.hide(); + } + _installApk(); + } + } + + /// 安装apk + Future _installApk() async { + await OpenFile.open(appPath + '/' + apkName); + } + + /// 获取apk存储位置 + Future get _apkLocalPath async { + final directory = await getExternalStorageDirectory(); + String path = directory.path + Platform.pathSeparator + 'Download'; + ; + final savedDir = Directory(path); + bool hasExisted = await savedDir.exists(); + if (!hasExisted) { + await savedDir.create(); + } + appPath = path; + + // this.setState(() { + // appPath = path; + // }); + + return path; + } + + //4、判断,自动更新 + // 版本比较 + //R:\FlutterProject\FlutterProject33\hyzp_ybqx\lib\pages\MyMsics\05_updated\MyUpdatedNew.dart line 343 + Future verCompare({String newVer, String oldVer}) async { + //解决newVer中不包含字符“+”号报错失败问题 + if (newVer.indexOf('+') > -1) { + //解决App.Car_Ver.Getver接口返回值变化后,1.3.11+20210729字符串转换为数字报错问题 + print('newVer = $newVer'); + newVer = newVer.substring(0, newVer.indexOf('+')); // substring是含头不含尾 + print('newVer2 = $newVer'); + } + + List listNewVer = await tran2int(newVer.split('.')); + List listOldVer = await tran2int(oldVer.split('.')); + + int len = listNewVer.length; + for (int i = 0; i < len; i++) { + if (listNewVer[i] > listOldVer[i]) { + return true; + } + } + return false; + } + + Future tran2int(List _list) async { + List listRet = []; + int len = _list.length; + for (int i = 0; i < len; i++) { + listRet.add(int.parse(_list[i].trim())); + } + return listRet; + } + + ///https://blog.csdn.net/zcylyzhi4/article/details/108002879 +//5、自动更新代码 + /// 执行版本更新的网络请求 + + /// 检查当前版本是否为最新,若不是,则更新 +// void _checkVersionCode() { +// PackageInfo.fromPlatform().then((PackageInfo packageInfo) { +// var currentVersionCode = packageInfo.version; +// if (double.parse(serviceVersionCode.substring(0, 3)) > +// double.parse(currentVersionCode.substring(0, 3))) { +// _showNewVersionAppDialog(); +// } +// }); +// } +} diff --git a/lib/pages/MyMsics/07_myAbout/MyAbout.dart b/lib/pages/MyMsics/07_myAbout/MyAbout.dart new file mode 100644 index 0000000..44aafdd --- /dev/null +++ b/lib/pages/MyMsics/07_myAbout/MyAbout.dart @@ -0,0 +1,139 @@ +import 'package:flutter/material.dart'; +import '../../../components/commonFun.dart'; + +import '../../../widget/JdButton.dart'; +import '../../../components/commonFun.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class MyAbout extends StatefulWidget { + MyAbout({Key key, this.ver, this.date}) : super(key: key); + String ver = '1.0.0'; + String date = ''; + + _LoginPageState createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + var _username = new TextEditingController(); + + void initState() { + // TODO: implement initState + super.initState(); + } + + //监听登录页面销毁的事件 + dispose() { + super.dispose(); + } + + doLogin() async { + //临时跳转 + //Navigator.pushNamed(context, '/tabs', arguments: g_iIndex); + Navigator.pop(context); //返回 + return; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: PreferredSize( + preferredSize: Size.fromHeight(ScreenUtil().setHeight(173)), // 设置appBar高度 + // 设置appBar高度 + child: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + titleSpacing: 0.0, + //设置title的左边距 + flexibleSpace: Container( + //SizedBox(height: ScreenUtil().statusBarHeight), //显示顶部状态栏 + // SizedBox(height: ScreenUtil().setHeight(10)), //显示顶部状态栏 + padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight), //留出顶部状态栏高度 + child: Container( + //height: ScreenUtil().setHeight(173), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + // decoration: BoxDecoration( + // gradient: LinearGradient(colors: [ + // Color(0xFF0018EB), + // Color(0xFF01C1D9), + // ], begin: Alignment.bottomCenter, end: Alignment.topCenter), + // ), + ), + ), + title: Padding( + padding: EdgeInsets.only(left: 0, right: 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + getIconAndTextButton( + iconColor: Colors.white, + iconData: Icons.chevron_left_outlined, + onPress: () { + Navigator.pop(context); + }, + ), + Expanded( + child: Text("关于", + style: TextStyle(color: Colors.white, fontSize: 20), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis), + ), + SizedBox(width: 50), + ], + ), + ), + ), + ), + body: Container( + padding: EdgeInsets.only(top: 20, bottom: 20, left: 5, right: 5), + child: ListView( + children: [ + Center( + child: Container( + margin: EdgeInsets.only(top: 30), + height: ScreenUtil().setWidth(160), + width: ScreenUtil().setWidth(160), + //child: Image.asset('assets/images/user.png', fit: BoxFit.cover), + child: Image.asset('assets/images/ybsthbj.png', fit: BoxFit.fitHeight), + // child: Image.network( + // 'https://www.itying.com/images/flutter/list5.jpg', + // fit: BoxFit.cover), + ), + ), + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(height: 60), + Text('宜宾市黑烟车电子抓拍系统', style: TextStyle(fontSize: 20)), + Text('v${widget.ver}(${widget.date})', style: TextStyle(fontSize: 20)), + SizedBox(height: 60), + Text('© 宜宾市生态环境局 四川省踏石科技 版权所有\n服务热线:187-8467-8300', + maxLines: 2, style: TextStyle(fontSize: 16), textAlign: TextAlign.center), + ], + ), + ), + SizedBox(height: 100), + Container( + padding: EdgeInsets.only(top: 20, bottom: 20, left: 20, right: 20), + child: JdButton( + height: 126, + text: "确认", + color: Colors.blueAccent, + onTop: doLogin, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/Order.dart b/lib/pages/Order.dart new file mode 100644 index 0000000..ac00e3b --- /dev/null +++ b/lib/pages/Order.dart @@ -0,0 +1,156 @@ +import 'package:flutter/material.dart'; + +import '../config/Config.dart'; +import 'package:dio/dio.dart'; + +//订单列表数据模型 +import '../model/OrderModel.dart'; + +import '../services/UserServices.dart'; +import '../services/SignServices.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + + +class OrderPage extends StatefulWidget { + OrderPage({Key key}) : super(key: key); + + _OrderPageState createState() => _OrderPageState(); +} + +class _OrderPageState extends State { + List _orderList = []; + + @override + void initState() { + // TODO: implement initState + super.initState(); + this._getListData(); + } + + void _getListData() async { + List userinfo = await UserServices.getUserInfo(); + + var tempJson = {"uid": userinfo[0]['_id'], "salt": userinfo[0]["salt"]}; + + var sign = SignServices.getSign(tempJson); + + var api = + '${Config.domain}api/orderList?uid=${userinfo[0]['_id']}&sign=${sign}'; + + var response = await Dio().get(api); + print(response.data is Map); + + setState(() { + var orderMode = new OrderModel.fromJson(response.data); + + this._orderList = orderMode.result; + + print(this._orderList[0].name); + }); + } + + //自定义商品列表组件 + + List _orderItemWidget(orderItems) { + List tempList = []; + for (var i = 0; i < orderItems.length; i++) { + tempList.add(Column( + children: [ + SizedBox(height: 10), + ListTile( + leading: Container( + width: ScreenUtil().setWidth(120), + height: ScreenUtil().setHeight(120), + child: Image.network( + '${orderItems[i].productImg}', + fit: BoxFit.cover, + ), + ), + title: Text("${orderItems[i].productTitle}"), + trailing: Text('x${orderItems[i].productCount}'), + ), + SizedBox(height: 10) + ], + )); + } + return tempList; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("我的订单"), + ), + body: Stack( + children: [ + Container( + margin: EdgeInsets.fromLTRB(0, ScreenUtil().setHeight(80), 0, 0), + padding: EdgeInsets.all(ScreenUtil().setWidth(16)), + child: ListView( + children: this._orderList.map((value) { + return InkWell( + onTap: (){ + + Navigator.pushNamed(context, '/orderinfo'); + }, + child: Card( + child: Column( + children: [ + ListTile( + title: Text("订单编号:${value.sId}", + style: TextStyle(color: Colors.black54)), + ), + Divider(), + Column( + children: this._orderItemWidget(value.orderItem), + ), + SizedBox(height: 10), + ListTile( + leading: Text("合计:¥${value.allPrice}"), + trailing: FlatButton( + child: Text("申请售后"), + onPressed: () {}, + color: Colors.grey[100], + ), + ), + ], + ), + ), + ); + }).toList()), + ), + Positioned( + top: 0, + width: ScreenUtil().setWidth(750), + height: ScreenUtil().setHeight(76), + child: Container( + width: ScreenUtil().setWidth(750), + height: ScreenUtil().setHeight(76), + color: Colors.white, + child: Row( + children: [ + Expanded( + child: Text("全部", textAlign: TextAlign.center), + ), + Expanded( + child: Text("待付款", textAlign: TextAlign.center), + ), + Expanded( + child: Text("待收货", textAlign: TextAlign.center), + ), + Expanded( + child: Text("已完成", textAlign: TextAlign.center), + ), + Expanded( + child: Text("已取消", textAlign: TextAlign.center), + ) + ], + ), + ), + ) + ], + ), + ); + } +} diff --git a/lib/pages/OrderInfo.dart b/lib/pages/OrderInfo.dart new file mode 100644 index 0000000..2197b59 --- /dev/null +++ b/lib/pages/OrderInfo.dart @@ -0,0 +1,188 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + + + +class OrderInfoPage extends StatefulWidget { + OrderInfoPage({Key key}) : super(key: key); + + _OrderInfoPageState createState() => _OrderInfoPageState(); +} + +class _OrderInfoPageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text("订单详情")), + body: Container( + child: ListView( + children: [ + //收货地址 + Container( + color: Colors.white, + child: Column( + children: [ + SizedBox(height: 10), + ListTile( + leading: Icon(Icons.add_location), + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("张三 15201686455"), + SizedBox(height: 10), + Text("北京市海淀区 西二旗"), + ], + ), + ), + SizedBox(height: 10), + ], + ), + ), + SizedBox(height: 16), + //列表 + Container( + color: Colors.white, + padding: EdgeInsets.all(10), + child: Column( + children: [ + Row( + children: [ + Container( + margin: EdgeInsets.fromLTRB(0, 10, 0, 0), + width: ScreenUtil().setWidth(120), + child: Image.network( + "https://www.itying.com/images/flutter/list2.jpg", + fit: BoxFit.cover), + ), + Expanded( + flex: 1, + child: Container( + padding: EdgeInsets.fromLTRB(10, 10, 10, 5), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("四季沐歌 (MICOE) 洗衣机水龙头 洗衣机水嘴 单冷快开铜材质龙头", + maxLines: 2, + style: TextStyle(color: Colors.black54)), + Text("水龙头 洗衣机", + maxLines: 2, + style: TextStyle(color: Colors.black54)), + ListTile( + leading: Text("¥100", + style: TextStyle(color: Colors.red)), + trailing: Text("x2"), + ) + ], + ), + )) + ], + ), + Row( + children: [ + Container( + margin: EdgeInsets.fromLTRB(0, 10, 0, 0), + width: ScreenUtil().setWidth(120), + child: Image.network( + "https://www.itying.com/images/flutter/list2.jpg", + fit: BoxFit.cover), + ), + Expanded( + flex: 1, + child: Container( + padding: EdgeInsets.fromLTRB(10, 10, 10, 5), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("四季沐歌 (MICOE) 洗衣机水龙头 洗衣机水嘴 单冷快开铜材质龙头", + maxLines: 2, + style: TextStyle(color: Colors.black54)), + Text("水龙头 洗衣机", + maxLines: 2, + style: TextStyle(color: Colors.black54)), + ListTile( + leading: Text("¥100", + style: TextStyle(color: Colors.red)), + trailing: Text("x2"), + ) + ], + ), + )) + ], + ), + ], + ), + ), + + //详情信息 + Container( + color: Colors.white, + margin: EdgeInsets.fromLTRB(0, 10, 0, 0), + child: Column( + children: [ + + ListTile( + title: Row( + children: [ + Text("订单编号:",style: TextStyle(fontWeight: FontWeight.bold)), + Text("124215215xx324") + ], + ), + ), + + ListTile( + title: Row( + children: [ + Text("下单日期:",style: TextStyle(fontWeight: FontWeight.bold)), + Text("2019-12-09") + ], + ), + ), + + ListTile( + title: Row( + children: [ + Text("支付方式:",style: TextStyle(fontWeight: FontWeight.bold)), + Text("微信支付") + ], + ), + ), + + ListTile( + title: Row( + children: [ + Text("配送方式:",style: TextStyle(fontWeight: FontWeight.bold)), + Text("顺丰") + ], + ), + ) + + ], + ), + ), + SizedBox(height: 16), + Container( + color: Colors.white, + margin: EdgeInsets.fromLTRB(0, 10, 0, 0), + child: Column( + children: [ + ListTile( + title: Row( + children: [ + Text("总金额:",style: TextStyle(fontWeight: FontWeight.bold)), + Text("¥414元",style: TextStyle( + color: Colors.red + )) + ], + ) + ) + ], + ), + ) + ], + ), + ), + ); + } +} diff --git a/lib/pages/Pay.dart b/lib/pages/Pay.dart new file mode 100644 index 0000000..ce611b9 --- /dev/null +++ b/lib/pages/Pay.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import '../widget/JdButton.dart'; + +class PayPage extends StatefulWidget { + PayPage({Key key}) : super(key: key); + + _PayPageState createState() => _PayPageState(); +} + +class _PayPageState extends State { + List payList = [ + { + "title": "支付宝支付", + "chekced": true, + "image": "https://www.itying.com/themes/itying/images/alipay.png" + }, + { + "title": "微信支付", + "chekced": false, + "image": "https://www.itying.com/themes/itying/images/weixinpay.png" + } + ]; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("去支付"), + ), + body: Column( + children: [ + Container( + height: 400, + padding: EdgeInsets.all(20), + child: ListView.builder( + itemCount: this.payList.length, + itemBuilder: (context, index) { + return Column( + children: [ + ListTile( + leading: + Image.network("${this.payList[index]["image"]}"), + title: Text("${this.payList[index]["title"]}"), + trailing: this.payList[index]["chekced"] + ? Icon(Icons.check) + : Text(""), + onTap: () { + //让payList里面的checked都等于false + setState(() { + for (var i = 0; i < this.payList.length; i++) { + this.payList[i]['chekced'] = false; + } + this.payList[index]["chekced"] = true; + }); + }, + ), + Divider(), + ], + ); + }, + )), + JdButton( + text: "支付", + color: Colors.red, + height: 74, + onTop: () { + print('支付1111'); + }, + ) + ], + ), + ); + } +} diff --git a/lib/pages/ProductList.dart b/lib/pages/ProductList.dart new file mode 100644 index 0000000..590d1b9 --- /dev/null +++ b/lib/pages/ProductList.dart @@ -0,0 +1,387 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import '../services/SearchServices.dart'; + +import '../config/Config.dart'; +import 'package:dio/dio.dart'; +import '../model/ProductModel.dart'; +import '../widget/LoadingWidget.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + + + +class ProductListPage extends StatefulWidget { + Map arguments; + ProductListPage({Key key, this.arguments}) : super(key: key); + + _ProductListPageState createState() => _ProductListPageState(); +} + +class _ProductListPageState extends State { + //Scaffold key + final GlobalKey _scaffoldKey = new GlobalKey(); + + //用于上拉分页 listview 的控制器 + ScrollController _scrollController = ScrollController(); + //分页 + int _page = 1; + //每页有多少条数据 + int _pageSize = 8; + //数据 + List _productList = []; + /* + 排序:价格升序 sort=price_1 价格降序 sort=price_-1 销量升序 sort=salecount_1 销量降序 sort=salecount_-1 + */ + String _sort = ""; + //解决重复请求的问题 + bool flag = true; + //是否有数据 + bool _hasMore = true; + + + //是否有搜索的数据 + bool _hasData = true; + + /*二级导航数据*/ + List _subHeaderList = [ + { + "id": 1, + "title": "综合", + "fileds": "all", + "sort": + -1, //排序 升序:price_1 {price:1} 降序:price_-1 {price:-1} + }, + {"id": 2, "title": "销量", "fileds": 'salecount', "sort": -1}, + {"id": 3, "title": "价格", "fileds": 'price', "sort": -1}, + {"id": 4, "title": "筛选"} + ]; + //二级导航选中判断 + int _selectHeaderId = 1; + + + //配置search搜索框的值 + + var _initKeywordsController=new TextEditingController(); + + //cid + + //keywords + + var _cid; + + var _keywords; + + + + @override + void initState() { + super.initState(); + + this._cid=widget.arguments["cid"]; + this._keywords=widget.arguments["keywords"]; + //给search框框赋值 + this._initKeywordsController.text=this._keywords; + + + _getProductListData(); + //监听滚动条滚动事件 + _scrollController.addListener(() { + //_scrollController.position.pixels //获取滚动条滚动的高度 + //_scrollController.position.maxScrollExtent //获取页面高度 + if (_scrollController.position.pixels > + _scrollController.position.maxScrollExtent - 20) { + if (this.flag && this._hasMore) { + _getProductListData(); + } + } + }); + + + } + + //获取商品列表的数据 + _getProductListData() async { + setState(() { + this.flag = false; + }); + var api; + if(this._keywords==null){ + api ='${Config.domain}api/plist?cid=${this._cid}&page=${this._page}&sort=${this._sort}&pageSize=${this._pageSize}'; + }else{ + api ='${Config.domain}api/plist?search=${this._keywords}&page=${this._page}&sort=${this._sort}&pageSize=${this._pageSize}'; + } + // print(api); + var result = await Dio().get(api); + + var productList = new ProductModel.fromJson(result.data); + + //判断是否有搜索数据 + if(productList.result.length==0 && this._page==1){ + setState(() { + this._hasData=false; + }); + }else{ + this._hasData=true; + } + //判断最后一页有没有数据 + if (productList.result.length < this._pageSize) { + setState(() { + this._productList.addAll(productList.result); + this._hasMore = false; + this.flag = true; + }); + } else { + setState(() { + this._productList.addAll(productList.result); + this._page++; + this.flag = true; + }); + } + + + } + + //显示加载中的圈圈 + Widget _showMore(index) { + if (this._hasMore) { + return (index == this._productList.length - 1) + ? LoadingWidget() //显示加载中的圈圈 + : Text(""); + } else { + return (index == this._productList.length - 1) + ? Text("--我是有底线的--") + : Text(""); + ; + } + } + + //商品列表 + Widget _productListWidget() { + if (this._productList.length > 0) { + return Container( + padding: EdgeInsets.all(10), + margin: EdgeInsets.only(top: ScreenUtil().setHeight(80)), + child: ListView.builder( + controller: _scrollController, + itemBuilder: (context, index) { + //处理图片 + String pic = this._productList[index].pic; + pic = Config.domain + pic.replaceAll('\\', '/'); + //每一个元素 + return Column( + children: [ + Row( + children: [ + Container( + width: ScreenUtil().setWidth(180), + height: ScreenUtil().setHeight(180), + child: Image.network("${pic}", fit: BoxFit.cover), + ), + Expanded( + flex: 1, + child: Container( + height: ScreenUtil().setHeight(180), + margin: EdgeInsets.only(left: 10), + // color: Colors.red, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("${this._productList[index].num}", + maxLines: 2, overflow: TextOverflow.ellipsis), + Row( + children: [ + Container( + height: ScreenUtil().setHeight(36), + margin: EdgeInsets.only(right: 10), + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + + //注意 如果Container里面加上decoration属性,这个时候color属性必须得放在BoxDecoration + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Color.fromRGBO(230, 230, 230, 0.9), + ), + + child: Text("4g"), + ), + Container( + height: ScreenUtil().setHeight(36), + margin: EdgeInsets.only(right: 10), + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Color.fromRGBO(230, 230, 230, 0.9), + ), + child: Text("126"), + ), + ], + ), + Text( + "¥${this._productList[index].price}", + style: TextStyle(color: Colors.red, fontSize: 16), + ) + ], + ), + ), + ) + ], + ), + Divider(height: 20), + _showMore(index) + ], + ); + }, + itemCount: this._productList.length, + ), + ); + } else { + //加载中 + return LoadingWidget(); + } + } + + //导航改变的时候触发 + _subHeaderChange(id) { + if (id == 4) { + _scaffoldKey.currentState.openEndDrawer(); + setState(() { + this._selectHeaderId = id; + }); + } else { + setState(() { + this._selectHeaderId = id; + this._sort ="${this._subHeaderList[id - 1]["fileds"]}_${this._subHeaderList[id - 1]["sort"]}"; + + //重置分页 + this._page = 1; + //重置数据 + this._productList = []; + //改变sort排序 + this._subHeaderList[id - 1]['sort'] = + this._subHeaderList[id - 1]['sort'] * -1; + //回到顶部 + _scrollController.jumpTo(0); + //重置_hasMore + this._hasMore = true; + //重新请求 + this._getProductListData(); + }); + } + } + + //显示header Icon + Widget _showIcon(id){ + if(id==2|| id ==3){ + if(this._subHeaderList[id-1]["sort"]==1){ + return Icon(Icons.arrow_drop_down); + } + return Icon(Icons.arrow_drop_up); + } + return Text(""); + } + //筛选导航 + Widget _subHeaderWidget() { + return Positioned( + top: 0, + height: ScreenUtil().setHeight(80), + width: ScreenUtil().setWidth(750), + child: Container( + width: ScreenUtil().setWidth(750), + height: ScreenUtil().setHeight(80), + // color: Colors.red, + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + width: 1, color: Color.fromRGBO(233, 233, 233, 0.9)))), + child: Row( + children: this._subHeaderList.map((value) { + return Expanded( + flex: 1, + child: InkWell( + child: Padding( + padding: EdgeInsets.fromLTRB( + 0, ScreenUtil().setHeight(16), 0, ScreenUtil().setHeight(16)), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "${value["title"]}", + textAlign: TextAlign.center, + style: TextStyle( + color: (this._selectHeaderId == value["id"]) + ? Colors.red + : Colors.black54), + ), + _showIcon(value["id"]) + ], + ), + ), + onTap: () { + _subHeaderChange(value["id"]); + }, + ), + ); + }).toList(), + ), + ), + ); + } + @override + Widget build(BuildContext context) { + + + return Scaffold( + key: _scaffoldKey, + appBar:AppBar( + title: Container( + child: TextField( + controller: this._initKeywordsController, + autofocus: false, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(30), + borderSide: BorderSide.none)), + onChanged: (value){ + setState(() { + this._keywords=value; + }); + }, + ), + height: ScreenUtil().setHeight(68), + decoration: BoxDecoration( + color: Color.fromRGBO(233, 233, 233, 0.8), + borderRadius: BorderRadius.circular(30)), + ), + actions: [ + InkWell( + child: Container( + height: ScreenUtil().setHeight(68), + width: ScreenUtil().setWidth(80), + child: Row( + children: [Text("搜索")], + ), + ), + onTap: () { + SearchServices.setHistoryData(this._keywords); + this._subHeaderChange(1); + }, + ) + ], + ), + endDrawer: Drawer( + child: Container( + child: Text("实现筛选功能"), + ), + ), + body: _hasData?Stack( + children: [ + _productListWidget(), + _subHeaderWidget(), + ], + ):Center( + child: Text("没有您要浏览的数据") + ) + ); + } +} diff --git a/lib/pages/RegisterFirst.dart b/lib/pages/RegisterFirst.dart new file mode 100644 index 0000000..2224f76 --- /dev/null +++ b/lib/pages/RegisterFirst.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; + +import '../widget/JdText.dart'; +import '../widget/JdButton.dart'; + +import '../config/Config.dart'; +import 'package:dio/dio.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + + +class RegisterFirstPage extends StatefulWidget { + RegisterFirstPage({Key key}) : super(key: key); + + _RegisterFirstPageState createState() => _RegisterFirstPageState(); +} + +class _RegisterFirstPageState extends State { + String tel=""; + sendCode() async { + RegExp reg = new RegExp(r"^1\d{10}$"); + if (reg.hasMatch(this.tel)) { + var api = '${Config.domain}api/sendCode'; + var response = await Dio().post(api, data: {"tel": this.tel}); + if (response.data["success"]) { + + print(response); //演示期间服务器直接返回 给手机发送的验证码 + + Navigator.pushNamed(context, '/registerSecond', arguments: { + "tel":this.tel + }); + + } else { + Fluttertoast.showToast( + msg: '${response.data["message"]}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + } else { + Fluttertoast.showToast( + msg: '手机号格式不对', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("用户注册-第一步"), + ), + body: Container( + padding: EdgeInsets.all(ScreenUtil().setWidth(20)), + child: ListView( + children: [ + SizedBox(height: 50), + JdText( + height: ScreenUtil().setHeight(300), + text: "请输入手机号", + onChanged: (value) { + // print(value); + this.tel = value; + }, + ), + SizedBox(height: 20), + JdButton( + height: ScreenUtil().setHeight(350), + text: "下一步", + color: Colors.red, + onTop: sendCode, + ) + ], + ), + ), + ); + } +} diff --git a/lib/pages/RegisterSecond.dart b/lib/pages/RegisterSecond.dart new file mode 100644 index 0000000..e1656d6 --- /dev/null +++ b/lib/pages/RegisterSecond.dart @@ -0,0 +1,139 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import '../widget/JdText.dart'; +import '../widget/JdButton.dart'; + +import 'dart:async'; //Timer定时器需要引入 +import '../config/Config.dart'; +import 'package:dio/dio.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + + +class RegisterSecondPage extends StatefulWidget { + Map arguments; + RegisterSecondPage({Key key, this.arguments}) : super(key: key); + + _RegisterSecondPageState createState() => _RegisterSecondPageState(); +} + +class _RegisterSecondPageState extends State { + String tel; + bool sendCodeBtn = false; + int seconds = 10; + String code; + + @override + void initState() { + // TODO: implement initState + super.initState(); + this.tel = widget.arguments['tel']; + this._showTimer(); + } + + //倒计时 + _showTimer() { + Timer t; + t = Timer.periodic(Duration(milliseconds: 1000), (timer) { + setState(() { + this.seconds--; + }); + if (this.seconds == 0) { + t.cancel(); //清除定时器 + setState(() { + this.sendCodeBtn = true; + }); + } + }); + } + + //重新发送验证码 + sendCode() async { + setState(() { + this.sendCodeBtn = false; + this.seconds = 10; + this._showTimer(); + }); + var api = '${Config.domain}api/sendCode'; + var response = await Dio().post(api, data: {"tel": this.tel}); + if (response.data["success"]) { + print(response); //演示期间服务器直接返回 给手机发送的验证码 + } + } + //验证验证码 + + validateCode() async { + var api = '${Config.domain}api/validateCode'; + var response = + await Dio().post(api, data: {"tel": this.tel, "code": this.code}); + if (response.data["success"]) { + Navigator.pushNamed(context, '/registerThird',arguments: { + "tel":this.tel, + "code":this.code + }); + } else { + Fluttertoast.showToast( + msg: '${response.data["message"]}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("用户注册-第二步"), + ), + body: Container( + padding: EdgeInsets.all(ScreenUtil().setWidth(20)), + child: ListView( + children: [ + SizedBox(height: 50), + Container( + padding: EdgeInsets.only(left: 10), + child: Text("验证码已经发送到了您的${this.tel}手机,请输入${this.tel}手机号收到的验证码"), + ), + SizedBox(height: 40), + Stack( + children: [ + Container( + child: JdText( + text: "请输入验证码", + onChanged: (value) { + // print(value); + this.code = value; + }, + ), + height: ScreenUtil().setHeight(150), + ), + Positioned( + right: 0, + top: 0, + child: this.sendCodeBtn + ? RaisedButton( + child: Text('重新发送'), + onPressed: this.sendCode, + ) + : RaisedButton( + child: Text('${this.seconds}秒后重发'), + onPressed: () {}, + ), + ) + ], + ), + SizedBox(height: 20), + JdButton( + height: ScreenUtil().setHeight(350), + text: "下一步", + color: Colors.red, + onTop: this.validateCode, + ) + ], + ), + ), + ); + } +} diff --git a/lib/pages/RegisterThird.dart b/lib/pages/RegisterThird.dart new file mode 100644 index 0000000..4e050af --- /dev/null +++ b/lib/pages/RegisterThird.dart @@ -0,0 +1,117 @@ +import 'package:flutter/material.dart'; +import '../widget/JdText.dart'; +import '../widget/JdButton.dart'; + + +import '../config/Config.dart'; +import 'package:dio/dio.dart'; +import 'package:fluttertoast/fluttertoast.dart'; + +import '../services/Storage.dart'; +import 'dart:convert'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + + +//引入Tabs + +import '../pages/tabs/Tabs.dart'; + +class RegisterThirdPage extends StatefulWidget { + Map arguments; + + RegisterThirdPage({Key key, this.arguments}) : super(key: key); + + _RegisterThirdPageState createState() => _RegisterThirdPageState(); +} + +class _RegisterThirdPageState extends State { + String tel; + String code; + String password = ''; + String rpassword = ''; + + @override + void initState() { + // TODO: implement initState + super.initState(); + this.tel = widget.arguments["tel"]; + this.code = widget.arguments["code"]; + } + + //注册 + doRegister() async { + if (password.length < 6) { + Fluttertoast.showToast( + msg: '密码长度不能小于6位', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } else if (rpassword != password) { + Fluttertoast.showToast( + msg: '密码和确认密码不一致', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } else { + var api = '${Config.domain}api/register'; + var response = await Dio().post(api, data: {"tel": this.tel, "code": this.code, "password": this.password}); + if (response.data["success"]) { + //保存用户信息 + Storage.setString('userInfo', json.encode(response.data["userinfo"])); + + //返回到根 +// Navigator.of(context) +// .pushAndRemoveUntil(new MaterialPageRoute(builder: (context) => new Tabs()), (route) => route == null); + //临时跳转 + Navigator.pushNamed(context, '/', arguments: 1); + } else { + Fluttertoast.showToast( + msg: '${response.data["message"]}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("用户注册-第三步"), + ), + body: Container( + padding: EdgeInsets.all(ScreenUtil().setWidth(20)), + child: ListView( + children: [ + SizedBox(height: 50), + JdText( + height: ScreenUtil().setHeight(300), + text: "请输入密码", + password: true, + onChanged: (value) { + this.password = value; + }, + ), + SizedBox(height: 10), + JdText( + height: ScreenUtil().setHeight(300), + text: "请输入确认密码", + password: true, + onChanged: (value) { + this.rpassword = value; + }, + ), + SizedBox(height: 20), + JdButton( + text: "注册", + color: Colors.red, + height: ScreenUtil().setHeight(350), + onTop: doRegister, + ) + ], + ), + ), + ); + } +} diff --git a/lib/pages/Search.dart b/lib/pages/Search.dart new file mode 100644 index 0000000..ed9e3a5 --- /dev/null +++ b/lib/pages/Search.dart @@ -0,0 +1,235 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../services/SearchServices.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + + +class SearchPage extends StatefulWidget { + SearchPage({Key key}) : super(key: key); + + _SearchPageState createState() => _SearchPageState(); +} + +class _SearchPageState extends State { + var _keywords; + + List _historyListData = []; + + @override + void initState() { + super.initState(); + this._getHistoryData(); + } + + _getHistoryData() async { + var _historyListData = await SearchServices.getHistoryList(); + setState(() { + this._historyListData=_historyListData; + }); + + } + + _showAlertDialog(keywords) async{ + + var result= await showDialog( + barrierDismissible:false, //表示点击灰色背景的时候是否消失弹出框 + context:context, + builder: (context){ + return AlertDialog( + title: Text("提示信息!"), + content:Text("您确定要删除吗?") , + actions: [ + FlatButton( + child: Text("取消"), + onPressed: (){ + print("取消"); + Navigator.pop(context,'Cancle'); + }, + ), + FlatButton( + child: Text("确定"), + onPressed: () async{ + //注意异步 + await SearchServices.removeHistoryData(keywords); + this._getHistoryData(); + Navigator.pop(context,"Ok"); + }, + ) + ], + + ); + } + ); + + // print(result); + + } + + Widget _historyListWidget() { + if (_historyListData.length > 0) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Text("历史记录", style: Theme.of(context).textTheme.title), + ), + Divider(), + Column( + children: this._historyListData.map((value) { + return Column( + children: [ + ListTile( + title: Text("${value}"), + onLongPress: (){ + this._showAlertDialog("${value}"); + }, + ), + Divider() + ], + ); + }).toList(), + ), + SizedBox(height: 100), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + InkWell( + onTap: () { + SearchServices.clearHistoryList(); + this._getHistoryData(); + + }, + child: Container( + width: ScreenUtil().setWidth(400), + height: ScreenUtil().setHeight(64), + decoration: BoxDecoration( + border: Border.all(color: Colors.black45, width: 1)), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [Icon(Icons.delete), Text("清空历史记录")], + ), + ), + ) + ], + ) + ], + ); + } else { + return Text(""); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Container( + child: TextField( + autofocus: true, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(30), + borderSide: BorderSide.none)), + onChanged: (value) { + this._keywords = value; + }, + ), + height: ScreenUtil().setHeight(68), + decoration: BoxDecoration( + color: Color.fromRGBO(233, 233, 233, 0.8), + borderRadius: BorderRadius.circular(30)), + ), + actions: [ + InkWell( + child: Container( + height: ScreenUtil().setHeight(68), + width: ScreenUtil().setWidth(80), + child: Row( + children: [Text("搜索")], + ), + ), + onTap: () { + SearchServices.setHistoryData(this._keywords); + Navigator.pushReplacementNamed(context, '/productList', + arguments: {"keywords": this._keywords}); + }, + ) + ], + ), + body: Container( + padding: EdgeInsets.all(10), + child: ListView( + children: [ + Container( + child: Text("热搜", style: Theme.of(context).textTheme.title), + ), + Divider(), + Wrap( + children: [ + Container( + padding: EdgeInsets.all(10), + margin: EdgeInsets.all(10), + decoration: BoxDecoration( + color: Color.fromRGBO(233, 233, 233, 0.9), + borderRadius: BorderRadius.circular(10)), + child: Text("女装"), + ), + Container( + padding: EdgeInsets.all(10), + margin: EdgeInsets.all(10), + decoration: BoxDecoration( + color: Color.fromRGBO(233, 233, 233, 0.9), + borderRadius: BorderRadius.circular(10)), + child: Text("女装"), + ), + Container( + padding: EdgeInsets.all(10), + margin: EdgeInsets.all(10), + decoration: BoxDecoration( + color: Color.fromRGBO(233, 233, 233, 0.9), + borderRadius: BorderRadius.circular(10)), + child: Text("笔记本电脑"), + ), + Container( + padding: EdgeInsets.all(10), + margin: EdgeInsets.all(10), + decoration: BoxDecoration( + color: Color.fromRGBO(233, 233, 233, 0.9), + borderRadius: BorderRadius.circular(10)), + child: Text("女装111"), + ), + Container( + padding: EdgeInsets.all(10), + margin: EdgeInsets.all(10), + decoration: BoxDecoration( + color: Color.fromRGBO(233, 233, 233, 0.9), + borderRadius: BorderRadius.circular(10)), + child: Text("女装"), + ), + Container( + padding: EdgeInsets.all(10), + margin: EdgeInsets.all(10), + decoration: BoxDecoration( + color: Color.fromRGBO(233, 233, 233, 0.9), + borderRadius: BorderRadius.circular(10)), + child: Text("女装"), + ), + Container( + padding: EdgeInsets.all(10), + margin: EdgeInsets.all(10), + decoration: BoxDecoration( + color: Color.fromRGBO(233, 233, 233, 0.9), + borderRadius: BorderRadius.circular(10)), + child: Text("女装"), + ) + ], + ), + SizedBox(height: 10), + //历史记录 + _historyListWidget() + ], + ), + )); + } +} diff --git a/lib/pages/Works/DWDT/basic_map.dart b/lib/pages/Works/DWDT/basic_map.dart new file mode 100644 index 0000000..1e72aff --- /dev/null +++ b/lib/pages/Works/DWDT/basic_map.dart @@ -0,0 +1,514 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bmfbase/BaiduMap/bmfmap_base.dart'; +import 'package:flutter_bmfmap/BaiduMap/bmfmap_map.dart'; +import 'package:flutter_screenutil/screen_util.dart'; +import 'package:hyzp_ybqx/components/commonFun.dart'; +import 'package:hyzp_ybqx/components/hyxx_data_handle.dart'; + +import '../../../components/dioFun.dart'; +import 'dwInfoDialog.dart'; +import 'dwInfo_data.dart'; + +class BasicMap extends StatefulWidget { + BasicMap({this.hyshlx = 'dwdt', this.title = "点位地图"}); + + String hyshlx; + String title; + + @override + _BasicMapState createState() => _BasicMapState(); +} + +class _BasicMapState extends State { + Size screenSize; + BMFMapOptions mapOptions; + BMFMapController myMapController; + + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + @override + void initState() { + super.initState(); + mapOptions = BMFMapOptions( + //center: BMFCoordinate(39.965, 116.404),//北京市 + //30 116.395645038,39.9299857781 北京-北京市 + //center: BMFCoordinate(34.263161, 108.948024), //西安市 + /// 设置地图显示中心点坐标,必须是百度官方发布的百度地图-城市中心点坐标 + /// 为宜宾市白塔山: BMFCoordinate(104.644079, 28.77914) + //center: BMFCoordinate(104.644079, 28.77914), //宜宾市白塔山,无效,经纬度搞反了 + //52 104.633019062,28.7696747963 四川省-宜宾市 + + ///Flutter百度地图采坑注意: + // BMFCoordinate(this.latitude, this.longitude); + // BMFCoordinate 构造方法参数是纬度在前、经度在后,latitude 纬度,longitude 经度 + // 百度官方发布的城市中心点坐标是经度在前、纬度在后,必须对调才行,否则无法正确显示指定城市的地图 + // 比如://52 104.633019062,28.7696747963 四川省-宜宾市 + // 必须将经纬度对调才行:center: BMFCoordinate(28.7696747963, 104.633019062), //四川省-宜宾市 + //center: BMFCoordinate(28.7696747963, 104.633019062), //四川省-宜宾市 + center: BMFCoordinate(28.77914, 104.644079), + //宜宾市白塔山 + //宜宾市白塔山 + showMapPoi: _showMapPoi, + //设定地图是否显示底图poi标注(不包含室内图标注),默认true + zoomLevel: g_zoomLevel, + maxZoomLevel: 16, + minZoomLevel: 12, + mapPadding: BMFEdgeInsets(left: 30, top: 0, right: 30, bottom: 0), + //showMapScaleBar: true, + ); + + iPage = 0; + listDwspGetList2.clear(); + + ///从接口 mapHyshlx[hyshlx]['api'] 获取第 iPage 页的列表数据,返回 list + getPageList(theHyshlx: widget.hyshlx, bShowToast: false).then((value) async { + //mapHyshlx[hyshlx]['api']; + listDwspGetList2 = value; + listDwinfoGetList2 = value; + //try_setState(); + + //按照用户选择的_selectedValue、_descending对listDwspGetList2进行排序,并延时更新 + + // int len = listDwspGetList2.length; + // for (int i = 0; i < len; i++) { + // getDwspUrl(index: listDwspGetList2[i]['id']).then((value) { + // listDwspGetList2[i]['dwsp_ok'] = true; + // if (value == '' || value == null) { + // listDwspGetList2[i]['dwsp_ok'] = false; + // } + // setState(() {}); + // }); + // } + }); + //为播放点位视频读取数据 + //在 Page1_Works 页面,获取 startGetStatisData() 时,便已经获取了 listDwinfoGetList2 + + // ///从接口 mapHyshlx[hyshlx]['api'] 获取第 iPage 页的列表数据,返回 list + // getPageList(theHyshlx: 'dwxx', bShowToast: false).then((value) async { + // //mapHyshlx[hyshlx]['api']; + // listDwspGetList2 = value; + // + // //按_selectedValue排序,升序 + // listDwspGetList2 + // .sort((a, b) => (a[mapWzxxDataText['点位编号']]).compareTo(b[mapWzxxDataText['点位编号']])); + // }); + } + + //设定地图是否显示底图poi标注(不包含室内图标注),默认true + bool _showMapPoi = true; + + /// 创建完成回调 + void onBMFMapCreated(BMFMapController controller) async { + myMapController = controller; + + /// 设置地图状态改变完成后回调接口 + myMapController?.setMapStatusDidChangedCallback(callback: () { + myMapController.getZoomLevel().then((zoomLevel) { + print('Current zoomLevel = $zoomLevel'); + g_zoomLevel = zoomLevel; //缩放倍数 + + getListBMFMarker().then((value) async { + ///先清除地图上的所有Marker + await myMapController?.removeMarkers(g_listBMFMarker); + + ///批量添加定位标记 + await myMapController?.addMarkers(g_listBMFMarker); + + ///批量添加文字标记,作为定位标记的说明文字 + for (var text in g_listBMFText) { + //text.setMethodChannel(MethodChannel()); + await myMapController?.addText(text); + } + + //加在此处无效 + //点击Marker时会回调BaiduMap.OnMarkerClickListener,监听器的实现方式示例如下: + /// 地图marker点击回调 + //myMapController?.setMapClickedMarkerCallback(callback: _markerCallback); + }); + setState(() {}); + }); + }); + + /// 地图加载回调 + myMapController?.setMapDidLoadCallback(callback: () { + print('mapDidLoad-地图加载完成'); + }); + + ///批量添加定位标记 + getListBMFMarker().then((value) { + myMapController?.addMarkers(g_listBMFMarker); + + ///批量添加文字标记,作为定位标记的说明文字 + for (var text in g_listBMFText) { + myMapController?.addText(text); + } + + ///1、底图标注 点击回调: + // 点中底图标注后会回调此接口 + myMapController?.setMapOnClickedMapPoiCallback(callback: (BMFMapPoi mapPoi) async { + print('mapPoi = ${mapPoi.toMap().toString()}'); + myMapController.getZoomLevel().then((value) { + g_zoomLevel = value; + print('myMapController.getZoomLevel() = ${value.toString()}'); + }); + //I/flutter (11895): mapPoi = {text: 森林小区, pt: {latitude: 28.765632464539106, longitude: 104.60481101089357}, uid: ea4cae6307501c5f4d2fdfe9} + onMarkerClicked(mapPoi.pt); + }); + + ///2、底图空白处 点击回调 + // 点中底图空白处会回调此接口 + /// coordinate 经纬度 + myMapController?.setMapOnClickedMapBlankCallback(callback: (BMFCoordinate coordinate) { + print('点击底图空白处响应:coordinate = ${coordinate.toMap().toString()}'); + myMapController.getZoomLevel().then((value) { + g_zoomLevel = value; + print('myMapController.getZoomLevel() = ${value.toString()}'); + }); + onMarkerClicked(coordinate); + }); + + //3、Marker 点击响应,点击文本标签没反应,加在此处有效 + //点击Marker时会回调BaiduMap.OnMarkerClickListener,监听器的实现方式示例如下: + /// 地图marker点击回调 + myMapController?.setMapClickedMarkerCallback(callback: (String id, dynamic extra) async { + myMapController.getZoomLevel().then((value) { + g_zoomLevel = value; + print('myMapController.getZoomLevel() = ${value.toString()}'); + }); + print('点击 Marker 标签响应:id = ${id}'); + //百度地图的脑残设计,需要在添加BMFMarker时自己保存ID + //g_listBMFMarkerIDmap.add({marker.getId(): listDwinfo[i]["id"]}); + print('Marker的 标签响应:id = ${listDwinfoGetList2[g_map_BMFMarkerID_dwIndex[id]]["dwmc"]}'); + + _markerCallback(id, 'test'); //响应用户点击 + }); + }); + //批量添加定位标记 + } + + Map mapBMFTextCoordinateList = {}; + + //1、江北振兴大道 估算坐标区域 + // 左上角 coordinate = {latitude: 28.807221307340154, longitude: 104.60667948635371} + // 右下角 coordinate = {latitude: 28.805069070677582, longitude: 104.62110627283919} + // listMarkerCoordinate是包含13个Map点位标记的List,每个Map元素包括4个子元素, + // 分别是左上角坐标(纬度 latitude,经度 longitude)、右下角坐标(纬度 latitude,经度 longitude) + List listMarkerCoordinate1 = [ + { + 'topLatitude': 28.807221307340154, + 'leftLongitude': 104.60667948635371, + 'bottomLatitude': 28.805069070677582, + 'rightLongitude': 104.62110627283919 + } + ]; + + //12、大麦坝 + //左上角 coordinate = coordinate = {latitude: 28.8044360513459, longitude: 104.63110441316194} + //右下角 coordinate = coordinate = {latitude: 28.803415299489192, longitude: 104.63598221207951} + // listMarkerCoordinate是包含13个Map点位标记的List,每个Map元素包括4个子元素, + // 分别是左上角坐标(纬度 latitude,经度 longitude)、右下角坐标(纬度 latitude,经度 longitude) + //15倍放大时设置,地图放到18倍时,点击没有问题, + //缩小到13倍时,不行了 + //15倍放大 + //左下角coordinate = {latitude: 28.79913436190729, longitude: 104.66847392236468} + //左上角coordinate = {latitude: 28.806612039019615, longitude: 104.62731356391107} + List listMarkerCoordinate = [ + { + 'topLatitude': 28.80778309701711, + 'leftLongitude': 104.63028695514814, + 'bottomLatitude': 28.79801859852449, + 'rightLongitude': 104.66974951618843 + } + ]; + + //以13倍放大时的数据设置 + //左下角coordinate = {latitude: 28.80399293550177, longitude: 104.63028695514814} + //左上角coordinate = {latitude: 28.79801859852449, longitude: 104.66974951618843} + + // 中国在世界地图上的经纬度范围? // 2019-04-14 · 把复杂的事情简单说给你听 + + // top 北起黑龙江省漠河以北的黑龙江主航道的中心线 北纬 53°31′ + // bottom 南达南海南沙群岛的曾母内暗沙 北纬 4°15′ + // left 西起新容疆维吾尔自治区乌恰县以西的帕米尔高原 东经 73° + // right 东至黑龙江省抚远县境内的黑龙江与乌苏里江汇合处 东经 135°。 + // + // 中国位于地球的东半球北半部、亚欧大陆的东南部、亚洲的东部和中部、太平洋的西岸。 + + int i = 0; + + //纬度 latitude,经度 longitude + Future onMarkerClicked(BMFCoordinate coordinate) { + //double scale = g_zoomLevel / 15; + if (listMarkerCoordinate[i]['topLatitude'] > coordinate.latitude && + coordinate.latitude > listMarkerCoordinate[i]['bottomLatitude'] && + listMarkerCoordinate[i]['rightLongitude'] > coordinate.longitude && + coordinate.longitude > listMarkerCoordinate[i]['leftLongitude']) { + _markerCallback('123', 'test'); + } //响应用户点击 + } + + bool isDoing = false; + + Future _markerCallback(String id, dynamic extra) async { + if (isDoing) { + print('正在处理中...,不能重复进入'); + return; + } + print('开始处理'); + print('mapClickedMarker--\n marker = $id'); + + //已经能够弹出Flutter对话框。但只有点击 Marker 标签有反应,点击文本标签没反应。 + + //百度地图的脑残设计,需要在添加BMFMarker时自己保存ID + //g_listBMFMarkerIDmap.add({marker.getId(): listDwinfo[i]["id"]}); + int dwIndex = g_map_BMFMarkerID_dwIndex[id]; + print('Marker的 标签响应:id = ${listDwinfoGetList2[dwIndex]["dwmc"]}'); + + getStatisData(statisType: 'zptj', ip: listDwinfoGetList2[dwIndex]['dwip']) + .then((mapStatisData) async { + String title = + listDwinfoGetList2[dwIndex]["id"].toString() + '、' + listDwinfoGetList2[dwIndex]["dwmc"]; + + List listCoordinate = listDwinfoGetList2[dwIndex]["dwzb"].trim().split('|'); + String content = listDwinfoGetList2[dwIndex]["dwms"] + + '。\n经度:${listCoordinate[0]}\n纬度:${listCoordinate[1]}' + + '\n今日抓拍:${mapStatisData['today']['total']}, 累计抓拍:${mapStatisData['all']}' + + '\n运行状态:${listDwinfoGetList2[dwIndex]['dwzt']}'; + await Navigator.of(context) + .push( + PageRouteBuilder( + opaque: false, + pageBuilder: (context, animation, secondaryAnimation) => + dwInfoDialog(id: id, dwIndex: dwIndex, title: title, content: content), + ), + ) + .then((value) async { + print('value = $value'); + if (value) { + print('用户已确认,开始处理推送交警!'); + //return; + } else { + print('用户取消了推送交警操作'); + } + }); + + setState(() { + // _markerID = id; + // _action = "点击"; + }); + isDoing = false; + }); + } + + double _widthBtn = 45; + double _heightBtn = 45; + double _edge = 16; + + @override + Widget build(BuildContext context) { + screenSize = MediaQuery.of(context).size; + return Scaffold( + appBar: PreferredSize( + preferredSize: Size.fromHeight(ScreenUtil().setHeight(173)), // 设置appBar高度 + // 设置appBar高度 + child: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + titleSpacing: 0.0, + //设置title的左边距 + flexibleSpace: Container( + //SizedBox(height: ScreenUtil().statusBarHeight), //显示顶部状态栏 + // SizedBox(height: ScreenUtil().setHeight(10)), //显示顶部状态栏 + padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight), //留出顶部状态栏高度 + child: Container( + //height: ScreenUtil().setHeight(173), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + // decoration: BoxDecoration( + // gradient: LinearGradient(colors: [ + // Color(0xFF0018EB), + // Color(0xFF01C1D9), + // ], begin: Alignment.bottomCenter, end: Alignment.topCenter), + // ), + ), + ), + title: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + getIconAndTextButton( + iconColor: Colors.white, + iconData: Icons.chevron_left_outlined, + onPress: () { + Navigator.pop(context); + }, + ), + Expanded( + child: Text(widget.title + '(${g_zoomLevel}倍)', + style: TextStyle(color: Colors.white, fontSize: 20), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis), + ), + SizedBox(width: 10), + ], + ), + actions: [ + Row( + children: [ + //SizedBox(width: ScreenUtil().setWidth(45)), + InkWell( + onTap: () { + //Navigator.pushNamed(context, '/registerFirst'); + this.setState(() { + _showMapPoi = !_showMapPoi; + myMapController?.updateMapOptions(BMFMapOptions(showMapPoi: _showMapPoi)); + }); + }, + child: Container( + alignment: Alignment(0, 0), + //width: 150, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text('标注:', style: TextStyle(fontSize: 20, color: Colors.white)), + Container( + alignment: Alignment(0, 0), + height: 25, + width: 25, + padding: EdgeInsets.only(top: ScreenUtil().setHeight(6)), + child: _showMapPoi + ? Icon(Icons.check_box, color: Colors.white) + : Icon(Icons.check_box_outline_blank, color: Colors.white), + ), + SizedBox(width: ScreenUtil().setWidth(65)), + ], + ), + ), + ), + ], + ), + ], + ), + ), + body: Stack( + children: [ + Align( + alignment: Alignment.center, + child: Container( + height: screenSize.height, + width: screenSize.width, + child: BMFMapWidget( + onBMFMapCreated: (controller) { + onBMFMapCreated(controller); + }, + mapOptions: mapOptions, + ), + ), + ), + Align( + alignment: Alignment(1, 1), + child: Padding( + padding: EdgeInsets.only(bottom: 5, right: _edge), + child: Container( + width: _widthBtn, + height: _heightBtn * 2 + 3, + decoration: new BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(2.0)), + border: new Border.all(width: 1, color: Colors.blue), + ), + child: Column( + children: [ + Container( + width: _widthBtn, + height: _heightBtn, + //color: Colors.white, + child: InkWell( + // child: Icon(Icons.my_location_outlined, color: Colors.black45), + child: Icon(Icons.add, color: g_zoomLevel < 16 ? Colors.blue : Colors.grey), + onTap: g_zoomLevel < 16 + ? () { + //放大按钮,缩放限制12-16 + if (g_zoomLevel < 16) { + g_zoomLevel++; + myMapController + ?.updateMapOptions(BMFMapOptions(zoomLevel: g_zoomLevel)); + } + } + : null, + ), + ), + Divider(height: 1.0, color: Colors.blue), + Container( + width: _widthBtn, + height: _heightBtn, + //color: Colors.white, + child: InkWell( + // child: Icon(Icons.my_location_outlined, color: Colors.black45), + child: Icon(Icons.horizontal_rule, + color: g_zoomLevel > 12 ? Colors.blue : Colors.grey), + onTap: g_zoomLevel > 12 + ? () { + //缩小按钮,缩放限制12-16 + g_zoomLevel--; + myMapController + ?.updateMapOptions(BMFMapOptions(zoomLevel: g_zoomLevel)); + } + : null, + ), + ), + ], + ), + ), + ), + ), + Align( + alignment: Alignment(-1, 1), + child: Padding( + padding: EdgeInsets.only(bottom: 50, left: _edge), + child: Container( + decoration: new BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(2.0)), + border: new Border.all(width: 1, color: Colors.blue), + ), + width: _widthBtn, + height: _heightBtn, + //color: Colors.white, + child: InkWell( + // child: Icon(Icons.my_location_outlined, color: Colors.black45), + // 没有 + child: Icon(Icons.radio_button_checked_rounded, + color: g_zoomLevel == 14 ? Colors.grey : Colors.blue), + onTap: () { + g_zoomLevel = 14; + //还原按钮,重新设置中心位置、缩放级别 + //myMapController.setCenterCoordinate(coordinate, animated); + //没有myMapController.getCenterCoordinate,无法简单地获取当前中心坐标; + myMapController?.updateMapOptions( + BMFMapOptions(center: BMFCoordinate(28.77914, 104.644079))); + myMapController?.updateMapOptions(BMFMapOptions(zoomLevel: g_zoomLevel)); + }, + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/Works/DWDT/dwInfoDialog.dart b/lib/pages/Works/DWDT/dwInfoDialog.dart new file mode 100644 index 0000000..7437318 --- /dev/null +++ b/lib/pages/Works/DWDT/dwInfoDialog.dart @@ -0,0 +1,118 @@ +import 'package:flutter/material.dart'; +import 'package:hyzp_ybqx/components/dioFun.dart'; + +//import 'package:hyzp_ybqx/widget/player_pro.dart'; +import '../../../components/commonFun.dart'; + +//确认对话框 +class dwInfoDialog extends Dialog { + dwInfoDialog({@required this.id, this.title = "", @required this.dwIndex, this.content}); + + int dwIndex; + String id; + String title; + String content; + bool ret = false; + + @override + Widget build(BuildContext context) { + Size mediaSize = MediaQuery.of(context).size; + return WillPopScope( + child: Material( + type: MaterialType.transparency, + child: Container( + padding: EdgeInsets.only(top: 116), + alignment: Alignment(0, -1), + color: Colors.black12, + child: Container( + // height: 260, + // width: 300, + height: mediaSize.height * 0.51, + width: mediaSize.width * 0.95, + //color: Colors.white, //Cannot provide both a color and a decoration + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Colors.blue, width: 2.0), + borderRadius: BorderRadius.all( + Radius.circular(5), + ), + ), + child: Column( + children: [ + Padding( + padding: EdgeInsets.fromLTRB(10, 10, 10, 0), + child: Stack( + children: [ + Align( + alignment: Alignment.center, + child: Text( + "${this.title}", + style: TextStyle( + fontSize: 20.0, + ), + ), + ), + Align( + alignment: Alignment.centerRight, + child: InkWell( + child: Icon(Icons.close), + onTap: () { + getingDwVideo = false; + Navigator.pop(context, ret); + }, + ), + ) + ], + ), + ), + Divider(color: Colors.blue), + Container( + padding: EdgeInsets.fromLTRB(20, 10, 20, 10), + width: double.infinity, + height: mediaSize.height * 0.28, + child: SingleChildScrollView( + child: Text(content, style: TextStyle(fontSize: 18.0, color: Colors.blue)), + ), + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + RaisedButton( + onPressed: () { + ret = true; + //getDwspUrl(index: dwIndex, context: context); + getDwspUrlNew(indexRecord: dwIndex, context: context); + }, + child: Text("视频"), + ), + RaisedButton( + onPressed: () async { + ret = true; + getingDwVideo = false; + Navigator.pop(context, ret); //关闭弹框,返回sRet + }, + child: Text("确认"), + ), + RaisedButton( + child: Text("取消"), + onPressed: () async { + getingDwVideo = false; + Navigator.pop(context, ret); //关闭弹框,返回sRet + }, + ) + ], + ), + ], + ), + ), + ), + ), + onWillPop: () { + // 屏蔽点击返回键的操作 + getingDwVideo = false; + Navigator.pop(context, ret); + }, + ); + } +} diff --git a/lib/pages/Works/DWDT/dwInfo_data.dart b/lib/pages/Works/DWDT/dwInfo_data.dart new file mode 100644 index 0000000..52ddec1 --- /dev/null +++ b/lib/pages/Works/DWDT/dwInfo_data.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bmfbase/BaiduMap/bmfmap_base.dart'; +import 'package:flutter_bmfmap/BaiduMap/bmfmap_map.dart'; +import 'package:hyzp_ybqx/components/hyxx_data_handle.dart'; + +///批量添加定位标记 +bool enable = true; +bool dragable = true; +int g_zoomLevel = 14; //缩放倍数 +///批量添加定位标记 +List g_listBMFMarker = []; +Map g_map_BMFMarkerID_dwIndex = {}; +List g_listBMFText = []; + +//https://www.cnblogs.com/ybmj/p/14408263.html +//百度地图的脑残设计,附上代码,为后来的码农们... +Future getListBMFMarker({List listDwinfo}) async { + if (null == listDwinfo) { + listDwinfo = listDwinfoGetList2; + } + + //double _scale = 9900 / 10000; //自己控制off_latitude、off_longitude效果不好 + + // BMFMarker marker1 = BMFMarker( + // position: BMFCoordinate(28.807061, 104.607091), + // title: '1、江北振兴大道', + // subtitle: 'test', + // identifier: 'flutter_marker', + // icon: 'assets/images/location.png', + // enabled: enable, + // draggable: dragable); + + int len = listDwinfo.length; + for (int i = 0; i < len; i++) { + BMFMarker marker = BMFMarker( + position: getBMFCoordinate(listDwinfo[i]["dwzb"]), + title: '${listDwinfo[i]["id"].toString()}、${listDwinfo[i]["dwmc"]}', + subtitle: 'test', + identifier: '${listDwinfo[i]["id"].toString()}、${listDwinfo[i]["dwmc"]}', + icon: 'assets/images/location.png', + + /// 默认情况下, annotation view的中心位于annotation的坐标位置, + /// 可以设置centerOffset改变view的位置,正的偏移使view朝右下方移动,负的朝左上方,单位是像素 + /// 目前Android只支持Y轴设置偏移量对应SDK的 yOffset(int yOffset) 方法 + /// 添加标记 BMFMarker 百度官方有 centerOffset 偏移参数, + /// 文本标签 BMFText 官方没有 centerOffset 偏移参数,我取消了 BMFMarker 的偏移。你看这样行吗? + /// 按公司要求,为更准确定位,取消 BMFMarker 的偏移 + centerOffset: BMFPoint(0, 0), + //标记中心偏移 + enabled: enable, + draggable: dragable); + // 百度地图的脑残设计,用Flutter添加多个BMFMarker时,必须在添加BMFMarker时自己保存ID, + // 否则响应点击时无法确定用户点击的是哪个定位标注 + // 代码不会自动返回,也没有任何文档说明,是花了一天时间搜索网络无果,是自己翻江倒海摸索出来的 + // 下一句是关键代码,将添加的每个BMFMarker的id保存到一个map中, + // 这样在用setMapClickedMarkerCallback添加BMFMarker的通用响应函数中,便可以根据id号来判断用户点击的是哪一个定位标注 + g_map_BMFMarkerID_dwIndex[marker.getId()] = i; + g_listBMFMarker.add(marker); + + g_listBMFText.add(BMFText( + text: '${listDwinfo[i]["id"].toString()}、${listDwinfo[i]["dwmc"]}', + //纬度偏移-上下 off_latitude, 经度偏移-左右 off_longitude + //已经在zoomLevel = 15时调整好定位标记与文本标记的相对位置 + // ,当地图缩放时,会发生位置变化,必须使用 Provider 或者 EventBus 进行跟踪更新 + + //https://time.geekbang.org/column/article/131890 + //老师,provider、eventBus的用途有啥区别吗,都可以做状态的通知 + // 作者回复: Provider 主要是用来做数据读写共享;event_bus主要是用来做数据状态通知、实现组件间单向数据传递。 + //如果我们的应用足够简单,数据流动的方向和顺序是清晰的,我们只需要将数据映射成视图就可以了。 + // 作为声明式的框架,Flutter 可以自动处理数据到渲染的全过程,通常并不需要 Provider 状态管理。 + // position: getBMFCoordinate(listDwinfo[i]["dwzb"], + // off_latitude: -0.0002 * g_zoomLevel * _scale, + // off_longitude: -0.00009 * g_zoomLevel * _scale), + position: getBMFCoordinate(listDwinfo[i]["dwzb"]), + //自己控制off_latitude、off_longitude效果不好 + bgColor: Colors.yellow, + fontColor: Colors.black, + fontSize: 35, + // typeFace: + // BMFTypeFace(familyName: BMFFamilyName.sMonospace, textStype: BMFTextStyle.BOLD_ITALIC), + typeFace: BMFTypeFace(familyName: BMFFamilyName.sMonospace, textStype: BMFTextStyle.BOLD), + alignY: BMFVerticalAlign.ALIGN_TOP, + alignX: BMFHorizontalAlign.ALIGN_LEFT, + rotate: 0.0, + zIndex: 99)); + } +} + +//文字覆盖物 +// 文字(Text)在地图上也是一种覆盖物,由BMFText类定义,示例代码如下: +// +// /// text经纬度信息 +// BMFCoordinate position = new BMFCoordinate(39.73235, 116.350338); +// +// /// 构造text +// BMFText bmfText = BMFText( +// text: 'hello world', +// position: position, +// bgColor: Colors.blue, +// fontColor: Colors.red, +// fontSize: 40, +// typeFace: BMFTypeFace( familyName: BMFFamilyName.sMonospace, +// textStype: BMFTextStyle.BOLD_ITALIC), +// alignY: BMFVerticalAlign.ALIGN_TOP, +// alignX: BMFHorizontalAlign.ALIGN_LEFT, +// rotate: 30.0); +// +// /// 添加text +// myMapController.addText(bmfText); + +//从"dwzb": "104.607091|28.807061",得到BMFCoordinate(28.807061, 104.607091) +//纬度偏移-上下 off_latitude, 经度偏移-左右 off_longitude +//自己控制off_latitude、off_longitude效果不好 +BMFCoordinate getBMFCoordinate(String dwzb, {double off_latitude = 0, double off_longitude = 0}) { + off_latitude = 0; //取消偏移 + off_longitude = 0; //取消偏移 + List _listCoordinateItem = dwzb.trim().split('|'); + return BMFCoordinate(double.parse(_listCoordinateItem[1]) - off_latitude, + double.parse(_listCoordinateItem[0]) - off_longitude); +} diff --git a/lib/pages/Works/DWSP/dwsp_getList.dart b/lib/pages/Works/DWSP/dwsp_getList.dart new file mode 100644 index 0000000..f024c70 --- /dev/null +++ b/lib/pages/Works/DWSP/dwsp_getList.dart @@ -0,0 +1,567 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hyzp_ybqx/widget/my_delay_toast.dart'; + +import '../../../components/commonFun.dart'; +//import 'package:hyzp_ybqx/widget/player_pro.dart'; + +import '../../../components/dioFun.dart'; +import '../../../components/hyxx_data_handle.dart'; +import '../../../services/EventBus.dart'; + +//dwsp是本项目中“点位视频”的统一缩写 +class DwspGetList extends StatefulWidget { + //hyshlx为黑烟审核类型,处理dwsp信息。mapHyshlx[hyshlx]获取为各种类型的设置数据 + DwspGetList({this.hyshlx = 'dwxx', Key key}) : super(key: key); + String hyshlx; + + _DwspPageState createState() => _DwspPageState(); +} + +class _DwspPageState extends State { + //try_setState(); //避免异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + @override + void dispose() { + _controller.dispose(); //销毁控制器 + super.dispose(); + } + + @override + void initState() { + hyshlx = widget.hyshlx; + iPage = 0; + listDwspGetList2.clear(); + + ///从接口 mapHyshlx[hyshlx]['api'] 获取第 iPage 页的列表数据,返回 list + getPageList(theHyshlx: hyshlx, bShowToast: false).then((value) async { + //mapHyshlx[hyshlx]['api']; + listDwspGetList2 = value; + //按照用户选择的_selectedValue、_descending对listDwspGetList2进行排序,并延时更新 + _listSort(); + firstIndex = 0; + lastIndex = 7; + + listDwinfoGetList2 = listDwspGetList2; + // int len = listDwspGetList2.length; + // for (int i = 0; i < len; i++) { + // getDwspUrl(index: listDwspGetList2[i]['id']).then((value) { + // listDwspGetList2[i]['dwsp_ok'] = true; + // if (value == '' || value == null) { + // listDwspGetList2[i]['dwsp_ok'] = false; + // } + // setState(() {}); + // }); + // } + }); + + // ///从接口 mapHyshlx[theHyshlx]['api'] 获取指定类型第 page 页的列表数据,返回 list + // ///获取点位信息数据 + // listDwinfoGetList2.clear(); + // getThePageList(theHyshlx: 'dwxx').then((value) { + // listDwinfoGetList2 = value; + // print('listDwinfoGetList2 = \n$listDwinfoGetList2'); + // }); + + //监听点位视频信息数据更新事件 + eventBus.on().listen((event) async { + print(event.str); + try_setState(); + }); + + super.initState(); + } + + //点位信息数据 + //{ + // "id": 1, + // "dwip": "172.16.3.1", + // "dwmc": "江北振兴大道", + // "dwbh": 1, + // "dwinfo": "江北振兴大道入城方向", + // "dwzb": "104.607091|28.807061", + // "dwms": "江北振兴大道入城方向,识别孜岩、红坝路入城排放黑烟车辆", + // "dwzt": "正常" + //}, + + // Widget getDropdownButton() { + // //DropdownMenuItem项目文本list + // List itemList = [ + // '主键ID', + // '点位IP', + // '点位名称', + // '点位编号', + // '点位信息', + // '点位坐标', + // '点位描述', + // '点位状态', + // ]; + + Widget _getMonitorImage(String _image, int indexRecord, + {double width = 140, double height = 140}) { + return Container( + margin: EdgeInsets.only(), + width: ScreenUtil().setWidth(width), + height: ScreenUtil().setHeight(height), + //child: Image.asset('assets/images/ybsthbj.png', fit: BoxFit.fitHeight), + child: Image.asset( + _image, + fit: BoxFit.cover, + //color: listDwspGetList2[indexRecord]['dwsp_ok'] ? null : Theme.of(context).disabledColor + ), + ); + } + + Widget _getListTile(BuildContext context, int indexRecord) { + List listCoordinate = listDwspGetList2[indexRecord]["dwzb"].trim().split('|'); + + return Column( + children: [ + Container( + decoration: getingIndex == indexRecord + ? BoxDecoration( + border: Border.all(width: 3, color: Colors.red), + borderRadius: BorderRadius.all(Radius.circular(5.0)), + ) + : null, + child: ListTile( + //leading: new Icon(Icons.phone), + contentPadding: EdgeInsets.symmetric(horizontal: 15.0, vertical: 0), + title: Text( + "${listDwspGetList2[indexRecord]['dwbh'].toString()}. ${listDwspGetList2[indexRecord]['dwmc']}", + style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), + // subtitle: Text( + // 'dwIP:${listDwspGetList2[indexRecord]['dwip']},${listDwspGetList2[indexRecord]['dwzt']}', + // style: TextStyle(fontSize: 10)), + trailing: Container( + width: 200, + child: Row( + children: [ + _getMonitorImage('assets/images/monitor.png', indexRecord), + SizedBox(width: 20), + Container( + width: 120, + child: Text( + '${listDwspGetList2[indexRecord]['dwms']}' + + ', 经纬度:${listCoordinate[0]}、${listCoordinate[1]}', + maxLines: 2, + overflow: TextOverflow.ellipsis, + //textAlign: TextAlign.right, + style: TextStyle(fontSize: 14), + ), + ) + ], + ), + ), + enabled: true, + onTap: () async { + // int ret = await Navigator.of(context).push( + // MaterialPageRoute( + // builder: (context) => DwspContent( + // title: '点位视频信息详情', + // index: indexRecord, + // hyshlx: widget.hyshlx, + // ), + // ), + // ); + + //{ + // "ret": 200, + // "data": { + // "id": 13, + // "dwip": "172.16.3.13", + // "dwmc": "外江路", + // "dwbh": 13, + // "dwinfo": "外江路往高铁站方向", + // "dwzb": "104.623547|28.74798", + // "dwms": "外江路往高铁站方向,识别中坝大桥往高铁站排放黑烟车辆", + // "dwzt": "正常", + // "video12": null, + // "video13": null, + // "video14": null, + // "video15": null, + // "video16": "/rtp/gb_play_34020000001320013016_34020000001320013016/hls.m3u8", + // "play_urlhead": "http://125.64.218.67:9903" + // }, + // "msg": "" + // } + + //getDwspUrl(index: indexRecord, context: context); + getDwspUrlNew(indexRecord: indexRecord, context: context); + //@山不在高水不在深 我用PC播放这个地址也等了45秒才开始, + // http://125.64.218.67:9903/rtp/gb_play_34020000001320013016_34020000001320013016/hls.m3u8 + + // getDwspUrl(index: indexRecord + 1).then((url) { + // print('index = ${(indexRecord + 1).toString()}, url = $url'); + // urlnew = url; + // + // //获取视频地址失败 + // if (!isVideoUrl(urlnew)) { + // return; + // } + // + // var ret = Navigator.of(context).push(MaterialPageRoute( + // builder: (context) => PlayerPro( + // url: urlnew, + // title: + // '点位视频\n${(indexRecord + 1)}、${listDwinfoGetList2[indexRecord]['dwmc']}', + // //initVideoSize: Size(704.0, 576.0), //16比13 + // //initVideoSize: Size(16, 9), + // ))); + // print('ret = $ret'); + // }); + }, + ), + ), + Divider(height: 1.0), + ], + ); + } + + ScrollController _controller = ScrollController(); //ListView控制器 + bool isLoading = false; //正在处理下载数据、跳转到首项、跳转到尾项等操作 + int firstIndex = 0; //ListView当前显示页面首项0基序号 + int lastIndex = 0; //ListView当前显示页面末项0基序号 + int itemOnPage = 8; //估计一屏显示的项目数量 + + Widget getIconButton({IconData iconData, var onPressed, double iconSize = 22}) { + return SizedBox( + height: iconSize, + width: iconSize + 10, + child: IconButton( + padding: EdgeInsets.all(0.0), + icon: Icon(iconData, size: iconSize), + onPressed: onPressed, + ), + ); + } + + @override + Widget build(BuildContext context) { + return WillPopScope( + child: Scaffold( + appBar: PreferredSize( + preferredSize: Size.fromHeight(ScreenUtil().setHeight(173)), // 设置appBar高度 + // 设置appBar高度 + child: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + titleSpacing: 0.0, + //设置title的左边距 + flexibleSpace: Container( + //SizedBox(height: ScreenUtil().statusBarHeight), //显示顶部状态栏 + // SizedBox(height: ScreenUtil().setHeight(10)), //显示顶部状态栏 + padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight), //留出顶部状态栏高度 + child: Container( + //height: ScreenUtil().setHeight(173), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + // decoration: BoxDecoration( + // gradient: LinearGradient(colors: [ + // Color(0xFF0018EB), + // Color(0xFF01C1D9), + // ], begin: Alignment.bottomCenter, end: Alignment.topCenter), + // ), + ), + ), + title: Padding( + padding: EdgeInsets.only(left: 0, right: 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + getIconAndTextButton( + iconColor: Colors.white, + iconData: Icons.chevron_left_outlined, + onPress: () { + getingDwVideo = false; + Navigator.pop(context); + }, + ), + Expanded( + child: Text("${mapHyshlx[hyshlx]['text']}", + style: TextStyle(color: Colors.white, fontSize: 20), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis), + ), + SizedBox(width: 50), + ], + ), + ), + ), + ), + body: (0 == listDwspGetList2.length) + ? getMoreWidget(color: Colors.black38) + : ListView.custom( + //itemExtent: 75.0, //列表项高度 + controller: _controller, + cacheExtent: 1.0, // 只有设置了1.0 才能够准确的标记 position 位置 + childrenDelegate: MyChildrenDelegate( + _getListTile, + childCount: listDwspGetList2.length, + ), + ), + ), + onWillPop: () { + // 屏蔽点击返回键的操作 + getingDwVideo = false; + Navigator.pop(context); + }, + ); + } + + //DropdownButton需要设置初始值的时候,初始值必须是显示列表里面的值,否则会导致弹出框异常。 + // 比如说:你的DropdownButton的items属性使用的是list这个列表里面的值,那么你的初始值应该在list[index]里面取,要不就会报错。 + // + // There should be exactly one item with [DropdownButton]'s value: 0.0. + // Either zero or 2 or more [DropdownMenuItem]s were detected with the same value + // 'package:flutter/src/material/dropdown.dart': + // Failed assertion: line 834 pos 15: 'items == null || items.isEmpty || value == null || + // items.where((DropdownMenuItem item) { + // return item.value == value; + // }).length == 1' + String _selectedValue = '点位编号'; + bool _descending = false; //默认升序排列 + + Widget _getImage(String _image) { + return Container( + margin: EdgeInsets.only(), + height: ScreenUtil().setWidth(38), + width: ScreenUtil().setWidth(38), + //child: Image.asset('assets/images/ybsthbj.png', fit: BoxFit.fitHeight), + child: Image.asset(_image, + fit: BoxFit.cover, color: isLoading ? Theme.of(context).disabledColor : null)); + } + + //按照用户选择的_selectedValue、_descending对listDwspGetList2进行排序,并延时更新 + Future _listSort({bool bShowToast = false}) { + if (!isLoading && listDwspGetList2.length > 0) { + isLoading = true; + try_setState(); + + switch (_selectedValue) { + default: + if (_descending) { + //按_selectedValue排序,降序 + listDwspGetList2.sort((a, b) => + (b[mapWzxxDataText[_selectedValue]]).compareTo(a[mapWzxxDataText[_selectedValue]])); + } else { + //按_selectedValue排序,升序 + listDwspGetList2.sort((a, b) => + (a[mapWzxxDataText[_selectedValue]]).compareTo(b[mapWzxxDataText[_selectedValue]])); + } + break; + } + + Future.delayed(const Duration(milliseconds: 1000), () { + if (bShowToast) { + Fluttertoast.showToast( + msg: '按“${_selectedValue}”${_descending ? '降序' : '升序'}排列完成!', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + isLoading = false; + try_setState(); //避免如下异常报错 + }); + } + } + + Widget getDropdownButtonItemText(String item) { + return Row( + //crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + alignment: Alignment(0, 0.28), + child: item == _selectedValue + ? _descending + ? _getImage('assets/images/descending.png') + : _getImage('assets/images/ascending.png') + // ? Icon(Icons.arrow_downward_outlined, size: 20) + // : Icon(Icons.arrow_upward_outlined, size: 20) + : SizedBox(), + ), + SizedBox( + width: 3, + ), + Container( + //alignment: Alignment(0, -1), + child: Text(item, + style: isLoading + ? TextStyle(color: Theme.of(context).disabledColor) + : (item == _selectedValue ? TextStyle(color: Colors.blue) : null)), + ), + ], + ); + } + + //点位信息数据 + //{ + // "id": 1, + // "dwip": "172.16.3.1", + // "dwmc": "江北振兴大道", + // "dwbh": 1, + // "dwinfo": "江北振兴大道入城方向", + // "dwzb": "104.607091|28.807061", + // "dwms": "江北振兴大道入城方向,识别孜岩、红坝路入城排放黑烟车辆", + // "dwzt": "正常" + //}, + + Widget getDropdownButton() { + //DropdownMenuItem项目文本list + List itemList = [ + '主键ID', + '点位IP', + '点位名称', + '点位编号', + '点位信息', + '点位坐标', + '点位描述', + '点位状态', + ]; + + //添加按'推送状态'排序 + if (hyshlx != 'dwxx') { + // itemList.removeLast(); + // itemList.addAll(['推送状态', '主键ID']); + } + + //获取DropdownMenuItem项目组件list + List> _dropDownMenuItems = + itemList.map>((String item) { + return DropdownMenuItem( + value: item, + child: getDropdownButtonItemText(item), + ); + }).toList(); + + return Padding( + padding: EdgeInsets.only(top: 10, bottom: 10), + child: Container( + alignment: Alignment(0.7, 0), + width: 125, + margin: EdgeInsets.only(bottom: 0), + padding: EdgeInsets.only(left: 0, bottom: 0), + decoration: BoxDecoration( + border: Border.all(width: 0), + //边框圆角设置 + borderRadius: + BorderRadius.vertical(top: Radius.elliptical(2, 2), bottom: Radius.elliptical(2, 2)), + ), + //DropdownButton默认有一条下划线,DropdownButtonHideUnderline去除下划线 + child: DropdownButtonHideUnderline( + child: DropdownButton( + isDense: true, + value: _selectedValue, + items: _dropDownMenuItems, + onChanged: (String selectedValue) { + if (isLoading) { + return; + } + + if (_selectedValue == selectedValue) { + _descending = !_descending; + } else { + _descending = true; + } + _selectedValue = selectedValue; + print('_selectedValue = $_selectedValue'); + + //按照用户选择的_selectedValue、_descending对listDwspGetList2进行排序,并延时更新 + _listSort(bShowToast: true); + }, + ), + ), + ), + ); + } +} + +//https://blog.csdn.net/u014803467/article/details/103750018 +//Flutter 使用SliverChildBuilderDelegate获取ListView的第一个和最后一个可见Item序号 +// 秋名山交警X 2019-12-28 23:52:52 +class _SaltedValueKey extends ValueKey { + const _SaltedValueKey(Key key) + : assert(key != null), + super(key); +} + +class MyChildrenDelegate extends SliverChildBuilderDelegate { + MyChildrenDelegate( + Widget Function(BuildContext, int) builder, { + int childCount, + bool addAutomaticKeepAlive = true, + bool addRepaintBoundaries = true, + }) : super(builder, + childCount: childCount, + addAutomaticKeepAlives: addAutomaticKeepAlive, + addRepaintBoundaries: addRepaintBoundaries); + + // Return a Widget for the given Exception + Widget _createErrorWidget(dynamic exception, StackTrace stackTrace) { + final FlutterErrorDetails details = FlutterErrorDetails( + exception: exception, + stack: stackTrace, + library: 'widgets library', + context: ErrorDescription('building'), + ); + FlutterError.reportError(details); + return ErrorWidget.builder(details); + } + + @override + Widget build(BuildContext context, int index) { + assert(builder != null); + if (index < 0 || (childCount != null && index >= childCount)) return null; + Widget child; + try { + child = builder(context, index); + } catch (exception, stackTrace) { + child = _createErrorWidget(exception, stackTrace); + } + if (child == null) return null; + final Key key = child.key != null ? _SaltedValueKey(child.key) : null; + if (addRepaintBoundaries) child = RepaintBoundary(child: child); + if (addSemanticIndexes) { + final int semanticIndex = semanticIndexCallback(child, index); + if (semanticIndex != null) + child = IndexedSemantics(index: semanticIndex + semanticIndexOffset, child: child); + } + if (addAutomaticKeepAlives) child = AutomaticKeepAlive(child: child); + return KeyedSubtree(child: child, key: key); + } + + @override + void didFinishLayout(int _firstIndex, int _lastIndex) { + // TODO: implement didFinishLayout + super.didFinishLayout(_firstIndex, _lastIndex); + } + + ///监听 在可见的列表中 显示的第一个位置和最后一个位置 + @override + double estimateMaxScrollOffset( + int _firstIndex, int _lastIndex, double _leadingScrollOffset, double _trailingScrollOffset) { + //违章信息Listview滚动广播 + eventBus.fire(WzxxDataScrollEvent(_firstIndex, _lastIndex)); + + return super.estimateMaxScrollOffset( + _firstIndex, _lastIndex, _leadingScrollOffset, _trailingScrollOffset); + } +} diff --git a/lib/pages/Works/HYSH/fhycx_content_new.dart b/lib/pages/Works/HYSH/fhycx_content_new.dart new file mode 100644 index 0000000..82271c1 --- /dev/null +++ b/lib/pages/Works/HYSH/fhycx_content_new.dart @@ -0,0 +1,828 @@ +//import '../../../widget/player_pro.dart'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flustars/flustars.dart' + as flustars; //该组件中有ScreenUtil,// 获取网络图片尺寸flustars.WidgetUtil +import 'package:flutter/material.dart'; +import 'package:flutter_drag_scale/flutter_drag_scale.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hyzp_ybqx/widget/my_superplayer.dart'; +import 'package:keyboard_avoider/keyboard_avoider.dart'; + +// +import '../../../components/commonFun.dart'; +//import 'package:hyzp_ybqx/widget/player_pro_new.dart'; +import '../../../components/dioFun.dart'; +import '../../../components/doJSON.dart'; +import '../../../components/hyxx_data_handle.dart'; + +class FhycxContentNew extends StatefulWidget { + FhycxContentNew({ + @required this.hyshlx, + @required this.title, + this.indexRecord, + this.id, + Key key, + }) : super(key: key); + String title; + int indexRecord = 0; + int id = -1; + String hyshlx; + + _FhycxPageState createState() => _FhycxPageState(); +} + +//用TabController实现顶部tab切换 +class _FhycxPageState extends State { + //try_setState(); //避免如下异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + dispose() { + super.dispose(); + } + + BuildContext _context; + String _title = ''; + + //flutter_screenUtil 4.x 用法,ScreenUtil.screenWidth (sdk>=2.6 : 1.sw) //设备宽度 + double _screenWidth = 1.sw; + + double _marginLeft = 25; + double _marginCenter = 20; + double _fontSize = 16; + double _widthLeft = 40; // = _screenWidth / 3; + double _iconSize = 18; + double _stampSize = 100; + double _listTileHeight = 30; + Color _iconColor = Colors.blue; + double _marginVer = 10; + + Map _mapTsjjGetTsStatus = {}; + + void initState() { + super.initState(); + + _context = context; + _widthLeft = _screenWidth / 2.6; + getListFlields(); + } + + Map _mapGetTsjjGetData = { + "ret": 200, + "data": { + "id": 1203, + "plate_id": "川QS9661", + "plate_color": "蓝色", + "zpsj": 1612770967, + "yjxx_id": "1379", + "workflow": 2, + "video_url": "video/9_6063_20210208_155607_川QS9661.mp4", + "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/926f5168f1fe109a9b8ec88dcac7ac2c.jpg", + "clfl": "皮卡车", + "dwip": "172.16.3.9", + "dwms": "宜长路出城方向", + "lgmzs": 3, + "jczxd": "863", + "sfhy": "黑烟车" + }, + "msg": "" + }; + double _radioImage = 9 / 16; + + getListFlields() async { + _mapGetTsjjGetData = await getWzxxItemData(widget.id); //获取指定id的违章信息返回 _mapGetTsjjGetData + print('_mapGetTsjjGetData = ${_mapGetTsjjGetData}'); + await getShenheData(widget.id); //获取指定id的审核信息存入 listGetShenheData + await getMapGetShenheData(); //从listGetShenheData中取出数据分别存入mapGetHycsShenheData、mapGetHyfhShenheData + + // 获取推送交警状态信息 + // tsjjGetTsStatus返回字段 类型 说明 + // id 整型 违章记录ID + // checkid 整型 推送的抓拍记录ID + // tszt 整型 推送状态:0-未推送 | 1-推送失败 | 3-推送成功 + // ts_time 字符串 推送时间 + //Future tsjjGetTsStatus(int _wzxxID) + _mapTsjjGetTsStatus = await tsjjGetTsStatus(widget.id); + + // 获取网络图片尺寸 + flustars.WidgetUtil.getImageWH(url: getMediaUrl(_mapGetTsjjGetData['pic_url'])).then((rect) { + if (null != rect) { + _radioImage = rect.height / rect.width; + } + }); + + imageWztp = getWztp(); //得到违章图片 + + //下面读取的是全局数据,该模块不需要修改 + // 川Q565H4 + try { + //_title = '${widget.title}(${(widget.indexRecord + 1).toString()} / ${listHycsGetList2.length})id:${listHycsGetList2[widget.indexRecord]['id']}'; + //_title = '${widget.title}(${(widget.indexRecord + 1).toString()} / ${listHycsGetList2.length}):${listHycsGetList2[widget.indexRecord]["plate_id"]}(${listHycsGetList2[widget.indexRecord]["plate_color"]})'; + _title = + '${widget.title}(${(widget.indexRecord + 1).toString()} / ${listHycsGetList2.length})'; + setState(() {}); + } catch (e) {} + } + + // 使用 cached_network_image 插件实现网络图片缓存 + // 使用 flutter_drag_scale 实现可缩放可拖拽双击放大的图片功能。PhotoView插件不好用,有问题 + Widget getNetworkImage(String url) { + return CachedNetworkImage( + imageUrl: url, + alignment: Alignment.topCenter, + imageBuilder: (context, imageProvider) => DragScaleContainer( + doubleTapStillScale: true, child: Image(image: imageProvider) + // child: Image( + // image: NetworkImage( + // 'http://h.hiphotos.baidu.com/zhidao/wh%3D450%2C600/sign=0d023672312ac65c67506e77cec29e27/9f2f070828381f30dea167bbad014c086e06f06c.jpg'), + // ), + ), + // imageBuilder: (context, imageProvider) => PhotoView( + // imageProvider: imageProvider, + // ), + //placeholder: (context, url) => CircularProgressIndicator(), + placeholder: (context, url) => + getMoreWidget(color: Colors.black38, size: 20.0, strokeWidth: 2.0), + errorWidget: (context, url, error) => Icon(Icons.error), + ); + } + + // 167 50 3.34 + Widget getLgmzs(int lgmzs, {double width = 127, double height = 127}) { + int _rgb = (255 * (5 - lgmzs)) ~/ 5; + return Stack( + children: [ + Container( + width: ScreenUtil().setWidth(width), + height: ScreenUtil().setHeight(height), + padding: EdgeInsets.only( + right: ScreenUtil().setWidth(6), left: ScreenUtil().setWidth(6), top: 0, bottom: 4), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text('$lgmzs级${lgmzs * 20}%', style: TextStyle(fontSize: 10)), + //SizedBox(height: 0), + Container( + width: ScreenUtil().setWidth(66), + height: ScreenUtil().setHeight(66), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + color: Color.fromRGBO(_rgb, _rgb, _rgb, 1.0), + borderRadius: new BorderRadius.circular(0), + ), + ) + ], + ), + ), + Positioned( + top: ScreenUtil().setHeight(4), + child: Container( + width: ScreenUtil().setWidth(width), + height: ScreenUtil().setHeight(height - 8), + padding: EdgeInsets.only( + right: ScreenUtil().setWidth(6), left: ScreenUtil().setWidth(6), top: 0, bottom: 0), + decoration: BoxDecoration( + border: Border.all( + color: (lgmzs == _mapGetTsjjGetData['lgmzs']) + ? Colors.red + : Color.fromRGBO(244, 244, 244, 1), + width: 2), + //color: Colors.lightBlue, + borderRadius: new BorderRadius.circular(3.0), + ), + ), + ) + ], + ); + } + + //得到tsjj页面组件 + //1、得到格林曼黑度标准和视频播放按钮组件 + Widget getHdAndPlay() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + getLgmzs(0), + getLgmzs(1), + getLgmzs(2), + getLgmzs(3), + getLgmzs(4), + getLgmzs(5, width: 153), + getIconBtnSizeX( + height: 104, + //getIconBtnSizeX 中已经使用ScreenUtil().setHeight(126),此处不能传 ScreenUtil().setHeight(126) ,否则严重错位 + width: 168, + text: "视频", + textSize: 12, + circular: 4, + color: Color.fromRGBO(52, 157, 237, 1), + onTop: () async { + if (Playing) { + //禁止同时启动两次播放器 + return; + } + + Playing = true; //禁止同时启动两次播放器 + urlnew = getMediaUrl(_mapGetTsjjGetData['video_url']); + + //获取视频地址失败 + if (!isVideoUrl(urlnew)) { + return; + } + + Navigator.of(_context).push(MaterialPageRoute( + builder: (context) => SuperPlayerPage( + loop: 0, //设置播放循环,默认播放器的循环次数是1, 即不循环播放。如果设置循环次数0,表示无限循环。 + url: urlnew, + title: + '违章视频: ${_mapGetTsjjGetData["plate_id"]}\n${_mapGetTsjjGetData['dwms']}'))); + + // Navigator.of(_context).push(MaterialPageRoute( + // builder: (context) => PlayerProNew( + // loop: 0, //设置播放循环,默认播放器的循环次数是1, 即不循环播放。如果设置循环次数0,表示无限循环。 + // url: urlnew, + // title: + // '违章视频: ${_mapGetTsjjGetData["plate_id"]}\n${_mapGetTsjjGetData['dwms']}'))); + }, + ), + SizedBox(width: ScreenUtil().setWidth(15)), + ], + ); + } + + Widget imageWztp; + + //2、得到违章图片组件 + Widget getWztp() { + //ratioList[index] = 0.5714285714285714 + return Stack( + children: [ + Container( + width: ScreenUtil().setWidth(1022), + //height: ScreenUtil().setHeight(639), + //height: ScreenUtil().setHeight(22 + 1022 * _radioImage), + height: ScreenUtil().setHeight(30 + 1022 * _radioImage), + decoration: BoxDecoration( + //color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12), + ), + ), + ), + Positioned( + //left: ScreenUtil().setWidth(_marginLeft), + top: ScreenUtil().setHeight(_marginLeft), + child: Container( + width: ScreenUtil().setWidth(1022), + height: ScreenUtil().setHeight(30 + 1022 * _radioImage), + child: getNetworkImage(getMediaUrl(_mapGetTsjjGetData['pic_url'])), + ), + ) + ], + ); + } + + // void printScreenInformation() { + // print('Device width dp:${1.sw}dp'); + // print('Device height dp:${1.sh}dp'); + // print('Device pixel density:${ScreenUtil().pixelRatio}'); + // print('Bottom safe zone distance dp:${ScreenUtil().bottomBarHeight}dp'); + // print('Status bar height dp:${ScreenUtil().statusBarHeight}dp'); + // print('The ratio of actual width to UI design:${ScreenUtil().scaleWidth}'); + // print( + // 'The ratio of actual height to UI design:${ScreenUtil().scaleHeight}'); + // print('System font scaling:${ScreenUtil().textScaleFactor}'); + // print('0.5 times the screen width:${0.5.sw}dp'); + // print('0.5 times the screen height:${0.5.sh}dp'); + // } + + void printScreenInformation() { + print('ScreenUtil().screenWidth = ${ScreenUtil().screenWidth}'); + print('设备宽度:${1.sw}dp'); + print('"1.w" = ${1.w}'); + print('"1.sw" = ${1.sw}'); + print('设备高度:${1.sh}dp'); + print('设备的像素密度:${ScreenUtil().pixelRatio}'); + print('底部安全区距离:${ScreenUtil().bottomBarHeight}dp'); + print('状态栏高度:${ScreenUtil().statusBarHeight}dp'); + print('实际宽度的dp与设计稿px的比例:${ScreenUtil().scaleWidth}'); + print('实际高度的dp与设计稿px的比例:${ScreenUtil().scaleHeight}'); + print('宽度和字体相对于设计稿放大的比例:${ScreenUtil().scaleWidth * ScreenUtil().pixelRatio}'); + print('高度相对于设计稿放大的比例:${ScreenUtil().scaleHeight * ScreenUtil().pixelRatio}'); + print('系统的字体缩放比例:${ScreenUtil().textScaleFactor}'); + print('屏幕宽度的0.5:${0.5.sw}dp'); + print('屏幕高度的0.5:${0.5.sh}dp'); + } + + //3、得到违章图片说明信息组件 + Widget getWztpSmxx() { + return Container( + width: ScreenUtil().setWidth(1022), + height: ScreenUtil().setHeight(280), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12), + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + getWzxxPart1(), + getWzxxPart2(), + getWzxxPart3(), + ], + ), + ); + } + + // I/flutter (22989): ScreenUtil().screenWidth = 360.0 + // I/flutter (22989): 设备宽度:360.0dp + // I/flutter (22989): "1.w" = 0.3333333333333333 + // I/flutter (22989): "1.sw" = 360.0 + // I/flutter (22989): 设备高度:640.0dp + // I/flutter (22989): 设备的像素密度:3.0 + // I/flutter (22989): 底部安全区距离:0.0dp + // I/flutter (22989): 状态栏高度:24.0dp + // I/flutter (22989): 实际宽度的dp与设计稿px的比例:0.3333333333333333 + // I/flutter (22989): 实际高度的dp与设计稿px的比例:0.3333333333333333 + // I/flutter (22989): 宽度和字体相对于设计稿放大的比例:1.0 + // I/flutter (22989): 高度相对于设计稿放大的比例:1.0 + // I/flutter (22989): 系统的字体缩放比例:1.0 + // I/flutter (22989): 屏幕宽度的0.5:180.0dp + // I/flutter (22989): 屏幕高度的0.5:320.0dp + + //3、得到违章信息组件1:车牌号码、车牌颜色 + //车牌颜色Map cpysMap = { + // '蓝色': cpysItem( + // cpysText: '蓝色', + // cpysBackground: Colors.blue, + // cpysFont: Colors.white, + // cpysBorder: Colors.orange), + // } + Widget getWzxxPart1() { + //printScreenInformation(); + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + getTitleText('车牌号码:'), + getBoderText(_mapGetTsjjGetData['plate_id'].toString(), + width: ScreenUtil().setWidth(1022 / 3.2)), + SizedBox(width: ScreenUtil().setWidth(_marginCenter)), + getTitleText('颜色:'), + getBoderText(_mapGetTsjjGetData['plate_color'], width: ScreenUtil().setWidth(1022 / 4.8)), + ], + ); + } + + Widget getTitleText(String text, {double fontSize = 16}) { + return Text(text, + style: TextStyle(fontSize: fontSize), + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis); + } + + Widget getTrailText(String text, {double fontSize = 16, double off = 0}) { + return Container( + width: _screenWidth - _widthLeft - off - (2 * ScreenUtil().setWidth(_marginLeft)), + child: Text(text, + style: TextStyle(fontSize: fontSize), + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis), + ); + } + + Widget getBoderText(String text, {double width = 40}) { + cpysItem _cpysItem = cpysMap[_mapGetTsjjGetData['plate_color']]; + + return Container( + //color: _cpysItem.cpysBackground, + alignment: Alignment(0, -1), + width: width, + decoration: BoxDecoration( + border: Border.all(color: _cpysItem.cpysBorder, width: 2), + color: _cpysItem.cpysBackground, + borderRadius: BorderRadius.circular(3), + ), + child: Padding( + padding: EdgeInsets.only(bottom: 3), + child: Text(text, + style: TextStyle(fontSize: _fontSize, color: _cpysItem.cpysFont), + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis), + ), + ); + } + + //I/flutter (17555): _mapGetTsjjGetData = { + // id: 1222, plate_id: 川Q736X2, plate_color: 蓝色, zpsj: 1612857077, yjxx_id: 1399, workflow: 999, + // video_url: video/9_6063_20210209_155117_川Q736X2.mp4, + // pic_url: /wwwroot/admin/Api/wwwroot/public/uploads/9d2f45fd24b41f2b94abe42b30970d75.jpg, + // clfl: 集装箱卡车, dwip: 172.16.3.9, dwms: 宜长路出城方向, lgmzs: 3, jczxd: 994, sfhy: 黑烟车 + // } + + Widget getIcon(IconData _iconData) { + return Container( + width: _iconSize - 2, + height: _iconSize, + child: Padding( + padding: EdgeInsets.only(top: 2), + child: Icon(_iconData, size: _iconSize, color: _iconColor), + ), + ); + } + + //4、得到违章信息组件2:抓拍时间组件 + Widget getWzxxPart2() { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + Container( + alignment: Alignment(-1, 0), + width: ScreenUtil().setWidth(1022) - ScreenUtil().setWidth(_marginLeft), + child: getTitleText( + '抓拍时间:' + + getDate( + (_mapGetTsjjGetData['zpsj'] is int) + ? _mapGetTsjjGetData['zpsj'] + : int.parse(_mapGetTsjjGetData['zpsj']), + ), + ), + ), + ], + ); + } + + Widget getWzxxPart3() { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + Container( + alignment: Alignment(-1, 0), + width: ScreenUtil().setWidth(1022) - ScreenUtil().setWidth(_marginLeft), + child: getTitleText('抓拍地点:' + _mapGetTsjjGetData['dwms']), + ), + ], + ); + } + + //6、得到审核信息组件4:违章类型、格林曼黑度组件 + Widget getWzxxPart4() { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + Container( + alignment: Alignment(-1, 0), + width: _widthLeft, + child: getTitleText('违章类型:' + _mapGetTsjjGetData['sfhy']), + ), + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + //getIcon(Icons.location_on_outlined), + Container( + width: _iconSize, + height: _iconSize, + decoration: BoxDecoration( + //color: Colors.white, + image: DecorationImage(image: AssetImage("assets/images/hyc.png"), fit: BoxFit.contain), + ), + alignment: Alignment.center, + //child: + ), + getTrailText(':' + _mapGetTsjjGetData['lgmzs'].toString(), off: _iconSize), + ], + ); + } + + //从listGetShenheData中取出数据分别存入mapGetHycsShenheData、mapGetHyfhShenheData + //7、得到审核信息组件5:初审人员,初审时间 + Widget getWzxxPart5() { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + Container( + alignment: Alignment(-1, 0), + width: _widthLeft, + child: getTitleText('初审人员:' + mapGetHycsShenheData['uname']), + ), + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + getIcon(Icons.query_builder), + getTrailText(':' + mapGetHycsShenheData['addtime'], off: _iconSize), + ], + ); + } + + //8、得到审核信息组件6:复审人员,复审时间 + Widget getWzxxPart6() { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + Container( + alignment: Alignment(-1, 0), + width: _widthLeft, + child: getTitleText('复审人员:' + mapGetHyfhShenheData['uname']), + ), + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + getIcon(Icons.query_builder), + getTrailText(':' + mapGetHyfhShenheData['addtime'], off: _iconSize), + ], + ); + } + + //5、得到黑烟初审'hycsInfo'、或者黑烟'hyfhInfo'複核信息组件 + //style: TextStyle(fontSize: _fontSize), + Widget getHyshInfo(String _hyshInfo) { + String _hyshlx = '初审'; + Map _hyshMap = mapGetHycsShenheData; + if (_hyshInfo == 'hyfhInfo') { + _hyshlx = '复审'; + if (mapGetHyfhShenheData.isNotEmpty) { + _hyshMap = mapGetHyfhShenheData; + } else { + //保留_hyshMap所有字段,但遍历清空所有字段的值 + _hyshMap.forEach((var key, var value) { + _hyshMap[key] = ''; + }); + } + } + double _radio = 2.2; + return Container( + width: ScreenUtil().setWidth(1022), + height: ScreenUtil().setHeight(161), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12), + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + alignment: Alignment(-1, 0), + width: ScreenUtil().setWidth(1022) / _radio, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + Text('$_hyshlx结果:' + _hyshMap['title'], + textAlign: TextAlign.left, overflow: TextOverflow.ellipsis), + SizedBox(width: ScreenUtil().setWidth(6)), + Container( + width: my_iconSize, + height: my_iconSize, + decoration: _hyshMap['title'] == '' + ? null + : BoxDecoration( + //color: Colors.white, + image: DecorationImage( + image: AssetImage(_hyshMap['title'] == "黑烟车" + ? "assets/images/hyc.png" + : "assets/images/fhyc.png"), + fit: BoxFit.contain), + ), + alignment: Alignment.center, + //child: + ), + ], + ), + ), + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + Expanded( + child: Text('意见:' + _hyshMap['shuoming'], + textAlign: TextAlign.left, overflow: TextOverflow.ellipsis), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + alignment: Alignment(-1, 0), + width: ScreenUtil().setWidth(1022) / _radio, + child: Row(mainAxisAlignment: MainAxisAlignment.start, children: [ + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + Text('$_hyshlx用户:' + _hyshMap['uname'], + textAlign: TextAlign.left, overflow: TextOverflow.ellipsis), + ]), + ), + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + Expanded( + child: Text('时间:' + _hyshMap['addtime'], + textAlign: TextAlign.left, overflow: TextOverflow.ellipsis), + ), + ], + ), + ], + ), + ); + } + + //9、得到推送交警状态信息组件7:推送状态 + // tszt 整型 推送状态:0-未推送 | 1-推送失败 | 3-推送成功 + //_mapTsjjGetTsStatus['tszt'] + Widget getWzxxPart7() { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + Container( + alignment: Alignment(-1, 0), + width: _screenWidth - ScreenUtil().setWidth(_marginLeft), + child: getTitleText('推送状态:' + mapTsztText[_mapTsjjGetTsStatus['tszt']]), + ), + ], + ); + } + + //10、得到推送交警确认组件 + Widget getTsjjQr() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + getBtnSizeX( + text: "返回", + onPressedFun: () async { + Navigator.pop(context); + }, + width: 90.0), + ], + ); + } + + bool showMoreWidget = false; + + @override + Widget build(BuildContext context) { + return Scaffold( + //resizeToAvoidBottomPadding: false, + appBar: PreferredSize( + preferredSize: Size.fromHeight(ScreenUtil().setHeight(173)), // 设置appBar高度 + // 设置appBar高度 + child: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + titleSpacing: 0.0, + //设置title的左边距 + flexibleSpace: Container( + //SizedBox(height: ScreenUtil().statusBarHeight), //显示顶部状态栏 + // SizedBox(height: ScreenUtil().setHeight(10)), //显示顶部状态栏 + padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight), //留出顶部状态栏高度 + child: Container( + //height: ScreenUtil().setHeight(173), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + // decoration: BoxDecoration( + // gradient: LinearGradient(colors: [ + // Color(0xFF0018EB), + // Color(0xFF01C1D9), + // ], begin: Alignment.bottomCenter, end: Alignment.topCenter), + // ), + ), + ), + title: Padding( + padding: EdgeInsets.only(left: 0, right: 0), + child: Row( + //mainAxisAlignment: MainAxisAlignment.start, + children: [ + getIconAndTextButton( + iconColor: Colors.white, + iconData: Icons.chevron_left_outlined, + onPress: showMoreWidget + ? null + : () { + Navigator.pop(context); + }, + ), + Expanded( + child: Text(_title, + style: TextStyle(color: Colors.white, fontSize: 20), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis), + ), + SizedBox(width: 30), + ], + ), + ), + ), + ), + body: null == imageWztp + // 显示加载中的圈圈 + ? getMoreWidget(color: Colors.black38, size: 20.0, strokeWidth: 2.0) + : Stack( + children: [ + //SizedBox.shrink() 创建父类允许最小尺寸的约束Box + showMoreWidget + ? Align( + alignment: Alignment(0, 0.8), + child: Container( + height: 200, + width: 200, + child: getMoreWidget2( + text: '推送中...', + color: Colors.red, + size: 40.0, + strokeWidth: 3.0), //显示加载中的圈圈, + ), + ) + : SizedBox.shrink(), + KeyboardAvoider( + autoScroll: true, + child: Container( + color: Color.fromRGBO(244, 244, 244, 1), + child: Column( + children: [ + //1、得到格林曼黑度标准和视频播放按钮组件 + getHdAndPlay(), + //2、得到违章图片组件 + imageWztp, + SizedBox(height: ScreenUtil().setHeight(_marginVer)), + //3、得到违章图片说明信息组件 + getWztpSmxx(), + SizedBox(height: ScreenUtil().setHeight(_marginVer)), + //7、得到黑烟初审信息组件 + getHyshInfo('hycsInfo'), + SizedBox(height: ScreenUtil().setHeight(_marginVer)), + //8、得到黑烟复审信息组件 + getHyshInfo('hyfhInfo'), + SizedBox(height: ScreenUtil().setHeight(_marginVer)), + // //7、得到审核信息组件5:初审人员,初审时间 + // getWzxxPart5(), + // //8、得到审核信息组件6:复审人员,复审时间 + // getWzxxPart6(), + // SizedBox(height: 6), + // Divider(height: 1.0, color: Colors.blue), + // SizedBox(height: 6), + //6、得到推送交警状态信息组件7:推送状态 + //getWzxxPart7(), + // SizedBox(height: 8), + // Divider(height: 1.0, color: Colors.blue), + SizedBox(height: 18), + //9、得到推送交警确认组件 + getTsjjQr(), + SizedBox(height: 10), + ], + ), + ), + ), + Positioned( + //alignment: Alignment(0.9, 0.35), + //alignment: Alignment(0.8, 0.45), + right: ScreenUtil().setWidth(80), + top: ScreenUtil().setHeight(1045), + child: Container( + //alignment: Alignment(0.5, -0.5), + width: _stampSize, + height: _stampSize, + //color: Colors.black12, + decoration: BoxDecoration( + //color: Colors.white, + image: DecorationImage( + //image: AssetImage("assets/images/jkzx_stamp.png"), fit: BoxFit.contain), + image: AssetImage("assets/images/fhyc.png"), + fit: BoxFit.contain), + ), + //child: + ), + ), + ], + ), + ); + } + + Widget getBtnSizeX({@required text, width = 70.0, height = 35.0, onPressedFun}) { + return Container( + color: Colors.white12, //onPressedFun为null时无效 + width: width, + height: height, + child: RaisedButton( + padding: EdgeInsets.all(0), + textColor: Colors.black, + child: Text(text), + onPressed: onPressedFun, + ), + ); + } +} diff --git a/lib/pages/Works/HYSH/hysh_content_new.dart b/lib/pages/Works/HYSH/hysh_content_new.dart new file mode 100644 index 0000000..510f2d8 --- /dev/null +++ b/lib/pages/Works/HYSH/hysh_content_new.dart @@ -0,0 +1,919 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hyzp_ybqx/widget/JdButton.dart'; +//import 'package:hyzp_ybqx/widget/player_pro_new.dart'; + +import '../../../widget/CarNumberAndCpysItems.dart'; +import '../../../components/dioFun.dart'; +import '../../../components/doJSON.dart'; +import '../../../components/hyxx_data_handle.dart'; + +import '../../../services/EventBus.dart'; +import '../../../components/commonFun.dart'; +import '../../../config/service_url.dart'; +//import '../../../widget/player_pro.dart'; +import '../../../widget/customRadioWidget.dart'; + +import 'package:dio/dio.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:photo_view/photo_view.dart'; + +//屏幕适配 flutter_screenutil 库与 flustars(获取网络图片尺寸要用到其 WidgetUtil) 库使用的 ScreenUtil() 类名冲突 +// 解决办法是将 flustars 库取个别名 as flustars +import 'package:flustars/flustars.dart' as flustars; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +import 'package:keyboard_avoider/keyboard_avoider.dart'; +import 'package:flutter_drag_scale/flutter_drag_scale.dart'; + +import 'hysh_group.dart'; +import 'package:hyzp_ybqx/widget/my_superplayer.dart'; + +class HyshContentNew extends StatefulWidget { + HyshContentNew({ + @required this.hyshlx, + @required this.title, + this.indexRecord, + this.id, + Key key, + }) : super(key: key); + String title; + int indexRecord = 0; + int id = -1; + String hyshlx; + + _HyshPageState createState() => _HyshPageState(); +} + +//用TabController实现顶部tab切换 +class _HyshPageState extends State with SingleTickerProviderStateMixin { + //try_setState(); //避免如下异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + //获取黑烟初审单条数据 + //接口地址:http://49.235.208.235:9001/?s=App.Car_Yjxx.Get + //接口参数 + // 参数名字 类型 是否必须 默认值 其他 说明 + // id 字符串 必须 最小:1 ID + + //List listController = []; + + //List> listZpljController = []; + List ratioList = []; + + //用TabController实现顶部tab切换 + TabController _tabController; + + //监听登录页面销毁的事件 + dispose() { + _tabController?.dispose(); + super.dispose(); + //eventBus.fire(UserEvent('登录成功...')); + } + + //int listLen = 0; + String nums = ''; + bool bModifiable = false; + String cpysText = ''; + String myCpys = '蓝色'; + + void initState() { + // TODO: implement initState + fh_hyc = true; + tsjj = true; + _context = context; + getListFlields(); + + // //监听违章信息数据更新事件 + // eventBus.on().listen((event) { + // print(event.str); + // getListFlields(); + // }); + + // //监听违章信息数据审核事件 + // eventBus.on().listen((event) { + // print('HycsContentModify: ' + event.str); + // getListFlields(); + // }); + + //黑监听烟初审数据审核Radio选项改变事件 + eventBus.on().listen((event) { + print(event.str); + print('event.selectedRadio = ${event.selectedRadio.toString()}'); + if (null != _tabController) { + topTabs_map['auditTitle'][_tabController.index] = + (event.selectedRadio == 0) ? hyc_text : fhyc_text; + topTabs_map['auditShuoming_Controller_List'][_tabController.index].text = + ((widget.hyshlx == 'hyfh') ? '初审为' + mapGetHycsShenheData['title'] + '。复审' : '') + + ((event.selectedRadio == 0) ? hyc_shyj : fhyc_shyj); + + //解决 setState(() {}); 抛异常Unhandled Exception: setState() called after dispose(): + try_setState(); //避免如下异常报错 + } + }); + + //黑烟审核推送交警Checkbox改变事件 + eventBus.on().listen((event) { + print(event.str); + if (null != _tabController) { + //解决 setState(() {}); 抛异常Unhandled Exception: setState() called after dispose(): + try_setState(); //避免如下异常报错 + } + }); + + //黑烟初审数据审核Dropdown选项改变事件 + eventBus.on().listen((event) { + print(event.str); + print('event.selectedValue = ${event.selectedValue}'); + print('_tabController.index = ${_tabController.index}'); + //topTabs_map['cpysText_List'][_tabController.index] = event.selectedValue; //这样报错如下 + myCpys = event.selectedValue; + + try_setState(); //避免如下异常报错 + + //This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer inc + // ludes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback. + // topTabs_map['carNumberAndCpys_List'][_tabController.index] = + // getCarNumberAndCpys(_tabController.index); + //延时500毫秒执行 + // Future.delayed(const Duration(milliseconds: 10), () { + // print('延时后输出:_tabController.index = ${_tabController.index}'); + // //延时执行的代码 + // //延时更新状态 + // setState(() {}); + // }); + }); + + super.initState(); + } + + //避免如下异常报错 + // try { + // setState(() {}); + // } catch (e) {} + //E/flutter (12227): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: setState() called after dispose(): _LoginPageState#c4f60(lifecycle state: defunct, not mo + // unted, ticker inactive) + // E/flutter (12227): This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer inc + // ludes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback. + // E/flutter (12227): The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" prop + // erty of this object before calling setState() to ensure the object is still in the tree. + // E/flutter (12227): This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has bee + // n removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose(). + // E/flutter (12227): #0 State.setState. (package:flutter/src/widgets/framework.dart:1208:9) + // E/flutter (12227): #1 State.setState (package:flutter/src/widgets/framework.dart:1243:6) + // E/flutter (12227): #2 _LoginPageState.getZpjlFields (package:hyzp_ybqx/pages/Works/HYSH/hysh_content.dart:163:7) + // E/flutter (12227): #3 _rootRunUnary (dart:async/zone.dart:1198:47) + // E/flutter (12227): #4 _CustomZone.runUnary (dart:async/zone.dart:1100:19) + // E/flutter (12227): #5 _FutureListener.handleValue (dart:async/future_impl.dart:143:18) + // E/flutter (12227): #6 Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:696:45) + // E/flutter (12227): #7 Future._propagateToListeners (dart:async/future_impl.dart:725:32) + // E/flutter (12227): #8 Future._completeWithValue (dart:async/future_impl.dart:529:5) + // E/flutter (12227): #9 Future._asyncCompleteWithValue. (dart:async/future_impl.dart:567:7) + // E/flutter (12227): #10 _rootRun (dart:async/zone.dart:1190:13) + // E/flutter (12227): #11 _CustomZone.run (dart:async/zone.dart:1093:19) + // E/flutter (12227): #12 _CustomZone.runGuarded (dart:async/zone.dart:997:7) + // E/flutter (12227): #13 _CustomZone.bindCallbackGuarded. (dart:async/zone.dart:1037:23) + // E/flutter (12227): #14 _microtaskLoop (dart:async/schedule_microtask.dart:41:21) + // E/flutter (12227): #15 _startMicrotaskLoop (dart:async/schedule_microtask.dart:50:5) + // E/flutter (12227): + // E/flutter (12227): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: 'package:flutter/src/widgets/media_query.dart': Failed assertion: line 812 pos 12: 'conte + // xt != null': is not true. + // E/flutter (12227): #0 _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:46:39) + // E/flutter (12227): #1 _AssertionError._throwNew (dart:core-patch/errors_patch.dart:36:5) + // E/flutter (12227): #2 MediaQuery.of (package:flutter/src/widgets/media_query.dart:812:12) + // E/flutter (12227): #3 _LoginPageState.getTopTabsMap (package:hyzp_ybqx/pages/Works/HYSH/hysh_content.dart:415:41) + // E/flutter (12227): #4 _LoginPageState.getZpjlFields (package:hyzp_ybqx/pages/Works/HYSH/hysh_content.dart:162:7) + + //getListFlields({bool b = true}) async { + getListFlields() async { + //await getHycsGetData(); + //Unhandled Exception: type 'List' is not a subtype of type 'Map' + //print('getListFlields().mapGetHycsGetData = ${mapGetHycsGetData}'); + //if (b) { + //listZpljController.clear(); + listGetZpjl.clear(); //必须先行清空,否则会出现记录数据错位问题 + listFieldModify.clear(); + mapGetHycsGetData.clear(); + + //1、获取指定id的抓拍记录列表存入listGetZpjl + //2、如果是黑烟复审,还需获取指定id的违章记录审核信息存入mapGetShenheData + await getItemData(widget.id); + //} + print('getListFlields().listGetZpjl = ${listGetZpjl}'); + //print('getListFlields().mapGetHycsGetData = ${mapGetHycsGetData}'); + + await getZpjlFields(); + + //nums = '${(widget.indexRecord + 1).toString()} / $listLen'; + try { + // nums = '(${(widget.indexRecord + 1).toString()} / ${listHycsGetList2.length})id:${listHycsGetList2[widget.indexRecord]['id']}'; + nums = '(${(widget.indexRecord + 1).toString()} / ${listHycsGetList2.length})'; + setState(() {}); + } catch (e) {} + //setState(() {}); //刚进入时,执行该语句会会抛异常 + //print('mapGetHycsGetData = \n${mapGetHycsGetData}'); + } + + Future getZpjlFields() async { + if (listGetZpjl.isNotEmpty) { + int len = listGetZpjl.length; + for (int i = 0; i < len; i++) { + // 获取网络图片尺寸 + Rect rect = + await flustars.WidgetUtil.getImageWH(url: getMediaUrl(listGetZpjl[i]['pic_url'])); + ratioList.add(rect.height / rect.width); + print("rect: " + rect.toString()); + print("ratio: " + ratioList[i].toString()); + } + await getTopTabsMap(); + + //用TabController实现顶部tab切换 + _tabController = TabController(vsync: this, length: topTabs_map['listView_List'].length); + _tabController.addListener(() { + print('_tabController.index = ${_tabController.index}'); + + //Tab切换时,设置 sfyc 和 tsjj,只处理当前选中的抓拍记录 _tabController.index + set_sfyc_tsjj(int.parse(listGetZpjl[_tabController.index]['zpsj'])); + + //Tab切换时,初始化所有审核意见 + int len = listGetZpjl.length; + for (int i = 0; i < len; i++) { + topTabs_map['auditShuoming_Controller_List'][i].text = + ((widget.hyshlx == 'hyfh') ? '初审为' + mapGetHycsShenheData['title'] + '。复审' : '') + + hyc_shyj; + } + }); + + print('first : _tabController.index = ${_tabController.index}'); + set_sfyc_tsjj(int.parse(listGetZpjl[_tabController.index]['zpsj'])); + + //避免如下异常报错 + try_setState(); //避免如下异常报错 + } + } + + // 使用 cached_network_image 插件实现网络图片缓存 + // 使用 flutter_drag_scale 实现可缩放可拖拽双击放大的图片功能。PhotoView插件不好用,有问题 + Widget getNetworkImage(String url) { + return CachedNetworkImage( + imageUrl: url, + alignment: Alignment.topCenter, + imageBuilder: (context, imageProvider) => DragScaleContainer( + doubleTapStillScale: true, child: Image(image: imageProvider) + // child: Image( + // image: NetworkImage( + // 'http://h.hiphotos.baidu.com/zhidao/wh%3D450%2C600/sign=0d023672312ac65c67506e77cec29e27/9f2f070828381f30dea167bbad014c086e06f06c.jpg'), + // ), + ), + // imageBuilder: (context, imageProvider) => PhotoView( + // imageProvider: imageProvider, + // ), + //placeholder: (context, url) => CircularProgressIndicator(), + placeholder: (context, url) => + getMoreWidget(color: Colors.black38, size: 20.0, strokeWidth: 2.0), + errorWidget: (context, url, error) => Icon(Icons.error), + ); + } + + //使用 cached_network_image 插件实现网络图片缓存 + Widget getNetworkImage1(String url) { + return CachedNetworkImage( + imageUrl: url, + alignment: Alignment.topCenter, + //placeholder: (context, url) => CircularProgressIndicator(), + imageBuilder: (context, imageProvider) => PhotoView( + imageProvider: imageProvider, + ), + placeholder: (context, url) => + getMoreWidget(color: Colors.black38, size: 20.0, strokeWidth: 2.0), + errorWidget: (context, url, error) => Icon(Icons.error), + ); + } + + //3、得到违章图片说明信息组件 + Widget getWztpSmxx(int index) { + return Container( + width: ScreenUtil().setWidth(1022), + height: ScreenUtil().setHeight(141), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12), + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + getWztpSmxx1(index), + getWztpSmxx2(index), + ], + ), + ); + } + + //3.1、得到违章图片说明信息1:黑度、抓拍时间组件 + Widget getWztpSmxx1(int index) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + alignment: Alignment.centerLeft, + padding: EdgeInsets.only(left: ScreenUtil().setWidth(_marginLeft)), + width: ScreenUtil().setWidth(_preItem), + child: + Text('黑度:' + listGetZpjl[index]['lgmzs'].toString(), overflow: TextOverflow.ellipsis), + ), + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + Icon( + Icons.query_builder, + size: ScreenUtil().setWidth(_iconSize), + color: _iconColor, + ), + Container( + alignment: Alignment.centerLeft, + width: ScreenUtil().setWidth(1022 - _preItem - _marginLeft - _iconSize), + child: Text( + ' :' + + getDate((listGetZpjl[index]['zpsj'] is int) + ? listGetZpjl[index]['zpsj'] + : int.parse(listGetZpjl[index]['zpsj'])), + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis), + ), + ], + ); + } + + //3.2、得到违章图片说明信息:车型、抓拍地点组件 + Widget getWztpSmxx2(int index) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + alignment: Alignment.centerLeft, + padding: EdgeInsets.only(left: ScreenUtil().setWidth(_marginLeft)), + width: ScreenUtil().setWidth(_preItem), + child: Text('车型:' + listGetZpjl[index]['clfl'], overflow: TextOverflow.ellipsis), + ), + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + Icon( + Icons.location_on_outlined, + size: ScreenUtil().setWidth(_iconSize), + color: _iconColor, + ), + Container( + alignment: Alignment.centerLeft, + width: ScreenUtil().setWidth(1022 - _preItem - _marginLeft - _iconSize), + child: Text(' :' + listGetZpjl[index]['dwms'], + textAlign: TextAlign.left, overflow: TextOverflow.ellipsis)), + ], + ); + } + + //4、得到黑烟初审结果组件,在复审页面需要 + Widget getHycsResult(int index) { + double _radio = 2.2; + return Column( + children: [ + Container( + width: ScreenUtil().setWidth(1022), + height: ScreenUtil().setHeight(141), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12), + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + alignment: Alignment(-1, 0), + width: ScreenUtil().setWidth(1022 / _radio), + //height: ScreenUtil().setHeight(my_listTileHeight2), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + Text('初审结果: ' + mapGetHycsShenheData['title'], + textAlign: TextAlign.left, overflow: TextOverflow.ellipsis), + SizedBox(width: ScreenUtil().setWidth(6)), + Container( + width: ScreenUtil().setWidth(_iconSize), + height: ScreenUtil().setHeight(_iconSize), + decoration: BoxDecoration( + //color: Colors.white, + image: DecorationImage( + image: AssetImage(mapGetHycsShenheData['title'] == "黑烟车" + ? "assets/images/hyc.png" + : "assets/images/fhyc.png"), + fit: BoxFit.contain), + ), + alignment: Alignment.center, + //child: + ), + ], + ), + ), + SizedBox(width: ScreenUtil().setWidth(10)), + Expanded( + child: Text('意见: ' + mapGetHycsShenheData['shuoming'], + textAlign: TextAlign.left, overflow: TextOverflow.ellipsis), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + alignment: Alignment(-1, 0), + width: ScreenUtil().setWidth(1022 / _radio), + child: Row(mainAxisAlignment: MainAxisAlignment.start, children: [ + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + Text('初审用户: ' + mapGetHycsShenheData['uname'], + textAlign: TextAlign.left, overflow: TextOverflow.ellipsis), + ]), + ), + SizedBox(width: ScreenUtil().setWidth(10)), + Expanded( + child: Text('时间: ' + mapGetHycsShenheData['addtime'], + textAlign: TextAlign.left, overflow: TextOverflow.ellipsis), + ), + ], + ), + ], + ), + ), + SizedBox(height: ScreenUtil().setHeight(_marginVer)), + ], + ); + } + + // 167 50 3.34 + Widget getLgmzs(int index, int lgmzs, {double width = 127, double height = 127}) { + int _rgb = (255 * (5 - lgmzs)) ~/ 5; + return Stack( + children: [ + Container( + width: ScreenUtil().setWidth(width), + height: ScreenUtil().setHeight(height), + padding: EdgeInsets.only( + right: ScreenUtil().setWidth(6), left: ScreenUtil().setWidth(6), top: 0, bottom: 4), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text('$lgmzs级${lgmzs * 20}%', style: TextStyle(fontSize: 10)), + //SizedBox(height: 0), + Container( + width: ScreenUtil().setWidth(66), + height: ScreenUtil().setHeight(66), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + color: Color.fromRGBO(_rgb, _rgb, _rgb, 1.0), + borderRadius: new BorderRadius.circular(0), + ), + ) + ], + ), + ), + Positioned( + top: ScreenUtil().setHeight(4), + child: Container( + width: ScreenUtil().setWidth(width), + height: ScreenUtil().setHeight(height - 8), + padding: EdgeInsets.only( + right: ScreenUtil().setWidth(6), left: ScreenUtil().setWidth(6), top: 0, bottom: 0), + decoration: BoxDecoration( + border: Border.all( + color: (lgmzs == listGetZpjl[index]['lgmzs']) + ? Colors.red + : Color.fromRGBO(244, 244, 244, 1), + width: 2), + //color: Colors.lightBlue, + borderRadius: new BorderRadius.circular(3.0), + ), + ), + ) + ], + ); + } + + //1、得到格林曼黑度标准和视频播放按钮组件 + Widget getHdAndPlay(int index) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + getLgmzs(index, 0), + getLgmzs(index, 1), + getLgmzs(index, 2), + getLgmzs(index, 3), + getLgmzs(index, 4), + getLgmzs(index, 5, width: 153), + getIconBtnSizeX( + height: 104, + //getIconBtnSizeX 中已经使用ScreenUtil().setHeight(126),此处不能传 ScreenUtil().setHeight(126) ,否则严重错位 + width: 168, + text: "视频", + textSize: 12, + circular: 4, + color: Color.fromRGBO(52, 157, 237, 1), + onTop: () async { + if (Playing) { + //禁止同时启动两次播放器 + return; + } + + Playing = true; //禁止同时启动两次播放器 + urlnew = getMediaUrl(listGetZpjl[index]['video_url']); + + //获取视频地址失败 + if (!isVideoUrl(urlnew)) { + return; + } + + Navigator.of(_context).push(MaterialPageRoute( + builder: (context) => SuperPlayerPage( + loop: 0, //设置播放循环,默认播放器的循环次数是1, 即不循环播放。如果设置循环次数0,表示无限循环。 + url: urlnew, + title: + '违章视频: ${listGetZpjl[index]["car_number"]}(抓拍${index + 1})\n${listGetZpjl[index]['dwms']}'))); + + // Navigator.of(_context).push(MaterialPageRoute( + // builder: (context) => PlayerProNew( + // loop: 0, //设置播放循环,默认播放器的循环次数是1, 即不循环播放。如果设置循环次数0,表示无限循环。 + // url: urlnew, + // title: + // '违章视频: ${listGetZpjl[index]["car_number"]}(抓拍${index + 1})\n${listGetZpjl[index]['dwms']}'))); + }, + ), + SizedBox(width: ScreenUtil().setWidth(15)), + ], + ); + } + + //2、得到违章图片组件 + Widget getWztp(int index) { + print('ratioList[index] = ${ratioList[index]}'); + //ratioList[index] = 0.5714285714285714 + return Stack( + children: [ + Container( + width: ScreenUtil().setWidth(1022), + //height: ScreenUtil().setHeight(639), + height: ScreenUtil() + .setHeight(22 + 1022 * (ratioList.isNotEmpty ? ratioList[index] : 9 / 16)), + decoration: BoxDecoration( + //color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12), + ), + ), + ), + Positioned( + //left: ScreenUtil().setWidth(25), + top: ScreenUtil().setHeight(11), + child: Container( + width: ScreenUtil().setWidth(1022), + height: + ScreenUtil().setHeight(1022 * (ratioList.isNotEmpty ? ratioList[index] : 9 / 16)), + child: getNetworkImage(getMediaUrl(listGetZpjl[index]['pic_url'])), + ), + ) + ], + ); + } + + double _fontSize = 16; + double _listTileHeight = 30; + double _iconSize = 55; + Color _iconColor = Colors.blue; + double _textFieldHeight = 8; + double _preItem = 360; + double _marginLeft = 33; + double _marginVer = 6; + BuildContext _context; + + double _marginVertical1 = 3; + double _marginVertical2 = 5; + + //double _marginVertical3 = 6; + double _marginVertical3 = 3; + + //double _marginVertical4 = 10; + double _marginVertical4 = 6; + double _marginVertical5 = 10; + double _marginVertical6 = 20; + + Future getTopTabsMap() async { + //map遍历,清空数据 + topTabs_map.forEach((key, value) { + topTabs_map[key].clear(); + }); + + int len = listGetZpjl.length; + for (int index = 0; index < len; index++) { + //顶部Tab标题 + //topTabs_map['tabs_list'].add(Tab(text: '抓拍ID:${listGetZpjl[index]['id']}')); + topTabs_map['tabs_list'].add(Tab(text: '抓拍 ${(index + 1).toString()}')); + //可供用户修改的车牌号码 + topTabs_map['car_number_List'].add(listGetZpjl[index]['car_number']); + //可供用户修改的车牌颜色 + topTabs_map['cpysText_List'].add(listGetZpjl[index]['cpys']); + //可供用户修改的审核意见 + topTabs_map['auditShuoming_Controller_List'].add( + TextEditingController.fromValue(TextEditingValue( + text: ((widget.hyshlx == 'hyfh') ? '初审为' + mapGetHycsShenheData['title'] + '。复审' : '') + + hyc_shyj, + // 保持光标在最后 + selection: TextSelection.fromPosition( + TextPosition(affinity: TextAffinity.downstream, offset: hyc_shyj.length)))), + ); + //可供用户修改的审核结果 + topTabs_map['auditTitle'].add(hyc_text); + //车牌号码、车牌颜色 + topTabs_map['carNumberAndCpys_List'] + .add(CarNumberAndCpysItems(index, topTabs_map['cpysText_List'][index])); + + //Tab页面 + topTabs_map['listView_List'].add( + //flutter开发弹起键盘出现Overflow问题的解决方法,我出现的情况,这三种方法就可以解决。 + KeyboardAvoider( + autoScroll: true, + child: Container( + decoration: new BoxDecoration( + color: Color.fromRGBO(244, 244, 244, 1), + ), + child: Column( + children: [ + //1、得到格林曼黑度标准和视频播放按钮组件 + getHdAndPlay(index), + //2、得到违章图片组件 + getWztp(index), + SizedBox(height: ScreenUtil().setHeight(_marginVer)), + //3、得到违章图片说明信息组件 + getWztpSmxx(index), + SizedBox(height: ScreenUtil().setHeight(_marginVer)), + //4、得到黑烟初审结果组件,在复审页面需要 + widget.hyshlx == 'hyfh' ? getHycsResult(index) : SizedBox.shrink(), + //为了用户在切换审核结果Radio时显示不同图片,必须将以下组件都移入到RadioListItems类中 + //5-6、得到黑烟审核组件、审核确认组件 + HyshGroup( + index: index, + hyshlx: hyshlx, + fontSize: _fontSize, + size: Size(_listTileHeight, _listTileHeight), + id: widget.id), + //为了用户在切换审核结果Radio时显示不同图片,必须将以下组件都移入到RadioListItems类中 + // SizedBox(height: 6), + // Divider(height: 1.0, color: Colors.blue), + // SizedBox(height: 10), + // //9、得到审核确认组件 + // getShqr(index), + ], + ), + ), + ), + ); + } + } + + //flutter开发弹起键盘出现Overflow问题的解决方法 + // 方法1: + // //Scaffold节点下添加resizeToAvoidBottomPadding: false,这样页面就不会随着键盘弹起而滚动。 + // Scaffold( + // resizeToAvoidBottomPadding: false, + // body: Column() + // ); + // 方法2: + // //外层使用SingleChildScrollView包裹一层,这样页面回随着键盘弹起而向上滚动。 + // SingleChildScrollView( + // child: Column( + // children: [ + // TextField() + // ], + // ), + // ), + // 方法3: + // //使用第三方库:keyboard_avoider,并且设置autoScroll为true + // pubspec.yaml文件下添加依赖: + // dependencies: + // keyboard_avoider: ^0.1.2 + // 外层使用KeyboardAvoider包裹,设置autoScroll为true + // KeyboardAvoider( + // autoScroll: true + // child: Column( + // children: [ + // TextField() + // ], + // ) + // ), + // 我出现的情况,这三种方法就可以解决。 + + @override + Widget build(BuildContext context) { + print('topTabs_map[\'tabs_list\'].length = ${topTabs_map['tabs_list'].length}'); + print('topTabs_map[\'listViewList\'].length = ${topTabs_map['listView_List'].length}'); + + return DefaultTabController( + //length: 8, //必须使用常数 + //length: topTabs_map['tabs_list'].length, + length: topTabs_map['listView_List'].length, + //报错:Another exception was thrown: A RenderFlex overflowed by 99896 pixels on the bottom + //length: g_tabs, //报错: Another exception was thrown: RangeError (index): Invalid value: Valid value range is empty: 0 + child: Scaffold( + //resizeToAvoidBottomPadding: false, + appBar: PreferredSize( + preferredSize: Size.fromHeight(ScreenUtil().setHeight(173)), // 设置appBar高度 + child: AppBar( + //backgroundColor: Colors.black12, + // title: Text("${mapHyshlx[hyshlx]['text']}详情$nums", + // style: TextStyle( + // fontSize: _fontSize, color: myCpys == '绿色' ? Colors.green : Colors.blue)), + + // title: Text("${mapHyshlx[hyshlx]['text']}详情$nums", + // style: TextStyle(fontSize: _fontSize)), + + automaticallyImplyLeading: false, + centerTitle: true, + //leading: Text(''), + titleSpacing: 0.0, + //设置title的左边距 + flexibleSpace: Container( + //SizedBox(height: ScreenUtil().statusBarHeight), //显示顶部状态栏 + // SizedBox(height: ScreenUtil().setHeight(10)), //显示顶部状态栏 + padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight), //留出顶部状态栏高度 + child: Container( + //height: ScreenUtil().setHeight(173), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + // decoration: BoxDecoration( + // gradient: LinearGradient(colors: [ + // Color(0xFF0018EB), + // Color(0xFF01C1D9), + // ], begin: Alignment.bottomCenter, end: Alignment.topCenter), + // ), + ), + ), + title: Padding( + padding: EdgeInsets.only(left: 0, right: 0), + child: Row( + //mainAxisAlignment: MainAxisAlignment.start, + children: [ + //1.1、返回按钮 + getIconAndTextButton( + iconColor: Colors.white, + iconData: Icons.chevron_left_outlined, + onPress: () { + Navigator.pop(context); + }, + ), + Expanded( + // child: Text("${mapHyshlx[hyshlx]['text']}", + // textAlign: TextAlign.left, overflow: TextOverflow.ellipsis), + child: Text("${mapHyshlx[hyshlx]['text']}详情$nums", + style: TextStyle(color: Colors.white, fontSize: 20), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis), + ), + SizedBox(width: 30), + ], + ), + ), + ), + ), + body: Scaffold( + appBar: PreferredSize( + preferredSize: Size.fromHeight(ScreenUtil().setHeight(99)), // 设置appBar高度 + child: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + //leading: Text(''), + titleSpacing: 0.0, + //设置title的左边距 + title: Padding( + padding: EdgeInsets.only(left: 0, right: 0), + ), + //bottom必须要套PreferredSize,否则会报错:The argument type 'Container' can't be assigned to the parameter type 'PreferredSizeWidget'. + bottom: PreferredSize( + preferredSize: Size.fromHeight(30.0), // 设置TabBar高度 + child: Container( + //color: Colors.blueAccent, + alignment: Alignment.centerLeft, //左对齐,有效 + height: 30, // 设置TabBar高度 + child: listGetZpjl.isNotEmpty + ? TabBar( + //labelColor: Colors.blueAccent, + controller: _tabController, + //注意:用TabController实现顶部tab切换,必须添加该行 + isScrollable: true, + //如果多个按钮的话可以自动左右移动 + tabs: (topTabs_map['listView_List'].isNotEmpty) + ? topTabs_map['tabs_list'] + : [], + ) + : PreferredSize( + preferredSize: Size.fromHeight(48.0), + child: Theme( + data: Theme.of(context).copyWith(accentColor: Colors.white), + child: Container(), + ), + ), + ), + ), + ), + ), + //: Container(), + //type 'Container' is not a subtype of type 'PreferredSizeWidget' + body: listGetZpjl.isNotEmpty + ? TabBarView( + controller: _tabController, //注意:用TabController实现顶部tab切换,必须添加该行 + physics: NeverScrollableScrollPhysics(), //必须放到TabBarView下面,禁止TabBarView左右滑动-OK + children: + (topTabs_map['listView_List'].isNotEmpty) ? topTabs_map['listView_List'] : [], + ) + : getMoreWidget(color: Colors.black38, size: 20.0, strokeWidth: 2.0), //显示加载中的圈圈 + ), + ), + ); + } + + Widget getBtnSizeX({@required text, width = 70.0, height = 35.0, onPressedFun}) { + return Container( + color: Colors.white12, //onPressedFun为null时无效 + width: width, + height: height, + child: RaisedButton( + padding: EdgeInsets.all(0), + textColor: Colors.black, + child: Text(text), + onPressed: onPressedFun, + ), + ); + } + + // App.Car_Yjxx.Workflow + // 审核数据 + // 接口地址:http://49.235.208.235:9001/?s=App.Car_Yjxx.Workflow + // 接口文档 + // 根据ID审核数据库中的一条纪录数据 + // + // 接口参数 + // 参数名字 类型 是否必须 默认值 其他 说明 + // id 整型 必须 最小:1 ID + // workflow 整型 必须 2 最小:1 审核标记: 2=>初审通过 | 999=>复审通过 | 1000=>确认为非黑烟车 + // shuoming 字符串 必须 最小:1 审核意见:如 黑烟超标,交由交警处罚 + // uid 字符串 必须 最小:1 审核用户ID + // 返回结果 + // 返回字段 类型 说明 + // code 整型 更新的结果,1表示成功,0表示无更新,false表示失败 + + //违章信息审核 + Future auditWzxxData() async { + var api = ServicePath.auditWzxxUrl; + print(api); + + try { + print('开始处理网络请求...'); + Response response; + Dio dio = Dio(); + + await copyMapUpdateWzxxData(mapGetHycsGetData); + response = await dio.post(api, data: mapUpdateWzxxData); + print('response = ${response.toString()}'); + + if (response.statusCode == 200) { + print('违章信息更新网络请求过程正常完成'); + return true; + } else { + throw Exception('后端接口出现异常,请检测代码和服务器情况.........'); + } + } catch (e) { + print('网络请求过程异常e:${e}'); + Fluttertoast.showToast( + msg: 'ERROR:======>${e}', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + + return false; + } +} diff --git a/lib/pages/Works/HYSH/hysh_getList_fliter.dart b/lib/pages/Works/HYSH/hysh_getList_fliter.dart new file mode 100644 index 0000000..cd9e3ba --- /dev/null +++ b/lib/pages/Works/HYSH/hysh_getList_fliter.dart @@ -0,0 +1,1310 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_datetime_picker/flutter_datetime_picker.dart'; +import 'package:flutter_easyrefresh/easy_refresh.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hyzp_ybqx/components/commonFun.dart'; +import 'package:hyzp_ybqx/pages/Works/HYSH/tsjj_content_new.dart'; +import 'package:hyzp_ybqx/services/EventBus.dart'; +import 'package:hyzp_ybqx/widget/DropdownItem.dart'; + +import '../../../components/commonFun.dart'; +import '../../../components/dioFun.dart'; +import '../../../components/doJSON.dart'; +import '../../../components/hyxx_data_handle.dart'; +import '../../../services/EventBus.dart'; +import 'fhycx_content_new.dart'; + +///flutter中如何获取子类Widget并调用它的方法 萤火虫离别的礼物 2019.08.07 15:46:08 https://www.jianshu.com/p/b16f70dd692c +//在flutter中开发中,会发现当子类Widget是StatefulWidget类型的时候,想要获取它的State并调用State中的方法,感觉无从下手。 +// 不像是在iOS中,可以直接调用一个类的公开的方法,flutter可以通过key来实现。每个Widget都是唯一标识的。此唯一标识对应于可选的Key参数。 +// 如果省略,Flutter将为您生成一个。key主要分为四种:GlobalKey,LocalKey,UniqueKey或ObjectKey,GlobalKey确保key是在整个应用程序唯一的, +// 这次我们就要使用它来实现。我们需要给子Widget定义一个唯一的GlobalKey,然后根据这个key获取到这个Widget,进行相关的操作,下面是相关的代码: +//这里就是关键的代码,定义一个key +//GlobalKey _myFijkPanelWidgetBuilderStateKey = new GlobalKey(); + +//Hycs是本项目中“黑烟初审”的统一缩写 +//Hysh是本项目中“黑烟审核(包括:黑烟初审、黑烟复审、推送交警)”的统一缩写 +class HyshGetListFliter extends StatefulWidget { + //hyshlx为黑烟审核类型,处理'hycs'黑烟初审、'hyfh'黑烟复审。mapHyshlx[hyshlx]获取为各种类型的设置数据 + HyshGetListFliter({@required this.hyshlx, this.title, Key key}) : super(key: key); + String hyshlx; + String title = ''; + + _HyshPageState createState() => _HyshPageState(); +} + +class _HyshPageState extends State { + //try_setState(); //避免如下异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + @override + void dispose() { + _controller.dispose(); //销毁控制器 + super.dispose(); + } + + @override + void initState() { + hyshlx = widget.hyshlx; + + ///从接口 mapHyshlx[theHyshlx]['api'] 获取指定类型第 page 页的列表数据,返回 list + ///获取点位信息数据 + listDwinfoGetList2.clear(); + getThePageList(theHyshlx: 'dwxx').then((value) { + listDwinfoGetList2 = value; + print('listDwinfoGetList2 = \n$listDwinfoGetList2'); + getListFlields().then((value) { + getSelectLedDw(370, 14).then((value) { + _selectLedDw = value; + try_setState(); + }); + }); + }); + + //getHycsGetList(); + iPage = 0; + listHycsGetList2.clear(); + //listTsjjTszt.clear(); //与listHycsGetList2对应 + //getAllItems(1000); + + //从接口mapHyshlx[hyshlx]['api']获取数据,通过list返回。再添加到listHycsGetList2中 + getPageList(perpage: 100).then((value) { + listHycsGetList2 = value; + if (listHycsGetList2.isEmpty) { + _empty = _emptyFilterInfo; + try_setState(); + return; + } + + // 以 listHycsGetList2 为基础,依据过滤条件进行筛选,结果存到 listHycsFliter + fliterHycsList().then((value) { + //按照用户选择的_selectedValue、_descending对listHycsFliter进行排序,并延时更新 + _listSort(); + firstIndex = 0; + lastIndex = 6; + }); + }); + + //监听违章信息数据更新事件 + eventBus.on().listen((event) async { + print(event.str); + //按照用户选择的_selectedValue、_descending对listHycsGetList2进行排序,并延时更新 + iPage = 0; + getPageList(perpage: 100).then((value) { + listHycsGetList2 = value; + + if (listHycsGetList2.isEmpty) { + _empty = _emptyFilterInfo; + try_setState(); + return; + } + + // 以 listHycsGetList2 为基础,依据过滤条件进行筛选,结果存到 listHycsFliter + fliterHycsList().then((value) { + //按照用户选择的_selectedValue、_descending对listHycsFliter进行排序,并延时更新 + _listSort(); + // firstIndex = 0; + // lastIndex = 6; + }); + }); + }); + + //监听违章信息推送状态更新事件 + // eventBus.on().listen((event) async { + // print(event.str); + // try_setState(); + // //_listSort(); + // }); + + //监听违章信息数据审核事件 + eventBus.on().listen((event) async { + print('HycsGetList: ' + event.str); + //按照用户选择的_selectedValue、_descending对listHycsGetList2进行排序,并延时更新 + if (listHycsGetList2.isEmpty) { + _empty = _emptyFilterInfo; + try_setState(); + return; + } + + // 以 listHycsGetList2 为基础,依据过滤条件进行筛选,结果存到 listHycsFliter + fliterHycsList().then((value) { + //按照用户选择的_selectedValue、_descending对listHycsFliter进行排序,并延时更新 + _listSort(); + // firstIndex = 0; + // lastIndex = 6; + }); + }); + + //监听违章信息Listview滚动事件 + eventBus.on().listen((event) async { + firstIndex = event.firstIndex; + lastIndex = event.lastIndex; + try_setState(); + }); + + //监听 选择点位过滤 更新事件 + eventBus.on().listen((event) async { + print('event.selectedValue = ${event.selectedValue}'); + _selectedDwValue = event.selectedValue; + + //按照用户选择的_selectedValue、_descending对listHycsGetList2进行排序,并延时更新 + if (listHycsGetList2.isEmpty) { + _empty = _emptyFilterInfo; + try_setState(); + return; + } + + getDwip(_selectedDwValue).then((value) { + _dwip = value; + print('_dwip = ${_dwip}'); + // 以 listHycsGetList2 为基础,依据过滤条件进行筛选,结果存到 listHycsGetList3 + fliterHycsList().then((value) { + //按照用户选择的_selectedValue、_descending对listHycsGetList3进行排序,并延时更新 + isLoading = false; + _listSort(); + firstIndex = 0; + lastIndex = 6; + }); + }); + }); + + super.initState(); + } + + bool _containsNocase(String str, String sub) { + str = str.trim(); + str = str.toLowerCase(); + + sub = sub.trim(); + sub = sub.toLowerCase(); + + return str.contains(sub); + } + + // 以 listHycsGetList2 为基础,依据过滤条件进行筛选,结果存到 listHycsFliter + Future fliterHycsList() async { + listHycsFliter.clear(); + + _zpsj_section.clear(); + if (null != _mapDataTiem['beginDateTime'] && null != _mapDataTiem['endDateTime']) { + _zpsj_section.add(_mapDataTiem['beginDateTime'].millisecondsSinceEpoch ~/ 1000); + _zpsj_section.add(_mapDataTiem['endDateTime'].millisecondsSinceEpoch ~/ 1000); + } + + //1、依据点位进行过滤 + int len = listHycsGetList2.length; + for (int i = 0; i < len; i++) { + // print('listHycsGetList2[i][\'plate_id\'] = ${listHycsGetList2[i]['plate_id']}'); + // print('_plate_id = ${_plate_id}'); + // print( + // '_containsNocase(listHycsGetList2[i][\'plate_id\'], _plate_id) = ${_containsNocase(listHycsGetList2[i]['plate_id'], _plate_id)}'); + if ((_dwip.isEmpty || _dwip == listHycsGetList2[i]['dwip']) //过滤条件1:点位ip + && + (_plate_id.isEmpty || + _containsNocase(listHycsGetList2[i]['plate_id'], _plate_id)) //过滤条件2:车牌号 + && + (_zpsj_section.isEmpty || + (listHycsGetList2[i][_selTiemStr] != null && // 'zpsj',_selTiemStr + _zpsj_section[0] <= listHycsGetList2[i][_selTiemStr] && + listHycsGetList2[i][_selTiemStr] <= _zpsj_section[1])) //过滤条件3:抓拍时间段 或 推送时间 + ) { + listHycsFliter.add(listHycsGetList2[i]); + if (listHycsFliter[listHycsFliter.length - 1]["ts_time"] == null) { + listHycsFliter[listHycsFliter.length - 1]["ts_time"] = 0; + } + //print('listHycsFliter.length = ${listHycsFliter.length}'); + } + } + //print('listHycsFliter.length = ${listHycsFliter.length}'); + + if (listHycsFliter.isEmpty) { + _empty = _emptyFilterInfo; + } else { + _empty = ''; + } + + return listHycsFliter; + } + + // 车牌号码 + // 车牌颜色 + // 车辆类型 + // 林格曼黑度 + // 抓拍次数 + // 首次抓拍时间 + // 首次抓拍地点 + int today = 0; + int sum = 0; + String _emptyFilterInfo = '\n没有符合条件的记录'; + String _empty = ''; + String _dwip = ''; //过滤条件1:点位ip + String _plate_id = ''; //过滤条件2:车牌号 + //List _zpsj_section = [0, DateTime.now().millisecondsSinceEpoch ~/ 1000]; //过滤条件3:抓拍时间段 + List _zpsj_section = []; //过滤条件3:抓拍时间段 + + ScrollController _controller = ScrollController(); //ListView控制器 + bool isLoading = false; //正在处理下载数据、跳转到首项、跳转到尾项等操作 + int firstIndex = 0; //ListView当前显示页面首项0基序号 + int lastIndex = 0; //ListView当前显示页面末项0基序号 + int itemOnePage = 7; //估计一屏显示的项目数量 + Widget _selectLedDw; //点位选择过滤器 + + String _selectedDwValue = ''; + String _selectedValue = '抓拍时间'; + int _selTimeType = 0; // 0 抓拍时间,1 推送时间 + String _selTiemStr = 'zpsj'; //'zpsj','ts_time' + bool _descending = true; + + Widget _getListTile(BuildContext context, indexRecord) { + // print('indexRecord = ${indexRecord}'); + // print('listTsjjTszt = ${listTsjjTszt}'); + // Color markColor; + // FontWeight markBold; + // print('EndDayOfYesterDayStamp = ${getEndDayOfYesterDayStamp()}'); + // print('zpsj = ${listHycsFliter[indexRecord]['zpsj']}'); + // if (listHycsFliter[indexRecord]['zpsj'] > getEndDayOfYesterDayStamp()) { + // Color markColor = Colors.lightBlue; + // FontWeight markBold = FontWeight.bold; + // print('markColor = $markColor, markBold = $markBold'); + // } + + // _selTiemStr = 'zpsj'; + // _selTiemStr = 'tssj'; + return Column( + children: [ + ListTile( + //leading: new Icon(Icons.phone), + title: Text( + "${(indexRecord + 1).toString()}. " + + listHycsFliter[indexRecord]['plate_id'] + + '(${listHycsFliter[indexRecord]['plate_color']}),${listHycsFliter[indexRecord]['clfl']}', + style: TextStyle(fontSize: _fontSize)), + subtitle: Text( + (_selTimeType == 0 ? '抓拍:' : '推送:') + + getDate(listHycsFliter[indexRecord][_selTiemStr]), + // 'zpsj',_selTiemStr + style: TextStyle( + fontSize: _fontSize, + color: listHycsFliter[indexRecord][_selTiemStr] > + getEndDayOfYesterdayStamp() // 'zpsj' + ? Colors.lightBlue + : null, + fontWeight: listHycsFliter[indexRecord][_selTiemStr] > + getEndDayOfYesterdayStamp() // 'zpsj' + ? FontWeight.bold + : null)), + trailing: Container( + width: 110, + child: Text( + (hyshlx != 'hycs' ? '${mapTsztText[listHycsFliter[indexRecord]['tszt']]},' : '') + + '黑度 ${listHycsFliter[indexRecord]['lgmzs'].toString()},' + + // listHycsFliter[indexRecord]['dwms'] + + getDwmc(listHycsFliter[indexRecord]['dwip']) + + '。 抓拍 ${listHycsFliter[indexRecord]['yjxx_id'].split(',').length.toString()} 次', + //+ ',id:${listHycsFliter[indexRecord]['id'].toString()}', + maxLines: 2, + overflow: TextOverflow.ellipsis, + //textAlign: TextAlign.right, + style: TextStyle(fontSize: _fontSize), + ), + ), + contentPadding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 0), + enabled: true, + onTap: () async { + int ret = -1; + if (hyshlx == 'fhycx') { + ret = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => FhycxContentNew( + hyshlx: widget.hyshlx, + title: '非黑烟车', + indexRecord: indexRecord, + id: listHycsFliter[indexRecord]['id']), + ), + ); + print('hyshContentFirstAudit整型返回值:-1 表示出现异常,其余为更新结果,1表示成功,0表示无更新,false表示失败'); + } else if (hyshlx == 'tsjj') { + ret = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => TsjjContentNew( + hyshlx: widget.hyshlx, + title: '推送交警详情', + indexRecord: indexRecord, + id: listHycsFliter[indexRecord]['id']), + ), + ); + print('hyshContentFirstAudit整型返回值:-1 表示出现异常,其余为更新结果,1表示成功,0表示无更新,false表示失败'); + } + print('ret = $ret'); + }, + // onLongPress: () async { + // bFlash = false; + // Navigator.of(context) + // .push( + // PageRouteBuilder( + // opaque: false, + // pageBuilder: (context, animation, secondaryAnimation) => + // customDialogG(title: '收到的消息', index: indexRecord), + // ), + // ) + // .then((value) { + // print('Page2_Contacts bFlash = $bFlash'); + // if (bFlash) { + // try_setState(); //避免如下异常报错 + // } + // }); + // }, + ), + Divider( + height: 1.0, + ), + ], + ); + } + + Widget getIconButton({IconData iconData, var onPressed, double iconSize = 22}) { + return SizedBox( + height: iconSize, + width: iconSize + 10, + child: IconButton( + padding: EdgeInsets.all(0.0), + icon: Icon(iconData, size: iconSize, color: Colors.white), + onPressed: onPressed, + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: PreferredSize( + preferredSize: Size.fromHeight(ScreenUtil().setHeight(173)), // here the desired height + child: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + //leading: Text(''), + titleSpacing: 0.0, + //设置title的左边距 + flexibleSpace: Container( + //SizedBox(height: ScreenUtil().statusBarHeight), //显示顶部状态栏 + // SizedBox(height: ScreenUtil().setHeight(10)), //显示顶部状态栏 + padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight), //留出顶部状态栏高度 + child: Container( + //height: ScreenUtil().setHeight(173), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + // decoration: BoxDecoration( + // gradient: LinearGradient(colors: [ + // Color(0xFF0018EB), + // Color(0xFF01C1D9), + // ], begin: Alignment.bottomCenter, end: Alignment.topCenter), + // ), + ), + ), + //1、第1行组件,工具按钮 + title: Padding( + padding: EdgeInsets.only(left: 0, right: 0), + child: Row( + //mainAxisAlignment: MainAxisAlignment.start, + children: [ + //1.1、返回按钮 + getIconAndTextButton( + iconColor: Colors.white, + iconData: Icons.chevron_left_outlined, + onPress: () { + Navigator.pop(context); + }, + ), + //1.2、title 显示标题 + Expanded( + child: Column( + children: [ + Text("${mapHyshlx[hyshlx]['text']}", + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis, + style: TextStyle(color: Colors.white, fontSize: 20)), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('${today.toString()}', + style: TextStyle( + fontSize: 16, + color: Colors.redAccent, + fontWeight: FontWeight.w900)), + Text(' / ${sum.toString()}', + style: TextStyle(fontSize: 16, color: Colors.white)) + ], + ) + ], + ), + ), + SizedBox(width: 5), + ], + ), + ), + //1.3、尾部工具按钮组 + actions: [ + //getDropdownButton(), + getIconButton( + iconData: Icons.cloud_download, + onPressed: !isLoading + ? () async { + if (isLoading) { + return; + } + isLoading = true; + try_setState(); + //print("I Pressed the Iconbutton"); + int oldItems = listHycsGetList2.length; + for (int i = 0; i < 10; i++) { + List list = await getPageList(perpage: 100); + if (list.length > 0) { + listHycsGetList2.addAll(list); //加载累加 + } else { + break; + } + } + Future.delayed(const Duration(milliseconds: 1000), () async { + if (listHycsGetList2.length > oldItems) { + //eventBus.fire(WzxxDataAuditEvent('${mapHyshlx[hyshlx]['text']}数据已更新')); + Fluttertoast.showToast( + msg: '新增 ${listHycsGetList2.length - oldItems} 条数据下载完成!', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + // _selectedValue = '抓拍次数'; + // _descending = true; + //getDropdownButton(); + + //这种方式刷新后,按钮状态无法正常恢复 + //listSort().then((value) { + // //_controller.jumpTo(1); // _controller.position 能够有效刷新,人眼可察觉的滚动 + // _controller.jumpTo(0.001); // _controller.position 能够有效刷新,人眼不可见察觉的滚动 + // //第二次跳转必须延时,否则 _controller.position 不能有效刷新 + // Future.delayed(Duration(milliseconds: 500), () { + // _controller.jumpTo(_controller.position.minScrollExtent); + // }); + //}); + + //监听 选择点位 更新事件 + eventBus + .fire(SelectDwfliterUpdateEvent('Dropdown选项已改变', _selectedDwValue)); + + // //按照用户选择的_selectedValue、_descending对listHycsGetList2进行排序,并延时更新 + // _listSort(); + // //_controller.jumpTo(1); // _controller.position 能够有效刷新,人眼可察觉的滚动 + // _controller.jumpTo(0.001); // _controller.position 完美解决有效刷新,而且人眼不可见察觉滚动 + // //第二次跳转必须延时,否则 _controller.position 不能有效刷新 + // Future.delayed(Duration(milliseconds: 500), () { + // _controller.jumpTo(_controller.position.minScrollExtent); + // }); + } + // isLoading = false; + // try_setState(); + }); + } + : null, + ), + SizedBox(width: ScreenUtil().setWidth(32)), + getIconButton( + iconData: Icons.vertical_align_top_outlined, + onPressed: (!isLoading && (firstIndex > 0)) //未到顶部 + ? () async { + if (isLoading) { + return; + } + isLoading = true; + try_setState(); + + //必须延时执行,否则不能及时完成按钮状态更新 + Timer( + Duration(milliseconds: 500), + () { + _controller.jumpTo(_controller.position.minScrollExtent); + }, + ); + + Timer( + Duration(milliseconds: 1000), + () { + // Fluttertoast.showToast( + // msg: '已经跳转到开头记录!', + // toastLength: Toast.LENGTH_SHORT, + // gravity: ToastGravity.CENTER, + // ); + firstIndex = 0; + lastIndex = itemOnePage - 1; //0基序号,所以需要减1 + isLoading = false; + try_setState(); + }, + ); + } + : null, + ), + SizedBox(width: ScreenUtil().setWidth(32)), + getIconButton( + iconData: Icons.vertical_align_bottom_outlined, + onPressed: (!isLoading && (lastIndex < listHycsFliter.length)) //未到尾部 + ? () async { + if (isLoading) { + return; + } + isLoading = true; + try_setState(); + + //必须延时执行,否则不能及时完成按钮状态更新 + Timer( + Duration(milliseconds: 500), + () { + _controller.jumpTo(_controller.position.maxScrollExtent); + }, + ); + + Timer( + Duration(milliseconds: 1000), + () { + // Fluttertoast.showToast( + // msg: '已经跳转到末尾记录!', + // toastLength: Toast.LENGTH_SHORT, + // gravity: ToastGravity.CENTER, + // ); + //0基序号需要减1,再加上底部有一组显示信息,所以需要减2 + firstIndex = listHycsFliter.length - itemOnePage - 2; + lastIndex = listHycsFliter.length; + isLoading = false; + try_setState(); + }, + ); + } + : null, + ), + SizedBox(width: ScreenUtil().setWidth(32)), + ], + ), + ), + body: GestureDetector( + onTap: () { + FocusScope.of(context).requestFocus(FocusNode()); + }, + child: Column( + children: [ + //2、第2行排序按钮 + Container( + height: ScreenUtil().setHeight(142), + decoration: new BoxDecoration(border: new Border.all(color: Colors.red)), + child: Row( + children: [ + SizedBox(width: ScreenUtil().setWidth(50)), + Text('排序', style: TextStyle(fontSize: 18)), + Expanded(child: SizedBox.shrink()), + getDropdownButton(), + SizedBox(width: ScreenUtil().setWidth(20)), + ], + ), + ), + //3、第3行搜索条件组件 + (hyshlx == 'tsjj' || hyshlx == 'fhycx') + ? Container( + height: ScreenUtil().setHeight(hyshlx == 'tsjj' ? 264 : 224), + decoration: BoxDecoration( + border: Border( + left: BorderSide(color: Colors.red), + right: BorderSide(color: Colors.red), + bottom: BorderSide(color: Colors.red))), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + //getSelectLedDw(370, 14), + null == _selectLedDw ? SizedBox.shrink() : _selectLedDw, + //SizedBox(height: ScreenUtil().setHeight(10)), + getOpenTime(), + ], + ), + ) + : SizedBox.shrink(), + //4、第4行组件,数据显示区域 + _empty.isNotEmpty + ? Text(_empty, style: TextStyle(fontSize: 20)) + : (0 == listHycsFliter.length) + ? getMoreWidget(color: Colors.black38) + : Expanded( + child: EasyRefresh( + child: ListView.custom( + controller: _controller, + cacheExtent: 1.0, // 只有设置了1.0 才能够准确的标记position 位置 + childrenDelegate: MyChildrenDelegate( + // (BuildContext context, int index) { + // return new Dismissible( + // key: new Key(listHycsGetList2[index]), + // onDismissed: (direction) { + // //被移除回掉 + // listHycsGetList2.removeAt(index); + // var item = listHycsGetList2[index]; + // Scaffold.of(context).showSnackBar(new SnackBar(content: new Text("$item"))); + // }, + // child: new ListTile( + // title: new Text(listHycsGetList2[index]), + // ), + // ); + // }, + _getListTile, + childCount: listHycsFliter.length, + ), + ), + + // child: ListView.builder( + // controller: _controller, + // itemCount: listHycsGetList2.length, + // itemBuilder: _getListTile, + // ), + onRefresh: () async { + // await Future.delayed(const Duration(seconds: 1), () async { + // iPage = 0; + // //await getWzxxGetList(); + // listHycsGetList2 = await getPageList(); + // eventBus.fire(WzxxDataAuditEvent('${mapHyshlx[hyshlx]['text']}数据已更新')); + // }); + }, + onLoad: () async { + if (isLoading) { + return; + } + isLoading = true; + try_setState(); + + print('iPage = $iPage'); + await Future.delayed(const Duration(seconds: 1), () async { + //await getWzxxGetList(); + List list = await getPageList(perpage: 100); + if (list.length > 0) { + listHycsGetList2.addAll(list); //加载累加 + //eventBus.fire(WzxxDataAuditEvent('${mapHyshlx[hyshlx]['text']}数据已更新')); + //监听 选择点位 更新事件 + eventBus.fire(SelectDwfliterUpdateEvent( + '${mapHyshlx[hyshlx]['text']}数据已更新', _selectedDwValue)); + } + isLoading = false; + try_setState(); + }); + }, + header: getHeader(), + footer: getFooter(), + )) + ], + ), + ), + ); + } + + // Widget getDropdownButton() { + // return DropdownButton( + // value: _selectedValue, + // onChanged: (String newValue) { + // setState(() { + // _selectedValue = newValue; + // }); + // }, + // items: ['One', 'Two', 'Free', 'Four'].map>((String value) { + // return DropdownMenuItem( + // value: value, + // child: Text(value), + // ); + // }).toList(), + // ); + // } + + //DropdownButton需要设置初始值的时候,初始值必须是显示列表里面的值,否则会导致弹出框异常。 + // 比如说:你的DropdownButton的items属性使用的是list这个列表里面的值,那么你的初始值应该在list[index]里面取,要不就会报错。 + // + // There should be exactly one item with [DropdownButton]'s value: 0.0. + // Either zero or 2 or more [DropdownMenuItem]s were detected with the same value + // 'package:flutter/src/material/dropdown.dart': + // Failed assertion: line 834 pos 15: 'items == null || items.isEmpty || value == null || + // items.where((DropdownMenuItem item) { + // return item.value == value; + // }).length == 1' + + Widget _getImage(String _image) { + return Container( + margin: EdgeInsets.only(), + height: ScreenUtil().setWidth(48), + width: ScreenUtil().setWidth(48), + //child: Image.asset('assets/images/ybsthbj.png', fit: BoxFit.fitHeight), + child: Image.asset(_image, + fit: BoxFit.cover, color: isLoading ? Theme.of(context).disabledColor : null)); + } + + //统计今日抓拍次数 today + Future getToday() async { + today = 0; + int _yesterdayStamp = getEndDayOfYesterdayStamp(); + for (var item in listHycsFliter) { + //统计今日抓拍次数 + if (item['zpsj'] > _yesterdayStamp) { + today++; + } + } + } + + //按照用户选择的_selectedValue、_descending对listHycsFliter进行排序,并延时更新 + Future _listSort({bool bShowToast = false}) { + if (!isLoading && listHycsFliter.length > 0) { + //dart中,对List结构数据进行排序,注意 sort方法 会直接改变原List数组结构 + //myList.sort((a, b) => (b.age).compareTo(a.age)); /// sort List> + // "yjxx_id": "654", + + //按抓拍次数排序,升序 + //listHycsFliter.sort((a, b) => (a["yjxx_id"].slipt(',')).compareTo(b["yjxx_id"]).slipt(',')); + //按抓拍次数排序,降序 + //listHycsFliter.sort((a, b) => (b["yjxx_id"].slipt(',')).compareTo(a["yjxx_id"]).slipt(',')); + + //listHycsFliter.sort((a, b) => (a["lgmzs"]).compareTo(b["lgmzs"])); //按lgmzs排序,升序 + //listHycsFliter.sort((a, b) => (b["lgmzs"]).compareTo(a["lgmzs"])); //按lgmzs排序,降序 + + //按抓拍次数排序,升序 + // listHycsFliter.sort((a, b) => (a["yjxx_id"].split(',').length.toString()) + // .compareTo(b["yjxx_id"].split(',').length.toString())); + //按抓拍次数排序,降序 + // listHycsFliter.sort((a, b) => (b["yjxx_id"].split(',').length.toString()) + // .compareTo(a["yjxx_id"].split(',').length.toString())); + + isLoading = true; + try_setState(); + sum = listHycsFliter.length; //当前记录总数 + getToday(); //统计今日抓拍次数 today + + switch (_selectedValue) { + case '抓拍次数': + if (_descending) { + //按抓拍次数排序,降序 + listHycsFliter.sort((a, b) => + (b[mapWzxxDataText[_selectedValue]].split(',').length.toString()) + .compareTo(a[mapWzxxDataText[_selectedValue]].split(',').length.toString())); + } else { + //按抓拍次数排序,升序 + listHycsFliter.sort((a, b) => + (a[mapWzxxDataText[_selectedValue]].split(',').length.toString()) + .compareTo(b[mapWzxxDataText[_selectedValue]].split(',').length.toString())); + } + break; + case '推送状态': + if (_descending) { + // Unhandled Exception: NoSuchMethodError: The method 'compareTo' was called on null. + //按_selectedValue排序,降序 + listHycsFliter.sort((a, b) => + (b[mapWzxxDataText[_selectedValue]]).compareTo(a[mapWzxxDataText[_selectedValue]])); + } else { + //按_selectedValue排序,升序 + listHycsFliter.sort((a, b) => + (a[mapWzxxDataText[_selectedValue]]).compareTo(b[mapWzxxDataText[_selectedValue]])); + } + break; + default: + if (_descending) { + //按_selectedValue排序,降序 + listHycsFliter.sort((a, b) => + (b[mapWzxxDataText[_selectedValue]]).compareTo(a[mapWzxxDataText[_selectedValue]])); + } else { + //按_selectedValue排序,升序 + listHycsFliter.sort((a, b) => + (a[mapWzxxDataText[_selectedValue]]).compareTo(b[mapWzxxDataText[_selectedValue]])); + } + break; + } + + Future.delayed(const Duration(milliseconds: 1000), () { + if (bShowToast) { + Fluttertoast.showToast( + msg: '按“${_selectedValue}”${_descending ? '降序' : '升序'}排列完成!', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + + // _controller.jumpTo(0.001); // _controller.position 完美解决有效刷新,而且人眼不可见察觉滚动 + // //第二次跳转必须延时,否则 _controller.position 不能有效刷新 + // Future.delayed(Duration(milliseconds: 500), () { + // _controller.jumpTo(_controller.position.minScrollExtent); + // }); + }); + } + isLoading = false; + try_setState(); //避免如下异常报错 + } + + Widget getDropdownButton() { + //DropdownMenuItem项目文本list + List itemList = [ + '抓拍次数', + '林格曼黑度', + '抓拍时间', + '抓拍地点', + '车牌颜色', + //'车牌号码', + '车辆类型', + //'主键ID', + ]; + + //添加按'推送状态'排序 + itemList.addAll(['推送状态']); + if (hyshlx == 'tsjj') { + itemList.addAll(['推送时间']); + } + // if (hyshlx != 'hycs') { + // // itemList.removeLast(); + // // itemList.addAll(['推送状态', '主键ID']); + // if (hyshlx == 'tsjj') { + // itemList.addAll(['推送状态', '推送时间']); + // } + // } + + //获取DropdownMenuItem项目组件list + List> _dropDownMenuItems = + itemList.map>((String item) { + return DropdownMenuItem( + value: item, + child: getDropdownButtonItemText(item), + ); + }).toList(); + + return Padding( + padding: EdgeInsets.only(top: 0, bottom: 0), + child: Container( + alignment: Alignment(0, 0), + width: 145, + margin: EdgeInsets.only(bottom: 0), + padding: EdgeInsets.only(left: 0, bottom: 0), + // decoration: BoxDecoration( + // border: Border.all(width: 0), + // //边框圆角设置 + // borderRadius: + // BorderRadius.vertical(top: Radius.elliptical(2, 2), bottom: Radius.elliptical(2, 2)), + // ), + //DropdownButton默认有一条下划线,DropdownButtonHideUnderline去除下划线 + child: DropdownButtonHideUnderline( + child: DropdownButton( + iconSize: ScreenUtil().setHeight(100), + //itemHeight: ScreenUtil().setHeight(372), + isDense: true, + value: _selectedValue, + items: _dropDownMenuItems, + onChanged: (String selectedValue) { + if (isLoading) { + return; + } + + if (_selectedValue == selectedValue) { + _descending = !_descending; + } else { + _descending = true; + } + _selectedValue = selectedValue; + print('_selectedValue = $_selectedValue'); + + //按抓拍次数排序,降序 + // listHycsGetList2.sort((a, b) => (b["yjxx_id"].split(',').length.toString()) + // .compareTo(a["yjxx_id"].split(',').length.toString())); + + //按照用户选择的_selectedValue、_descending对listHycsGetList2进行排序,并延时更新 + _listSort(); + }, + ), + ), + ), + ); + + //没有padding: ,会报错Failed assertion: line 1644 pos 15: 'padding != null': is + // return Padding( + // // child: OutlineButton( + // // borderSide: BorderSide(width: 4.0), + // // child: Text('Hi'), + // // onPressed: () {}, + // // padding: EdgeInsets.all(0), + // // ), + // ); + + // return SizedBox( + // height: 30, + // width: 30, + // child: Container( + // color: Colors.black45, + // ), + // ); + + // return Container( + // decoration: new BoxDecoration( + // border: new Border.all(color: Color(0xFFFF0000), width: 0.5), + // color: Color(0xFF9E9E9E), + // borderRadius: new BorderRadius.circular((20.0))), + // height: 28.0, + // width: 130, + // alignment: Alignment.center, + // child: Text('sdsd'), + // ); + + // ConstrainedBox( + // constraints: BoxConstraints( + // //minWidth: double.infinity, //宽度尽可能大 + // //minHeight: _listTileHeight, //最小高度 + // maxHeight: 20, //最大高度 + // ), + // ); + } + + Widget getDropdownButtonItemText(String item) { + return Padding( + padding: EdgeInsets.only(bottom: ScreenUtil().setHeight(3)), + child: Row( + //crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + alignment: Alignment(0, 0), + padding: EdgeInsets.only(top: ScreenUtil().setHeight(10)), + child: item == _selectedValue + ? _descending + ? _getImage('assets/images/descending.png') + : _getImage('assets/images/ascending.png') + // ? Icon(Icons.arrow_downward_outlined, size: 20) + // : Icon(Icons.arrow_upward_outlined, size: 20) + : SizedBox(), + ), + SizedBox( + width: ScreenUtil().setWidth(10), + ), + Container( + //alignment: Alignment(0, -1), + child: Text(item, + style: TextStyle( + fontSize: 18, + color: isLoading + ? Theme.of(context).disabledColor + : item == _selectedValue + ? Colors.blue + : null)), + // style: isLoading + // ? TextStyle(color: Theme.of(context).disabledColor) + // : (item == _selectedValue ? TextStyle(color: Colors.blue) : null)), + ), + ], + ), + ); + } + + List _listItems = [ + // '全部', + // '1、江北振兴大道', + // '2、宜飞路', + // '3、宜宾南收费站', + // '4、一曼路', + // '5、柏溪收费站', + // '6、七星路万达广场', + // '7、宜宾财政局', + // '8、宜威路南广镇', + // '9、宜长路', + // '10、宜南快速通道', + // '11、观斗山隧道', + // '12、大麦坝', + // '13、外江路', + ]; + double _marginLeft = 35; + double _widgetHeight = 30; + + // String _beginTime = '2021-03-28\n00:00'; + // String _endTime = '2021-03-29\n00:00'; + DateTime _beginDataTiem = DateTime(2021, 3, 18, 00, 00); + Map _mapDataTiem = {'beginDateTime': null, 'endDateTime': null}; + double _fontSize = 14; + + //1、得到 点位 选择组件 + Future getSelectLedDw(double _width, double _fontsize) async { + return Row( + children: [ + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + Text('点位:', style: TextStyle(fontSize: _fontSize)), + DropdownItem( + listItems: _listItems, + //初始值 initValue 必须是 listItems 中的已有元素 + initValue: _listItems[0], + dropdownEvent: 'SelectDwfliterUpdateEvent', + width: ScreenUtil().setWidth(_width), + height: _widgetHeight, + fontSize: _fontsize, + ), + SizedBox(width: ScreenUtil().setWidth(37)), + Text('车牌号:', style: TextStyle(fontSize: _fontSize)), + getPlateIdInBox(_widgetHeight), + //SizedBox(width: ScreenUtil().setWidth(20)), + ], + ); + } + + Widget getSizeText(String text, {double fontSize = 16, double width = 100, double top = 0}) { + return Container( + margin: EdgeInsets.only(top: top), + width: width, + child: Text(text, + style: TextStyle(fontSize: fontSize), + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis), + ); + } + + Future getListFlields() async { + //实时更新_listItems内容 + _listItems = ['全部']; + int len = listDwinfoGetList2.length; + for (int i = 0; i < len; i++) { + _listItems.add(listDwinfoGetList2[i]['dwmc']); + } + print('_listItems = $_listItems'); + } + + //3、得到 启用时段 组件 + Widget getOpenTime() { + DateTime _nowDate = DateTime.now(); + // Map _mapDataTiem = {'beginDateTime': null, 'endDateTime': null}; + if (null == _mapDataTiem['beginDateTime'] && null == _mapDataTiem['endDateTime']) { + _mapDataTiem['beginDateTime'] = DateTime(2021, 3, 18, 00, 00); + _mapDataTiem['endDateTime'] = + DateTime(_nowDate.year, _nowDate.month, _nowDate.day + 1, 00, 00); + } + + return Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + //Text('抓拍时间:', style: TextStyle(fontSize: _fontSize)), + hyshlx == 'tsjj' ? getSelectWidget() : Text('抓拍时间:', style: TextStyle(fontSize: _fontSize)), + getInBox('beginDateTime', _widgetHeight), + Text(' - '), + getInBox('endDateTime', _widgetHeight), + ], + ); + } + + //3.1、得到 抓拍时间、推送时间 选择组件 + Widget getSelectWidget() { + Widget _selectWidget = Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + InkWell( + child: Text('抓拍时间:', + style: TextStyle( + fontSize: _fontSize, + color: _selTimeType == 0 ? Colors.blueAccent : null, + fontWeight: _selTimeType == 0 ? FontWeight.bold : null, + )), + onTap: _updateSelectTiemType), + InkWell( + child: Text('推送时间:', + style: TextStyle( + fontSize: _fontSize, + color: _selTimeType == 1 ? Colors.blueAccent : null, + fontWeight: _selTimeType == 1 ? FontWeight.bold : null, + )), + onTap: _updateSelectTiemType), + SizedBox( + height: ScreenUtil().setHeight(9), // 微调上下对齐 + ), + ], + ); + + return _selectWidget; + } + + _updateSelectTiemType() { + _selTimeType = 1 - _selTimeType; + // print('_selItem = $_selTimeType'); + _selTiemStr = _selTimeType == 0 ? 'zpsj' : 'ts_time'; + _selectedValue = _selTimeType == 0 ? '抓拍时间' : '推送时间'; + _listSort(); + + //监听 选择点位 更新事件 + eventBus.fire(SelectDwfliterUpdateEvent('Dropdown选项已改变', _selectedDwValue)); + } + + String getDateTimeStr(DateTime _dateTime) { + return '${_dateTime.year}-${_dateTime.month}-${_dateTime.day}\n00:00'; + } + + Widget getInBox(String _dateTime, double _height) { + DateTime _nowDate = DateTime.now(); + return Container( + alignment: Alignment(0, 0), + height: _height, + width: 122, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey, width: 2.0), + borderRadius: BorderRadius.all(Radius.circular(3.0)), + ), + child: InkWell( + onTap: () { + DatePicker.showDateTimePicker( + context, + showTitleActions: true, + minTime: _beginDataTiem, + maxTime: DateTime(_nowDate.year, _nowDate.month, _nowDate.day + 1, 00, 00), + currentTime: _mapDataTiem[_dateTime], + onChanged: (date) { + print('change $date in time zone ' + date.timeZoneOffset.inHours.toString()); + }, + onConfirm: (date) { + print('confirm $date'); + _mapDataTiem[_dateTime] = date; + //监听 选择点位 更新事件 + eventBus.fire(SelectDwfliterUpdateEvent('Dropdown选项已改变', _selectedDwValue)); + }, + locale: LocaleType.zh, + ); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: Text(getDateTimeStr(_mapDataTiem[_dateTime]), + style: TextStyle(fontSize: 12), maxLines: 2, textAlign: TextAlign.center), + ), + Icon(Icons.date_range_outlined, color: Colors.blue), + SizedBox(width: 2), + ], + ), + ), + ); + } + + // 车牌号输入框 + Widget getPlateIdInBox(double _height) { + return Container( + alignment: Alignment(0, 0), + height: _height, + width: 100, + // decoration: BoxDecoration( + // border: Border.all(color: Colors.grey, width: 2), + // borderRadius: BorderRadius.all(Radius.circular(3.0)), + // ), + child: TextField( + textAlign: TextAlign.center, + style: TextStyle(fontSize: _fontSize), + decoration: InputDecoration( + hintText: '输入车牌号', + contentPadding: EdgeInsets.all(0), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey, width: 2.0), + borderRadius: BorderRadius.circular(3.0)), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey, width: 2.0), + borderRadius: BorderRadius.circular(3.0)), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(3.0), + borderSide: BorderSide( + color: Colors.grey, + width: 2.0, + ), + ), + ), + controller: TextEditingController.fromValue(TextEditingValue(text: '')), + enabled: true, + onChanged: (value) { + _plate_id = value; + //监听 选择点位 更新事件 + eventBus.fire(SelectDwfliterUpdateEvent('Dropdown选项已改变', _selectedDwValue)); + }, + ), + ); + } +} + +//https://blog.csdn.net/u014803467/article/details/103750018 +//Flutter 使用SliverChildBuilderDelegate获取ListView的第一个和最后一个可见Item序号 +// 秋名山交警X 2019-12-28 23:52:52 +class _SaltedValueKey extends ValueKey { + const _SaltedValueKey(Key key) + : assert(key != null), + super(key); +} + +class MyChildrenDelegate extends SliverChildBuilderDelegate { + MyChildrenDelegate( + Widget Function(BuildContext, int) builder, { + int childCount, + bool addAutomaticKeepAlive = true, + bool addRepaintBoundaries = true, + }) : super(builder, + childCount: childCount, + addAutomaticKeepAlives: addAutomaticKeepAlive, + addRepaintBoundaries: addRepaintBoundaries); + + // Return a Widget for the given Exception + Widget _createErrorWidget(dynamic exception, StackTrace stackTrace) { + final FlutterErrorDetails details = FlutterErrorDetails( + exception: exception, + stack: stackTrace, + library: 'widgets library', + context: ErrorDescription('building'), + ); + FlutterError.reportError(details); + return ErrorWidget.builder(details); + } + + @override + Widget build(BuildContext context, int index) { + assert(builder != null); + if (index < 0 || (childCount != null && index >= childCount)) return null; + Widget child; + try { + child = builder(context, index); + } catch (exception, stackTrace) { + child = _createErrorWidget(exception, stackTrace); + } + if (child == null) return null; + final Key key = child.key != null ? _SaltedValueKey(child.key) : null; + if (addRepaintBoundaries) child = RepaintBoundary(child: child); + if (addSemanticIndexes) { + final int semanticIndex = semanticIndexCallback(child, index); + if (semanticIndex != null) + child = IndexedSemantics(index: semanticIndex + semanticIndexOffset, child: child); + } + if (addAutomaticKeepAlives) child = AutomaticKeepAlive(child: child); + return KeyedSubtree(child: child, key: key); + } + + @override + void didFinishLayout(int _firstIndex, int _lastIndex) { + // TODO: implement didFinishLayout + super.didFinishLayout(_firstIndex, _lastIndex); + } + + ///监听 在可见的列表中 显示的第一个位置和最后一个位置 + @override + double estimateMaxScrollOffset( + int _firstIndex, int _lastIndex, double _leadingScrollOffset, double _trailingScrollOffset) { + // print( + // 'firstIndex = $_firstIndex, lastIndex = $_lastIndex, leadingScrollOffset = $_leadingScrollOffset,' + // 'trailingScrollOffset : $_trailingScrollOffset '); + + //违章信息Listview滚动广播 + eventBus.fire(WzxxDataScrollEvent(_firstIndex, _lastIndex)); + + return super.estimateMaxScrollOffset( + _firstIndex, _lastIndex, _leadingScrollOffset, _trailingScrollOffset); + } +} diff --git a/lib/pages/Works/HYSH/hysh_getList_new.dart b/lib/pages/Works/HYSH/hysh_getList_new.dart new file mode 100644 index 0000000..d005481 --- /dev/null +++ b/lib/pages/Works/HYSH/hysh_getList_new.dart @@ -0,0 +1,1079 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_easyrefresh/easy_refresh.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hyzp_ybqx/components/commonFun.dart'; +import 'package:hyzp_ybqx/pages/Works/HYSH/tsjj_content_new.dart'; +import 'package:hyzp_ybqx/services/EventBus.dart'; + +import '../../../components/commonFun.dart'; +import '../../../components/dioFun.dart'; +import '../../../components/doJSON.dart'; +import '../../../components/hyxx_data_handle.dart'; +import '../../../services/EventBus.dart'; +import 'fhycx_content_new.dart'; +import 'hysh_content_new.dart'; + +///flutter中如何获取子类Widget并调用它的方法 萤火虫离别的礼物 2019.08.07 15:46:08 https://www.jianshu.com/p/b16f70dd692c +//在flutter中开发中,会发现当子类Widget是StatefulWidget类型的时候,想要获取它的State并调用State中的方法,感觉无从下手。 +// 不像是在iOS中,可以直接调用一个类的公开的方法,flutter可以通过key来实现。每个Widget都是唯一标识的。此唯一标识对应于可选的Key参数。 +// 如果省略,Flutter将为您生成一个。key主要分为四种:GlobalKey,LocalKey,UniqueKey或ObjectKey,GlobalKey确保key是在整个应用程序唯一的, +// 这次我们就要使用它来实现。我们需要给子Widget定义一个唯一的GlobalKey,然后根据这个key获取到这个Widget,进行相关的操作,下面是相关的代码: +//这里就是关键的代码,定义一个key +//GlobalKey _myFijkPanelWidgetBuilderStateKey = new GlobalKey(); + +//Hycs是本项目中“黑烟初审”的统一缩写 +//Hysh是本项目中“黑烟审核(包括:黑烟初审、黑烟复审、推送交警)”的统一缩写 +class HyshGetListNew extends StatefulWidget { + //hyshlx为黑烟审核类型,处理'hycs'黑烟初审、'hyfh'黑烟复审。mapHyshlx[hyshlx]获取为各种类型的设置数据 + HyshGetListNew({this.hyshlx = 'hycs', this.title, Key key}) : super(key: key); + String hyshlx; + String title = ''; + + _HyshPageState createState() => _HyshPageState(); +} + +class _HyshPageState extends State { + //try_setState(); //避免如下异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + @override + void dispose() { + _controller.dispose(); //销毁控制器 + super.dispose(); + } + + @override + void initState() { + hyshlx = widget.hyshlx; + + ///从接口 mapHyshlx[theHyshlx]['api'] 获取指定类型第 page 页的列表数据,返回 list + ///获取点位信息数据 + listDwinfoGetList2.clear(); + getThePageList(theHyshlx: 'dwxx').then((value) { + listDwinfoGetList2 = value; + print('listDwinfoGetList2 = \n$listDwinfoGetList2'); + }); + + //getHycsGetList(); + iPage = 0; + listHycsGetList2.clear(); + //listTsjjTszt.clear(); //与listHycsGetList2对应 + //getAllItems(1000); + + //从接口mapHyshlx[hyshlx]['api']获取数据,通过list返回。再添加到listHycsGetList2中 + getPageList(perpage: 100).then((value) { + listHycsGetList2 = value; + if (listHycsGetList2.isEmpty) { + _empty = _emptyInfo; + try_setState(); + return; + } + //按照用户选择的_selectedValue、_descending对listHycsGetList2进行排序,并延时更新 + _listSort(); + firstIndex = 0; + lastIndex = 7; + }); + + //监听违章信息数据更新事件 + eventBus.on().listen((event) async { + print(event.str); + //按照用户选择的_selectedValue、_descending对listHycsGetList2进行排序,并延时更新 + iPage = 0; + listHycsGetList2 = await getPageList(perpage: 100); + if (listHycsGetList2.isEmpty) { + _empty = _emptyInfo; + try_setState(); + return; + } + + _listSort(); + }); + + //监听违章信息推送状态更新事件 + // eventBus.on().listen((event) async { + // print(event.str); + // try_setState(); + // //_listSort(); + // }); + + //监听违章信息数据审核事件 + eventBus.on().listen((event) async { + print('HycsGetList: ' + event.str); + //按照用户选择的_selectedValue、_descending对listHycsGetList2进行排序,并延时更新 + if (listHycsGetList2.isEmpty) { + _empty = _emptyInfo; + try_setState(); + return; + } + _listSort(); + }); + + //监听违章信息Listview滚动事件 + eventBus.on().listen((event) async { + firstIndex = event.firstIndex; + lastIndex = event.lastIndex; + try_setState(); + }); + + super.initState(); + } + + // 车牌号码 + // 车牌颜色 + // 车辆类型 + // 林格曼黑度 + // 抓拍次数 + // 首次抓拍时间 + // 首次抓拍地点 + int today = 0; + int sum = 0; + String _emptyInfo = '\n当前没有待审记录'; + String _empty = ''; + + Widget _getListTile(BuildContext context, indexRecord) { + // print('indexRecord = ${indexRecord}'); + // print('listTsjjTszt = ${listTsjjTszt}'); + // Color markColor; + // FontWeight markBold; + // print('EndDayOfYesterDayStamp = ${getEndDayOfYesterDayStamp()}'); + // print('zpsj = ${listHycsGetList2[indexRecord]['zpsj']}'); + // if (listHycsGetList2[indexRecord]['zpsj'] > getEndDayOfYesterDayStamp()) { + // Color markColor = Colors.lightBlue; + // FontWeight markBold = FontWeight.bold; + // print('markColor = $markColor, markBold = $markBold'); + // } + + return Column( + children: [ + ListTile( + //leading: new Icon(Icons.phone), + title: Text( + "${(indexRecord + 1).toString()}. " + + listHycsGetList2[indexRecord]['plate_id'] + + '(${listHycsGetList2[indexRecord]['plate_color']}),${listHycsGetList2[indexRecord]['clfl']}', + style: TextStyle(fontSize: 14)), + subtitle: Text(getDate(listHycsGetList2[indexRecord]['zpsj']), + style: TextStyle( + fontSize: 14, + color: listHycsGetList2[indexRecord]['zpsj'] > getEndDayOfYesterdayStamp() + ? Colors.lightBlue + : null, + fontWeight: listHycsGetList2[indexRecord]['zpsj'] > getEndDayOfYesterdayStamp() + ? FontWeight.bold + : null)), + trailing: Container( + width: 110, + child: Text( + (hyshlx != 'hycs' ? '${mapTsztText[listHycsGetList2[indexRecord]['tszt']]},' : '') + + '黑度 ${listHycsGetList2[indexRecord]['lgmzs'].toString()},' + + // listHycsGetList2[indexRecord]['dwms'] + + getDwmc(listHycsGetList2[indexRecord]['dwip']) + + '。 抓拍 ${listHycsGetList2[indexRecord]['yjxx_id'].split(',').length.toString()} 次', + //+ ',id:${listHycsGetList2[indexRecord]['id'].toString()}', + maxLines: 2, + overflow: TextOverflow.ellipsis, + //textAlign: TextAlign.right, + style: TextStyle(fontSize: 14), + ), + ), + contentPadding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 0), + enabled: true, + onTap: () async { + int ret = -1; + if (hyshlx == 'fhycx') { + ret = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => FhycxContentNew( + hyshlx: widget.hyshlx, + title: '非黑烟车', + indexRecord: indexRecord, + id: listHycsGetList2[indexRecord]['id']), + ), + ); + print('hyshContentFirstAudit整型返回值:-1 表示出现异常,其余为更新结果,1表示成功,0表示无更新,false表示失败'); + } else if (hyshlx == 'tsjj') { + ret = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => TsjjContentNew( + hyshlx: widget.hyshlx, + title: '推送交警详情', + indexRecord: indexRecord, + id: listHycsGetList2[indexRecord]['id']), + ), + ); + print('hyshContentFirstAudit整型返回值:-1 表示出现异常,其余为更新结果,1表示成功,0表示无更新,false表示失败'); + } else { + ret = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => HyshContentNew( + hyshlx: widget.hyshlx, + title: '违章审核详情', + indexRecord: indexRecord, + id: listHycsGetList2[indexRecord]['id']), + ), + ); + print('hyshContentFirstAudit整型返回值:-1 表示出现异常,其余为更新结果,1表示成功,0表示无更新,false表示失败'); + + if (1 == ret) { + iPage = 0; + listHycsGetList2 = await getPageList(perpage: 100); + eventBus.fire(WzxxDataAuditEvent('${mapHyshlx[hyshlx]['text']}数据已更新')); + print('${mapHyshlx[hyshlx]['text']}结果已成功上传服务器。'); + // Fluttertoast.showToast( + // msg: '${mapHyshlx[hyshlx]['text']}结果已成功上传服务器。', gravity: ToastGravity.CENTER); + } else { + // Fluttertoast.showToast( + // msg: '违章信息${mapHyshlx[hyshlx]['text']}结果上传失败。', gravity: ToastGravity.CENTER); + } + } + print('ret = $ret'); + }, + // onLongPress: () async { + // bFlash = false; + // Navigator.of(context) + // .push( + // PageRouteBuilder( + // opaque: false, + // pageBuilder: (context, animation, secondaryAnimation) => + // customDialogG(title: '收到的消息', index: indexRecord), + // ), + // ) + // .then((value) { + // print('Page2_Contacts bFlash = $bFlash'); + // if (bFlash) { + // try_setState(); //避免如下异常报错 + // } + // }); + // }, + ), + Divider( + height: 1.0, + ), + ], + ); + } + + ScrollController _controller = ScrollController(); //ListView控制器 + bool isLoading = false; //正在处理下载数据、跳转到首项、跳转到尾项等操作 + int firstIndex = 0; //ListView当前显示页面首项0基序号 + int lastIndex = 0; //ListView当前显示页面末项0基序号 + int itemOnPage = 8; //估计一屏显示的项目数量 + + Widget getIconButton({IconData iconData, var onPressed, double iconSize = 22}) { + return SizedBox( + height: iconSize, + width: iconSize + 10, + child: IconButton( + padding: EdgeInsets.all(0.0), + icon: Icon(iconData, size: iconSize, color: Colors.white), + onPressed: onPressed, + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: PreferredSize( + preferredSize: Size.fromHeight(ScreenUtil().setHeight(173)), // here the desired height + child: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + //leading: Text(''), + titleSpacing: 0.0, + //设置title的左边距 + flexibleSpace: Container( + //SizedBox(height: ScreenUtil().statusBarHeight), //显示顶部状态栏 + // SizedBox(height: ScreenUtil().setHeight(10)), //显示顶部状态栏 + padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight), //留出顶部状态栏高度 + child: Container( + //height: ScreenUtil().setHeight(173), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + // decoration: BoxDecoration( + // gradient: LinearGradient(colors: [ + // Color(0xFF0018EB), + // Color(0xFF01C1D9), + // ], begin: Alignment.bottomCenter, end: Alignment.topCenter), + // ), + ), + ), + //1、第1行组件,工具按钮 + title: Padding( + padding: EdgeInsets.only(left: 0, right: 0), + child: Row( + //mainAxisAlignment: MainAxisAlignment.start, + children: [ + //1.1、返回按钮 + getIconAndTextButton( + iconColor: Colors.white, + iconData: Icons.chevron_left_outlined, + onPress: () { + Navigator.pop(context); + }, + ), + //1.2、title 显示控制 + Expanded( + child: Column( + children: [ + Text("${mapHyshlx[hyshlx]['text']}", + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis, + style: TextStyle(color: Colors.white, fontSize: 20)), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('${today.toString()}', + style: TextStyle( + fontSize: 16, + color: Colors.redAccent, + fontWeight: FontWeight.w900)), + Text(' / ${sum.toString()}', + style: TextStyle(fontSize: 16, color: Colors.white)) + ], + ) + ], + ), + ), + SizedBox(width: 5), + ], + ), + ), + //1.3、尾部工具按钮组 + actions: [ + //getDropdownButton(), + getIconButton( + iconData: Icons.cloud_download, + onPressed: !isLoading + ? () async { + if (isLoading) { + return; + } + isLoading = true; + try_setState(); + //print("I Pressed the Iconbutton"); + int oldItems = listHycsGetList2.length; + for (int i = 0; i < 10; i++) { + List list = await getPageList(perpage: 100); + if (list.length > 0) { + listHycsGetList2.addAll(list); //加载累加 + } else { + break; + } + } + Future.delayed(const Duration(milliseconds: 1000), () async { + if (listHycsGetList2.length > oldItems) { + eventBus.fire(WzxxDataAuditEvent('${mapHyshlx[hyshlx]['text']}数据已更新')); + Fluttertoast.showToast( + msg: '新增 ${listHycsGetList2.length - oldItems} 条数据下载完成!', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + // _selectedValue = '抓拍次数'; + // _descending = true; + //getDropdownButton(); + + //这种方式刷新后,按钮状态无法正常恢复 + //listSort().then((value) { + // //_controller.jumpTo(1); // _controller.position 能够有效刷新,人眼可察觉的滚动 + // _controller.jumpTo(0.001); // _controller.position 能够有效刷新,人眼不可见察觉的滚动 + // //第二次跳转必须延时,否则 _controller.position 不能有效刷新 + // Future.delayed(Duration(milliseconds: 500), () { + // _controller.jumpTo(_controller.position.minScrollExtent); + // }); + //}); + + //按照用户选择的_selectedValue、_descending对listHycsGetList2进行排序,并延时更新 + _listSort(); + //_controller.jumpTo(1); // _controller.position 能够有效刷新,人眼可察觉的滚动 + _controller.jumpTo(0.001); // _controller.position 完美解决有效刷新,而且人眼不可见察觉滚动 + //第二次跳转必须延时,否则 _controller.position 不能有效刷新 + Future.delayed(Duration(milliseconds: 500), () { + _controller.jumpTo(_controller.position.minScrollExtent); + }); + } + isLoading = false; + try_setState(); + }); + } + : null, + ), + SizedBox(width: ScreenUtil().setWidth(32)), + getIconButton( + iconData: Icons.vertical_align_top_outlined, + onPressed: (!isLoading && (firstIndex > 0)) //未到顶部 + ? () async { + if (isLoading) { + return; + } + isLoading = true; + try_setState(); + + //必须延时执行,否则不能及时完成按钮状态更新 + Timer( + Duration(milliseconds: 500), + () { + _controller.jumpTo(_controller.position.minScrollExtent); + }, + ); + + Timer( + Duration(milliseconds: 1000), + () { + // Fluttertoast.showToast( + // msg: '已经跳转到开头记录!', + // toastLength: Toast.LENGTH_SHORT, + // gravity: ToastGravity.CENTER, + // ); + firstIndex = 0; + lastIndex = itemOnPage - 1; //0基序号,所以需要减1 + isLoading = false; + try_setState(); + }, + ); + } + : null, + ), + SizedBox(width: ScreenUtil().setWidth(32)), + getIconButton( + iconData: Icons.vertical_align_bottom_outlined, + onPressed: (!isLoading && (lastIndex < listHycsGetList2.length)) //未到尾部 + ? () async { + if (isLoading) { + return; + } + isLoading = true; + try_setState(); + + //必须延时执行,否则不能及时完成按钮状态更新 + Timer( + Duration(milliseconds: 500), + () { + _controller.jumpTo(_controller.position.maxScrollExtent); + }, + ); + + Timer( + Duration(milliseconds: 1000), + () { + // Fluttertoast.showToast( + // msg: '已经跳转到末尾记录!', + // toastLength: Toast.LENGTH_SHORT, + // gravity: ToastGravity.CENTER, + // ); + //0基序号需要减1,再加上底部有一组显示信息,所以需要减2 + firstIndex = listHycsGetList2.length - itemOnPage - 2; + lastIndex = listHycsGetList2.length; + isLoading = false; + try_setState(); + }, + ); + } + : null, + ), + SizedBox(width: ScreenUtil().setWidth(32)), + ], + ), + ), + body: Column( + children: [ + //2、第2行排序按钮 + Container( + height: ScreenUtil().setHeight(142), + decoration: new BoxDecoration(border: new Border.all(color: Colors.red)), + child: Row( + children: [ + SizedBox(width: ScreenUtil().setWidth(50)), + Text('排序', style: TextStyle(fontSize: 18)), + Expanded(child: SizedBox.shrink()), + getDropdownButton(), + SizedBox(width: ScreenUtil().setWidth(20)), + ], + ), + ), + //3、第3行组件,数据显示区域 + _empty.isNotEmpty + ? Text(_empty, style: TextStyle(fontSize: 20)) + : (0 == listHycsGetList2.length) + ? getMoreWidget(color: Colors.black38) + : Expanded( + child: EasyRefresh( + child: ListView.custom( + controller: _controller, + cacheExtent: 1.0, // 只有设置了1.0 才能够准确的标记position 位置 + childrenDelegate: MyChildrenDelegate( + // (BuildContext context, int index) { + // return new Dismissible( + // key: new Key(listHycsGetList2[index]), + // onDismissed: (direction) { + // //被移除回掉 + // listHycsGetList2.removeAt(index); + // var item = listHycsGetList2[index]; + // Scaffold.of(context).showSnackBar(new SnackBar(content: new Text("$item"))); + // }, + // child: new ListTile( + // title: new Text(listHycsGetList2[index]), + // ), + // ); + // }, + _getListTile, + childCount: listHycsGetList2.length, + ), + ), + + // child: ListView.builder( + // controller: _controller, + // itemCount: listHycsGetList2.length, + // itemBuilder: _getListTile, + // ), + onRefresh: () async { + // await Future.delayed(const Duration(seconds: 1), () async { + // iPage = 0; + // //await getWzxxGetList(); + // listHycsGetList2 = await getPageList(); + // eventBus.fire(WzxxDataAuditEvent('${mapHyshlx[hyshlx]['text']}数据已更新')); + // }); + }, + onLoad: () async { + if (isLoading) { + return; + } + isLoading = true; + try_setState(); + + print('iPage = $iPage'); + await Future.delayed(const Duration(seconds: 1), () async { + //await getWzxxGetList(); + List list = await getPageList(perpage: 100); + if (list.length > 0) { + listHycsGetList2.addAll(list); //加载累加 + eventBus.fire(WzxxDataAuditEvent('${mapHyshlx[hyshlx]['text']}数据已更新')); + } + isLoading = false; + try_setState(); + }); + }, + header: getHeader(), + footer: getFooter(), + )) + ], + ), + ); + } + + // Widget getDropdownButton() { + // return DropdownButton( + // value: _selectedValue, + // onChanged: (String newValue) { + // setState(() { + // _selectedValue = newValue; + // }); + // }, + // items: ['One', 'Two', 'Free', 'Four'].map>((String value) { + // return DropdownMenuItem( + // value: value, + // child: Text(value), + // ); + // }).toList(), + // ); + // } + + //DropdownButton需要设置初始值的时候,初始值必须是显示列表里面的值,否则会导致弹出框异常。 + // 比如说:你的DropdownButton的items属性使用的是list这个列表里面的值,那么你的初始值应该在list[index]里面取,要不就会报错。 + // + // There should be exactly one item with [DropdownButton]'s value: 0.0. + // Either zero or 2 or more [DropdownMenuItem]s were detected with the same value + // 'package:flutter/src/material/dropdown.dart': + // Failed assertion: line 834 pos 15: 'items == null || items.isEmpty || value == null || + // items.where((DropdownMenuItem item) { + // return item.value == value; + // }).length == 1' + String _selectedValue = '抓拍时间'; + bool _descending = true; + + Widget _getImage(String _image) { + return Container( + margin: EdgeInsets.only(), + height: ScreenUtil().setWidth(48), + width: ScreenUtil().setWidth(48), + //child: Image.asset('assets/images/ybsthbj.png', fit: BoxFit.fitHeight), + child: Image.asset(_image, + fit: BoxFit.cover, color: isLoading ? Theme.of(context).disabledColor : null)); + } + + //统计今日抓拍次数 today + Future getToday() async { + today = 0; + int _yesterDayStamp = getEndDayOfYesterdayStamp(); + for (var item in listHycsGetList2) { + //统计今日抓拍次数 + if (item['zpsj'] > _yesterDayStamp) { + today++; + } + } + } + + //按照用户选择的_selectedValue、_descending对listHycsGetList2进行排序,并延时更新 + Future _listSort({bool bShowToast = false}) { + if (!isLoading && listHycsGetList2.length > 0) { + //dart中,对List结构数据进行排序,注意 sort方法 会直接改变原List数组结构 + //myList.sort((a, b) => (b.age).compareTo(a.age)); /// sort List> + // "yjxx_id": "654", + + //按抓拍次数排序,升序 + //listHycsGetList2.sort((a, b) => (a["yjxx_id"].slipt(',')).compareTo(b["yjxx_id"]).slipt(',')); + //按抓拍次数排序,降序 + //listHycsGetList2.sort((a, b) => (b["yjxx_id"].slipt(',')).compareTo(a["yjxx_id"]).slipt(',')); + + //listHycsGetList2.sort((a, b) => (a["lgmzs"]).compareTo(b["lgmzs"])); //按lgmzs排序,升序 + //listHycsGetList2.sort((a, b) => (b["lgmzs"]).compareTo(a["lgmzs"])); //按lgmzs排序,降序 + + //按抓拍次数排序,升序 + // listHycsGetList2.sort((a, b) => (a["yjxx_id"].split(',').length.toString()) + // .compareTo(b["yjxx_id"].split(',').length.toString())); + //按抓拍次数排序,降序 + // listHycsGetList2.sort((a, b) => (b["yjxx_id"].split(',').length.toString()) + // .compareTo(a["yjxx_id"].split(',').length.toString())); + + isLoading = true; + try_setState(); + sum = listHycsGetList2.length; //当前记录总数 + getToday(); //统计今日抓拍次数 today + + switch (_selectedValue) { + case '抓拍次数': + if (_descending) { + //按抓拍次数排序,降序 + listHycsGetList2.sort((a, b) => + (b[mapWzxxDataText[_selectedValue]].split(',').length.toString()) + .compareTo(a[mapWzxxDataText[_selectedValue]].split(',').length.toString())); + } else { + //按抓拍次数排序,升序 + listHycsGetList2.sort((a, b) => + (a[mapWzxxDataText[_selectedValue]].split(',').length.toString()) + .compareTo(b[mapWzxxDataText[_selectedValue]].split(',').length.toString())); + } + break; + case '推送状态': + if (_descending) { + // Unhandled Exception: NoSuchMethodError: The method 'compareTo' was called on null. + //按_selectedValue排序,降序 + listHycsGetList2.sort((a, b) => + (b[mapWzxxDataText[_selectedValue]]).compareTo(a[mapWzxxDataText[_selectedValue]])); + } else { + //按_selectedValue排序,升序 + listHycsGetList2.sort((a, b) => + (a[mapWzxxDataText[_selectedValue]]).compareTo(b[mapWzxxDataText[_selectedValue]])); + } + break; + default: + if (_descending) { + //按_selectedValue排序,降序 + listHycsGetList2.sort((a, b) => + (b[mapWzxxDataText[_selectedValue]]).compareTo(a[mapWzxxDataText[_selectedValue]])); + } else { + //按_selectedValue排序,升序 + listHycsGetList2.sort((a, b) => + (a[mapWzxxDataText[_selectedValue]]).compareTo(b[mapWzxxDataText[_selectedValue]])); + } + break; + } + + Future.delayed(const Duration(milliseconds: 1000), () { + if (bShowToast) { + Fluttertoast.showToast( + msg: '按“${_selectedValue}”${_descending ? '降序' : '升序'}排列完成!', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + isLoading = false; + try_setState(); //避免如下异常报错 + }); + } + } + + Widget getDropdownButton() { + //DropdownMenuItem项目文本list + List itemList = [ + '抓拍次数', + '林格曼黑度', + '抓拍时间', + '抓拍地点', + '车牌颜色', + //'车牌号码', + '车辆类型', + //'主键ID', + ]; + + //添加按'推送状态'排序 + if (hyshlx != 'hycs') { + // itemList.removeLast(); + // itemList.addAll(['推送状态', '主键ID']); + itemList.addAll(['推送状态']); + } + + //获取DropdownMenuItem项目组件list + List> _dropDownMenuItems = + itemList.map>((String item) { + return DropdownMenuItem( + value: item, + child: getDropdownButtonItemText(item), + ); + }).toList(); + + return Padding( + padding: EdgeInsets.only(top: 0, bottom: 0), + child: Container( + alignment: Alignment(0, 0), + width: 145, + margin: EdgeInsets.only(bottom: 0), + padding: EdgeInsets.only(left: 0, bottom: 0), + // decoration: BoxDecoration( + // border: Border.all(width: 0), + // //边框圆角设置 + // borderRadius: + // BorderRadius.vertical(top: Radius.elliptical(2, 2), bottom: Radius.elliptical(2, 2)), + // ), + //DropdownButton默认有一条下划线,DropdownButtonHideUnderline去除下划线 + child: DropdownButtonHideUnderline( + child: DropdownButton( + iconSize: ScreenUtil().setHeight(100), + //itemHeight: ScreenUtil().setHeight(372), + isDense: true, + value: _selectedValue, + items: _dropDownMenuItems, + onChanged: (String selectedValue) { + if (isLoading) { + return; + } + + if (_selectedValue == selectedValue) { + _descending = !_descending; + } else { + _descending = true; + } + _selectedValue = selectedValue; + print('_selectedValue = $_selectedValue'); + + //按抓拍次数排序,降序 + // listHycsGetList2.sort((a, b) => (b["yjxx_id"].split(',').length.toString()) + // .compareTo(a["yjxx_id"].split(',').length.toString())); + + //按照用户选择的_selectedValue、_descending对listHycsGetList2进行排序,并延时更新 + _listSort(); + }, + ), + ), + ), + ); + + //没有padding: ,会报错Failed assertion: line 1644 pos 15: 'padding != null': is + // return Padding( + // // child: OutlineButton( + // // borderSide: BorderSide(width: 4.0), + // // child: Text('Hi'), + // // onPressed: () {}, + // // padding: EdgeInsets.all(0), + // // ), + // ); + + // return SizedBox( + // height: 30, + // width: 30, + // child: Container( + // color: Colors.black45, + // ), + // ); + + // return Container( + // decoration: new BoxDecoration( + // border: new Border.all(color: Color(0xFFFF0000), width: 0.5), + // color: Color(0xFF9E9E9E), + // borderRadius: new BorderRadius.circular((20.0))), + // height: 28.0, + // width: 130, + // alignment: Alignment.center, + // child: Text('sdsd'), + // ); + + // ConstrainedBox( + // constraints: BoxConstraints( + // //minWidth: double.infinity, //宽度尽可能大 + // //minHeight: _listTileHeight, //最小高度 + // maxHeight: 20, //最大高度 + // ), + // ); + } + + Widget getDropdownButtonItemText(String item) { + return Padding( + padding: EdgeInsets.only(bottom: ScreenUtil().setHeight(3)), + child: Row( + //crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + alignment: Alignment(0, 0), + padding: EdgeInsets.only(top: ScreenUtil().setHeight(10)), + child: item == _selectedValue + ? _descending + ? _getImage('assets/images/descending.png') + : _getImage('assets/images/ascending.png') + // ? Icon(Icons.arrow_downward_outlined, size: 20) + // : Icon(Icons.arrow_upward_outlined, size: 20) + : SizedBox(), + ), + SizedBox( + width: ScreenUtil().setWidth(10), + ), + Container( + //alignment: Alignment(0, -1), + child: Text(item, + style: TextStyle( + fontSize: 18, + color: isLoading + ? Theme.of(context).disabledColor + : item == _selectedValue + ? Colors.blue + : null)), + // style: isLoading + // ? TextStyle(color: Theme.of(context).disabledColor) + // : (item == _selectedValue ? TextStyle(color: Colors.blue) : null)), + ), + ], + ), + ); + } + +// Widget getDropdownButtonItemText(String item) { +// return Row( +// //crossAxisAlignment: CrossAxisAlignment.center, +// children: [ +// Container( +// alignment: Alignment(0, 0.28), +// child: item == _selectedValue +// ? _descending +// ? _getImage('assets/images/descending.png') +// : _getImage('assets/images/ascending.png') +// // ? Icon(Icons.arrow_downward_outlined, size: 20) +// // : Icon(Icons.arrow_upward_outlined, size: 20) +// : SizedBox(), +// ), +// SizedBox( +// width: 3, +// ), +// Container( +// //alignment: Alignment(0, -1), +// child: Text(item, +// style: isLoading +// ? TextStyle(color: Theme.of(context).disabledColor) +// : (item == _selectedValue ? TextStyle(color: Colors.blue) : null)), +// ), +// ], +// ); +// } +// +// Widget getDropdownButton() { +// //DropdownMenuItem项目文本list +// List itemList = [ +// '抓拍次数', +// '林格曼黑度', +// '抓拍时间', +// '抓拍地点', +// '车牌颜色', +// '车牌号码', +// '车辆类型', +// '主键ID', +// ]; +// +// //添加按'推送状态'排序 +// if (hyshlx != 'hycs') { +// itemList.removeLast(); +// itemList.addAll(['推送状态', '主键ID']); +// } +// +// //获取DropdownMenuItem项目组件list +// List> _dropDownMenuItems = +// itemList.map>((String item) { +// return DropdownMenuItem( +// value: item, +// child: getDropdownButtonItemText(item), +// ); +// }).toList(); +// +// return Padding( +// padding: EdgeInsets.only(top: 10, bottom: 10), +// child: Container( +// alignment: Alignment(0.7, 0), +// width: 125, +// margin: EdgeInsets.only(bottom: 0), +// padding: EdgeInsets.only(left: 0, bottom: 0), +// decoration: BoxDecoration( +// border: Border.all(width: 0), +// //边框圆角设置 +// borderRadius: +// BorderRadius.vertical(top: Radius.elliptical(2, 2), bottom: Radius.elliptical(2, 2)), +// ), +// //DropdownButton默认有一条下划线,DropdownButtonHideUnderline去除下划线 +// child: DropdownButtonHideUnderline( +// child: DropdownButton( +// isDense: true, +// value: _selectedValue, +// items: _dropDownMenuItems, +// onChanged: (String selectedValue) { +// if (isLoading) { +// return; +// } +// +// if (_selectedValue == selectedValue) { +// _descending = !_descending; +// } else { +// _descending = true; +// } +// _selectedValue = selectedValue; +// print('_selectedValue = $_selectedValue'); +// +// //按抓拍次数排序,降序 +// // listHycsGetList2.sort((a, b) => (b["yjxx_id"].split(',').length.toString()) +// // .compareTo(a["yjxx_id"].split(',').length.toString())); +// +// //按照用户选择的_selectedValue、_descending对listHycsGetList2进行排序,并延时更新 +// _listSort(); +// }, +// ), +// ), +// ), +// ); +// +// //没有padding: ,会报错Failed assertion: line 1644 pos 15: 'padding != null': is +// // return Padding( +// // // child: OutlineButton( +// // // borderSide: BorderSide(width: 4.0), +// // // child: Text('Hi'), +// // // onPressed: () {}, +// // // padding: EdgeInsets.all(0), +// // // ), +// // ); +// +// // return SizedBox( +// // height: 30, +// // width: 30, +// // child: Container( +// // color: Colors.black45, +// // ), +// // ); +// +// // return Container( +// // decoration: new BoxDecoration( +// // border: new Border.all(color: Color(0xFFFF0000), width: 0.5), +// // color: Color(0xFF9E9E9E), +// // borderRadius: new BorderRadius.circular((20.0))), +// // height: 28.0, +// // width: 130, +// // alignment: Alignment.center, +// // child: Text('sdsd'), +// // ); +// +// // ConstrainedBox( +// // constraints: BoxConstraints( +// // //minWidth: double.infinity, //宽度尽可能大 +// // //minHeight: _listTileHeight, //最小高度 +// // maxHeight: 20, //最大高度 +// // ), +// // ); +// } +} + +//https://blog.csdn.net/u014803467/article/details/103750018 +//Flutter 使用SliverChildBuilderDelegate获取ListView的第一个和最后一个可见Item序号 +// 秋名山交警X 2019-12-28 23:52:52 +class _SaltedValueKey extends ValueKey { + const _SaltedValueKey(Key key) + : assert(key != null), + super(key); +} + +class MyChildrenDelegate extends SliverChildBuilderDelegate { + MyChildrenDelegate( + Widget Function(BuildContext, int) builder, { + int childCount, + bool addAutomaticKeepAlive = true, + bool addRepaintBoundaries = true, + }) : super(builder, + childCount: childCount, + addAutomaticKeepAlives: addAutomaticKeepAlive, + addRepaintBoundaries: addRepaintBoundaries); + + // Return a Widget for the given Exception + Widget _createErrorWidget(dynamic exception, StackTrace stackTrace) { + final FlutterErrorDetails details = FlutterErrorDetails( + exception: exception, + stack: stackTrace, + library: 'widgets library', + context: ErrorDescription('building'), + ); + FlutterError.reportError(details); + return ErrorWidget.builder(details); + } + + @override + Widget build(BuildContext context, int index) { + assert(builder != null); + if (index < 0 || (childCount != null && index >= childCount)) return null; + Widget child; + try { + child = builder(context, index); + } catch (exception, stackTrace) { + child = _createErrorWidget(exception, stackTrace); + } + if (child == null) return null; + final Key key = child.key != null ? _SaltedValueKey(child.key) : null; + if (addRepaintBoundaries) child = RepaintBoundary(child: child); + if (addSemanticIndexes) { + final int semanticIndex = semanticIndexCallback(child, index); + if (semanticIndex != null) + child = IndexedSemantics(index: semanticIndex + semanticIndexOffset, child: child); + } + if (addAutomaticKeepAlives) child = AutomaticKeepAlive(child: child); + return KeyedSubtree(child: child, key: key); + } + + @override + void didFinishLayout(int _firstIndex, int _lastIndex) { + // TODO: implement didFinishLayout + super.didFinishLayout(_firstIndex, _lastIndex); + } + + ///监听 在可见的列表中 显示的第一个位置和最后一个位置 + @override + double estimateMaxScrollOffset( + int _firstIndex, int _lastIndex, double _leadingScrollOffset, double _trailingScrollOffset) { + // print( + // 'firstIndex = $_firstIndex, lastIndex = $_lastIndex, leadingScrollOffset = $_leadingScrollOffset,' + // 'trailingScrollOffset : $_trailingScrollOffset '); + + //违章信息Listview滚动广播 + eventBus.fire(WzxxDataScrollEvent(_firstIndex, _lastIndex)); + + return super.estimateMaxScrollOffset( + _firstIndex, _lastIndex, _leadingScrollOffset, _trailingScrollOffset); + } +} diff --git a/lib/pages/Works/HYSH/hysh_group.dart b/lib/pages/Works/HYSH/hysh_group.dart new file mode 100644 index 0000000..bd0d1d0 --- /dev/null +++ b/lib/pages/Works/HYSH/hysh_group.dart @@ -0,0 +1,538 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/screen_util.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hyzp_ybqx/components/EncryptUtil.dart'; +import 'package:hyzp_ybqx/components/customDialogF.dart'; +import 'package:hyzp_ybqx/components/customDialogHysh.dart'; +import 'package:hyzp_ybqx/components/dioFun.dart'; + +import '../../../components/doJSON.dart'; +import '../../../components/hyxx_data_handle.dart'; +import '../../../services/EventBus.dart'; +import '../../../widget/CheckboxButtonItem.dart'; + +class HyshGroup extends StatefulWidget { + HyshGroup( + {@required this.index, + @required this.hyshlx, + this.size = const Size(30, 30), + this.fontSize = 16, + this.selectedRadio = 0, + this.id = -1}); + + //I don't know what is this index for but I will put it in anyway + final int index; + Size size; + double fontSize; + String hyshlx; + int selectedRadio; + int id; + + @override + _HyshGroupState createState() => _HyshGroupState(); +} + +class _HyshGroupState extends State { + //try_setState(); //避免如下异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + int _selectedRadio = 0; + String _sfcyTextTrue = '当前时间 > 抓拍时间 + 审核间隔,只能审核,不推送交警'; + String _sfcyTextFalse = '当前时间 < 抓拍时间 + 审核间隔,复审后可以推送交警'; + + void initState() { + _selectedRadio = widget.selectedRadio; + + //黑烟审核推送交警Checkbox改变事件 + eventBus.on().listen((event) { + print(event.str); + try_setState(); //避免如下异常报错 + }); + + super.initState(); + } + + Widget getRadio(int index, Size size) { + return Container( + alignment: Alignment(-1, 1), + width: size.width, + height: size.height, + child: Radio( + value: index, + onChanged: (value) { + _selectedRadio = value; + //黑烟初审数据审核Radio选项改变广播 + eventBus.fire(HycsDataAuditRadioEvent('黑烟初审数据审核Radio选项已改变', _selectedRadio)); + setState(() {}); + print('selectedRadio = ${_selectedRadio.toString()}'); + }, + groupValue: _selectedRadio, + ), + ); + } + + //Couldn't infer type parameter 'T'. Tried to infer 'dynamic' for 'T' which doesn't work: + // Parameter 'onChanged' declared as 'void Function(T)' but argument is 'Null Function(String)'. + // The type 'dynamic' was inferred from: Parameter 'value' declared as 'T' but argument is 'String'. + // Parameter 'groupValue' declared as 'T' but argument is 'dynamic'. + // Consider passing explicit type argument(s) to the generic. + + //原因是selectedRadio的类型为int,示例中_radValue为String + //无法推断类型参数“t”。试图推断“T”的“dynamic”无效:参数“onChanged”声明为“void Function(T)”, + // 但参数为“Null Function(String)”。类型“dynamic”的推断依据:参数“value”声明为“T”, + // 但参数为“String”。参数“groupValue”声明为“T”,但参数为“dynamic”。考虑将显式类型参数传递给泛型。 + + //5、得到黑烟审核组件 + getHyshItems(int index) { + return Container( + width: ScreenUtil().setWidth(1022), + height: ScreenUtil().setHeight(550), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12), + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(height: ScreenUtil().setHeight(20)), + //5.1、车牌号码、车牌颜色 + topTabs_map['carNumberAndCpys_List'][index], + SizedBox(height: ScreenUtil().setHeight(15)), + //5.2、审核意见 + getShyj(widget.index), + //SizedBox(height: ScreenUtil().setHeight(25)), + //5.3、得到审核结果组件 + getShjg(widget.index), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + //5、得到黑烟审核组件 + getHyshItems(widget.index), + SizedBox(height: ScreenUtil().setHeight(6)), + //6、得到审核确认组件 + getShqr(widget.index), + ], + ); + } + + //6、得到审核确认组件 + Widget getShqr(int index) { + return Container( + //padding: EdgeInsets.only(top: ScreenUtil().setWidth(6)), + width: ScreenUtil().setWidth(1022), + height: ScreenUtil() + .setHeight(widget.hyshlx == 'hyfh' ? (1 == sfyc ? 215 : 150) : (1 == sfyc ? 215 : 150)), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12), + ), + ), + child: Column( + children: [ + SizedBox(height: ScreenUtil().setHeight(20)), + 1 == sfyc + ? Container( + alignment: Alignment.centerLeft, + padding: EdgeInsets.only(left: ScreenUtil().setWidth(30)), + child: Text( + _sfcyTextTrue, + textAlign: TextAlign.left, + style: TextStyle(color: Colors.blue, fontSize: 13), + )) + : SizedBox.shrink(), + SizedBox(height: ScreenUtil().setHeight(1 == sfyc ? 6 : 0)), + widget.hyshlx == 'hyfh' + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + CheckboxButtonItem(widget.index), + //_getShjgImage(widget.tabController, _selectedRadio, 35), + Container( + width: ScreenUtil().setWidth(100), + height: ScreenUtil().setHeight(100), + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(_selectedRadio == 0 + ? "assets/images/hyc.png" + : "assets/images/fhyc.png"), + fit: BoxFit.contain), + ), + ), + getBtnSizeX( + text: '复审提交', + fontColor: 0 == _selectedRadio ? Colors.red : Colors.green, + onPressedFun: 1 == sfyc + ? null + : () async { + int ret = -1; + print('等待复审提交确认'); + await Navigator.of(context) + .push( + PageRouteBuilder( + opaque: false, + pageBuilder: (context, animation, secondaryAnimation) => + CustomDialogHysh( + shjg: 0 == _selectedRadio ? hyc_text : fhyc_text, + title: '复审', + content: + '是否进行复审提交${tsjj && 0 == sfyc ? '、同时推送交警' : ''}?\n${1 == sfyc ? _sfcyTextTrue : ''}'), + ), + ) + .then((value) async { + print('value = $value'); + if (value) { + print('用户已确认,开始处理复审提交!'); + +//复审接口增加是否延迟字段 sfyc (是否延迟)整型 必须 是否延误,0-正常 1-延误。延误状态的不推送 +// 初审不用判断,sfyc 直接提交0即可。只有复审的时候才判断时间 +// A、若在规定时间内,则 int sfyc = 0,审核完毕后正常推送交警。 +// B、若超出规定时间,即当前时间>抓拍时间+间隔时间,则 sfyc = 1,不推送交警。 + + //设置 sfyc 和 tsjj + set_sfyc_tsjj(int.parse(listGetZpjl[widget.index]['zpsj'])) + .then((value) async { + hyshContentFirstAudit( + widget.id, + widget.index, + mapHyshlx[hyshlx]['audit_workflow'], + topTabs_map['auditShuoming_Controller_List'][widget.index] + .text, + topTabs_map['auditTitle'][widget.index], + sfyc: sfyc, + ).then((value) { + eventBus.fire(HycsDataUpdateEvent( + '${mapHyshlx[hyshlx]['text']}数据已更新')); + //必须等待审核过程完成后,再处理同时推送交警,否则推送交警总是失败 + print('tsjj = $tsjj'); + if (tsjj) { + print('before tsjjFun(widget.id, _plateAndID)'); + + String _plateAndID = + topTabs_map['car_number_List'].toString() + + '(ID:${widget.id.toString()})'; + + tsjjFun(widget.id, _plateAndID); + + print('after tsjjFun(widget.id, _plateAndID)'); + + Fluttertoast.showToast( + msg: '$_plateAndID 已推送交警,请等待返回结果。', + gravity: ToastGravity.CENTER); + } + }); + }); + } else { + print('用户取消了复审提交'); + } + }); + Navigator.pop(context, ret); + }, + width: 90.0, + height: 34.0), //'复审提交' + getBtnSizeX( + text: "取消", + onPressedFun: () async { + Navigator.pop(context); + }, + width: 60.0, + height: 34.0), //'取消' + ], + ) + : Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + //_getShjgImage(widget.tabController, _selectedRadio, 35), + Container( + width: 35, + height: 35, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(_selectedRadio == 0 + ? "assets/images/hyc.png" + : "assets/images/fhyc.png"), + fit: BoxFit.contain), + ), + ), + getBtnSizeX( + text: '初审提交', + fontColor: 0 == _selectedRadio ? Colors.red : Colors.green, + onPressedFun: 1 == sfyc + ? null + : () async { + int ret = -1; + print('等待初审提交确认'); + await Navigator.of(context) + .push( + PageRouteBuilder( + opaque: false, + pageBuilder: (context, animation, secondaryAnimation) => + CustomDialogHysh( + shjg: 0 == _selectedRadio ? hyc_text : fhyc_text, + title: '初审', + content: '是否进行初审提交?'), + ), + ) + .then((value) async { + print('value = $value'); + if (value) { + print('用户已确认,开始处理初审提交!'); + //return; + + hyshContentFirstAudit( + widget.id, + widget.index, + mapHyshlx[hyshlx]['audit_workflow'], + topTabs_map['auditShuoming_Controller_List'][widget.index] + .text, + topTabs_map['auditTitle'][widget.index], + sfyc: 0, + ).then((value) { + eventBus.fire( + HycsDataUpdateEvent('${mapHyshlx[hyshlx]['text']}数据已更新')); + }); + } else { + print('用户取消了初审提交'); + } + }); + Navigator.pop(context, ret); + }, + width: 90.0), //'初审提交' + getBtnSizeX( + text: "取消", + onPressedFun: () async { + Navigator.pop(context); + }, + width: 60.0), //'取消' + ], + ), + ], + ), + ); + } + + //5.3、得到审核结果组件 + Widget getShjg(int index) { + return Container( + width: ScreenUtil().setWidth(1022), + height: ScreenUtil().setHeight(175), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: my_marginLeft), + Text((widget.hyshlx == 'hyfh' ? '复审' : '初审') + '结果: ', + style: TextStyle( + fontSize: widget.fontSize, + color: 0 == _selectedRadio ? Colors.red : Colors.green)), + CustomRadioWidget( + value: 0, + title: mapHyshlx[hyshlx]['nick_text'] + "为黑烟车", + fontSize: widget.fontSize, + width: ScreenUtil().setWidth(400), + groupValue: _selectedRadio, + onChanged: (int value) { + _selectedRadio = value; + fh_hyc = true; //复审为黑烟车 + tsjj = true; //同时推送交警 + //黑烟初审数据审核Radio选项改变广播 + eventBus.fire(HycsDataAuditRadioEvent('黑烟初审数据审核Radio选项已改变', _selectedRadio)); + setState(() {}); + print('selectedRadio = ${_selectedRadio.toString()}'); + }, + ), + ], + ), + SizedBox(height: ScreenUtil().setHeight(15)), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: my_marginLeft), + Text((widget.hyshlx == 'hyfh' ? '复审' : '初审') + '结果: ', + style: TextStyle(fontSize: widget.fontSize, color: Colors.white)), + CustomRadioWidget( + value: 1, + title: "非黑烟车", + fontSize: widget.fontSize, + width: ScreenUtil().setWidth(400), + groupValue: _selectedRadio, + onChanged: (int value) { + _selectedRadio = value; + fh_hyc = false; //复审为黑烟车 + tsjj = false; //同时推送交警 + //黑烟初审数据审核Radio选项改变广播 + eventBus.fire(HycsDataAuditRadioEvent('黑烟初审数据审核Radio选项已改变', _selectedRadio)); + setState(() {}); + print('selectedRadio = ${_selectedRadio.toString()}'); + }, + ), + ], + ), + ], + ), + ); + } + + //5.2、得到审核意见组件 + Widget getShyj(int index) { + // return ConstrainedBox( + // constraints: BoxConstraints( + // minWidth: double.infinity, //宽度尽可能大 + // //minHeight: _listTileHeight, //最小高度 + // maxHeight: my_listTileHeight, //最大高度 + // ), + return Container( + width: ScreenUtil().setWidth(1022), + height: ScreenUtil().setHeight(120), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.baseline, + children: [ + SizedBox(width: my_marginLeft), + Text((widget.hyshlx == 'hyfh' ? '复审' : '初审') + '意见: ', + style: TextStyle( + fontSize: my_fontSize, color: 0 == _selectedRadio ? Colors.red : Colors.green)), + Container( + alignment: Alignment(-1, 0), + height: 120, + //widthTrail = 400报错,360刚能显示,300换行,266 + width: ScreenUtil().setWidth(775), + child: TextField( + //textAlign: TextAlign.right, + //style: TextStyle(fontSize: _fontSize, color: cpysList[getIndexOfCpysList(colorText: topTabs_map['cpysText_List'][i])].cpysFont), + //style: TextStyle(fontSize: _fontSize, color: cpysList[getIndexOfCpysList(colorText: myCpys)].cpysFont), + style: TextStyle(fontSize: my_fontSize), + textAlign: TextAlign.left, + decoration: InputDecoration( + hintText: '請輸入审核意见', + //border: InputBorder.none, //TextField去掉下划线 + //contentPadding: EdgeInsets.only(right: 0), + //contentPadding: EdgeInsets.symmetric(vertical: my_textFieldHeight), + contentPadding: EdgeInsets.only(left: 4, right: 4), //这行代码是关键,设置这个之后,居中 + //contentPadding: EdgeInsets.zero, //这行代码是关键,设置这个之后,居中 + border: OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey[600]), + //borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(3), + ), + ), + controller: topTabs_map['auditShuoming_Controller_List'][index], + maxLines: 1, + minLines: 1, + //maxLengthEnforced: false, + //maxLength: 10, + enabled: true, + //利用控制器初始化文本 + onChanged: (value) { + topTabs_map['auditShuoming_Controller_List'][index].text = value; + }, + ), + ), + ], + ), + ); + } + + Widget getBtnSizeX( + {@required text, width = 70.0, height = 35.0, onPressedFun, fontColor = Colors.black}) { + return Container( + color: Colors.white12, //onPressedFun为null时无效 + width: width, + height: height, + child: RaisedButton( + padding: EdgeInsets.all(0), + textColor: Colors.black, + child: Text(text, style: TextStyle(color: fontColor)), + onPressed: onPressedFun, + ), + ); + } +} + +class CustomRadioWidget extends StatelessWidget { + final T value; + final String title; + final double fontSize; + final T groupValue; + final ValueChanged onChanged; + final double width; + final double height; + + CustomRadioWidget( + {this.value, + this.title = '', + this.fontSize = 16, + this.groupValue, + this.onChanged, + this.width = 25, + this.height = 25}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(0), + child: GestureDetector( + onTap: () { + onChanged(this.value); + }, + child: Container( + //alignment: Alignment(0, 0), + height: this.height, + width: this.width, + child: value == groupValue + ? Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.only(top: 2), + child: Icon(Icons.radio_button_checked_rounded, + color: onChanged == null ? Colors.grey : Colors.blue), + ), + SizedBox(width: title.isEmpty ? 0 : 2), + Text( + title, + style: TextStyle( + fontSize: fontSize, + color: onChanged == null ? Colors.grey : Colors.blue, + fontWeight: FontWeight.bold, + ), + ), + ], + ) + : Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.only(top: 2), + child: Icon(Icons.radio_button_unchecked_rounded, + color: onChanged == null ? Colors.grey : Colors.black), + ), + SizedBox(width: title.isEmpty ? 0 : 2), + Text(title, + style: TextStyle( + fontSize: fontSize, + color: onChanged == null ? Colors.grey : Colors.black)), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/Works/HYSH/tsjj_content_new.dart b/lib/pages/Works/HYSH/tsjj_content_new.dart new file mode 100644 index 0000000..6601b8f --- /dev/null +++ b/lib/pages/Works/HYSH/tsjj_content_new.dart @@ -0,0 +1,926 @@ +//import '../../../widget/player_pro.dart'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flustars/flustars.dart' as flustars; //该组件中有ScreenUtil +import 'package:flutter/material.dart'; +import 'package:flutter_drag_scale/flutter_drag_scale.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hyzp_ybqx/components/customDialogF.dart'; +import 'package:hyzp_ybqx/services/EventBus.dart'; +import 'package:hyzp_ybqx/widget/my_superplayer.dart'; +import 'package:keyboard_avoider/keyboard_avoider.dart'; + +// +import '../../../components/commonFun.dart'; +//import 'package:hyzp_ybqx/widget/player_pro_new.dart'; +import '../../../components/dioFun.dart'; +import '../../../components/doJSON.dart'; +import '../../../components/hyxx_data_handle.dart'; + +class TsjjContentNew extends StatefulWidget { + TsjjContentNew({ + @required this.hyshlx, + @required this.title, + this.indexRecord, + this.id, + Key key, + }) : super(key: key); + String title; + int indexRecord = 0; + int id = -1; + String hyshlx; + + _LoginPageState createState() => _LoginPageState(); +} + +//用TabController实现顶部tab切换 +class _LoginPageState extends State with SingleTickerProviderStateMixin { + //try_setState(); //避免如下异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + dispose() { + super.dispose(); + } + + BuildContext _context; + String _title = ''; + + //flutter_screenUtil 4.x 用法,ScreenUtil.screenWidth (sdk>=2.6 : 1.sw) //设备宽度 + double _screenWidth = 1.sw; + + double _marginLeft = 8; + double _marginCenter = 70; + double _fontSize = 16; + double _widthLeft = 40; // = _screenWidth / 3; + double _iconSize = 18; + double _stampSize = 100; + double _listTileHeight = 30; + double _marginVer = 10; + Color _iconColor = Colors.blue; + + Map _mapTsjjGetTsStatus = {}; + + void initState() { + //黑烟审核推送交警Checkbox改变事件 + eventBus.on().listen((event) { + print(event.str); + try_setState(); //避免如下异常报错 + }); + + _widthLeft = _screenWidth / 2; + getListFlields(); + + _context = context; + super.initState(); + } + + Map _mapGetTsjjGetData = { + "id": 1203, + "plate_id": "川QS9661", + "plate_color": "蓝色", + "zpsj": 1612770967, + "yjxx_id": "1379", + "workflow": 2, + "video_url": "video/9_6063_20210208_155607_川QS9661.mp4", + "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/926f5168f1fe109a9b8ec88dcac7ac2c.jpg", + "clfl": "皮卡车", + "dwip": "172.16.3.9", + "dwms": "宜长路出城方向", + "lgmzs": 3, + "jczxd": "863", + "sfhy": "黑烟车", + }; + double _radioImage = 9 / 16; + Widget imageWztp; + + getListFlields() async { + _mapGetTsjjGetData = await getWzxxItemData(widget.id); //获取指定id的违章信息返回 _mapGetTsjjGetData + print('_mapGetTsjjGetData = ${_mapGetTsjjGetData}'); + await getShenheData(widget.id); //获取指定id的审核信息存入 listGetShenheData + await getMapGetShenheData(); //从listGetShenheData中取出数据分别存入mapGetHycsShenheData、mapGetHyfhShenheData + + //Tab切换时,设置 sfyc 和 tsjj,只处理当前选中的抓拍记录 _tabController.index,zpsj抓拍时间 + set_sfyc_tsjj(_mapGetTsjjGetData['zpsj']); + + // 获取推送交警状态信息 + // tsjjGetTsStatus返回字段 类型 说明 + // id 整型 违章记录ID + // checkid 整型 推送的抓拍记录ID + // tszt 整型 推送状态:0-未推送 | 1-推送失败 | 3-推送成功 + // 20210529更新: + // tszt 整型 推送状态:0-未推送 | 1-推送失败 | 2-推送成功 | 3-规定时间内已有违章记录,本次不推送 | 4-现场登记,不推送 + // ts_time 字符串 推送时间 + //Future tsjjGetTsStatus(int _wzxxID) + _mapTsjjGetTsStatus = await tsjjGetTsStatus(widget.id); + print('_mapTsjjGetTsStatus = $_mapTsjjGetTsStatus'); + + // 获取网络图片尺寸 + flustars.WidgetUtil.getImageWH(url: getMediaUrl(_mapGetTsjjGetData['pic_url'])).then((rect) { + if (null != rect) { + _radioImage = rect.height / rect.width; + } + }); + + imageWztp = getWztp(); //得到违章图片 + + //下面读取的是全局数据,该模块不需要修改 + // 川Q565H4 + try { + //_title = '违章黑烟车详情(${(widget.indexRecord + 1).toString()} / ${listHycsGetList2.length})id:${listHycsGetList2[widget.indexRecord]['id']}'; + //_title = '违章黑烟车(${(widget.indexRecord + 1).toString()} / ${listHycsGetList2.length}):${listHycsGetList2[widget.indexRecord]["plate_id"]}(${listHycsGetList2[widget.indexRecord]["plate_color"]})'; + _title = '推送交警详情(${(widget.indexRecord + 1).toString()} / ${listHycsGetList2.length})'; + setState(() {}); + } catch (e) {} + } + + // 使用 cached_network_image 插件实现网络图片缓存 + // 使用 flutter_drag_scale 实现可缩放可拖拽双击放大的图片功能。PhotoView插件不好用,有问题 + Widget getNetworkImage(String url) { + return CachedNetworkImage( + imageUrl: url, + alignment: Alignment.topCenter, + imageBuilder: (context, imageProvider) => DragScaleContainer( + doubleTapStillScale: true, child: Image(image: imageProvider) + // child: Image( + // image: NetworkImage( + // 'http://h.hiphotos.baidu.com/zhidao/wh%3D450%2C600/sign=0d023672312ac65c67506e77cec29e27/9f2f070828381f30dea167bbad014c086e06f06c.jpg'), + // ), + ), + // imageBuilder: (context, imageProvider) => PhotoView( + // imageProvider: imageProvider, + // ), + //placeholder: (context, url) => CircularProgressIndicator(), + placeholder: (context, url) => + getMoreWidget(color: Colors.black38, size: 20.0, strokeWidth: 2.0), + errorWidget: (context, url, error) => Icon(Icons.error), + ); + } + + // 167 50 3.34 + Widget getLgmzs(int lgmzs, {double width = 127, double height = 127}) { + int _rgb = (255 * (5 - lgmzs)) ~/ 5; + return Stack( + children: [ + Container( + width: ScreenUtil().setWidth(width), + height: ScreenUtil().setHeight(height), + padding: EdgeInsets.only( + right: ScreenUtil().setWidth(6), left: ScreenUtil().setWidth(6), top: 0, bottom: 4), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text('$lgmzs级${lgmzs * 20}%', style: TextStyle(fontSize: 10)), + //SizedBox(height: 0), + Container( + width: ScreenUtil().setWidth(66), + height: ScreenUtil().setHeight(66), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + color: Color.fromRGBO(_rgb, _rgb, _rgb, 1.0), + borderRadius: new BorderRadius.circular(0), + ), + ) + ], + ), + ), + Positioned( + top: ScreenUtil().setHeight(4), + child: Container( + width: ScreenUtil().setWidth(width), + height: ScreenUtil().setHeight(height - 8), + padding: EdgeInsets.only( + right: ScreenUtil().setWidth(6), left: ScreenUtil().setWidth(6), top: 0, bottom: 0), + decoration: BoxDecoration( + border: Border.all( + color: (lgmzs == _mapGetTsjjGetData['lgmzs']) + ? Colors.red + : Color.fromRGBO(244, 244, 244, 1), + width: 2), + //color: Colors.lightBlue, + borderRadius: new BorderRadius.circular(3.0), + ), + ), + ) + ], + ); + } + + //1、得到格林曼黑度标准和视频播放按钮组件 + Widget getHdAndPlay() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + getLgmzs(0), + getLgmzs(1), + getLgmzs(2), + getLgmzs(3), + getLgmzs(4), + getLgmzs(5, width: 153), + getIconBtnSizeX( + height: 104, + //getIconBtnSizeX 中已经使用ScreenUtil().setHeight(126),此处不能传 ScreenUtil().setHeight(126) ,否则严重错位 + width: 168, + text: "视频", + textSize: 12, + circular: 4, + color: Color.fromRGBO(52, 157, 237, 1), + onTop: () async { + if (Playing) { + //禁止同时启动两次播放器 + return; + } + + Playing = true; //禁止同时启动两次播放器 + urlnew = getMediaUrl(_mapGetTsjjGetData['video_url']); + + //获取视频地址失败 + if (!isVideoUrl(urlnew)) { + return; + } + + Navigator.of(_context).push(MaterialPageRoute( + builder: (context) => SuperPlayerPage( + loop: 0, //设置播放循环,默认播放器的循环次数是1, 即不循环播放。如果设置循环次数0,表示无限循环。 + url: urlnew, + title: + '违章视频: ${_mapGetTsjjGetData["plate_id"]}\n${_mapGetTsjjGetData['dwms']}'))); + + // Navigator.of(_context).push(MaterialPageRoute( + // builder: (context) => PlayerProNew( + // loop: 0, //设置播放循环,默认播放器的循环次数是1, 即不循环播放。如果设置循环次数0,表示无限循环。 + // url: urlnew, + // title: + // '违章视频: ${_mapGetTsjjGetData["plate_id"]}\n${_mapGetTsjjGetData['dwms']}'))); + }, + ), + SizedBox(width: ScreenUtil().setWidth(15)), + ], + ); + } + + //2、得到违章图片组件 + Widget getWztp() { + //ratioList[index] = 0.5714285714285714 + return Stack( + children: [ + Container( + width: ScreenUtil().setWidth(1022), + //height: ScreenUtil().setHeight(639), + //height: ScreenUtil().setHeight(22 + 1022 * _radioImage), + height: ScreenUtil().setHeight(30 + 1022 * _radioImage), + decoration: BoxDecoration( + //color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12), + ), + ), + ), + Positioned( + //left: ScreenUtil().setWidth(25), + top: ScreenUtil().setHeight(25), + child: Container( + width: ScreenUtil().setWidth(1022), + height: ScreenUtil().setHeight(30 + 1022 * _radioImage), + child: getNetworkImage(getMediaUrl(_mapGetTsjjGetData['pic_url'])), + ), + ) + ], + ); + } + + // void printScreenInformation() { + // print('Device width dp:${1.sw}dp'); + // print('Device height dp:${1.sh}dp'); + // print('Device pixel density:${ScreenUtil().pixelRatio}'); + // print('Bottom safe zone distance dp:${ScreenUtil().bottomBarHeight}dp'); + // print('Status bar height dp:${ScreenUtil().statusBarHeight}dp'); + // print('The ratio of actual width to UI design:${ScreenUtil().scaleWidth}'); + // print( + // 'The ratio of actual height to UI design:${ScreenUtil().scaleHeight}'); + // print('System font scaling:${ScreenUtil().textScaleFactor}'); + // print('0.5 times the screen width:${0.5.sw}dp'); + // print('0.5 times the screen height:${0.5.sh}dp'); + // } + + void printScreenInformation() { + print('ScreenUtil().screenWidth = ${ScreenUtil().screenWidth}'); + print('设备宽度:${1.sw}dp'); + print('"1.w" = ${1.w}'); + print('"1.sw" = ${1.sw}'); + print('设备高度:${1.sh}dp'); + print('设备的像素密度:${ScreenUtil().pixelRatio}'); + print('底部安全区距离:${ScreenUtil().bottomBarHeight}dp'); + print('状态栏高度:${ScreenUtil().statusBarHeight}dp'); + print('实际宽度的dp与设计稿px的比例:${ScreenUtil().scaleWidth}'); + print('实际高度的dp与设计稿px的比例:${ScreenUtil().scaleHeight}'); + print('宽度和字体相对于设计稿放大的比例:${ScreenUtil().scaleWidth * ScreenUtil().pixelRatio}'); + print('高度相对于设计稿放大的比例:${ScreenUtil().scaleHeight * ScreenUtil().pixelRatio}'); + print('系统的字体缩放比例:${ScreenUtil().textScaleFactor}'); + print('屏幕宽度的0.5:${0.5.sw}dp'); + print('屏幕高度的0.5:${0.5.sh}dp'); + } + + //3、得到违章图片说明信息组件 + Widget getWztpSmxx() { + return Container( + width: ScreenUtil().setWidth(1022), + height: ScreenUtil().setHeight(270), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12), + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + getWzxxPart1(), + getWzxxPart2(), + getWzxxPart3(), + ], + ), + ); + } + + // I/flutter (22989): ScreenUtil().screenWidth = 360.0 + // I/flutter (22989): 设备宽度:360.0dp + // I/flutter (22989): "1.w" = 0.3333333333333333 + // I/flutter (22989): "1.sw" = 360.0 + // I/flutter (22989): 设备高度:640.0dp + // I/flutter (22989): 设备的像素密度:3.0 + // I/flutter (22989): 底部安全区距离:0.0dp + // I/flutter (22989): 状态栏高度:24.0dp + // I/flutter (22989): 实际宽度的dp与设计稿px的比例:0.3333333333333333 + // I/flutter (22989): 实际高度的dp与设计稿px的比例:0.3333333333333333 + // I/flutter (22989): 宽度和字体相对于设计稿放大的比例:1.0 + // I/flutter (22989): 高度相对于设计稿放大的比例:1.0 + // I/flutter (22989): 系统的字体缩放比例:1.0 + // I/flutter (22989): 屏幕宽度的0.5:180.0dp + // I/flutter (22989): 屏幕高度的0.5:320.0dp + + //3、得到违章信息组件1:车牌号码、车牌颜色 + //车牌颜色Map cpysMap = { + // '蓝色': cpysItem( + // cpysText: '蓝色', + // cpysBackground: Colors.blue, + // cpysFont: Colors.white, + // cpysBorder: Colors.orange), + // } + Widget getWzxxPart1() { + //printScreenInformation(); + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: _marginLeft), + getTitleText('车牌号码:'), + getBoderText(_mapGetTsjjGetData['plate_id'].toString(), + width: ScreenUtil().setWidth(1022 / 3.2)), + SizedBox(width: ScreenUtil().setWidth(_marginCenter)), + getTitleText('颜色:'), + getBoderText(_mapGetTsjjGetData['plate_color'], width: ScreenUtil().setWidth(1022 / 4.8)), + ], + ); + } + + Widget getTitleText(String text, {double fontSize = 16, int lines = 1, Color color}) { + return Text(text, + style: TextStyle(fontSize: fontSize, color: color), + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis, + maxLines: lines); + } + + Widget getTrailText(String text, {double fontSize = 16, double off = 0}) { + return Container( + width: ScreenUtil().setWidth(1022) - _widthLeft - off - (2 * _marginLeft), + child: Text(text, + style: TextStyle(fontSize: fontSize), + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis), + ); + } + + Widget getBoderText(String text, {double width = 40}) { + cpysItem _cpysItem = cpysMap[_mapGetTsjjGetData['plate_color']]; + + return Container( + //color: _cpysItem.cpysBackground, + alignment: Alignment(0, -1), + width: width, + decoration: BoxDecoration( + border: Border.all(color: _cpysItem.cpysBorder, width: 2), + color: _cpysItem.cpysBackground, + borderRadius: BorderRadius.circular(3), + ), + child: Padding( + padding: EdgeInsets.only(bottom: 3), + child: Text(text, + style: TextStyle(fontSize: _fontSize, color: _cpysItem.cpysFont), + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis), + ), + ); + } + + //I/flutter (17555): _mapGetTsjjGetData = { + // id: 1222, plate_id: 川Q736X2, plate_color: 蓝色, zpsj: 1612857077, yjxx_id: 1399, workflow: 999, + // video_url: video/9_6063_20210209_155117_川Q736X2.mp4, + // pic_url: /wwwroot/admin/Api/wwwroot/public/uploads/9d2f45fd24b41f2b94abe42b30970d75.jpg, + // clfl: 集装箱卡车, dwip: 172.16.3.9, dwms: 宜长路出城方向, lgmzs: 3, jczxd: 994, sfhy: 黑烟车 + // } + + Widget getIcon(IconData _iconData) { + return Container( + width: _iconSize - 2, + height: _iconSize, + child: Padding( + padding: EdgeInsets.only(top: 2), + child: Icon(_iconData, size: _iconSize, color: _iconColor), + ), + ); + } + + //4、得到违章信息组件2:抓拍时间组件 + Widget getWzxxPart2() { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: _marginLeft), + Container( + alignment: Alignment(-1, 0), + width: ScreenUtil().setWidth(1022) - _marginLeft, + child: getTitleText( + '抓拍时间:' + + getDate( + (_mapGetTsjjGetData['zpsj'] is int) + ? _mapGetTsjjGetData['zpsj'] + : int.parse(_mapGetTsjjGetData['zpsj']), + ), + ), + ), + ], + ); + } + + Widget getWzxxPart3() { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: _marginLeft), + Container( + alignment: Alignment(-1, 0), + width: ScreenUtil().setWidth(1022) - _marginLeft, + child: getTitleText('抓拍地点:' + _mapGetTsjjGetData['dwms']), + ), + ], + ); + } + + //4、得到审核信息组件 + Widget getShxx() { + return Container( + width: ScreenUtil().setWidth(1022), + height: ScreenUtil().setHeight(380), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12), + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + getWzxxPart4(), + getWzxxPart5(), + getWzxxPart6(), + ], + ), + ); + } + + //6、得到审核信息组件4:违章类型、格林曼黑度组件 + Widget getWzxxPart4() { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: _marginLeft), + Container( + alignment: Alignment(-1, 0), + width: _widthLeft, + child: getTitleText('违章类型:' + _mapGetTsjjGetData['sfhy']), + ), + SizedBox(width: _marginLeft), + //getIcon(Icons.location_on_outlined), + Container( + width: _iconSize, + height: _iconSize, + decoration: BoxDecoration( + //color: Colors.white, + image: DecorationImage(image: AssetImage("assets/images/hyc.png"), fit: BoxFit.contain), + ), + alignment: Alignment.center, + //child: + ), + getTrailText(':' + _mapGetTsjjGetData['lgmzs'].toString(), off: _iconSize), + ], + ); + } + + //从listGetShenheData中取出数据分别存入mapGetHycsShenheData、mapGetHyfhShenheData + //7、得到审核信息组件5:初审人员,初审时间 + Widget getWzxxPart5() { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: _marginLeft), + Container( + alignment: Alignment(-1, 0), + width: 300, + child: getTitleText('初审人员:' + mapGetHycsShenheData['uname']), + ), + // SizedBox(width: _marginLeft), + // getIcon(Icons.query_builder), + // getTrailText(':' + mapGetHycsShenheData['addtime'], off: _iconSize), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: _marginLeft), + Container( + alignment: Alignment(-1, 0), + width: 300, + child: getTitleText('初审时间:' + mapGetHycsShenheData['addtime']), + ), + ], + ) + ], + ); + } + + //8、得到审核信息组件6:复审人员,复审时间 + Widget getWzxxPart6() { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: _marginLeft), + Container( + alignment: Alignment(-1, 0), + width: 300, + child: getTitleText('复审人员:' + mapGetHyfhShenheData['uname']), + ), + // SizedBox(width: _marginLeft), + // getIcon(Icons.query_builder), + // getTrailText(':' + mapGetHycsShenheData['addtime'], off: _iconSize), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: _marginLeft), + Container( + alignment: Alignment(-1, 0), + width: 300, + child: getTitleText('复审时间:' + mapGetHyfhShenheData['addtime']), + ), + ], + ) + ], + ); + } + + //5、得到推送交警状态组件 + Widget getTsjjStatus() { + return Container( + width: ScreenUtil().setWidth(1022), + height: ScreenUtil().setHeight(1 == _mapTsjjGetTsStatus['tszt'] || + 2 == _mapTsjjGetTsStatus['tszt'] || + 2 != _mapTsjjGetTsStatus['tszt'] && 1 == sfyc + ? 170 + : 90), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12), + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + getWzxxPart7(), + ], + ), + ); + } + + //7、得到推送交警状态信息组件7:推送状态 + // 20210529更新: + // tszt 整型 推送状态:0-未推送 | 1-推送失败 | 2-推送成功 | 3-规定时间内已有违章记录,本次不推送 | 4-现场登记,不推送 + //_mapTsjjGetTsStatus['tszt'] + Widget getWzxxPart7() { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: _marginLeft), + Container( + alignment: Alignment(-1, 0), + width: ScreenUtil().setWidth(1022) - _marginLeft, + child: Container( + child: getTitleText( + '推送状态:' + + mapTsztText[_mapTsjjGetTsStatus['tszt']] + + (1 == _mapTsjjGetTsStatus['tszt'] || 2 == _mapTsjjGetTsStatus['tszt'] + ? '\n推送时间:${getDate((_mapTsjjGetTsStatus['ts_time'] is int) ? _mapTsjjGetTsStatus['ts_time'] : int.parse(_mapTsjjGetTsStatus['ts_time']))}' + : '') + + (0 == _mapTsjjGetTsStatus['tszt'] && 1 == sfyc + ? ',但已超时。当前时间 > 抓拍时间 + 审核间隔,不能推送交警!' + : ''), + lines: 2, + color: Colors.blue), + ), + ), + ], + ); + } + + //10、得到推送交警确认组件 + Widget getTsjjQr() { + Widget qxButton = getBtnSizeX( + text: "取消", + onPressedFun: showMoreWidget + ? null + : () async { + Navigator.pop(context); + }, + width: 90.0); + + // App.Car_Hyc.GetTs接口返回值说明中没有tszt为2,但许多记录返回值都是2,返回值为2是否是推送成功? + // App.Car_Hyc.GetTs接口返回值说明有误,返回值为2表示推送成功、而不是3 + // tszt 整型 推送状态:0-未推送 | 1-推送失败 | 3-推送成功 + // 20210529更新: + // tszt 整型 推送状态:0-未推送 | 1-推送失败 | 2-推送成功 | 3-规定时间内已有违章记录,本次不推送 | 4-现场登记,不推送 + //_mapTsjjGetTsStatus['tszt'] + return Container( + //padding: EdgeInsets.only(top: ScreenUtil().setWidth(6)), + width: ScreenUtil().setWidth(1022), + height: ScreenUtil().setHeight(155), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12), + ), + ), + child: 2 == _mapTsjjGetTsStatus['tszt'] + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text('已推送', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + SizedBox(width: 2), + Padding( + padding: EdgeInsets.only(top: 3), + child: Icon( + Icons.check, + size: 18, + ), + ), + ], + ), + qxButton, //'取消' + ], + ) + : 1 == sfyc + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + getBtnSizeX(text: '已超时', onPressedFun: null, width: 90.0), //'复审提交' + qxButton, //'取消' + ], + ) + : Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + getBtnSizeX( + text: '推送', + onPressedFun: showMoreWidget + ? null + : () async { + print('等待推送交警确认'); + await Navigator.of(context) + .push( + PageRouteBuilder( + opaque: false, + pageBuilder: (context, animation, secondaryAnimation) => + CustomDialogF(title: '推送确认', content: '是否推送交警?'), + ), + ) + .then((value) async { + print('value = $value'); + if (value) { + print('用户已确认,开始处理推送交警!'); + //return; + print('before tsjjFun(widget.id)'); + showMoreWidget = true; + try_setState(); + String _plateAndID = _mapGetTsjjGetData['plate_id'].toString() + + '(ID:${widget.id.toString()})'; + tsjjFun(widget.id, _plateAndID); + showMoreWidget = false; + try_setState(); + + Fluttertoast.showToast( + msg: '$_plateAndID 已推送交警,请等待返回结果。', + gravity: ToastGravity.CENTER); + } else { + print('用户取消了推送交警操作'); + } + }); + + //Unhandled Exception: type 'String' is not a subtype of type 'int' of 'result' + Navigator.pop(context, -1); + }, + width: 90.0), //'复审提交' + qxButton, //'取消' + ], + ), + ); + } + + bool showMoreWidget = false; + + @override + Widget build(BuildContext context) { + return Scaffold( + //resizeToAvoidBottomPadding: false, + appBar: PreferredSize( + preferredSize: Size.fromHeight(ScreenUtil().setHeight(173)), // 设置appBar高度 + child: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + titleSpacing: 0.0, + flexibleSpace: Container( + //SizedBox(height: ScreenUtil().statusBarHeight), //显示顶部状态栏 + // SizedBox(height: ScreenUtil().setHeight(10)), //显示顶部状态栏 + padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight), //留出顶部状态栏高度 + child: Container( + //height: ScreenUtil().setHeight(173), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + // decoration: BoxDecoration( + // gradient: LinearGradient(colors: [ + // Color(0xFF0018EB), + // Color(0xFF01C1D9), + // ], begin: Alignment.bottomCenter, end: Alignment.topCenter), + // ), + ), + ), + //设置title的左边距 + title: Padding( + padding: EdgeInsets.only(left: 0, right: 0), + child: Row( + //mainAxisAlignment: MainAxisAlignment.start, + children: [ + //1.1、返回按钮 + getIconAndTextButton( + iconColor: Colors.white, + iconData: Icons.chevron_left_outlined, + onPress: () { + Navigator.pop(context); + }, + ), + Expanded( + child: Text(_title, + style: TextStyle(color: Colors.white, fontSize: 20), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis), + ), + SizedBox(width: 30), + ], + ), + ), + ), + ), + body: null == imageWztp + // 显示加载中的圈圈 + ? getMoreWidget(color: Colors.black38, size: 20.0, strokeWidth: 2.0) + : Stack( + children: [ + //SizedBox.shrink() 创建父类允许最小尺寸的约束Box + showMoreWidget + ? Align( + alignment: Alignment(0, 0.8), + child: Container( + height: 200, + width: 200, + child: getMoreWidget2( + text: '加载中...', + color: Colors.red, + size: 40.0, + strokeWidth: 3.0), //显示加载中的圈圈, + ), + ) + : SizedBox.shrink(), + KeyboardAvoider( + autoScroll: true, + child: Container( + color: Color.fromRGBO(244, 244, 244, 1), + child: Column( + children: [ + //1、得到格林曼黑度标准和视频播放按钮组件 + getHdAndPlay(), + //2、得到违章图片组件 + //getWztp(), + imageWztp, + SizedBox(height: ScreenUtil().setHeight(_marginVer)), + //3、得到违章图片说明信息组件 + getWztpSmxx(), + SizedBox(height: ScreenUtil().setHeight(_marginVer)), + //4、得到审核信息组件 + getShxx(), + SizedBox(height: ScreenUtil().setHeight(_marginVer)), + //5、得到推送交警状态组件 + getTsjjStatus(), + SizedBox(height: ScreenUtil().setHeight(_marginVer)), + //9、得到推送交警确认组件 + getTsjjQr(), + SizedBox(height: 10), + ], + ), + ), + ), + Positioned( + //alignment: Alignment(0.9, 0.35), + //alignment: Alignment(0.8, 0.48), + left: ScreenUtil().setWidth(80), + top: ScreenUtil().setHeight(1015), + child: Container( + //alignment: Alignment(0.5, -0.5), + width: _stampSize, + height: _stampSize, + //color: Colors.black12, + decoration: BoxDecoration( + //color: Colors.white, + image: DecorationImage( + image: AssetImage("assets/images/jkzx_stamp.png"), fit: BoxFit.contain), + ), + //child: + ), + ), + Positioned( + //alignment: Alignment(0.9, 0.35), + //alignment: Alignment(0.8, 0.48), + right: ScreenUtil().setWidth(80), + top: ScreenUtil().setHeight(1015), + child: Container( + //alignment: Alignment(0.5, -0.5), + width: _stampSize, + height: _stampSize, + //color: Colors.black12, + decoration: BoxDecoration( + //color: Colors.white, + image: DecorationImage( + image: AssetImage("assets/images/hyc.png"), fit: BoxFit.contain), + ), + //child: + ), + ), + ], + ), + ); + } + + Widget getBtnSizeX({@required text, width = 70.0, height = 35.0, onPressedFun}) { + return Container( + color: Colors.white12, //onPressedFun为null时无效 + width: width, + height: height, + child: RaisedButton( + padding: EdgeInsets.all(0), + textColor: Colors.black, + child: Text(text), + onPressed: onPressedFun, + ), + ); + } +} diff --git a/lib/pages/Works/LED_XSXX/led_xsxx_content.dart b/lib/pages/Works/LED_XSXX/led_xsxx_content.dart new file mode 100644 index 0000000..bb71fa1 --- /dev/null +++ b/lib/pages/Works/LED_XSXX/led_xsxx_content.dart @@ -0,0 +1,676 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hyzp_ybqx/services/EventBus.dart'; +import 'package:hyzp_ybqx/widget/JdButton.dart'; +import 'package:keyboard_avoider/keyboard_avoider.dart'; + +import '../../../components/commonFun.dart'; +import '../../../components/dioFun.dart'; +import '../../../components/hyxx_data_handle.dart'; +import '../../../widget/DropdownItem.dart'; + +class LedXsxxContent extends StatefulWidget { + LedXsxxContent({ + @required this.sbgllx, //sbgllx: 'led_xsxx', //设备管理类型:LED显示信息 + @required this.title, + @required this.id, + Key key, + }) : super(key: key); + String title; + int id; + String sbgllx; + + _LedXsxxPageState createState() => _LedXsxxPageState(); +} + +//用TabController实现顶部tab切换 +class _LedXsxxPageState extends State { + //try_setState(); //避免异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + dispose() { + super.dispose(); + } + + String _title = ''; + + //flutter_screenUtil 4.x 用法,ScreenUtil.screenWidth (sdk>=2.6 : 1.sw) //设备宽度 + double _screenWidth = 1.sw; + + double _marginLeft = 15; + double _marginCenter = 5; + double _marginCenterHeight = 20; + double _widgetHeight = 35; + double _fontSize = 16; + double _widthLeft = 40; // = _screenWidth / 3; + double _iconSize = 18; + double _stampSize = 100; + double _listTileHeight = 30; + Color _iconColor = Colors.blue; + double _titleWidth = 130; + + //处理全部记录 + bool _modifyAll = false; + + void initState() { + super.initState(); + + // listLedXsxxGetList2.clear(); + // ///从接口 mapHyshlx[hyshlx]['api'] 获取第 iPage 页的列表数据,返回 list + // getPageList().then((value) { + // listLedXsxxGetList2 = value; + // //按照用户选择的_selectedValue、_descending对listLedXsxxGetList2进行排序,并延时更新 + // }); + + ///从接口 mapHyshlx[theHyshlx]['api'] 获取指定类型第 page 页的列表数据,返回 list + ///获取点位信息数据 + listDwinfoGetList2.clear(); + getThePageList(theHyshlx: 'dwxx').then((value) { + listDwinfoGetList2 = value; + print('listDwinfoGetList2 = \n$listDwinfoGetList2'); + getListFlields(); + }); + //listDwinfoGetList2 = [{ + // "id": 1, + // "dwip": "172.16.3.1", + // "dwmc": "江北振兴大道", + // "dwbh": 1, + // "dwinfo": "江北振兴大道入城方向", + // "dwzb": "104.607091|28.807061", + // "dwms": "江北振兴大道入城方向,识别孜岩、红坝路入城排放黑烟车辆" + //}, + //{ + // "id": 2, + // "dwip": "172.16.3.2", + // "dwmc": "宜飞路", + // "dwbh": 2, + // "dwinfo": "宜宾南收费站宜飞路入城方向", + // "dwzb": "104.589904|28.787078", + // "dwms": "宜宾南收费站宜飞路入城方向,识别屏山、菜坝入城排放黑烟车辆" + //},]; + + _widthLeft = _screenWidth / 2.6; + + //监听 选择LED点位 更新事件 + eventBus.on().listen((event) async { + //获取用户选择项对应的记录id + widget.id = _listItems.indexOf(event.selectedValue); + print('原生 widget.id = ${widget.id}'); + if (widget.id < 1) { + _modifyAll = true; //处理全部记录 + widget.id = 1; + } else { + _modifyAll = false; //处理全部记录 + } + print('widget.id = ${widget.id}'); + + //刷新页面数据 + await getListFlields(); + + //刷新 显示违章记录 Dropdown + // print('_mapGetLedXsxxGetData = ${_mapGetLedXsxxGetData}'); + // print('_mapGetLedXsxxGetData["xsts"] = ${_mapGetLedXsxxGetData["xsts"]}'); + // print( + // 'getWzjlString(_mapGetLedXsxxGetData["xsts"]) = ${getWzjlString(_mapGetLedXsxxGetData["xsts"])}'); + eventBus.fire(SelectWzjlUpdateEvent( + 'external_SelectWzjlUpdateEvent', getWzjlString(_mapGetLedXsxxGetData["xsts"]))); + + try_setState(); //避免异常报错 + print(event.str); + }); + + //监听 选择显示违章记录 更新事件 + eventBus.on().listen((event) async { + print('SelectWzjlUpdateEvent: ${event.str}'); + print('SelectWzjlUpdateEvent: ${event.selectedValue}'); + _selectedValue = event.selectedValue; + _xsts = getWzjlCount(event.selectedValue); + //只响应内部发送的'insider_SelectWzjlUpdateEvent' + //不响应外部发送的'external_SelectWzjlUpdateEvent' + if (event.str == 'external_SelectWzjlUpdateEvent') { + //刷新页面数据 + try_setState(); //避免异常报错 + } + }); + } + + //App.Car_Led.Get接口获取的记录数据结构(比App.Car_Led.GetList接口获取的丰富) + Map _mapGetLedXsxxGetData = { + "id": 2, + "dwip": "172.16.3.2", + "xsnr": "绿水青山就是金山银山 宜宾市生态环境局宣。", + "xsts": 0, //显示抓拍到的多少条违章记录 + "stime": "07:00", + "etime": "23:00", + "addtime": "2021-01-20 10:16:07", + "updatetime": "2021-02-13 11:48:51" + }; + + //Web管理平台LED点位名称。除第一项外,估计后面都按ID号排列 + // 全部 + // 1 '江北振兴大道', + // 2 '宜飞路', + // 3 '宜宾南收费站', + // 4 '一曼路', + // 5 '柏溪收费站', + // 6 '七星路万达广场', + // 7 '宜宾财政局', + // 8 '宜威路南广镇', + // 9 '宜长路', + // 10 '宜南快速通道', + // 11 '观斗山隧道', + // 12 '大麦坝', + // 13 '外江路', + + //LED点位名称List,除第一项外,后面都按ID号排列 + List _listItems = [ + // '全部', + // '1、江北振兴大道', + // '2、宜飞路', + // '3、宜宾南收费站', + // '4、一曼路', + // '5、柏溪收费站', + // '6、七星路万达广场', + // '7、宜宾财政局', + // '8、宜威路南广镇', + // '9、宜长路', + // '10、宜南快速通道', + // '11、观斗山隧道', + // '12、大麦坝', + // '13、外江路', + ]; + + String _selectedValue = '不显示'; + int _xsts = 0; + String _startTime = '07:00'; + String _endTime = '23:00'; + String _ledMessage = '绿水青山就是金山银山 宜宾市生态环境局宣。'; + + //App.Car_Led.Get接口获取的记录数据结构(比App.Car_Led.GetList接口获取的丰富) + // Map _mapGetLedXsxxGetData = { + // "id": 2, + // "dwip": "172.16.3.2", + // "xsnr": "绿水青山就是金山银山 宜宾市生态环境局宣。", + // "xsts": 0, //显示抓拍到的多少条违章记录 + // "stime": "07:00", + // "etime": "23:00", + // "addtime": "2021-01-20 10:16:07", + // "updatetime": "2021-02-13 11:48:51" + // }; + + Future getListFlields() async { + //实时更新_listItems内容 + _listItems = ['全部']; //LED点位名称List,除第一项外,后面都按ID号排列 + int len = listDwinfoGetList2.length; + for (int i = 0; i < len; i++) { + _listItems.add('${i + 1}、${listDwinfoGetList2[i]['dwmc']}'); + } + print('_listItems = $_listItems'); + + //获取指定id的LED记录数据返回 _mapGetLedXsxxGetData + _mapGetLedXsxxGetData = await getLedXsxxGetData(id: widget.id, theSbgllx: widget.sbgllx); + //printWrapped('_mapGetLedXsxxGetData = ${_mapGetLedXsxxGetData}'); + //_mapGetLedXsxxGetData = { + // "id": 1, + // "dwip": "172.16.3.1", + // "xsnr": "绿水青山就是金山银山 宜宾市生态环境局宣。", + // "xsts": 0, + // "stime": "07:00", + // "etime": "23:00", + // "addtime": "2021-01-06 00:59:43", + // "updatetime": "2021-02-03 19:57:55" + // }; + + _xsts = _mapGetLedXsxxGetData['xsts']; + _ledMessage = _mapGetLedXsxxGetData['xsnr']; + _startTime = _mapGetLedXsxxGetData['stime']; + _endTime = _mapGetLedXsxxGetData['etime']; + + try { + // _title =listDwinfoGetList2 + // '${widget.title}(${(getIndex(widget.id) + 1).toString()} / ${listLedXsxxGetList2.length})id:${widget.id.toString()}'; + //_title = '${widget.title}(${widget.id.toString()} / ${listDwinfoGetList2.length})id:${widget.id.toString()}'; + //_title = '${widget.title}(${widget.id.toString()} / ${listDwinfoGetList2.length})${listDwinfoGetList2[widget.id - 1]['dwmc']}'; + _title = '${widget.title}(${widget.id.toString()} / ${listDwinfoGetList2.length})'; + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + + // List listLedXsxxGetData = []; + // for(int i = 0; i < 13; i++) { + // listLedXsxxGetData.add(await getLedXsxxGetData(i + 1)); + // } + // print('listLedXsxxGetData = \nlistLedXsxxGetData'); + } + + //获取 listLedXsxxGetList2 中ID号为 _id 的索引号 + // int getIndex(int _id) { + // int len = listLedXsxxGetList2.length; + // int _index; + // for (_index = 0; _index < len; _index++) { + // if (listLedXsxxGetList2[_index]['id'] == _id) { + // break; + // } + // } + // return _index; + // } + + Widget getTitleText(String text, {double fontSize = 16}) { + return Text(text, + style: TextStyle(fontSize: fontSize), + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis); + } + + Widget getSizeText(String text, {double fontSize = 16, double width = 100, double top = 0}) { + return Container( + margin: EdgeInsets.only(top: top), + width: width, + child: Text(text, + style: TextStyle(fontSize: fontSize), + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis), + ); + } + + Widget getIcon(IconData _iconData) { + return Container( + width: _iconSize - 2, + height: _iconSize, + child: Padding( + padding: EdgeInsets.only(top: 2), + child: Icon(_iconData, size: _iconSize, color: _iconColor), + ), + ); + } + + //5、得到LED显示信息确认组件 + Widget getLedQr() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + JdButton( + height: 126, + //JdText中已经使用ScreenUtil().setHeight(126),此处不能传 ScreenUtil().setHeight(126) ,否则严重错位 + width: 350, + text: "更新", + color: Color.fromRGBO(45, 202, 115, 1), + onTop: () { + printWrapped('_message = ${_ledMessage}'); + print('_xsts = ${_xsts}'); + print('_startTime = ${_startTime}'); + print('_endTime = ${_endTime}'); + + //updateLedData({@required int id, @required String theSbgllx, @required Map map}) + //_modifyAll = true; //id = -1,处理全部记录 + updateLedData(id: _modifyAll ? -1 : widget.id, theSbgllx: 'led_update', map: { + 'xsnr': _ledMessage, + 'xsts': _xsts, + 'stime': _startTime, + 'etime': _endTime, + }); + Navigator.pop(context); + }, + ), + JdButton( + height: 126, + //JdText中已经使用ScreenUtil().setHeight(126),此处不能传 ScreenUtil().setHeight(126) ,否则严重错位 + width: 350, + text: "取消", + color: Color.fromRGBO(43, 163, 255, 1), + onTop: () { + Navigator.pop(context); + }, + ), + ], + ); + } + + //得到LED信息页面组件 + //1、得到 选择LED点位 组件 + Widget getSelectLedDw() { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: _marginLeft), + getSizeText('选择点位:', width: _titleWidth), + //SizedBox(width: _marginCenter), + //Expanded(child: SizedBox.shrink()), + DropdownItem( + listItems: _listItems, + //初始值 initValue 必须是 listItems 中的已有元素 + initValue: _listItems[widget.id], + dropdownEvent: 'SelectLedDwUpdateEvent', + width: ScreenUtil().setWidth(590), + height: _widgetHeight, + ), //SizedBox(width: _marginLeft), + SizedBox(width: _marginLeft), + ], + ); + } + + //2、得到选择 显示违章记录 组件 + Widget getSelectWzjl() { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: _marginLeft), + getSizeText('显示违章记录:', width: _titleWidth), + // Expanded(child: SizedBox.shrink()), + DropdownItem( + listItems: _listItemsWzjl, + initValue: getWzjlString(_xsts), + width: ScreenUtil().setWidth(590), + height: _widgetHeight, + dropdownEvent: 'SelectWzjlUpdateEvent', + ), + SizedBox(width: _marginLeft), + ], + ); + } + + List _listItemsWzjl = [ + '不显示', + '显示3条', + '显示5条', + '显示10条', + ]; + + //通过违章信息的条数得到显示的字符串 + String getWzjlString(int wzjlCount) { + String _wzjlString = ''; + switch (wzjlCount) { + case 0: + _wzjlString = _listItemsWzjl[0]; + break; + case 3: + _wzjlString = _listItemsWzjl[1]; + break; + case 5: + _wzjlString = _listItemsWzjl[2]; + break; + case 10: + _wzjlString = _listItemsWzjl[3]; + break; + default: + break; + } + return _wzjlString; + } + + //通过字符串得到显示违章信息的条数 + int getWzjlCount(String wzjlString) { + int _wzjlCount = -1; + switch (wzjlString) { + case '不显示': + _wzjlCount = 0; + break; + case '显示3条': + _wzjlCount = 3; + break; + case '显示5条': + _wzjlCount = 5; + break; + case '显示10条': + _wzjlCount = 10; + break; + default: + break; + } + return _wzjlCount; + } + + //3、得到 启用时段 组件 + Widget getOpenTime() { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: _marginLeft), + getSizeText('启用时段:', width: _titleWidth), + //SizedBox(width: _marginCenter), + getInBox(0, _widgetHeight), //whichTime:0为_startTime,1为_endTime + Text(' - '), + getInBox(1, _widgetHeight), //whichTime:0为_startTime,1为_endTime + ], + ); + } + + //4、得到 显示信息 组件 + Widget getLedMessage(double _width, double _height) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(width: _marginLeft), + getSizeText('显示信息:', width: _titleWidth, top: 2), + ], + ), + SizedBox(height: ScreenUtil().setHeight(10)), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(width: _marginLeft), + getInBoxMsg(_width, _height), //whichTime:0为_startTime,1为_endTime + ], + ), + ], + ); + } + + //显示信息 输入框 + Widget getInBoxMsg(double _width, double _height) { + return ConstrainedBox( + constraints: BoxConstraints( + minHeight: ScreenUtil().setHeight(_height), + maxHeight: ScreenUtil().setHeight(_height), + minWidth: ScreenUtil().setWidth(_width), + maxWidth: ScreenUtil().setWidth(_width), + ), + child: TextField( + keyboardType: TextInputType.multiline, + maxLines: 10, + //不限制行数 + textAlign: TextAlign.justify, + style: TextStyle(fontSize: my_fontSize), + decoration: InputDecoration( + hintText: '请输入LED显示信息', + contentPadding: EdgeInsets.only(top: 4, left: 8, bottom: 4, right: 8), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey, width: 2.0), + borderRadius: BorderRadius.circular(3.0)), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey, width: 2.0), + borderRadius: BorderRadius.circular(3.0)), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(3), + borderSide: BorderSide( + color: Colors.grey, + width: 2.0, + ), + ), + // border: OutlineInputBorder( + // borderRadius: BorderRadius.circular(3.0), + // borderSide: BorderSide(color: Colors.grey, width: 0)), + ), + controller: TextEditingController.fromValue(TextEditingValue( + text: _ledMessage, + // 保持光标在最后 + // selection: TextSelection.fromPosition( + // TextPosition(affinity: TextAffinity.downstream, offset: _message.length)) + )), + enabled: true, + onChanged: (value) { + _ledMessage = value; + printWrapped(_ledMessage); + }, + ), + ); + } + + //whichTime:0为_startTime,1为_endTime + Widget getInBox(int _whichTime, double _height) { + return Container( + alignment: Alignment(0, 0), + height: _height, + width: 90, + // decoration: BoxDecoration( + // border: Border.all(color: Colors.grey, width: 2), + // borderRadius: BorderRadius.all(Radius.circular(3.0)), + // ), + child: TextField( + textAlign: TextAlign.center, + style: TextStyle(fontSize: my_fontSize), + decoration: InputDecoration( + hintText: _whichTime == 0 ? '启用时间' : '关闭时间', + contentPadding: EdgeInsets.all(0), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey, width: 2.0), + borderRadius: BorderRadius.circular(3.0)), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey, width: 2.0), + borderRadius: BorderRadius.circular(3.0)), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(3), + borderSide: BorderSide( + color: Colors.grey, + width: 2.0, + ), + ), + // border: OutlineInputBorder( + // borderRadius: BorderRadius.circular(3.0), + // borderSide: BorderSide(color: Colors.grey, width: 0)), + ), + controller: TextEditingController.fromValue(TextEditingValue( + text: (_whichTime == 0 ? _startTime : _endTime), + // 保持光标在最后 + selection: TextSelection.fromPosition(TextPosition( + affinity: TextAffinity.downstream, + offset: (_whichTime == 0 ? _startTime : _endTime).length)))), + enabled: true, + onChanged: (value) { + if (_whichTime == 0) { + _startTime = value; + } else { + _endTime = value; + } + }, + ), + ); + } + + String _sizedropDown = 'brown'; + + @override + Widget build(BuildContext context) { + return Scaffold( + //resizeToAvoidBottomPadding: false, + appBar: PreferredSize( + preferredSize: Size.fromHeight(ScreenUtil().setHeight(173)), // 设置appBar高度 + child: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + titleSpacing: 0.0, + //设置title的左边距 + flexibleSpace: Container( + //SizedBox(height: ScreenUtil().statusBarHeight), //显示顶部状态栏 + // SizedBox(height: ScreenUtil().setHeight(10)), //显示顶部状态栏 + padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight), //留出顶部状态栏高度 + child: Container( + //height: ScreenUtil().setHeight(173), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + // decoration: BoxDecoration( + // gradient: LinearGradient(colors: [ + // Color(0xFF0018EB), + // Color(0xFF01C1D9), + // ], begin: Alignment.bottomCenter, end: Alignment.topCenter), + // ), + ), + ), + title: Padding( + padding: EdgeInsets.only(left: 0, right: 0), + child: Row( + //mainAxisAlignment: MainAxisAlignment.start, + children: [ + getIconAndTextButton( + iconColor: Colors.white, + iconData: Icons.chevron_left_outlined, + onPress: () { + Navigator.pop(context); + }, + ), + Expanded( + child: Text(_title, + style: TextStyle(color: Colors.white, fontSize: 20), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis), + ), + SizedBox(width: 30), + ], + ), + ), + ), + ), + body: _listItems.isEmpty || _mapGetLedXsxxGetData == null + ? getMoreWidget(color: Colors.grey) + : KeyboardAvoider( + autoScroll: true, + child: Container( + child: Column( + children: [ + //1、得到选择LED点位组件 + SizedBox(height: _marginCenterHeight), + getSelectLedDw(), + Divider(height: _marginCenterHeight), + //2、得到选择 显示违章记录 组件 + getSelectWzjl(), + Divider(height: _marginCenterHeight), + //3、得到 启用时段 组件 + getOpenTime(), + Divider(height: _marginCenterHeight), + //4、得到 显示信息 组件 + getLedMessage(980, 570), + Divider(height: _marginCenterHeight), + SizedBox(height: _marginCenterHeight), + //5、得到Led确认组件 + getLedQr(), + SizedBox(height: _marginCenterHeight), + ], + ), + ), + ), + ); + } + + Widget getBtnSizeX({@required text, width = 70.0, height = 35.0, onPressedFun}) { + return Container( + color: Colors.white12, //onPressedFun为null时无效 + width: width, + height: height, + child: RaisedButton( + padding: EdgeInsets.all(0), + textColor: Colors.black, + child: Text(text), + onPressed: onPressedFun, + ), + ); + } +} diff --git a/lib/pages/Works/LED_XSXX/led_xsxx_getList.dart b/lib/pages/Works/LED_XSXX/led_xsxx_getList.dart new file mode 100644 index 0000000..493288a --- /dev/null +++ b/lib/pages/Works/LED_XSXX/led_xsxx_getList.dart @@ -0,0 +1,568 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; + +import 'led_xsxx_content.dart'; +import '../../../components/dioFun.dart'; +import '../../../components/hyxx_data_handle.dart'; +import '../../../components/commonFun.dart'; +import '../../../services/EventBus.dart'; +import 'package:flutter_easyrefresh/easy_refresh.dart'; +import '../../../components/doJSON.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + + +//LedXsxx是本项目中“LED显示信息”的统一缩写 +class LedXsxxGetList extends StatefulWidget { + //hyshlx为黑烟审核类型,处理'LedXsxx'LED显示信息。mapHyshlx[hyshlx]获取为各种类型的设置数据 + LedXsxxGetList({this.sbgllx = 'led_xsxx', Key key}) : super(key: key); + String sbgllx; + + _LedXsxxPageState createState() => _LedXsxxPageState(); +} + +class _LedXsxxPageState extends State { + //try_setState(); //避免异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + @override + void dispose() { + _controller.dispose(); //销毁控制器 + super.dispose(); + } + + @override + void initState() { + hyshlx = widget.sbgllx; + iPage = 0; + listLedXsxxGetList2.clear(); + + ///从接口 mapHyshlx[hyshlx]['api'] 获取第 iPage 页的列表数据,返回 list + getPageList().then((value) { + listLedXsxxGetList2 = value; + //按照用户选择的_selectedValue、_descending对listLedXsxxGetList2进行排序,并延时更新 + _listSort(); + firstIndex = 0; + lastIndex = 7; + }); + + ///从接口 mapHyshlx[theHyshlx]['api'] 获取指定类型第 page 页的列表数据,返回 list + ///获取点位信息数据 + listDwinfoGetList2.clear(); + getThePageList(theHyshlx: 'dwxx').then((value) { + listDwinfoGetList2 = value; + print('listDwinfoGetList2 = \n$listDwinfoGetList2'); + }); + + //监听违章信息数据更新事件 + eventBus.on().listen((event) async { + iPage = 0; + print(event.str); + //按照用户选择的_selectedValue、_descending对listLedXsxxGetList2进行排序,并延时更新 + _listSort(); + }); + + //监听违章信息数据审核事件 + eventBus.on().listen((event) async { + print('LedXsxxGetList: ' + event.str); + //按照用户选择的_selectedValue、_descending对listLedXsxxGetList2进行排序,并延时更新 + _listSort(); + }); + + //监听违章信息Listview滚动事件 + eventBus.on().listen((event) async { + firstIndex = event.firstIndex; + lastIndex = event.lastIndex; + try_setState(); + }); + + super.initState(); + } + + //Led数据变量 + // Map mapGetLedXsxxGetData = { + // "id": 2, + // "xsnr": "绿水青山就是金山银山 宜宾市生态环境局宣。", + // "addtime": "2021-01-20 10:16:07", + // "updatetime": "2021-02-13 11:48:51" + // }; + Widget _getListTile(BuildContext context, indexRecord) { + return Column( + children: [ + ListTile( + //leading: new Icon(Icons.phone), + title: Text( + "${(indexRecord + 1).toString()}. " + + listLedXsxxGetList2[indexRecord]['addtime'] + + ' 添加', + style: TextStyle(fontSize: 10)), + subtitle: Text(getDate(listLedXsxxGetList2[indexRecord]['updatetime']) + ' 更新', + style: TextStyle(fontSize: 10)), + trailing: Container( + width: 180, + child: Text( + 'id:' + + listLedXsxxGetList2[indexRecord]['id'].toString() + + ', 显示信息:' + + listLedXsxxGetList2[indexRecord]['xsnr'], + maxLines: 2, + overflow: TextOverflow.ellipsis, + //textAlign: TextAlign.right, + style: TextStyle(fontSize: 12), + ), + ), + contentPadding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 0), + enabled: true, + onTap: () async { + int ret = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => LedXsxxContent( + title: 'LED显示信息详情', + sbgllx: widget.sbgllx, + id: listLedXsxxGetList2[indexRecord]['id'], + ), + ), + ); + + print('ret = $ret'); + }, + ), + Divider(height: 1.0), + ], + ); + } + + ScrollController _controller = ScrollController(); //ListView控制器 + bool isLoading = false; //正在处理下载数据、跳转到首项、跳转到尾项等操作 + int firstIndex = 0; //ListView当前显示页面首项0基序号 + int lastIndex = 0; //ListView当前显示页面末项0基序号 + int itemOnPage = 8; //估计一屏显示的项目数量 + + Widget getIconButton({IconData iconData, var onPressed, double iconSize = 22}) { + return SizedBox( + height: iconSize, + width: iconSize + 10, + child: IconButton( + padding: EdgeInsets.all(0.0), + icon: Icon(iconData, size: iconSize), + onPressed: onPressed, + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + //leading: Text(''), + titleSpacing: 0.0, + //设置title的左边距 + title: Padding( + padding: EdgeInsets.only(left: 0, right: 0), + child: Row( + //mainAxisAlignment: MainAxisAlignment.start, + children: [ + getIconAndTextButton( + iconData: Icons.arrow_back, + onPress: () { + Navigator.pop(context); + }, + ), + Expanded( + child: Text("${mapHyshlx[hyshlx]['text']}", + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis, + style: widget.sbgllx == 'fhycx' ? TextStyle(fontSize: 16) : null), + ), + SizedBox(width: 5), + ], + ), + ), + actions: [ + getDropdownButton(), + SizedBox(width: 10), + getIconButton( + iconData: Icons.cloud_download, + onPressed: !isLoading + ? () async { + if (isLoading) { + return; + } + isLoading = true; + try_setState(); + //print("I Pressed the Iconbutton"); + int oldItems = listLedXsxxGetList2.length; + for (int i = 0; i < 10; i++) { + List list = await getPageList(); + if (list.length > 0) { + listLedXsxxGetList2.addAll(list); //加载累加 + } else { + break; + } + } + Future.delayed(const Duration(milliseconds: 1000), () async { + if (listLedXsxxGetList2.length > oldItems) { + eventBus.fire(WzxxDataAuditEvent('${mapHyshlx[hyshlx]['text']}数据已更新')); + Fluttertoast.showToast( + msg: '新增 ${listLedXsxxGetList2.length - oldItems} 条数据下载完成!', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + + //按照用户选择的_selectedValue、_descending对listLedXsxxGetList2进行排序,并延时更新 + _listSort(); + } + isLoading = false; + try_setState(); + }); + } + : null, + ), + getIconButton( + iconData: Icons.vertical_align_top_outlined, + onPressed: (!isLoading && (firstIndex > 0)) //未到顶部 + ? () async { + if (isLoading) { + return; + } + isLoading = true; + try_setState(); + + //必须延时执行,否则不能及时完成按钮状态更新 + Timer( + Duration(milliseconds: 500), + () { + _controller.jumpTo(_controller.position.minScrollExtent); + }, + ); + + Timer( + Duration(milliseconds: 1000), + () { + // Fluttertoast.showToast( + // msg: '已经跳转到开头记录!', + // toastLength: Toast.LENGTH_SHORT, + // gravity: ToastGravity.CENTER, + // ); + firstIndex = 0; + lastIndex = itemOnPage - 1; //0基序号,所以需要减1 + isLoading = false; + try_setState(); + }, + ); + } + : null, + ), + getIconButton( + iconData: Icons.vertical_align_bottom_outlined, + onPressed: (!isLoading && (lastIndex < listLedXsxxGetList2.length)) //未到尾部 + ? () async { + if (isLoading) { + return; + } + isLoading = true; + try_setState(); + + //必须延时执行,否则不能及时完成按钮状态更新 + Timer( + Duration(milliseconds: 500), + () { + _controller.jumpTo(_controller.position.maxScrollExtent); + }, + ); + + Timer( + Duration(milliseconds: 1000), + () { + // Fluttertoast.showToast( + // msg: '已经跳转到末尾记录!', + // toastLength: Toast.LENGTH_SHORT, + // gravity: ToastGravity.CENTER, + // ); + //0基序号需要减1,再加上底部有一组显示信息,所以需要减2 + firstIndex = listLedXsxxGetList2.length - itemOnPage - 2; + lastIndex = listLedXsxxGetList2.length; + isLoading = false; + try_setState(); + }, + ); + } + : null, + ), + ], + ), + body: (0 == listLedXsxxGetList2.length) + ? getMoreWidget(color: Colors.black38) + : EasyRefresh( + child: ListView.custom( + controller: _controller, + cacheExtent: 1.0, // 只有设置了1.0 才能够准确的标记position 位置 + childrenDelegate: MyChildrenDelegate( + _getListTile, + childCount: listLedXsxxGetList2.length, + ), + ), + onRefresh: () async { + // await Future.delayed(const Duration(seconds: 1), () async { + // iPage = 0; + // //await getWzxxGetList(); + // listLedXsxxGetList2 = await getPageList(); + // eventBus.fire(WzxxDataAuditEvent('${mapHyshlx[hyshlx]['text']}数据已更新')); + // }); + }, + onLoad: () async { + if (isLoading) { + return; + } + isLoading = true; + try_setState(); + + print('iPage = $iPage'); + await Future.delayed(const Duration(seconds: 1), () async { + //await getWzxxGetList(); + List list = await getPageList(); + if (list.length > 0) { + listLedXsxxGetList2.addAll(list); //加载累加 + eventBus.fire(WzxxDataAuditEvent('${mapHyshlx[hyshlx]['text']}数据已更新')); + } + isLoading = false; + try_setState(); + }); + }, + header: getHeader(), + footer: getFooter(), + ), + ); + } + + //DropdownButton需要设置初始值的时候,初始值必须是显示列表里面的值,否则会导致弹出框异常。 + // 比如说:你的DropdownButton的items属性使用的是list这个列表里面的值,那么你的初始值应该在list[index]里面取,要不就会报错。 + // + // There should be exactly one item with [DropdownButton]'s value: 0.0. + // Either zero or 2 or more [DropdownMenuItem]s were detected with the same value + // 'package:flutter/src/material/dropdown.dart': + // Failed assertion: line 834 pos 15: 'items == null || items.isEmpty || value == null || + // items.where((DropdownMenuItem item) { + // return item.value == value; + // }).length == 1' + String _selectedValue = '主键ID'; + bool _descending = false; //默认生效排列 + + Widget _getImage(String _image) { + return Container( + margin: EdgeInsets.only(), + height: ScreenUtil().setWidth(38), + width: ScreenUtil().setWidth(38), + //child: Image.asset('assets/images/ybsthbj.png', fit: BoxFit.fitHeight), + child: Image.asset(_image, + fit: BoxFit.cover, color: isLoading ? Theme.of(context).disabledColor : null)); + } + + //按照用户选择的_selectedValue、_descending对listLedXsxxGetList2进行排序,并延时更新 + Future _listSort({bool bShowToast = false}) { + if (!isLoading && listLedXsxxGetList2.length > 0) { + isLoading = true; + try_setState(); + + switch (_selectedValue) { + default: + if (_descending) { + //按_selectedValue排序,降序 + listLedXsxxGetList2.sort((a, b) => + (b[mapWzxxDataText[_selectedValue]]).compareTo(a[mapWzxxDataText[_selectedValue]])); + } else { + //按_selectedValue排序,升序 + listLedXsxxGetList2.sort((a, b) => + (a[mapWzxxDataText[_selectedValue]]).compareTo(b[mapWzxxDataText[_selectedValue]])); + } + break; + } + + Future.delayed(const Duration(milliseconds: 1000), () { + if (bShowToast) { + Fluttertoast.showToast( + msg: '按“${_selectedValue}”${_descending ? '降序' : '升序'}排列完成!', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + isLoading = false; + try_setState(); //避免如下异常报错 + }); + } + } + + Widget getDropdownButtonItemText(String item) { + return Row( + //crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + alignment: Alignment(0, 0.28), + child: item == _selectedValue + ? _descending + ? _getImage('assets/images/descending.png') + : _getImage('assets/images/ascending.png') + // ? Icon(Icons.arrow_downward_outlined, size: 20) + // : Icon(Icons.arrow_upward_outlined, size: 20) + : SizedBox(), + ), + SizedBox( + width: 3, + ), + Container( + //alignment: Alignment(0, -1), + child: Text(item, + style: isLoading + ? TextStyle(color: Theme.of(context).disabledColor) + : (item == _selectedValue ? TextStyle(color: Colors.blue) : null)), + ), + ], + ); + } + + Widget getDropdownButton() { + //DropdownMenuItem项目文本list + List itemList = [ + '主键ID', + '添加时间', + '更新时间', + '显示信息', + ]; + + //添加按'推送状态'排序 + if (hyshlx != 'led_xsxx') { + itemList.removeLast(); + itemList.addAll(['推送状态', '主键ID']); + } + + //获取DropdownMenuItem项目组件list + List> _dropDownMenuItems = + itemList.map>((String item) { + return DropdownMenuItem( + value: item, + child: getDropdownButtonItemText(item), + ); + }).toList(); + + return Padding( + padding: EdgeInsets.only(top: 10, bottom: 10), + child: Container( + alignment: Alignment(0.7, 0), + width: 125, + margin: EdgeInsets.only(bottom: 0), + padding: EdgeInsets.only(left: 0, bottom: 0), + decoration: BoxDecoration( + border: Border.all(width: 0), + //边框圆角设置 + borderRadius: + BorderRadius.vertical(top: Radius.elliptical(2, 2), bottom: Radius.elliptical(2, 2)), + ), + //DropdownButton默认有一条下划线,DropdownButtonHideUnderline去除下划线 + child: DropdownButtonHideUnderline( + child: DropdownButton( + isDense: true, + value: _selectedValue, + items: _dropDownMenuItems, + onChanged: (String selectedValue) { + if (isLoading) { + return; + } + + if (_selectedValue == selectedValue) { + _descending = !_descending; + } else { + _descending = true; + } + _selectedValue = selectedValue; + print('_selectedValue = $_selectedValue'); + + //按照用户选择的_selectedValue、_descending对listLedXsxxGetList2进行排序,并延时更新 + _listSort(bShowToast: true); + }, + ), + ), + ), + ); + } +} + +//https://blog.csdn.net/u014803467/article/details/103750018 +//Flutter 使用SliverChildBuilderDelegate获取ListView的第一个和最后一个可见Item序号 +// 秋名山交警X 2019-12-28 23:52:52 +class _SaltedValueKey extends ValueKey { + const _SaltedValueKey(Key key) + : assert(key != null), + super(key); +} + +class MyChildrenDelegate extends SliverChildBuilderDelegate { + MyChildrenDelegate( + Widget Function(BuildContext, int) builder, { + int childCount, + bool addAutomaticKeepAlive = true, + bool addRepaintBoundaries = true, + }) : super(builder, + childCount: childCount, + addAutomaticKeepAlives: addAutomaticKeepAlive, + addRepaintBoundaries: addRepaintBoundaries); + + // Return a Widget for the given Exception + Widget _createErrorWidget(dynamic exception, StackTrace stackTrace) { + final FlutterErrorDetails details = FlutterErrorDetails( + exception: exception, + stack: stackTrace, + library: 'widgets library', + context: ErrorDescription('building'), + ); + FlutterError.reportError(details); + return ErrorWidget.builder(details); + } + + @override + Widget build(BuildContext context, int index) { + assert(builder != null); + if (index < 0 || (childCount != null && index >= childCount)) return null; + Widget child; + try { + child = builder(context, index); + } catch (exception, stackTrace) { + child = _createErrorWidget(exception, stackTrace); + } + if (child == null) return null; + final Key key = child.key != null ? _SaltedValueKey(child.key) : null; + if (addRepaintBoundaries) child = RepaintBoundary(child: child); + if (addSemanticIndexes) { + final int semanticIndex = semanticIndexCallback(child, index); + if (semanticIndex != null) + child = IndexedSemantics(index: semanticIndex + semanticIndexOffset, child: child); + } + if (addAutomaticKeepAlives) child = AutomaticKeepAlive(child: child); + return KeyedSubtree(child: child, key: key); + } + + @override + void didFinishLayout(int _firstIndex, int _lastIndex) { + // TODO: implement didFinishLayout + super.didFinishLayout(_firstIndex, _lastIndex); + } + + ///监听 在可见的列表中 显示的第一个位置和最后一个位置 + @override + double estimateMaxScrollOffset( + int _firstIndex, int _lastIndex, double _leadingScrollOffset, double _trailingScrollOffset) { + //违章信息Listview滚动广播 + eventBus.fire(WzxxDataScrollEvent(_firstIndex, _lastIndex)); + + return super.estimateMaxScrollOffset( + _firstIndex, _lastIndex, _leadingScrollOffset, _trailingScrollOffset); + } +} diff --git a/lib/pages/Works/SBBJ/sbbj_content.dart b/lib/pages/Works/SBBJ/sbbj_content.dart new file mode 100644 index 0000000..f36f828 --- /dev/null +++ b/lib/pages/Works/SBBJ/sbbj_content.dart @@ -0,0 +1,595 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/screen_util.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hyzp_ybqx/components/commonFun.dart'; +import 'package:hyzp_ybqx/components/customDialogF.dart'; +import 'package:hyzp_ybqx/services/EventBus.dart'; +import 'package:hyzp_ybqx/widget/customRadioWidget.dart'; +import '../../../components/doJSON.dart'; +import '../../../components/dioFun.dart'; +import '../../../components/hyxx_data_handle.dart'; +import 'dart:io'; +import '../../../components/customDialogH.dart'; +import '../../../components/customDialogJ.dart'; + +class SbbjContent extends StatefulWidget { + //SbbjContent({Key key, this.title, this.mapData}) : super(key: key); + SbbjContent({ + @required this.title, + @required this.index, + @required this.hyshlx, + Key key, + }) : super(key: key); + String title; + int index = -1; + String hyshlx; + + _SbbjPageState createState() => _SbbjPageState(); +} + +class _SbbjPageState extends State { + Map _mapGetSbbjGetDataText = { + "id": "主键ID", + "bjlx": "设备类型", + "content": "报警内容", + "dwip": "点位IP", + "sbip": "设备IP", + "addtime": "添加时间", + "workflow": "处理状态" + }; + + Map _mapGetSbbjGetData = { + "id": 1177, + "bjlx": "风扇", + "content": "串口COM1获取风扇异常数据", + "dwip": "172.16.3.1", + "sbip": "192.168.1.10", + "addtime": "2021-02-07 23:56:45", + "workflow": 999 + }; + + List _listController = []; + int listLen = 0; + String nums = ''; + int _selectedRadio = 0; + + void initState() { + // TODO: implement initState + super.initState(); + listLen = listSbbjGetList2.length; + getListFlields(); + } + + //监听登录页面销毁的事件 + dispose() { + _controller.dispose(); + super.dispose(); + } + + getListFlields() async { + //获取指定id的sbbj记录数据返回 _mapGetSbbjGetData。由于 sbbj 信息需要进行查核处理,所以要重新读取最新的记录数据 + _mapGetSbbjGetData = + await getLedXsxxGetData(id: listSbbjGetList2[widget.index]['id'], theSbgllx: widget.hyshlx); + print('_mapGetSbbjGetData = ${_mapGetSbbjGetData}'); + + //_mapGetSbbjGetData = listSbbjGetList2[widget.index]; + _listController = List.generate(_mapGetSbbjGetData.length, (index) { + String key = _mapGetSbbjGetData.keys.elementAt(index); + + String strContent = _mapGetSbbjGetData[key].toString(); + //时间戳转换 + if (strContent.isNotEmpty && 'addtime' == key) { + strContent = getDate(strContent); + } + //workflow转换 + if (strContent.isNotEmpty && 'workflow' == key) { + strContent = strContent == '999' ? '已处理' : '待处理'; + } + var controller = TextEditingController(text: strContent); + + controller.selection = TextSelection.fromPosition( + TextPosition(affinity: TextAffinity.downstream, offset: '${controller.text}'.length), + ); + return controller; + }); + getPreBtn_NextBtn(); + nums = '${(widget.index + 1).toString()} / $listLen'; + setState(() {}); + } + + Widget getTrail(String key, int index, double widthTrail) { + if (0 == _listController.length) { + return Container(width: widthTrail); + } + + //print('key = $key'); + if ('avatar' == key) { + imagePath = _listController[index].text; + return getAvatar(width: widthTrail); + } else { + return Container( + alignment: Alignment(-1, 0), + //widthTrail = 400报错,360刚能显示,300换行,260 + width: widthTrail, + //解决信息显示不全问题 + child: Text(_listController[index].text, + style: TextStyle(fontSize: 16), textAlign: TextAlign.left), + ); + } + } + + Widget getTrail0(String key, int index, double widthTrail) { + if (0 == _listController.length) { + return Container(width: widthTrail); + } + + //print('key = $key'); + if ('avatar' == key) { + imagePath = _listController[index].text; + return getAvatar(width: widthTrail); + } else { + return Container( + alignment: Alignment(1, 0), + //widthTrail = 400报错,360刚能显示,300换行,260 + width: widthTrail, + child: TextField( + //textAlign: TextAlign.right, + style: TextStyle(fontSize: 16), + decoration: InputDecoration( + //hintText: '請輸入字段信息', + border: InputBorder.none, //TextField去掉下划线 + contentPadding: EdgeInsets.only(right: 0), + ), + controller: _listController[index], + enabled: 'content' == key ? true : false, + //解决报警信息显示不全问题,但软键盘会弹出,很难控制键盘弹出问题 + //利用控制器初始化文本 + onChanged: null, + ), + ); + } + } + + Future doContacts() async { + bFlash = false; + return showDialog( + context: context, + builder: (BuildContext context) { + return customDialogH( + title: "请选择头像修改操作", + content: "头像修改", + index: 0, + ); + }, + ).then((value) { + imagePath = value; + print('Page2_Contacts bFlash = $bFlash'); + if (imagePath.isNotEmpty) { + _image = Image.file(File(imagePath), fit: BoxFit.cover); + setState(() {}); + } + }); + } + + //Image.file(this._image); + String imagePath = ''; + Image _image; + + Widget getAvatar({double width = 260.0}) { + if (imagePath.isEmpty) { + _image = Image.asset('assets/images/user.png', fit: BoxFit.cover); + } else { + String head = imagePath.substring(0, 4).trim().toLowerCase(); + if ('http' == head) { + _image = Image.network(imagePath, fit: BoxFit.cover); + } + } + return Container( + alignment: Alignment(-1, 0), + width: width, + child: InkWell( + onTap: () async { + doContacts(); + }, + child: Container( + width: 40, + child: _image, + ), + ), + ); + } + + //添加、修改、删除联系人对话框 + doContacts2(String key, int index) async { + showDialog( + context: context, + builder: (BuildContext context) { + return CustomDialogJ( + theKey: key, + index: index, + ); + }, + ); + } + + static onNullFun() {} + + Widget _getListTile(String key, int index, double widthTrail, + {onTapFun = onNullFun, onLongPressFun = onNullFun, size = 16.0}) { + //print('key = $key, index = $index'); + return ListTile( + //leading: new Icon(Icons.phone), + title: Text((index + 1).toString() + ". " + '${_mapGetSbbjGetDataText[key]} :', + style: TextStyle(fontSize: 16)), + trailing: getTrail(key, index, widthTrail), + contentPadding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 0), + enabled: true, + onTap: () async { + // print('第 $index 项被点击了'); + // await doContacts2(key, index); + // if ('avatar' == key) { + // print('选择图片或拍照'); + // await doContacts(); + // } + }, + onLongPress: () {}, + ); + } + + TextEditingController _controller = TextEditingController.fromValue(TextEditingValue( + text: '已做简单处理。低级故障,不影响系统运行', + // 保持光标在最后 + selection: TextSelection.fromPosition( + TextPosition(affinity: TextAffinity.downstream, offset: '已处理'.length)))); + + //7、得到核查处理意见组件 + Widget getClyj(int index, bool _bCheck) { + if (_bCheck) { + _controller.text = ''; + } else { + _controller.text = (0 == _selectedRadio ? '已做简单处理。低级故障,不影响系统运行' : '已核查,属于误报'); + } + return ConstrainedBox( + constraints: BoxConstraints( + minWidth: double.infinity, //宽度尽可能大 + //minHeight: _listTileHeight, //最小高度 + maxHeight: _heigth, //最大高度 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.baseline, + children: [ + SizedBox(width: _marginLeft), + Text('处理意见:', + style: TextStyle( + fontSize: _fontSize, + color: _bCheck + ? Colors.grey + : 0 == _selectedRadio + ? Colors.red + : Colors.green)), + Container( + alignment: Alignment(-1, 0), + height: _heigth, + //widthTrail = 400报错,360刚能显示,300换行,260 + width: 240, + child: TextField( + //textAlign: TextAlign.right, + //style: TextStyle(fontSize: _fontSize, color: cpysList[getIndexOfCpysList(colorText: topTabs_map['cpysText_List'][i])].cpysFont), + //style: TextStyle(fontSize: _fontSize, color: cpysList[getIndexOfCpysList(colorText: myCpys)].cpysFont), + style: TextStyle(fontSize: _fontSize), + textAlign: TextAlign.left, + decoration: InputDecoration( + hintText: _bCheck ? '' : '請輸入审核意见', + //border: InputBorder.none, //TextField去掉下划线 + //contentPadding: EdgeInsets.only(right: 0), + //contentPadding: EdgeInsets.symmetric(vertical: _textFieldHeight), + contentPadding: EdgeInsets.only(left: 4, right: 4), //这行代码是关键,设置这个之后,居中 + //contentPadding: EdgeInsets.zero, //这行代码是关键,设置这个之后,居中 + border: OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey[600]), + //borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(3), + ), + ), + controller: _controller, + maxLines: 1, + minLines: 1, + //maxLengthEnforced: false, + //maxLength: 10, + enabled: !_bCheck, + //利用控制器初始化文本 + onChanged: (value) { + _controller.text = value; + }, + ), + ), + ], + ), + ); + } + + double _heigth = 30; + double _fontSize = 16; + double _marginLeft = 15; + + @override + Widget build(BuildContext context) { + bool bCheck = _mapGetSbbjGetData['workflow'] == 999; //已处理 + return Scaffold( + // appBar: AppBar( + // title: Text("设备报警信息详情($nums)"), + // centerTitle: true, + // ), + appBar: PreferredSize( + preferredSize: Size.fromHeight(ScreenUtil().setHeight(173)), // 设置appBar高度 + // 设置appBar高度 + child: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + titleSpacing: 0.0, + //设置title的左边距 + flexibleSpace: Container( + //SizedBox(height: ScreenUtil().statusBarHeight), //显示顶部状态栏 + // SizedBox(height: ScreenUtil().setHeight(10)), //显示顶部状态栏 + padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight), //留出顶部状态栏高度 + child: Container( + //height: ScreenUtil().setHeight(173), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + // decoration: BoxDecoration( + // gradient: LinearGradient(colors: [ + // Color(0xFF0018EB), + // Color(0xFF01C1D9), + // ], begin: Alignment.bottomCenter, end: Alignment.topCenter), + // ), + ), + ), + title: Padding( + padding: EdgeInsets.only(left: 0, right: 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + getIconAndTextButton( + iconColor: Colors.white, + iconData: Icons.chevron_left_outlined, + onPress: () { + Navigator.pop(context); + }, + ), + Expanded( + child: Text("设备报警信息详情($nums)", + style: TextStyle(color: Colors.white, fontSize: 20), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis), + ), + SizedBox(width: 50), + ], + ), + ), + ), + ), + body: Container( + child: Column( + children: [ + Expanded( + //https://www.it1352.com/2028416.html + //用Map而不是List的Flutter ListView.builder(Flutter listview with Map instead of List) + //使用ListView.separated设置分割线ListView.separated( + //child: ListView.separated( //这种方式不能设置每项高度,每页只能有7项 + child: ListView.builder( + //这种方式可以通过itemExtent设置每项高度,每页可以有9项 + //separatorBuilder: (BuildContext context, int index) => index %2 ==0? Divider(color: Colors.green) : Divider(color: Colors.red),//index为偶数,创建绿色分割线;index为奇数,则创建红色分割线 + //separatorBuilder: (BuildContext context, int index) => Divider(), + //itemExtent: 57.0, //列表项高度。56越界、57刚好。还是自动计算最好 + itemCount: _mapGetSbbjGetData.length, + itemBuilder: (BuildContext context, index) { + String key = _mapGetSbbjGetData.keys.elementAt(index); + return Column( + children: [ + _getListTile(key, index, 200.0), + Divider( + height: 1.0, + ), + ], + ); + }, + ), + ), + //7、得到核查处理意见组件 + Divider(height: 1.0, color: Colors.blue), + SizedBox(height: 15), + getClyj(widget.index, bCheck), + SizedBox(height: 10), + //8、处理结果 + Container( + height: _heigth, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: _marginLeft), + Text('处理结果:', + style: TextStyle( + fontSize: _fontSize, + color: bCheck + ? Colors.grey + : 0 == _selectedRadio + ? Colors.red + : Colors.green)), + CustomRadioWidget( + value: 0, + title: "已处理", + fontSize: _fontSize, + width: 120, + groupValue: _selectedRadio, + onChanged: bCheck + ? null + : (int value) { + _selectedRadio = value; + //黑烟初审数据审核Radio选项改变广播 + //eventBus.fire(HycsDataAuditRadioEvent('黑烟初审数据审核Radio选项已改变', _selectedRadio)); + _controller.text = '已做简单处理。低级故障,不影响系统运行'; + setState(() {}); + print('selectedRadio = ${_selectedRadio.toString()}'); + }, + ), + SizedBox(width: 20), + CustomRadioWidget( + value: 1, + title: "误报", + fontSize: _fontSize, + width: 100, + groupValue: _selectedRadio, + onChanged: bCheck + ? null + : (int value) { + _selectedRadio = value; + //黑烟初审数据审核Radio选项改变广播 + //eventBus.fire(HycsDataAuditRadioEvent('黑烟初审数据审核Radio选项已改变', _selectedRadio)); + _controller.text = '已核查,属于误报'; + setState(() {}); + print('selectedRadio = ${_selectedRadio.toString()}'); + }, + ), + ], + ), + ), + SizedBox(height: 10), + Divider(height: 1.0, color: Colors.blue), + SizedBox(height: 15), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + getBtnSizeX( + text: "处理", + onPressedFun: bCheck + ? null + : () async { + int ret = -1; + print('等待处理确认'); + await Navigator.of(context) + .push( + PageRouteBuilder( + opaque: false, + pageBuilder: (context, animation, secondaryAnimation) => + CustomDialogF(title: '处理确认', content: '是否进行设备报警信息核查处理?'), + ), + ) + .then((value) async { + print('value = $value'); + if (value) { + print('用户已确认,开始处理报警信息核查处理!'); + // sbglContentFirstAudit 整型返回值:更新的结果,1表示成功,0表示无更新,false表示失败 + ret = await sbglContentAudit( + sbgllx: widget.hyshlx, + sbglID: _mapGetSbbjGetData['id'], + shuoming: _controller.text, + title: 0 == _selectedRadio ? '已处理' : '误报', + ); + + if (1 == ret) { + eventBus.fire(SbglDataUpdateEvent( + '${mapHyshlx[widget.hyshlx]['text']}数据已更新')); + print('${mapHyshlx[widget.hyshlx]['text']}结果已成功上传服务器。'); + } + } else { + print('用户取消了报警信息核查处理'); + } + }); + Navigator.pop(context, ret); + }), + //getBtnSizeX(text: "删除", onPressedFun: () async {}), + preBtn, + nextBtn, + ], + ), + SizedBox(height: 20), + ], + ), + ), + ); + } + + //解决第一次进入报错问题。因为getPreBtn_NextBtn()还未执行,preBtn和nextBtn为空 + Widget preBtn = Container( + color: Colors.white12, //onPressedFun为null时无效 + width: 70.0, + height: 35.0, + child: RaisedButton( + padding: EdgeInsets.all(0), + textColor: Colors.black, + child: Text('上一条'), + onPressed: null, + ), + ); + + Widget nextBtn = Container( + color: Colors.white12, //onPressedFun为null时无效 + width: 70.0, + height: 35.0, + child: RaisedButton( + padding: EdgeInsets.all(0), + textColor: Colors.black, + child: Text('下一条'), + onPressed: null, + ), + ); + + getPreBtn_NextBtn() { + preBtn = getBtnSizeX( + text: "上一条", + onPressedFun: null, + ); + nextBtn = getBtnSizeX( + text: "下一条", + onPressedFun: null, + ); + + if (widget.index > 0 && listLen > 0) { + preBtn = getBtnSizeX( + text: "上一条", + onPressedFun: () async { + if (widget.index > 0) { + widget.index--; + getListFlields(); + } + }, + ); + } + + if (widget.index < (listLen - 1) && listLen > 0) { + nextBtn = getBtnSizeX( + text: "下一条", + onPressedFun: () async { + if (widget.index < listLen - 1) { + widget.index++; + getListFlields(); + } + }, + ); + } + } + + Widget getBtnSizeX({@required text, width = 70.0, height = 35.0, onPressedFun}) { + return Container( + color: Colors.white12, //onPressedFun为null时无效 + width: width, + height: height, + child: RaisedButton( + padding: EdgeInsets.all(0), + textColor: Colors.black, + child: Text(text), + onPressed: onPressedFun, + ), + ); + } +} diff --git a/lib/pages/Works/SBBJ/sbbj_getList.dart b/lib/pages/Works/SBBJ/sbbj_getList.dart new file mode 100644 index 0000000..5743ed2 --- /dev/null +++ b/lib/pages/Works/SBBJ/sbbj_getList.dart @@ -0,0 +1,799 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; + +import 'sbbj_content.dart'; +import '../../../components/dioFun.dart'; +import '../../../components/hyxx_data_handle.dart'; +import '../../../components/commonFun.dart'; +import '../../../services/EventBus.dart'; +import 'package:flutter_easyrefresh/easy_refresh.dart'; +import '../../../components/doJSON.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +//sbbj是本项目中“设备报警”的统一缩写 +class SbbjGetList extends StatefulWidget { + //hyshlx为黑烟审核类型,处理sbbj信息。mapHyshlx[hyshlx]获取为各种类型的设置数据 + SbbjGetList({this.hyshlx = 'sbbj', Key key}) : super(key: key); + String hyshlx; + + _SbbjPageState createState() => _SbbjPageState(); +} + +class _SbbjPageState extends State { + //try_setState(); //避免异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + @override + void dispose() { + _controller.dispose(); //销毁控制器 + super.dispose(); + } + + @override + void initState() { + hyshlx = widget.hyshlx; + iPage = 0; + listSbbjGetList2.clear(); + + ///从接口 mapHyshlx[hyshlx]['api'] 获取第 iPage 页的列表数据,返回 list + getPageList().then((value) { + listSbbjGetList2 = value; + //按照用户选择的_selectedValue、_descending对listSbbjGetList2进行排序,并延时更新 + _listSort(); + firstIndex = 0; + lastIndex = 7; + }); + + ///从接口 mapHyshlx[theHyshlx]['api'] 获取指定类型第 page 页的列表数据,返回 list + ///获取点位信息数据 + listDwinfoGetList2.clear(); + getThePageList(theHyshlx: 'dwxx').then((value) { + listDwinfoGetList2 = value; + print('listDwinfoGetList2 = \n$listDwinfoGetList2'); + }); + + //监听设备管理信息数据更新事件 + eventBus.on().listen((event) async { + print(event.str); + iPage = 0; + listSbbjGetList2.clear(); + + ///从接口 mapHyshlx[hyshlx]['api'] 获取第 iPage 页的列表数据,返回 list + getPageList().then((value) { + listSbbjGetList2 = value; + //按照用户选择的_selectedValue、_descending对listSbbjGetList2进行排序,并延时更新 + _listSort(); + firstIndex = 0; + lastIndex = 7; + }); + }); + + super.initState(); + } + + // List listSbbjGetList2 = [ + // { + // "id": 1196, + // "bjlx": "风扇", + // "content": "串口COM1获取风扇异常数据", + // "dwip": "172.16.3.12", + // "sbip": "192.168.1.10", + // "addtime": "2021-02-16 09:50:30", + // "workflow": 999 + // }, + // ]; + + Widget _getListTile(BuildContext context, indexRecord) { + return Column( + children: [ + ListTile( + //leading: new Icon(Icons.phone), + title: Text( + "${(indexRecord + 1).toString()}. " + + getDate(listSbbjGetList2[indexRecord]['addtime']) + + ',', + style: TextStyle(fontSize: 14)), + subtitle: Text( + getDwmc(listSbbjGetList2[indexRecord]['dwip']) + + ',' + + (listSbbjGetList2[indexRecord]['workflow'] == 999 ? '已处理' : '待处理'), + style: TextStyle(fontSize: 14)), + trailing: Container( + width: 160, + child: Text( + '${listSbbjGetList2[indexRecord]['bjlx']}, 报警:${listSbbjGetList2[indexRecord]['content']}' + + ', 设备IP:${listSbbjGetList2[indexRecord]['sbip']}' + + 'ID:${listSbbjGetList2[indexRecord]['id'].toString()}', + maxLines: 2, + overflow: TextOverflow.ellipsis, + //textAlign: TextAlign.right, + style: TextStyle(fontSize: 14), + ), + ), + contentPadding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 0), + enabled: true, + onTap: () async { + int ret = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => SbbjContent( + title: '设备报警信息', + index: indexRecord, + hyshlx: widget.hyshlx, + ), + ), + ); + + print('ret = $ret'); + }, + ), + Divider(height: 1.0), + ], + ); + } + + ScrollController _controller = ScrollController(); //ListView控制器 + bool isLoading = false; //正在处理下载数据、跳转到首项、跳转到尾项等操作 + int firstIndex = 0; //ListView当前显示页面首项0基序号 + int lastIndex = 0; //ListView当前显示页面末项0基序号 + int itemOnPage = 8; //估计一屏显示的项目数量 + + Widget getIconButton({IconData iconData, var onPressed, double iconSize = 22}) { + return SizedBox( + height: iconSize, + width: iconSize + 10, + child: IconButton( + padding: EdgeInsets.all(0.0), + icon: Icon(iconData, size: iconSize, color: Colors.white), + onPressed: onPressed, + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + // appBar: AppBar( + // automaticallyImplyLeading: false, + // centerTitle: true, + // //leading: Text(''), + // titleSpacing: 0.0, + // //设置title的左边距 + // title: Padding( + // padding: EdgeInsets.only(left: 0, right: 0), + // child: Row( + // //mainAxisAlignment: MainAxisAlignment.start, + // children: [ + // getIconAndTextButton( + // iconData: Icons.arrow_back, + // onPress: () { + // Navigator.pop(context); + // }, + // ), + // Expanded( + // child: Text("${mapHyshlx[hyshlx]['text']}", + // textAlign: TextAlign.left, + // overflow: TextOverflow.ellipsis, + // style: widget.hyshlx == 'fhycx' ? TextStyle(fontSize: 14) : null), + // ), + // SizedBox(width: 5), + // ], + // ), + // ), + // actions: [ + // getDropdownButton(), + // SizedBox(width: 10), + // getIconButton( + // iconData: Icons.cloud_download, + // onPressed: !isLoading + // ? () async { + // if (isLoading) { + // return; + // } + // isLoading = true; + // try_setState(); + // //print("I Pressed the Iconbutton"); + // int oldItems = listSbbjGetList2.length; + // for (int i = 0; i < 10; i++) { + // List list = await getPageList(); + // if (list.length > 0) { + // listSbbjGetList2.addAll(list); //加载累加 + // } else { + // break; + // } + // } + // Future.delayed(const Duration(milliseconds: 1000), () async { + // if (listSbbjGetList2.length > oldItems) { + // eventBus.fire(WzxxDataAuditEvent('${mapHyshlx[hyshlx]['text']}数据已更新')); + // Fluttertoast.showToast( + // msg: '新增 ${listSbbjGetList2.length - oldItems} 条数据下载完成!', + // toastLength: Toast.LENGTH_SHORT, + // gravity: ToastGravity.CENTER, + // ); + // + // //按照用户选择的_selectedValue、_descending对listSbbjGetList2进行排序,并延时更新 + // _listSort(); + // } + // isLoading = false; + // try_setState(); + // }); + // } + // : null, + // ), + // getIconButton( + // iconData: Icons.vertical_align_top_outlined, + // onPressed: (!isLoading && (firstIndex > 0)) //未到顶部 + // ? () async { + // if (isLoading) { + // return; + // } + // isLoading = true; + // try_setState(); + // + // //必须延时执行,否则不能及时完成按钮状态更新 + // Timer( + // Duration(milliseconds: 500), + // () { + // _controller.jumpTo(_controller.position.minScrollExtent); + // }, + // ); + // + // Timer( + // Duration(milliseconds: 1000), + // () { + // // Fluttertoast.showToast( + // // msg: '已经跳转到开头记录!', + // // toastLength: Toast.LENGTH_SHORT, + // // gravity: ToastGravity.CENTER, + // // ); + // firstIndex = 0; + // lastIndex = itemOnPage - 1; //0基序号,所以需要减1 + // isLoading = false; + // try_setState(); + // }, + // ); + // } + // : null, + // ), + // getIconButton( + // iconData: Icons.vertical_align_bottom_outlined, + // onPressed: (!isLoading && (lastIndex < listSbbjGetList2.length)) //未到尾部 + // ? () async { + // if (isLoading) { + // return; + // } + // isLoading = true; + // try_setState(); + // + // //必须延时执行,否则不能及时完成按钮状态更新 + // Timer( + // Duration(milliseconds: 500), + // () { + // _controller.jumpTo(_controller.position.maxScrollExtent); + // }, + // ); + // + // Timer( + // Duration(milliseconds: 1000), + // () { + // // Fluttertoast.showToast( + // // msg: '已经跳转到末尾记录!', + // // toastLength: Toast.LENGTH_SHORT, + // // gravity: ToastGravity.CENTER, + // // ); + // //0基序号需要减1,再加上底部有一组显示信息,所以需要减2 + // firstIndex = listSbbjGetList2.length - itemOnPage - 2; + // lastIndex = listSbbjGetList2.length; + // isLoading = false; + // try_setState(); + // }, + // ); + // } + // : null, + // ), + // ], + // ), + appBar: PreferredSize( + preferredSize: Size.fromHeight(ScreenUtil().setHeight(173)), // here the desired height + child: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + //leading: Text(''), + titleSpacing: 0.0, + //设置title的左边距 + flexibleSpace: Container( + //SizedBox(height: ScreenUtil().statusBarHeight), //显示顶部状态栏 + // SizedBox(height: ScreenUtil().setHeight(10)), //显示顶部状态栏 + padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight), //留出顶部状态栏高度 + child: Container( + //height: ScreenUtil().setHeight(173), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + // decoration: BoxDecoration( + // gradient: LinearGradient(colors: [ + // Color(0xFF0018EB), + // Color(0xFF01C1D9), + // ], begin: Alignment.bottomCenter, end: Alignment.topCenter), + // ), + ), + ), + //1、第1行组件,工具按钮 + title: Padding( + padding: EdgeInsets.only(left: 0, right: 0), + child: Row( + //mainAxisAlignment: MainAxisAlignment.start, + children: [ + //1.1、返回按钮 + getIconAndTextButton( + iconColor: Colors.white, + iconData: Icons.chevron_left_outlined, + onPress: () { + Navigator.pop(context); + }, + ), + //1.2、title 显示控制 + Expanded( + child: Text("${mapHyshlx[hyshlx]['text']}", + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + style: TextStyle(color: Colors.white, fontSize: 20)), + ), + SizedBox(width: 5), + ], + ), + ), + //1.3、尾部工具按钮组 + actions: [ + //getDropdownButton(), + getIconButton( + iconData: Icons.cloud_download, + onPressed: !isLoading + ? () async { + if (isLoading) { + return; + } + isLoading = true; + try_setState(); + //print("I Pressed the Iconbutton"); + int oldItems = listSbbjGetList2.length; + for (int i = 0; i < 10; i++) { + List list = await getPageList(); + if (list.length > 0) { + listSbbjGetList2.addAll(list); //加载累加 + } else { + break; + } + } + Future.delayed(const Duration(milliseconds: 1000), () async { + if (listSbbjGetList2.length > oldItems) { + eventBus.fire(WzxxDataAuditEvent('${mapHyshlx[hyshlx]['text']}数据已更新')); + Fluttertoast.showToast( + msg: '新增 ${listSbbjGetList2.length - oldItems} 条数据下载完成!', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + + //按照用户选择的_selectedValue、_descending对listSbbjGetList2进行排序,并延时更新 + _listSort(); + //_controller.jumpTo(1); // _controller.position 能够有效刷新,人眼可察觉的滚动 + _controller.jumpTo(0.001); // _controller.position 完美解决有效刷新,而且人眼不可见察觉滚动 + //第二次跳转必须延时,否则 _controller.position 不能有效刷新 + Future.delayed(Duration(milliseconds: 500), () { + _controller.jumpTo(_controller.position.minScrollExtent); + }); + } + isLoading = false; + try_setState(); + }); + } + : null, + ), + SizedBox(width: ScreenUtil().setWidth(32)), + getIconButton( + iconData: Icons.vertical_align_top_outlined, + onPressed: (!isLoading && (firstIndex > 0)) //未到顶部 + ? () async { + if (isLoading) { + return; + } + isLoading = true; + try_setState(); + + //必须延时执行,否则不能及时完成按钮状态更新 + Timer( + Duration(milliseconds: 500), + () { + _controller.jumpTo(_controller.position.minScrollExtent); + }, + ); + + Timer( + Duration(milliseconds: 1000), + () { + // Fluttertoast.showToast( + // msg: '已经跳转到开头记录!', + // toastLength: Toast.LENGTH_SHORT, + // gravity: ToastGravity.CENTER, + // ); + firstIndex = 0; + lastIndex = itemOnPage - 1; //0基序号,所以需要减1 + isLoading = false; + try_setState(); + }, + ); + } + : null, + ), + SizedBox(width: ScreenUtil().setWidth(32)), + getIconButton( + iconData: Icons.vertical_align_bottom_outlined, + onPressed: (!isLoading && (lastIndex < listSbbjGetList2.length)) //未到尾部 + ? () async { + if (isLoading) { + return; + } + isLoading = true; + try_setState(); + + //必须延时执行,否则不能及时完成按钮状态更新 + Timer( + Duration(milliseconds: 500), + () { + _controller.jumpTo(_controller.position.maxScrollExtent); + }, + ); + + Timer( + Duration(milliseconds: 1000), + () { + // Fluttertoast.showToast( + // msg: '已经跳转到末尾记录!', + // toastLength: Toast.LENGTH_SHORT, + // gravity: ToastGravity.CENTER, + // ); + //0基序号需要减1,再加上底部有一组显示信息,所以需要减2 + firstIndex = listSbbjGetList2.length - itemOnPage - 2; + lastIndex = listSbbjGetList2.length; + isLoading = false; + try_setState(); + }, + ); + } + : null, + ), + SizedBox(width: ScreenUtil().setWidth(32)), + ], + ), + ), + body: Column(children: [ + //2、第2行排序按钮 + Container( + height: ScreenUtil().setHeight(142), + decoration: new BoxDecoration(border: new Border.all(color: Colors.red)), + child: Row( + children: [ + SizedBox(width: ScreenUtil().setWidth(50)), + Text('排序', style: TextStyle(fontSize: 18)), + Expanded(child: SizedBox.shrink()), + getDropdownButton(), + SizedBox(width: ScreenUtil().setWidth(20)), + ], + ), + ), + (0 == listSbbjGetList2.length) + ? getMoreWidget(color: Colors.black38) + : Expanded( + child: EasyRefresh( + child: ListView.custom( + //itemExtent: 75.0, //列表项高度 + controller: _controller, + cacheExtent: 1.0, // 只有设置了1.0 才能够准确的标记 position 位置 + childrenDelegate: MyChildrenDelegate( + _getListTile, + childCount: listSbbjGetList2.length, + ), + ), + onRefresh: () async { + // await Future.delayed(const Duration(seconds: 1), () async { + // iPage = 0; + // //await getWzxxGetList(); + // listSbbjGetList2 = await getPageList(); + // eventBus.fire(WzxxDataAuditEvent('${mapHyshlx[hyshlx]['text']}数据已更新')); + // }); + }, + onLoad: () async { + if (isLoading) { + return; + } + isLoading = true; + try_setState(); + + print('iPage = $iPage'); + await Future.delayed(const Duration(seconds: 1), () async { + //await getWzxxGetList(); + List list = await getPageList(); + if (list.length > 0) { + listSbbjGetList2.addAll(list); //加载累加 + eventBus.fire(WzxxDataAuditEvent('${mapHyshlx[hyshlx]['text']}数据已更新')); + } + isLoading = false; + try_setState(); + }); + }, + header: getHeader(), + footer: getFooter(), + ), + ), + ]), + ); + } + + //DropdownButton需要设置初始值的时候,初始值必须是显示列表里面的值,否则会导致弹出框异常。 + // 比如说:你的DropdownButton的items属性使用的是list这个列表里面的值,那么你的初始值应该在list[index]里面取,要不就会报错。 + // + // There should be exactly one item with [DropdownButton]'s value: 0.0. + // Either zero or 2 or more [DropdownMenuItem]s were detected with the same value + // 'package:flutter/src/material/dropdown.dart': + // Failed assertion: line 834 pos 15: 'items == null || items.isEmpty || value == null || + // items.where((DropdownMenuItem item) { + // return item.value == value; + // }).length == 1' + String _selectedValue = '添加时间'; + bool _descending = true; //默认生效排列 + + Widget _getImage(String _image) { + return Container( + margin: EdgeInsets.only(), + height: ScreenUtil().setWidth(48), + width: ScreenUtil().setWidth(48), + //child: Image.asset('assets/images/ybsthbj.png', fit: BoxFit.fitHeight), + child: Image.asset(_image, + fit: BoxFit.cover, color: isLoading ? Theme.of(context).disabledColor : null)); + } + + //按照用户选择的_selectedValue、_descending对listSbbjGetList2进行排序,并延时更新 + Future _listSort({bool bShowToast = false}) { + if (!isLoading && listSbbjGetList2.length > 0) { + isLoading = true; + try_setState(); + + switch (_selectedValue) { + default: + if (_descending) { + //按_selectedValue排序,降序 + listSbbjGetList2.sort((a, b) => + (b[mapWzxxDataText[_selectedValue]]).compareTo(a[mapWzxxDataText[_selectedValue]])); + } else { + //按_selectedValue排序,升序 + listSbbjGetList2.sort((a, b) => + (a[mapWzxxDataText[_selectedValue]]).compareTo(b[mapWzxxDataText[_selectedValue]])); + } + break; + } + + Future.delayed(const Duration(milliseconds: 1000), () { + if (bShowToast) { + Fluttertoast.showToast( + msg: '按“${_selectedValue}”${_descending ? '降序' : '升序'}排列完成!', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + isLoading = false; + try_setState(); //避免如下异常报错 + }); + } + } + + Widget getDropdownButtonItemText(String item) { + return Padding( + padding: EdgeInsets.only(bottom: ScreenUtil().setHeight(3)), + child: Row( + //crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + alignment: Alignment(0, 0), + padding: EdgeInsets.only(top: ScreenUtil().setHeight(10)), + child: item == _selectedValue + ? _descending + ? _getImage('assets/images/descending.png') + : _getImage('assets/images/ascending.png') + // ? Icon(Icons.arrow_downward_outlined, size: 20) + // : Icon(Icons.arrow_upward_outlined, size: 20) + : SizedBox(), + ), + SizedBox( + width: ScreenUtil().setWidth(10), + ), + Container( + //alignment: Alignment(0, -1), + child: Text(item, + style: TextStyle( + fontSize: 18, + color: isLoading + ? Theme.of(context).disabledColor + : item == _selectedValue + ? Colors.blue + : null)), + // style: isLoading + // ? TextStyle(color: Theme.of(context).disabledColor) + // : (item == _selectedValue ? TextStyle(color: Colors.blue) : null)), + ), + ], + ), + ); + } + + // List listSbbjGetList2 = [ + // { + // "id": 1196, + // "bjlx": "风扇", + // "content": "串口COM1获取风扇异常数据", + // "dwip": "172.16.3.12", + // "sbip": "192.168.1.10", + // "addtime": "2021-02-16 09:50:30", + // "workflow": 999 + // }, + // ]; + + Widget getDropdownButton() { + //DropdownMenuItem项目文本list + List itemList = [ + '添加时间', + '主键ID', + '设备类型', + '报警内容', + '点位IP', + '设备IP', + '处理状态', + ]; + + //添加按'推送状态'排序 + if (hyshlx != 'sbbj') { + // itemList.removeLast(); + // itemList.addAll(['推送状态', '主键ID']); + } + + //获取DropdownMenuItem项目组件list + List> _dropDownMenuItems = + itemList.map>((String item) { + return DropdownMenuItem( + value: item, + child: getDropdownButtonItemText(item), + ); + }).toList(); + + return Padding( + padding: EdgeInsets.only(top: 0, bottom: 0), + child: Container( + alignment: Alignment(0, 0), + width: 135, + margin: EdgeInsets.only(bottom: 0), + padding: EdgeInsets.only(left: 0, bottom: 0), + // decoration: BoxDecoration( + // border: Border.all(width: 0), + // //边框圆角设置 + // borderRadius: + // BorderRadius.vertical(top: Radius.elliptical(2, 2), bottom: Radius.elliptical(2, 2)), + // ), + //DropdownButton默认有一条下划线,DropdownButtonHideUnderline去除下划线 + child: DropdownButtonHideUnderline( + child: DropdownButton( + iconSize: ScreenUtil().setHeight(100), + //itemHeight: ScreenUtil().setHeight(372), + isDense: true, + value: _selectedValue, + items: _dropDownMenuItems, + onChanged: (String selectedValue) { + if (isLoading) { + return; + } + + if (_selectedValue == selectedValue) { + _descending = !_descending; + } else { + _descending = true; + } + _selectedValue = selectedValue; + print('_selectedValue = $_selectedValue'); + + //按抓拍次数排序,降序 + // listSbbjGetList2.sort((a, b) => (b["yjxx_id"].split(',').length.toString()) + // .compareTo(a["yjxx_id"].split(',').length.toString())); + + //按照用户选择的_selectedValue、_descending对listSbbjGetList2进行排序,并延时更新 + _listSort(); + }, + ), + ), + ), + ); + } +} + +//https://blog.csdn.net/u014803467/article/details/103750018 +//Flutter 使用SliverChildBuilderDelegate获取ListView的第一个和最后一个可见Item序号 +// 秋名山交警X 2019-12-28 23:52:52 +class _SaltedValueKey extends ValueKey { + const _SaltedValueKey(Key key) + : assert(key != null), + super(key); +} + +class MyChildrenDelegate extends SliverChildBuilderDelegate { + MyChildrenDelegate( + Widget Function(BuildContext, int) builder, { + int childCount, + bool addAutomaticKeepAlive = true, + bool addRepaintBoundaries = true, + }) : super(builder, + childCount: childCount, + addAutomaticKeepAlives: addAutomaticKeepAlive, + addRepaintBoundaries: addRepaintBoundaries); + + // Return a Widget for the given Exception + Widget _createErrorWidget(dynamic exception, StackTrace stackTrace) { + final FlutterErrorDetails details = FlutterErrorDetails( + exception: exception, + stack: stackTrace, + library: 'widgets library', + context: ErrorDescription('building'), + ); + FlutterError.reportError(details); + return ErrorWidget.builder(details); + } + + @override + Widget build(BuildContext context, int index) { + assert(builder != null); + if (index < 0 || (childCount != null && index >= childCount)) return null; + Widget child; + try { + child = builder(context, index); + } catch (exception, stackTrace) { + child = _createErrorWidget(exception, stackTrace); + } + if (child == null) return null; + final Key key = child.key != null ? _SaltedValueKey(child.key) : null; + if (addRepaintBoundaries) child = RepaintBoundary(child: child); + if (addSemanticIndexes) { + final int semanticIndex = semanticIndexCallback(child, index); + if (semanticIndex != null) + child = IndexedSemantics(index: semanticIndex + semanticIndexOffset, child: child); + } + if (addAutomaticKeepAlives) child = AutomaticKeepAlive(child: child); + return KeyedSubtree(child: child, key: key); + } + + @override + void didFinishLayout(int _firstIndex, int _lastIndex) { + // TODO: implement didFinishLayout + super.didFinishLayout(_firstIndex, _lastIndex); + } + + ///监听 在可见的列表中 显示的第一个位置和最后一个位置 + @override + double estimateMaxScrollOffset( + int _firstIndex, int _lastIndex, double _leadingScrollOffset, double _trailingScrollOffset) { + //违章信息Listview滚动广播 + eventBus.fire(WzxxDataScrollEvent(_firstIndex, _lastIndex)); + + return super.estimateMaxScrollOffset( + _firstIndex, _lastIndex, _leadingScrollOffset, _trailingScrollOffset); + } +} diff --git a/lib/pages/Works/SBGL/dwxx_content.dart b/lib/pages/Works/SBGL/dwxx_content.dart new file mode 100644 index 0000000..c9ba130 --- /dev/null +++ b/lib/pages/Works/SBGL/dwxx_content.dart @@ -0,0 +1,476 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/screen_util.dart'; +import 'package:hyzp_ybqx/components/commonFun.dart'; + +import '../../../components/customDialogH.dart'; +import '../../../components/customDialogJ.dart'; +import '../../../components/dioFun.dart'; +//import 'package:hyzp_ybqx/widget/player_pro.dart'; +import '../../../components/doJSON.dart'; +import '../../../components/hyxx_data_handle.dart'; + +class DwxxContent extends StatefulWidget { + //SbglContent({Key key, this.title, this.mapData}) : super(key: key); + DwxxContent({ + @required this.title, + @required this.index, + @required this.hyshlx, + Key key, + }) : super(key: key); + String title; + int index = -1; + String hyshlx; + + _SbglPageState createState() => _SbglPageState(); +} + +class _SbglPageState extends State { + Map _mapGetSbglGetDataText = { + "id": "主键ID", + "dwip": "点位IP", + "dwmc": '点位名称', + "dwbh": '点位编号', + "dwinfo": '点位信息', + "dwzb": '点位坐标', + "dwms": '点位描述', + "dwzt": '点位状态', + }; + + //点位信息数据 + Map _mapGetSbglGetData = { + "id": 1, + "dwip": "172.16.3.1", + "dwmc": "江北振兴大道", + "dwbh": 1, + "dwinfo": "江北振兴大道入城方向", + "dwzb": "104.607091|28.807061", + "dwms": "江北振兴大道入城方向,识别孜岩、红坝路入城排放黑烟车辆", + "dwzt": "正常" + }; + + List _listController = []; + int listLen = 0; + String nums = ''; + int _selectedRadio = 0; + + void initState() { + // TODO: implement initState + super.initState(); + listLen = listSbglGetList2.length; + getListFlields(); + } + + //监听登录页面销毁的事件 + dispose() { + _controller.dispose(); + getingDwVideo = false; + super.dispose(); + } + + getListFlields() async { + //获取指定id的sbgl记录数据返回 _mapGetSbglGetData。由于 sbgl 信息需要进行查核处理,所以要重新读取最新的记录数据 + // _mapGetSbglGetData = + // await getLedXsxxGetData(id: listSbglGetList2[widget.index]['id'], theSbgllx: widget.hyshlx); + _mapGetSbglGetData = listSbglGetList2[widget.index]; + print('_mapGetSbglGetData = ${_mapGetSbglGetData}'); + + //_mapGetSbglGetData = listSbglGetList2[widget.index]; + _listController = List.generate(_mapGetSbglGetData.length, (index) { + String key = _mapGetSbglGetData.keys.elementAt(index); + + String strContent = _mapGetSbglGetData[key].toString(); + //时间戳转换 + if (strContent.isNotEmpty && 'addtime' == key) { + strContent = getDate(strContent); + } + //workflow转换 + if (strContent.isNotEmpty && 'workflow' == key) { + strContent = strContent == '999' ? '已处理' : '待处理'; + } + var controller = TextEditingController(text: strContent); + + controller.selection = TextSelection.fromPosition( + TextPosition(affinity: TextAffinity.downstream, offset: '${controller.text}'.length), + ); + return controller; + }); + getPreBtn_NextBtn(); + nums = '${(widget.index + 1).toString()} / $listLen'; + setState(() {}); + } + + Widget getTrail(String key, int index, double widthTrail) { + if (0 == _listController.length) { + return Container(width: widthTrail); + } + + //print('key = $key'); + if ('avatar' == key) { + imagePath = _listController[index].text; + return getAvatar(width: widthTrail); + } else { + return Container( + alignment: Alignment(-1, 0), + //widthTrail = 400报错,360刚能显示,300换行,260 + width: widthTrail, + //解决信息显示不全问题 + child: Text(_listController[index].text, + style: TextStyle(fontSize: 'dwms' == key ? 14 : 16), textAlign: TextAlign.left), + ); + } + } + + Widget getTrail0(String key, int index, double widthTrail) { + if (0 == _listController.length) { + return Container(width: widthTrail); + } + + //print('key = $key'); + if ('avatar' == key) { + imagePath = _listController[index].text; + return getAvatar(width: widthTrail); + } else { + return Container( + alignment: Alignment(1, 0), + //widthTrail = 400报错,360刚能显示,300换行,260 + width: widthTrail, + child: TextField( + //textAlign: TextAlign.right, + style: TextStyle(fontSize: 16), + decoration: InputDecoration( + //hintText: '請輸入字段信息', + border: InputBorder.none, //TextField去掉下划线 + contentPadding: EdgeInsets.only(right: 0), + ), + controller: _listController[index], + enabled: false, + //利用控制器初始化文本 + onChanged: (value) {}, + ), + ); + } + } + + Future doContacts() async { + bFlash = false; + return showDialog( + context: context, + builder: (BuildContext context) { + return customDialogH( + title: "请选择头像修改操作", + content: "头像修改", + index: 0, + ); + }, + ).then((value) { + imagePath = value; + print('Page2_Contacts bFlash = $bFlash'); + if (imagePath.isNotEmpty) { + _image = Image.file(File(imagePath), fit: BoxFit.cover); + setState(() {}); + } + }); + } + + //Image.file(this._image); + String imagePath = ''; + Image _image; + + Widget getAvatar({double width = 260.0}) { + if (imagePath.isEmpty) { + _image = Image.asset('assets/images/user.png', fit: BoxFit.cover); + } else { + String head = imagePath.substring(0, 4).trim().toLowerCase(); + if ('http' == head) { + _image = Image.network(imagePath, fit: BoxFit.cover); + } + } + return Container( + alignment: Alignment(-1, 0), + width: width, + child: InkWell( + onTap: () async { + doContacts(); + }, + child: Container( + width: 40, + child: _image, + ), + ), + ); + } + + //添加、修改、删除联系人对话框 + doContacts2(String key, int index) async { + showDialog( + context: context, + builder: (BuildContext context) { + return CustomDialogJ( + theKey: key, + index: index, + ); + }, + ); + } + + static onNullFun() {} + + Widget _getListTile(String key, int index, double widthTrail, + {onTapFun = onNullFun, onLongPressFun = onNullFun, size = 16.0}) { + //print('key = $key, index = $index'); + return ListTile( + //leading: new Icon(Icons.phone), + title: Text((index + 1).toString() + ". " + '${_mapGetSbglGetDataText[key]} :', + style: TextStyle(fontSize: 16)), + trailing: getTrail(key, index, widthTrail), + contentPadding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 0), + enabled: true, + onTap: () async { + // print('第 $index 项被点击了'); + // await doContacts2(key, index); + // if ('avatar' == key) { + // print('选择图片或拍照'); + // await doContacts(); + // } + }, + onLongPress: () {}, + ); + } + + TextEditingController _controller = TextEditingController.fromValue(TextEditingValue( + text: '已做简单处理。低级故障,不影响系统运行', + // 保持光标在最后 + selection: TextSelection.fromPosition( + TextPosition(affinity: TextAffinity.downstream, offset: '已处理'.length)))); + + double _heigth = 30; + double _fontSize = 16; + double _marginLeft = 15; + + @override + Widget build(BuildContext context) { + bool bCheck = _mapGetSbglGetData['workflow'] == 999; //已处理 + return Scaffold( + // appBar: AppBar( + // title: Text("设备点位信息详情($nums)"), + // centerTitle: true, + // ), + appBar: PreferredSize( + preferredSize: Size.fromHeight(ScreenUtil().setHeight(173)), // 设置appBar高度 + // 设置appBar高度 + child: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + titleSpacing: 0.0, + //设置title的左边距 + flexibleSpace: Container( + //SizedBox(height: ScreenUtil().statusBarHeight), //显示顶部状态栏 + // SizedBox(height: ScreenUtil().setHeight(10)), //显示顶部状态栏 + padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight), //留出顶部状态栏高度 + child: Container( + //height: ScreenUtil().setHeight(173), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + // decoration: BoxDecoration( + // gradient: LinearGradient(colors: [ + // Color(0xFF0018EB), + // Color(0xFF01C1D9), + // ], begin: Alignment.bottomCenter, end: Alignment.topCenter), + // ), + ), + ), + title: Padding( + padding: EdgeInsets.only(left: 0, right: 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + getIconAndTextButton( + iconColor: Colors.white, + iconData: Icons.chevron_left_outlined, + onPress: () { + getingDwVideo = false; + Navigator.pop(context); + }, + ), + Expanded( + child: Text("设备点位信息详情($nums)", + style: TextStyle(color: Colors.white, fontSize: 20), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis), + ), + SizedBox(width: 50), + ], + ), + ), + ), + ), + body: Container( + child: Column( + children: [ + Expanded( + //https://www.it1352.com/2028416.html + //用Map而不是List的Flutter ListView.builder(Flutter listview with Map instead of List) + //使用ListView.separated设置分割线ListView.separated( + //child: ListView.separated( //这种方式不能设置每项高度,每页只能有7项 + child: ListView.builder( + //这种方式可以通过itemExtent设置每项高度,每页可以有9项 + //separatorBuilder: (BuildContext context, int index) => index %2 ==0? Divider(color: Colors.green) : Divider(color: Colors.red),//index为偶数,创建绿色分割线;index为奇数,则创建红色分割线 + //separatorBuilder: (BuildContext context, int index) => Divider(), + //itemExtent: 57.0, //列表项高度。56越界、57刚好。还是自动计算最好 + itemCount: _mapGetSbglGetData.length, + itemBuilder: (BuildContext context, index) { + String key = _mapGetSbglGetData.keys.elementAt(index); + return Column( + children: [ + _getListTile(key, index, 200.0), + Divider( + height: 1.0, + ), + ], + ); + }, + ), + ), + //7、得到核查处理意见组件 + Divider(height: 1.0, color: Colors.blue), + SizedBox(height: 15), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + // getBtnSizeX( + // text: "复制", + // onPressedFun: () { + // // Flutter 复制文本到剪贴板 + // Clipboard.setData( + // ClipboardData(text: listSbglGetList2[widget.index].toString())); + // Fluttertoast.showToast(msg: '点位信息已复制到剪贴板', gravity: ToastGravity.CENTER); + // }), + //getBtnSizeX(text: "删除", onPressedFun: () async {}), + getBtnSizeX( + text: "视频", + onPressedFun: () { + //getDwspUrl(index: widget.index, context: context); + getDwspUrlNew(indexRecord: widget.index, context: context); + + // getDwspUrl(index: widget.index + 1).then((url) { + // print('index = ${(widget.index + 1).toString()}, url = $url'); + // urlnew = url; + // + // //获取视频地址失败 + // if (!isVideoUrl(urlnew)) { + // return; + // } + // + // var ret = Navigator.of(context).push(MaterialPageRoute( + // builder: (context) => PlayerPro( + // url: urlnew, + // title: + // '点位视频\n${(widget.index + 1)}、${listDwinfoGetList2[widget.index]['dwmc']}', + // //initVideoSize: Size(704.0, 576.0), + // ))); + // print('ret = $ret'); + // }); + + //getDwVideoUrl(index: widget.index); + //getData(); + // Flutter 复制文本到剪贴板 + // Clipboard.setData( + // ClipboardData(text: listSbglGetList2[widget.index].toString())); + // Fluttertoast.showToast(msg: '点位信息已复制到剪贴板', gravity: ToastGravity.CENTER); + }), + preBtn, + nextBtn, + ], + ), + SizedBox(height: 20), + ], + ), + ), + ); + } + + //解决第一次进入报错问题。因为getPreBtn_NextBtn()还未执行,preBtn和nextBtn为空 + Widget preBtn = Container( + color: Colors.white12, //onPressedFun为null时无效 + width: 70.0, + height: 35.0, + child: RaisedButton( + padding: EdgeInsets.all(0), + textColor: Colors.black, + child: Text('上一条'), + onPressed: null, + ), + ); + + Widget nextBtn = Container( + color: Colors.white12, //onPressedFun为null时无效 + width: 70.0, + height: 35.0, + child: RaisedButton( + padding: EdgeInsets.all(0), + textColor: Colors.black, + child: Text('下一条'), + onPressed: null, + ), + ); + + getPreBtn_NextBtn() { + preBtn = getBtnSizeX( + text: "上一条", + onPressedFun: null, + ); + nextBtn = getBtnSizeX( + text: "下一条", + onPressedFun: null, + ); + + if (widget.index > 0 && listLen > 0) { + preBtn = getBtnSizeX( + text: "上一条", + onPressedFun: () async { + if (widget.index > 0) { + widget.index--; + getListFlields(); + } + }, + ); + } + + if (widget.index < (listLen - 1) && listLen > 0) { + nextBtn = getBtnSizeX( + text: "下一条", + onPressedFun: () async { + if (widget.index < listLen - 1) { + widget.index++; + getListFlields(); + } + }, + ); + } + } + + Widget getBtnSizeX({@required text, width = 70.0, height = 35.0, onPressedFun}) { + return Container( + color: Colors.white12, //onPressedFun为null时无效 + width: width, + height: height, + child: RaisedButton( + padding: EdgeInsets.all(0), + textColor: Colors.black, + child: Text(text), + onPressed: onPressedFun, + ), + ); + } +} diff --git a/lib/pages/Works/SBGL/dwxx_getList.dart b/lib/pages/Works/SBGL/dwxx_getList.dart new file mode 100644 index 0000000..cd2bd4a --- /dev/null +++ b/lib/pages/Works/SBGL/dwxx_getList.dart @@ -0,0 +1,820 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; + +import 'dwxx_content.dart'; +import '../../../components/dioFun.dart'; +import '../../../components/hyxx_data_handle.dart'; +import '../../../components/commonFun.dart'; +import '../../../services/EventBus.dart'; +import 'package:flutter_easyrefresh/easy_refresh.dart'; +import '../../../components/doJSON.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +//sbgl是本项目中“设备管理”的统一缩写 +class DwxxGetList extends StatefulWidget { + //hyshlx为黑烟审核类型,处理sbgl信息。mapHyshlx[hyshlx]获取为各种类型的设置数据 + DwxxGetList({this.hyshlx = 'dwxx', Key key}) : super(key: key); + String hyshlx; + + _SbglPageState createState() => _SbglPageState(); +} + +class _SbglPageState extends State { + //try_setState(); //避免异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + @override + void dispose() { + _controller.dispose(); //销毁控制器 + super.dispose(); + } + + @override + void initState() { + hyshlx = widget.hyshlx; + iPage = 0; + listSbglGetList2.clear(); + + ///从接口 mapHyshlx[hyshlx]['api'] 获取第 iPage 页的列表数据,返回 list + getPageList().then((value) { + listSbglGetList2 = value; + //按照用户选择的_selectedValue、_descending对listSbglGetList2进行排序,并延时更新 + _listSort(); + firstIndex = 0; + lastIndex = 7; + + //为播放点位视频读取数据 + print('listDwspGetList2 = ${listDwspGetList2}'); + listDwspGetList2 = listSbglGetList2; + listDwinfoGetList2 = listSbglGetList2; + print('listDwspGetList2 = ${listDwspGetList2}'); + + }); + + // ///从接口 mapHyshlx[theHyshlx]['api'] 获取指定类型第 page 页的列表数据,返回 list + // ///获取点位信息数据 + // listDwinfoGetList2.clear(); + // getThePageList(theHyshlx: 'dwxx').then((value) { + // listDwinfoGetList2 = value; + // print('listDwinfoGetList2 = \n$listDwinfoGetList2'); + // }); + + //监听设备管理信息数据更新事件 + eventBus.on().listen((event) async { + print(event.str); + iPage = 0; + listSbglGetList2.clear(); + + ///从接口 mapHyshlx[hyshlx]['api'] 获取第 iPage 页的列表数据,返回 list + getPageList().then((value) { + listSbglGetList2 = value; + //按照用户选择的_selectedValue、_descending对listSbglGetList2进行排序,并延时更新 + _listSort(); + firstIndex = 0; + lastIndex = 7; + }); + }); + + super.initState(); + } + + //点位信息数据 + //{ + // "id": 1, + // "dwip": "172.16.3.1", + // "dwmc": "江北振兴大道", + // "dwbh": 1, + // "dwinfo": "江北振兴大道入城方向", + // "dwzb": "104.607091|28.807061", + // "dwms": "江北振兴大道入城方向,识别孜岩、红坝路入城排放黑烟车辆", + // "dwzt": "正常" + //}, + + // Widget getDropdownButton() { + // //DropdownMenuItem项目文本list + // List itemList = [ + // '主键ID', + // '点位IP', + // '点位名称', + // '点位编号', + // '点位信息', + // '点位坐标', + // '点位描述', + // '点位状态', + // ]; + + Widget _getListTile(BuildContext context, indexRecord) { + List listCoordinate = listSbglGetList2[indexRecord]["dwzb"].trim().split('|'); + + return Column( + children: [ + ListTile( + //leading: new Icon(Icons.phone), + title: Text( + "${listSbglGetList2[indexRecord]['dwbh'].toString()}. ${listSbglGetList2[indexRecord]['dwmc']}", + style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), + subtitle: Text( + 'dwIP:${listSbglGetList2[indexRecord]['dwip']},${listSbglGetList2[indexRecord]['dwzt']}', + style: TextStyle(fontSize: 14)), + trailing: Container( + width: 160, + child: Text( + '${listSbglGetList2[indexRecord]['dwms']}' + + ', 经纬度:${listCoordinate[0]}、${listCoordinate[1]}', + maxLines: 2, + overflow: TextOverflow.ellipsis, + //textAlign: TextAlign.right, + style: TextStyle(fontSize: 14), + ), + ), + contentPadding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 0), + enabled: true, + onTap: () async { + int ret = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => DwxxContent( + title: '设备管理信息详情', + index: indexRecord, + hyshlx: widget.hyshlx, + ), + ), + ); + + print('ret = $ret'); + }, + ), + Divider(height: 1.0), + ], + ); + } + + ScrollController _controller = ScrollController(); //ListView控制器 + bool isLoading = false; //正在处理下载数据、跳转到首项、跳转到尾项等操作 + int firstIndex = 0; //ListView当前显示页面首项0基序号 + int lastIndex = 0; //ListView当前显示页面末项0基序号 + int itemOnPage = 8; //估计一屏显示的项目数量 + + Widget getIconButton({IconData iconData, var onPressed, double iconSize = 22}) { + return SizedBox( + height: iconSize, + width: iconSize + 10, + child: IconButton( + padding: EdgeInsets.all(0.0), + icon: Icon(iconData, size: iconSize, color: Colors.white), + onPressed: onPressed, + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + // appBar: AppBar( + // automaticallyImplyLeading: false, + // centerTitle: true, + // //leading: Text(''), + // titleSpacing: 0.0, + // //设置title的左边距 + // title: Padding( + // padding: EdgeInsets.only(left: 0, right: 0), + // child: Row( + // //mainAxisAlignment: MainAxisAlignment.start, + // children: [ + // getIconAndTextButton( + // iconData: Icons.arrow_back, + // onPress: () { + // Navigator.pop(context); + // }, + // ), + // Expanded( + // child: Text("${mapHyshlx[hyshlx]['text']}", + // textAlign: TextAlign.left, + // overflow: TextOverflow.ellipsis, + // style: widget.hyshlx == 'fhycx' ? TextStyle(fontSize: 16) : null), + // ), + // SizedBox(width: 5), + // ], + // ), + // ), + // actions: [ + // getDropdownButton(), + // SizedBox(width: 10), + // // getIconButton( + // // iconData: Icons.cloud_download, + // // onPressed: !isLoading + // // ? () async { + // // if (isLoading) { + // // return; + // // } + // // isLoading = true; + // // try_setState(); + // // //print("I Pressed the Iconbutton"); + // // int oldItems = listSbglGetList2.length; + // // for (int i = 0; i < 10; i++) { + // // List list = await getPageList(); + // // if (list.length > 0) { + // // listSbglGetList2.addAll(list); //加载累加 + // // } else { + // // break; + // // } + // // } + // // Future.delayed(const Duration(milliseconds: 1000), () async { + // // if (listSbglGetList2.length > oldItems) { + // // eventBus.fire(WzxxDataAuditEvent('${mapHyshlx[hyshlx]['text']}数据已更新')); + // // Fluttertoast.showToast( + // // msg: '新增 ${listSbglGetList2.length - oldItems} 条数据下载完成!', + // // toastLength: Toast.LENGTH_SHORT, + // // gravity: ToastGravity.CENTER, + // // ); + // // + // // //按照用户选择的_selectedValue、_descending对listSbglGetList2进行排序,并延时更新 + // // _listSort(); + // // } + // // isLoading = false; + // // try_setState(); + // // }); + // // } + // // : null, + // // ), + // SizedBox(width: 10), + // getIconButton( + // iconData: Icons.vertical_align_top_outlined, + // onPressed: (!isLoading && (firstIndex > 0)) //未到顶部 + // ? () async { + // if (isLoading) { + // return; + // } + // isLoading = true; + // try_setState(); + // + // //必须延时执行,否则不能及时完成按钮状态更新 + // Timer( + // Duration(milliseconds: 500), + // () { + // _controller.jumpTo(_controller.position.minScrollExtent); + // }, + // ); + // + // Timer( + // Duration(milliseconds: 1000), + // () { + // // Fluttertoast.showToast( + // // msg: '已经跳转到开头记录!', + // // toastLength: Toast.LENGTH_SHORT, + // // gravity: ToastGravity.CENTER, + // // ); + // firstIndex = 0; + // lastIndex = itemOnPage - 1; //0基序号,所以需要减1 + // isLoading = false; + // try_setState(); + // }, + // ); + // } + // : null, + // ), + // SizedBox(width: 10), + // getIconButton( + // iconData: Icons.vertical_align_bottom_outlined, + // onPressed: (!isLoading && (lastIndex < listSbglGetList2.length)) //未到尾部 + // ? () async { + // if (isLoading) { + // return; + // } + // isLoading = true; + // try_setState(); + // + // //必须延时执行,否则不能及时完成按钮状态更新 + // Timer( + // Duration(milliseconds: 500), + // () { + // _controller.jumpTo(_controller.position.maxScrollExtent); + // }, + // ); + // + // Timer( + // Duration(milliseconds: 1000), + // () { + // // Fluttertoast.showToast( + // // msg: '已经跳转到末尾记录!', + // // toastLength: Toast.LENGTH_SHORT, + // // gravity: ToastGravity.CENTER, + // // ); + // //0基序号需要减1,再加上底部有一组显示信息,所以需要减2 + // firstIndex = listSbglGetList2.length - itemOnPage - 2; + // lastIndex = listSbglGetList2.length; + // isLoading = false; + // try_setState(); + // }, + // ); + // } + // : null, + // ), + // SizedBox(width: 10), + // ], + // ), + appBar: PreferredSize( + preferredSize: Size.fromHeight(ScreenUtil().setHeight(173)), // here the desired height + child: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + //leading: Text(''), + titleSpacing: 0.0, + //设置title的左边距 + flexibleSpace: Container( + //SizedBox(height: ScreenUtil().statusBarHeight), //显示顶部状态栏 + // SizedBox(height: ScreenUtil().setHeight(10)), //显示顶部状态栏 + padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight), //留出顶部状态栏高度 + child: Container( + //height: ScreenUtil().setHeight(173), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + // decoration: BoxDecoration( + // gradient: LinearGradient(colors: [ + // Color(0xFF0018EB), + // Color(0xFF01C1D9), + // ], begin: Alignment.bottomCenter, end: Alignment.topCenter), + // ), + ), + ), + //1、第1行组件,工具按钮 + title: Padding( + padding: EdgeInsets.only(left: 0, right: 0), + child: Row( + //mainAxisAlignment: MainAxisAlignment.start, + children: [ + //1.1、返回按钮 + getIconAndTextButton( + iconColor: Colors.white, + iconData: Icons.chevron_left_outlined, + onPress: () { + Navigator.pop(context); + }, + ), + //1.2、title 显示控制 + Expanded( + child: Text("${mapHyshlx[hyshlx]['text']}", + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + style: TextStyle(color: Colors.white, fontSize: 20)), + ), + SizedBox(width: 5), + ], + ), + ), + //1.3、尾部工具按钮组 + actions: [ + //getDropdownButton(), + getIconButton( + iconData: Icons.cloud_download, + onPressed: !isLoading + ? () async { + if (isLoading) { + return; + } + isLoading = true; + try_setState(); + //print("I Pressed the Iconbutton"); + int oldItems = listSbglGetList2.length; + for (int i = 0; i < 10; i++) { + List list = await getPageList(); + if (list.length > 0) { + listSbglGetList2.addAll(list); //加载累加 + } else { + break; + } + } + Future.delayed(const Duration(milliseconds: 1000), () async { + if (listSbglGetList2.length > oldItems) { + eventBus.fire(WzxxDataAuditEvent('${mapHyshlx[hyshlx]['text']}数据已更新')); + Fluttertoast.showToast( + msg: '新增 ${listSbglGetList2.length - oldItems} 条数据下载完成!', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + + //按照用户选择的_selectedValue、_descending对listSbglGetList2进行排序,并延时更新 + _listSort(); + //_controller.jumpTo(1); // _controller.position 能够有效刷新,人眼可察觉的滚动 + _controller.jumpTo(0.001); // _controller.position 完美解决有效刷新,而且人眼不可见察觉滚动 + //第二次跳转必须延时,否则 _controller.position 不能有效刷新 + Future.delayed(Duration(milliseconds: 500), () { + _controller.jumpTo(_controller.position.minScrollExtent); + }); + } + isLoading = false; + try_setState(); + }); + } + : null, + ), + SizedBox(width: ScreenUtil().setWidth(32)), + getIconButton( + iconData: Icons.vertical_align_top_outlined, + onPressed: (!isLoading && (firstIndex > 0)) //未到顶部 + ? () async { + if (isLoading) { + return; + } + isLoading = true; + try_setState(); + + //必须延时执行,否则不能及时完成按钮状态更新 + Timer( + Duration(milliseconds: 500), + () { + _controller.jumpTo(_controller.position.minScrollExtent); + }, + ); + + Timer( + Duration(milliseconds: 1000), + () { + // Fluttertoast.showToast( + // msg: '已经跳转到开头记录!', + // toastLength: Toast.LENGTH_SHORT, + // gravity: ToastGravity.CENTER, + // ); + firstIndex = 0; + lastIndex = itemOnPage - 1; //0基序号,所以需要减1 + isLoading = false; + try_setState(); + }, + ); + } + : null, + ), + SizedBox(width: ScreenUtil().setWidth(32)), + getIconButton( + iconData: Icons.vertical_align_bottom_outlined, + onPressed: (!isLoading && (lastIndex < listSbglGetList2.length)) //未到尾部 + ? () async { + if (isLoading) { + return; + } + isLoading = true; + try_setState(); + + //必须延时执行,否则不能及时完成按钮状态更新 + Timer( + Duration(milliseconds: 500), + () { + _controller.jumpTo(_controller.position.maxScrollExtent); + }, + ); + + Timer( + Duration(milliseconds: 1000), + () { + // Fluttertoast.showToast( + // msg: '已经跳转到末尾记录!', + // toastLength: Toast.LENGTH_SHORT, + // gravity: ToastGravity.CENTER, + // ); + //0基序号需要减1,再加上底部有一组显示信息,所以需要减2 + firstIndex = listSbglGetList2.length - itemOnPage - 2; + lastIndex = listSbglGetList2.length; + isLoading = false; + try_setState(); + }, + ); + } + : null, + ), + SizedBox(width: ScreenUtil().setWidth(32)), + ], + ), + ), + body: Column(children: [ + //2、第2行排序按钮 + Container( + height: ScreenUtil().setHeight(142), + decoration: new BoxDecoration(border: new Border.all(color: Colors.red)), + child: Row( + children: [ + SizedBox(width: ScreenUtil().setWidth(50)), + Text('排序', style: TextStyle(fontSize: 18)), + Expanded(child: SizedBox.shrink()), + getDropdownButton(), + SizedBox(width: ScreenUtil().setWidth(20)), + ], + ), + ), + (0 == listSbglGetList2.length) + ? getMoreWidget(color: Colors.black38) + : Expanded( + child: EasyRefresh( + child: ListView.custom( + //itemExtent: 75.0, //列表项高度 + controller: _controller, + cacheExtent: 1.0, // 只有设置了1.0 才能够准确的标记 position 位置 + childrenDelegate: MyChildrenDelegate( + _getListTile, + childCount: listSbglGetList2.length, + ), + ), + onRefresh: () async { + // await Future.delayed(const Duration(seconds: 1), () async { + // iPage = 0; + // //await getWzxxGetList(); + // listSbglGetList2 = await getPageList(); + // eventBus.fire(WzxxDataAuditEvent('${mapHyshlx[hyshlx]['text']}数据已更新')); + // }); + }, + onLoad: () async { + if (isLoading) { + return; + } + isLoading = true; + try_setState(); + + print('iPage = $iPage'); + await Future.delayed(const Duration(seconds: 1), () async { + //await getWzxxGetList(); + List list = await getPageList(); + if (list.length > 0) { + listSbglGetList2.addAll(list); //加载累加 + eventBus.fire(WzxxDataAuditEvent('${mapHyshlx[hyshlx]['text']}数据已更新')); + } + isLoading = false; + try_setState(); + }); + }, + header: getHeader(), + footer: getFooter(), + ), + ) + ]), + ); + } + + //DropdownButton需要设置初始值的时候,初始值必须是显示列表里面的值,否则会导致弹出框异常。 + // 比如说:你的DropdownButton的items属性使用的是list这个列表里面的值,那么你的初始值应该在list[index]里面取,要不就会报错。 + // + // There should be exactly one item with [DropdownButton]'s value: 0.0. + // Either zero or 2 or more [DropdownMenuItem]s were detected with the same value + // 'package:flutter/src/material/dropdown.dart': + // Failed assertion: line 834 pos 15: 'items == null || items.isEmpty || value == null || + // items.where((DropdownMenuItem item) { + // return item.value == value; + // }).length == 1' + String _selectedValue = '点位编号'; + bool _descending = false; //默认升序排列 + + Widget _getImage(String _image) { + return Container( + margin: EdgeInsets.only(), + height: ScreenUtil().setWidth(48), + width: ScreenUtil().setWidth(48), + //child: Image.asset('assets/images/ybsthbj.png', fit: BoxFit.fitHeight), + child: Image.asset(_image, + fit: BoxFit.cover, color: isLoading ? Theme.of(context).disabledColor : null)); + } + + //按照用户选择的_selectedValue、_descending对listSbglGetList2进行排序,并延时更新 + Future _listSort({bool bShowToast = false}) { + if (!isLoading && listSbglGetList2.length > 0) { + isLoading = true; + try_setState(); + + switch (_selectedValue) { + default: + if (_descending) { + //按_selectedValue排序,降序 + listSbglGetList2.sort((a, b) => + (b[mapWzxxDataText[_selectedValue]]).compareTo(a[mapWzxxDataText[_selectedValue]])); + } else { + //按_selectedValue排序,升序 + listSbglGetList2.sort((a, b) => + (a[mapWzxxDataText[_selectedValue]]).compareTo(b[mapWzxxDataText[_selectedValue]])); + } + break; + } + + Future.delayed(const Duration(milliseconds: 1000), () { + if (bShowToast) { + Fluttertoast.showToast( + msg: '按“${_selectedValue}”${_descending ? '降序' : '升序'}排列完成!', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + isLoading = false; + try_setState(); //避免如下异常报错 + }); + } + } + + Widget getDropdownButtonItemText(String item) { + return Padding( + padding: EdgeInsets.only(bottom: ScreenUtil().setHeight(3)), + child: Row( + //crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + alignment: Alignment(0, 0), + padding: EdgeInsets.only(top: ScreenUtil().setHeight(10)), + child: item == _selectedValue + ? _descending + ? _getImage('assets/images/descending.png') + : _getImage('assets/images/ascending.png') + // ? Icon(Icons.arrow_downward_outlined, size: 20) + // : Icon(Icons.arrow_upward_outlined, size: 20) + : SizedBox(), + ), + SizedBox( + width: ScreenUtil().setWidth(10), + ), + Container( + //alignment: Alignment(0, -1), + child: Text(item, + style: TextStyle( + fontSize: 18, + color: isLoading + ? Theme.of(context).disabledColor + : item == _selectedValue + ? Colors.blue + : null)), + // style: isLoading + // ? TextStyle(color: Theme.of(context).disabledColor) + // : (item == _selectedValue ? TextStyle(color: Colors.blue) : null)), + ), + ], + ), + ); + } + + //点位信息数据 + //{ + // "id": 1, + // "dwip": "172.16.3.1", + // "dwmc": "江北振兴大道", + // "dwbh": 1, + // "dwinfo": "江北振兴大道入城方向", + // "dwzb": "104.607091|28.807061", + // "dwms": "江北振兴大道入城方向,识别孜岩、红坝路入城排放黑烟车辆", + // "dwzt": "正常" + //}, + + Widget getDropdownButton() { + //DropdownMenuItem项目文本list + List itemList = [ + '主键ID', + '点位IP', + '点位名称', + '点位编号', + '点位信息', + '点位坐标', + '点位描述', + '点位状态', + ]; + + //添加按'推送状态'排序 + if (hyshlx != 'dwxx') { + // itemList.removeLast(); + // itemList.addAll(['推送状态', '主键ID']); + } + + //获取DropdownMenuItem项目组件list + List> _dropDownMenuItems = + itemList.map>((String item) { + return DropdownMenuItem( + value: item, + child: getDropdownButtonItemText(item), + ); + }).toList(); + + return Padding( + padding: EdgeInsets.only(top: 0, bottom: 0), + child: Container( + alignment: Alignment(0, 0), + width: 135, + margin: EdgeInsets.only(bottom: 0), + padding: EdgeInsets.only(left: 0, bottom: 0), + // decoration: BoxDecoration( + // border: Border.all(width: 0), + // //边框圆角设置 + // borderRadius: + // BorderRadius.vertical(top: Radius.elliptical(2, 2), bottom: Radius.elliptical(2, 2)), + // ), + //DropdownButton默认有一条下划线,DropdownButtonHideUnderline去除下划线 + child: DropdownButtonHideUnderline( + child: DropdownButton( + iconSize: ScreenUtil().setHeight(100), + //itemHeight: ScreenUtil().setHeight(372), + isDense: true, + value: _selectedValue, + items: _dropDownMenuItems, + onChanged: (String selectedValue) { + if (isLoading) { + return; + } + + if (_selectedValue == selectedValue) { + _descending = !_descending; + } else { + _descending = true; + } + _selectedValue = selectedValue; + print('_selectedValue = $_selectedValue'); + + //按抓拍次数排序,降序 + // listSbglGetList2.sort((a, b) => (b["yjxx_id"].split(',').length.toString()) + // .compareTo(a["yjxx_id"].split(',').length.toString())); + + //按照用户选择的_selectedValue、_descending对listSbglGetList2进行排序,并延时更新 + _listSort(); + }, + ), + ), + ), + ); + } +} + +//https://blog.csdn.net/u014803467/article/details/103750018 +//Flutter 使用SliverChildBuilderDelegate获取ListView的第一个和最后一个可见Item序号 +// 秋名山交警X 2019-12-28 23:52:52 +class _SaltedValueKey extends ValueKey { + const _SaltedValueKey(Key key) + : assert(key != null), + super(key); +} + +class MyChildrenDelegate extends SliverChildBuilderDelegate { + MyChildrenDelegate( + Widget Function(BuildContext, int) builder, { + int childCount, + bool addAutomaticKeepAlive = true, + bool addRepaintBoundaries = true, + }) : super(builder, + childCount: childCount, + addAutomaticKeepAlives: addAutomaticKeepAlive, + addRepaintBoundaries: addRepaintBoundaries); + + // Return a Widget for the given Exception + Widget _createErrorWidget(dynamic exception, StackTrace stackTrace) { + final FlutterErrorDetails details = FlutterErrorDetails( + exception: exception, + stack: stackTrace, + library: 'widgets library', + context: ErrorDescription('building'), + ); + FlutterError.reportError(details); + return ErrorWidget.builder(details); + } + + @override + Widget build(BuildContext context, int index) { + assert(builder != null); + if (index < 0 || (childCount != null && index >= childCount)) return null; + Widget child; + try { + child = builder(context, index); + } catch (exception, stackTrace) { + child = _createErrorWidget(exception, stackTrace); + } + if (child == null) return null; + final Key key = child.key != null ? _SaltedValueKey(child.key) : null; + if (addRepaintBoundaries) child = RepaintBoundary(child: child); + if (addSemanticIndexes) { + final int semanticIndex = semanticIndexCallback(child, index); + if (semanticIndex != null) + child = IndexedSemantics(index: semanticIndex + semanticIndexOffset, child: child); + } + if (addAutomaticKeepAlives) child = AutomaticKeepAlive(child: child); + return KeyedSubtree(child: child, key: key); + } + + @override + void didFinishLayout(int _firstIndex, int _lastIndex) { + // TODO: implement didFinishLayout + super.didFinishLayout(_firstIndex, _lastIndex); + } + + ///监听 在可见的列表中 显示的第一个位置和最后一个位置 + @override + double estimateMaxScrollOffset( + int _firstIndex, int _lastIndex, double _leadingScrollOffset, double _trailingScrollOffset) { + //违章信息Listview滚动广播 + eventBus.fire(WzxxDataScrollEvent(_firstIndex, _lastIndex)); + + return super.estimateMaxScrollOffset( + _firstIndex, _lastIndex, _leadingScrollOffset, _trailingScrollOffset); + } +} diff --git a/lib/pages/Works/TJXX/tj_data.dart b/lib/pages/Works/TJXX/tj_data.dart new file mode 100644 index 0000000..f5650b0 --- /dev/null +++ b/lib/pages/Works/TJXX/tj_data.dart @@ -0,0 +1,688 @@ +import 'package:hyzp_ybqx/components/commonFun.dart'; +import 'package:hyzp_ybqx/components/dioFun.dart'; +import 'package:hyzp_ybqx/components/hyxx_data_handle.dart'; +import 'package:hyzp_ybqx/services/EventBus.dart'; + +///获取车流量日统计数据 +bool cllRStatisDataGeting = false; //正在获取车流量日统计数据,禁止重入 +bool cllRStatisDataOk = false; //listCllrtjStatis 中的数据是否准备好 +//List listCllrtjStatis = []; +// [ +// { +// "dwbh": 4, +// "dwmc": "一曼路", +// "dwip": "172.16.3.4" +// "all": "770742", +// "cllmx": [ +// { +// "day": "2021-04-12", +// "am_order": "0730:0930", +// "pm_order": "1730:1930", +// "all": "17760", +// "am": "1272", +// "pm": "3153" +// }, +// { +// "day": "2021-04-11", +// "am_order": "0730:0930", +// "pm_order": "1730:1930", +// "all": "18005", +// "am": "1677", +// "pm": "2275" +// }, +// // ... +// ] +// }, +// { +// "dwbh": 5, +// "dwmc": "柏溪收费站", +// "dwip": "172.16.3.5" +// "all": "770742", +// "cllmx": [ +// { +// "day": "2021-04-12", +// "am_order": "0730:0930", +// "pm_order": "1730:1930", +// "all": "17760", +// "am": "1272", +// "pm": "3153" +// }, +// { +// "day": "2021-04-11", +// "am_order": "0730:0930", +// "pm_order": "1730:1930", +// "all": "18005", +// "am": "1677", +// "pm": "2275" +// }, +// // ... +// ] +// }, +// ] + +///获取抓拍统计数据 +List listZptjStatis = []; + +///获取审核黑烟车统计数据 +List listSh_hyc_tjStatis = []; + +///获取审核黑烟车统计数据 +List listClltjStatis = []; + +///获取车流量日统计数据 +List listCllrtjStatis = []; +bool statisDataOk = false; //listCllrtjStatis 中的数据是否准备好 +Map mapCllrtjStatis = {}; +/* +{ + "total": 6635167, + "am": 713074, + "pm": 981656, + "view": [ + { + "dwip": "172.16.3.1", + "dw_all": 701120, + "dw_am": 76210, + "dw_pm": 105153, + "cllmx": [ + { + "day": "2021-05-25", + "am_order": "0730:0930", + "pm_order": "1730:1930", + "all": "24190", + "am": "2148", + "pm": "5173" + }, + { + "day": "2021-05-24", + "am_order": "0730:0930", + "pm_order": "1730:1930", + "all": "20254", + "am": "1825", + "pm": "3975" + }, + ... + }, + { + "dwip": "172.16.3.2", + "dw_all": 533990, + "dw_am": 65276, + "dw_pm": 78055, + "cllmx": [ + { + "day": "2021-05-25", + "am_order": "0730:0930", + "pm_order": "1730:1930", + "all": "16355", + "am": "1375", + "pm": "3029" + }, + { + "day": "2021-05-24", + "am_order": "0730:0930", + "pm_order": "1730:1930", + "all": "15997", + "am": "1797", + "pm": "2216" + }, + ... +*/ + +// [ +// { +// "dwbh": 4, +// "dwmc": "一曼路", +// "dwip": "172.16.3.4" +// "all": "770742", +// "cllmx": [ +// { +// "day": "2021-04-12", +// "am_order": "0730:0930", +// "pm_order": "1730:1930", +// "all": "17760", +// "am": "1272", +// "pm": "3153" +// }, +// { +// "day": "2021-04-11", +// "am_order": "0730:0930", +// "pm_order": "1730:1930", +// "all": "18005", +// "am": "1677", +// "pm": "2275" +// }, +// // ... +// ] +// }, +// { +// "dwbh": 5, +// "dwmc": "柏溪收费站", +// "dwip": "172.16.3.5" +// "all": "770742", +// "cllmx": [ +// { +// "day": "2021-04-12", +// "am_order": "0730:0930", +// "pm_order": "1730:1930", +// "all": "17760", +// "am": "1272", +// "pm": "3153" +// }, +// { +// "day": "2021-04-11", +// "am_order": "0730:0930", +// "pm_order": "1730:1930", +// "all": "18005", +// "am": "1677", +// "pm": "2275" +// }, +// // ... +// ] +// }, +// ] + +// 一次性获取所有点位的统计数据存入 listCllrtjStatis +Future getCllrtjStatisNew(String statisType) async { + //抓拍统计数据 + //{ + // "today": 0, + // "all": 72 + // "dwbh": 1, + // "dwmc": "江北振兴大道", + // "dwip": "172.16.3.1" + //} + + ///从接口 mapStatisType[statisType]['api'] 获取指定 ip 的 statisType 类型的统计数据,返回 Map + //Future getStatisData({@required String statisType, @required String ip}) async { + listCllrtjStatis.clear(); + mapCllrtjStatis = await getStatisData(statisType: statisType); + listCllrtjStatis = mapCllrtjStatis["view"]; + + // int len = listDwinfoGetList2.length; + // for (int i = 0; i < len; i++) { + // Map map = await getStatisData(statisType: statisType, ip: listDwinfoGetList2[i]['dwip']); + // map['dwbh'] = listDwinfoGetList2[i]['dwbh']; + // map['dwmc'] = listDwinfoGetList2[i]['dwmc']; + // map['dwip'] = listDwinfoGetList2[i]['dwip']; + // listCllrtjStatis.add(map); + // } + //print('listZptjStatis = ${listZptjStatis}'); + //StorageDataToFile.writeCounter(json_print(listZptjStatis, 1)); +} + +//遍历 listDwinfoGetList2 中所有点位,得到对应点位 ip 的统计数据存入 listCllrtjStatis +Future getCllrtjStatis(String statisType) async { + //抓拍统计数据 + //{ + // "today": 0, + // "all": 72 + // "dwbh": 1, + // "dwmc": "江北振兴大道", + // "dwip": "172.16.3.1" + //} + + ///从接口 mapStatisType[statisType]['api'] 获取指定 ip 的 statisType 类型的统计数据,返回 Map + //Future getStatisData({@required String statisType, @required String ip}) async { + listCllrtjStatis.clear(); + int len = listDwinfoGetList2.length; + for (int i = 0; i < len; i++) { + Map map = await getStatisData(statisType: statisType, ip: listDwinfoGetList2[i]['dwip']); + map['dwbh'] = listDwinfoGetList2[i]['dwbh']; + map['dwmc'] = listDwinfoGetList2[i]['dwmc']; + map['dwip'] = listDwinfoGetList2[i]['dwip']; + listCllrtjStatis.add(map); + } + //print('listZptjStatis = ${listZptjStatis}'); + //StorageDataToFile.writeCounter(json_print(listZptjStatis, 1)); +} + +//遍历 listDwinfoGetList2 中所有点位,得到对应点位 ip 的统计数据存入 listZptjStatis +Future getZptjStatis(String statisType) async { + //抓拍统计数据 + //{ + // "today": 0, + // "all": 72 + // "dwbh": 1, + // "dwmc": "江北振兴大道", + // "dwip": "172.16.3.1" + //} + + ///从接口 mapStatisType[statisType]['api'] 获取指定 ip 的 statisType 类型的统计数据,返回 Map + //Future getStatisData({@required String statisType, @required String ip}) async { + listZptjStatis.clear(); + int len = listDwinfoGetList2.length; + for (int i = 0; i < len; i++) { + Map map = await getStatisData(statisType: statisType, ip: listDwinfoGetList2[i]['dwip']); + map['dwbh'] = listDwinfoGetList2[i]['dwbh']; + map['dwmc'] = listDwinfoGetList2[i]['dwmc']; + map['dwip'] = listDwinfoGetList2[i]['dwip']; + listZptjStatis.add(map); + } + //print('listZptjStatis = ${listZptjStatis}'); + //StorageDataToFile.writeCounter(json_print(listZptjStatis, 1)); +} + +//在Dart语言中,如何通过值获取MAP键?mapTjDataText +String getKey(String value) { + String key = mapTjDataText.keys.firstWhere((k) => mapTjDataText[k] == value, orElse: () => null); +} + +Map mapTjDataText = { + "dwmc": '点位名称', + "dwbh": '点位编号', + "dwip": '点位IP', + "today": '今日', + "all": '总共', + "total": '已审核', + "sends": '已推送' +}; + +////////////////////////////////////////////////////////////////// +///独立获取抓拍统计数据 +List listZptjStatisAlone = []; +List listTodayZpjl = []; //今日抓拍记录列表 + +//遍历 listDwinfoGetList2 中所有点位,独立得到对应点位 ip 的统计数据存入 listZptjStatisNew +Future getZptjStatisAlone() async { + //抓拍统计数据 + //{ + // "today": 0, + // "all": 72 + // "dwbh": 1, + // "dwmc": "江北振兴大道", + // "dwip": "172.16.3.1" + //} + + ///从接口 mapStatisType[statisType]['api'] 获取指定 ip 的 statisType 类型的统计数据,返回 Map + //Future getStatisData({@required String statisType, @required String ip}) async { + listZptjStatisAlone.clear(); + int len = listDwinfoGetList2.length; + for (int i = 0; i < len; i++) { + Map map = await getStatisData(statisType: 'zptj', ip: listDwinfoGetList2[i]['dwip']); + map['dwbh'] = listDwinfoGetList2[i]['dwbh']; + map['dwmc'] = listDwinfoGetList2[i]['dwmc']; + map['dwip'] = listDwinfoGetList2[i]['dwip']; + listZptjStatisAlone.add(map); + // if (listZptjStatisAlone.length >= dwSum) { + // //发送统计数据已更新广播 + // eventBus.fire(StatisDataUpdate('统计数据已更新')); + // } + getAllSum('today', listZptjStatisAlone).then((value) { + //mapStatisInfo['今日抓拍'] = value[1]; + listTodayZpjl = value[2]; + //try_setState(); + }); + } + //print('listZptjStatis = ${listZptjStatis}'); + //StorageDataToFile.writeCounter(json_print(listZptjStatis, 1)); +} + +///独立获取今日审核统计数据 +List listTodayShtj = []; +List listTodayChjl = []; //今日初审记录列表 +List listTodayFhjl = []; //今日复审记录列表 +List listTodayTsjl = []; //今日推送记录列表 + +//遍历 listDwinfoGetList2 中所有点位,独立得到对应点位 ip 的统计数据存入 listShtjStatisNew +Future getTodayShtj() async { + //今日审核统计数据 + //{ + // "total": 0,"today": 0, + // "sends": 0,"all": 72 + // "csnum": 0,"dwbh": 1, + // "fsnum": 0"dwmc": "江北振兴大道", + //} + + ///从接口 mapStatisType[statisType]['api'] 获取指定 ip 的 statisType 类型的统计数据,返回 Map + //Future getStatisData({@required String statisType, @required String ip}) async { + listTodayShtj.clear(); + DateTime dateTime = DateTime.now(); + String _mouth = dateTime.month.toString().trim(); + if (_mouth.length == 1) { + _mouth = '0' + _mouth; + } + + String _day = dateTime.day.toString().trim(); + if (_day.length == 1) { + _day = '0' + _day; + } + + String date = '${dateTime.year}-${_mouth}-${_day}'; + print('date = $date'); + int len = listDwinfoGetList2.length; + for (int i = 0; i < len; i++) { + Map map = + await getStatisData(statisType: 'sh_hyc_tj', ip: listDwinfoGetList2[i]['dwip'], date: date); + map['dwbh'] = listDwinfoGetList2[i]['dwbh']; + map['dwmc'] = listDwinfoGetList2[i]['dwmc']; + map['dwip'] = listDwinfoGetList2[i]['dwip']; + listTodayShtj.add(map); + // if (listTodayShtj.length >= dwSum) { + // print('listTodayShtj = $listTodayShtj'); + // //发送统计数据已更新广播 + // eventBus.fire(StatisDataUpdate('统计数据已更新')); + // } + getAllSum('csnum', listTodayShtj).then((value) { + //mapStatisInfo['今日初审'] = value[1]; + listTodayChjl = value[2]; + //try_setState(); + }); + getAllSum('fsnum', listTodayShtj).then((value) { + //mapStatisInfo['今日复审'] = value[1]; + listTodayFhjl = value[2]; + //try_setState(); + }); + getAllSum('sends', listTodayShtj).then((value) { + //mapStatisInfo['今日推送'] = value[1]; + listTodayTsjl = value[2]; + //try_setState(); + }); + } + //print('listZptjStatis = ${listZptjStatis}'); + //StorageDataToFile.writeCounter(json_print(listZptjStatis, 1)); +} + +///独立获取审核统计数据 +List listShtjStatisAlone = []; + +//遍历 listDwinfoGetList2 中所有点位,独立得到对应点位 ip 的统计数据存入 listShtjStatisNew +Future getShtjStatisAlone() async { + //抓拍统计数据 + //{ + // "today": 0, + // "all": 72 + // "dwbh": 1, + // "dwmc": "江北振兴大道", + // "dwip": "172.16.3.1" + //} + + ///从接口 mapStatisType[statisType]['api'] 获取指定 ip 的 statisType 类型的统计数据,返回 Map + //Future getStatisData({@required String statisType, @required String ip}) async { + listShtjStatisAlone.clear(); + int len = listDwinfoGetList2.length; + for (int i = 0; i < len; i++) { + Map map = await getStatisData(statisType: 'sh_hyc_tj', ip: listDwinfoGetList2[i]['dwip']); + map['dwbh'] = listDwinfoGetList2[i]['dwbh']; + map['dwmc'] = listDwinfoGetList2[i]['dwmc']; + map['dwip'] = listDwinfoGetList2[i]['dwip']; + listShtjStatisAlone.add(map); + // if (listShtjStatisAlone.length >= dwSum) { + // //发送统计数据已更新广播 + // eventBus.fire(StatisDataUpdate('统计数据已更新')); + // } + } + //print('listZptjStatis = ${listZptjStatis}'); + //StorageDataToFile.writeCounter(json_print(listZptjStatis, 1)); +} + +///独立获取车流量统计数据 +List listClltjStatisAlone = []; + +//遍历 listDwinfoGetList2 中所有点位,独立得到对应点位 ip 的统计数据存入 listShtjStatisNew +Future getClltjStatisAlone() async { + //抓拍统计数据 + //{ + // "today": 0, + // "all": 72 + // "dwbh": 1, + // "dwmc": "江北振兴大道", + // "dwip": "172.16.3.1" + //} + + ///从接口 mapStatisType[statisType]['api'] 获取指定 ip 的 statisType 类型的统计数据,返回 Map + //Future getStatisData({@required String statisType, @required String ip}) async { + listClltjStatisAlone.clear(); + int len = listDwinfoGetList2.length; + for (int i = 0; i < len; i++) { + Map map = await getStatisData(statisType: 'clltj', ip: listDwinfoGetList2[i]['dwip']); + map['dwbh'] = listDwinfoGetList2[i]['dwbh']; + map['dwmc'] = listDwinfoGetList2[i]['dwmc']; + map['dwip'] = listDwinfoGetList2[i]['dwip']; + listClltjStatisAlone.add(map); + // if (listClltjStatisAlone.length >= dwSum) { + // //发送统计数据已更新广播 + // eventBus.fire(StatisDataUpdate('统计数据已更新')); + // } + } + //print('listZptjStatis = ${listZptjStatis}'); + //StorageDataToFile.writeCounter(json_print(listZptjStatis, 1)); +} + +////////////////////////////////////////////////////////////////// +///获取所有统计数据 +List listAllStatisData = []; + +///车流量日统计数据类 +var trinityData; + +// 实现多个基于 Page1_Works 构建的页面共享统计数据 +Future startGetStatisDataNew() async { + ///获取点位信息数据 + //listDwinfoGetList2.clear(); + + if (listDwinfoGetList2.isEmpty) { + //若没有读取点位数据,便需要先读取 + getThePageList(theHyshlx: 'dwxx').then((value) { + listDwinfoGetList2 = value; + print('listDwinfoGetList2 = \n$listDwinfoGetList2'); + dwSum = listDwinfoGetList2.length; + //获取记录数据 + getZptjStatisAlone(); + getTodayShtj(); + getClltjStatisAlone(); + + //获取统计数据 + getAllStatisData().then((value) { + listAllStatisData = value; + eventBus.fire(StatisDataUpdate('统计数据已更新')); + }); + }); + } else { + if (mapStatisInfo['今日抓拍'] < 0) { + //获取记录数据 + getZptjStatisAlone(); + getTodayShtj(); + getClltjStatisAlone(); + + //获取统计数据 + getAllStatisData().then((value) { + listAllStatisData = value; + eventBus.fire(StatisDataUpdate('统计数据已更新')); + }); + } else { + //发送统计数据已更新广播 + eventBus.fire(StatisDataUpdate('统计数据已更新')); + } + } +} + +Map mapStatisInfo = { + '今日抓拍': -1, + '今日初审': -1, + '今日复审': -1, + '今日推送': -1, + '今日车流': -1, +}; + +//得到所有5项统计数据 +//适用的字段:抓拍统计的'today'、all;审核统计的"total"、"sends"; +Future getAllSumNew() async { + int items = 0; + mapStatisInfo['今日抓拍'] = 0; + mapStatisInfo['今日初审'] = 0; + mapStatisInfo['今日复审'] = 0; + mapStatisInfo['今日推送'] = 0; + mapStatisInfo['今日车流'] = 0; + + for (var item in listAllStatisData) { + items++; + if (item["zp_today"] > 0) { + //listRecords.addAll(item[field]["data"]); //将记录详情存入listRecords + mapStatisInfo['今日抓拍'] += item["zp_today"]; + } + if (item["csnum"] > 0) { + mapStatisInfo['今日初审'] += item["csnum"]; + } + if (item["fsnum"] > 0) { + mapStatisInfo['今日复审'] += item["fsnum"]; + } + if (item["sends"] > 0) { + mapStatisInfo['今日推送'] += item["sends"]; + } + if (item["cll_today"] > 0) { + mapStatisInfo['今日车流'] += item["cll_today"]; + } + } + mapStatisInfo['今日车流'] = mapStatisInfo['今日车流'] / 10000; + + // 统计点位数(未用),总计,记录列表 + //return [items, sum, listRecords]; +} + +/* +[ + { + "id": 1, + "dwip": "172.16.3.1", + "dwmc": "锦绣花园", + "zp_all": 54, + "hyc_all": 52, + "zp_today": 1, + "sends": 0, + "send_all": 33, + "csnum": 1, + "fsnum": 0, + "cll_today": 6420, + "cll_all": 3225733 + }, + { + "id": 2, + "dwip": "172.16.3.2", + "dwmc": "石马溪桥", + "zp_all": 377, + "hyc_all": 370, + "zp_today": 1, + "sends": 0, + "send_all": 204, + "csnum": 1, + "fsnum": 0, + "cll_today": 3430, + "cll_all": 2127839 + }, + ... +] +*/ + +// 实现多个基于 Page1_Works 构建的页面共享统计数据 +Future startGetStatisDataOld() async { + ///获取点位信息数据 + //listDwinfoGetList2.clear(); + + if (listDwinfoGetList2.isEmpty) { + //若没有读取点位数据,便需要先读取 + getThePageList(theHyshlx: 'dwxx').then((value) { + listDwinfoGetList2 = value; + print('listDwinfoGetList2 = \n$listDwinfoGetList2'); + dwSum = listDwinfoGetList2.length; + getZptjStatisAlone(); + getTodayShtj(); + getClltjStatisAlone(); + }); + } else { + if (mapStatisInfo['今日抓拍'] < 0) { + getZptjStatisAlone(); + } else { + //发送统计数据已更新广播 + eventBus.fire(StatisDataUpdate('统计数据已更新')); + } + + if (mapStatisInfo['今日初审'] < 0) { + getTodayShtj(); + } else { + //发送统计数据已更新广播 + eventBus.fire(StatisDataUpdate('统计数据已更新')); + } + + if (mapStatisInfo['今日车流'] < 0) { + getClltjStatisAlone(); + } else { + //发送统计数据已更新广播 + eventBus.fire(StatisDataUpdate('统计数据已更新')); + } + } +} + +//获取已审核黑烟车统计数据:App.Car_Statis.GetStaHyc +//{ +// "ret": 200, +// "data": { +// "total": 40, +// "sends": { +// "total": 0, +// "data": [] +// }, +// "csnum": { +// "total": 0, +// "data": [] +// }, +// "fsnum": { +// "total": 0, +// "data": [] +// } +// }, +// "msg": "" +// } + +//获取抓拍统计数据:App.Car_Statis.GetStaYjxx +//{ +// "ret": 200, +// "data": { +// "today": { +// "total": 0, +// "data": [] +// }, +// "all": 40 +// }, +// "msg": "" +// } +//得到 listStatis[field] 的统计数据, +//适用的字段:抓拍统计的'today'、all;审核统计的"total"、"sends"; +Future getAllSum(String field, List listStatis) async { + int items = 0; + int sum = 0; + List listRecords = []; //记录详情存入 + for (var item in listStatis) { + if (item[field]["total"] > 0) { + items++; + listRecords.addAll(item[field]["data"]); //将记录详情存入listRecords + sum += item[field]["total"]; + } + } + return [items, sum, listRecords]; +} + +//获取车流量统计数据:App.Car_Statis.GetStaCll +//{ +// "ret": 200, +// "data": { +// "today": 7010, +// "all": 1640350 +// }, +// "msg": "" +//} + +//得到 listStatis[field] 的统计数据, +//适用的字段:车流量统计的'today'、all +Future getAllSumCll(String field, List listStatis) async { + int items = 0; + int sum = 0; + for (var item in listStatis) { + if (item[field] > 0) { + items++; + sum += item[field]; + } + } + return [items, sum]; +} + +////////////////////////////////////////////////////////////////// diff --git a/lib/pages/Works/TJXX/today_list.dart b/lib/pages/Works/TJXX/today_list.dart new file mode 100644 index 0000000..4a74fd6 --- /dev/null +++ b/lib/pages/Works/TJXX/today_list.dart @@ -0,0 +1,1727 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hyzp_ybqx/components/commonFun.dart'; +import 'package:hyzp_ybqx/pages/Works/HYSH/tsjj_content_new.dart'; +import 'package:hyzp_ybqx/pages/Works/TJXX/tj_data.dart'; +import 'package:hyzp_ybqx/pages/Works/TJXX/today_list_zpjl_content.dart'; +import 'package:hyzp_ybqx/services/EventBus.dart'; + +import '../../../components/commonFun.dart'; +import '../../../components/doJSON.dart'; +import '../../../components/hyxx_data_handle.dart'; +import '../../../services/EventBus.dart'; +import '../HYSH/fhycx_content_new.dart'; +import '../HYSH/hysh_content_new.dart'; + +//TodayList是本项目中“今日记录列表”的统一缩写,包括:今日抓拍、今日初审、今日复审、今日推送 +class TodayList extends StatefulWidget { + //todayListLx为今日记录列表类型,'jrzp'今日抓拍、'hycs'今日初审、'hyfh'今日复审、'tsjj'今日推送 + TodayList({this.todayListLx, this.title, Key key}) : super(key: key); + String todayListLx; + String title = ''; + + _TodayListPageState createState() => _TodayListPageState(); +} + +class _TodayListPageState extends State { + //try_setState(); //避免如下异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + @override + void dispose() { + _controller.dispose(); //销毁控制器 + super.dispose(); + } + + List listTodayList = []; + Map mapTodayListLx = { + 'jrzp': { + 'text': '今日抓拍记录', + 'text0': '今日抓拍', + 'data': mapStatisInfo['今日抓拍'], + }, + 'hycs': { + 'text': '今日初审记录', + 'text0': '今日初审', + 'data': mapStatisInfo['今日初审'], + }, + 'hyfh': { + 'text': '今日复审记录', + 'text0': '今日复审', + 'data': mapStatisInfo['今日复审'], + }, + 'tsjj': { + 'text': '今日推送记录', + 'text0': '今日推送', + 'data': mapStatisInfo['今日推送'], + }, + }; + + /* + map['dwbh'] = listDwinfoGetList2[i]['dwbh']; + map['dwmc'] = listDwinfoGetList2[i]['dwmc']; + map['dwip'] = listDwinfoGetList2[i]['dwip']; + + listAllStatisData = + [ + { + "id": 1, + "dwip": "172.16.3.1", + "dwmc": "锦绣花园", + "zp_all": 54, + "hyc_all": 52, + "zp_today": 1, + "sends": 0, + "send_all": 33, + "csnum": 1, + "fsnum": 0, + "cll_today": 6420, + "cll_all": 3225733 + }, + ... + ]; + */ + + @override + void initState() { + //todayListLx为今日记录列表类型,'jrzp'今日抓拍、'hycs'今日初审、'hyfh'今日复审、'tsjj'今日推送 + switch (widget.todayListLx) { + case 'jrzp': + listTodayList = listTodayZpjl; + mapTodayListDataText['车牌颜色'] = 'cpys'; + break; + case 'hycs': + listTodayList = listTodayChjl; + break; + case 'hyfh': + listTodayList = listTodayFhjl; + break; + case 'tsjj': + listTodayList = listTodayTsjl; + break; + default: + break; + } + + if (listTodayList.isEmpty) { + _empty = '\n' + mapTodayListLx[widget.todayListLx]['text'] + _emptyInfo; + } + + //按照用户选择的_selectedValue、_descending对listHycsGetList2进行排序,并延时更新 + _listSort(); + firstIndex = 0; + lastIndex = 7; + + //监听违章信息数据审核事件 + eventBus.on().listen((event) async { + print('HycsGetList: ' + event.str); + //按照用户选择的_selectedValue、_descending对listHycsGetList2进行排序,并延时更新 + if (listTodayList.isEmpty) { + _empty = _emptyInfo; + try_setState(); + return; + } + _listSort(); + }); + + //监听违章信息Listview滚动事件 + eventBus.on().listen((event) async { + firstIndex = event.firstIndex; + lastIndex = event.lastIndex; + try_setState(); + }); + + super.initState(); + } + + // 车牌号码 + // 车牌颜色 + // 车辆类型 + // 林格曼黑度 + // 抓拍次数 + // 首次抓拍时间 + // 首次抓拍地点 + int today = 0; + int sum = 0; + String _emptyInfo = '为空'; + String _empty = ''; + + //List itemList = [ + // '林格曼黑度', + // '抓拍时间', + // '抓拍地点', + // '车牌颜色', + // '车辆类型', + // ]; + // + // //todayListLx为今日记录列表类型,'jrzp'今日抓拍、'hycs'今日初审、'hyfh'今日复审、'tsjj'今日推送 + // //添加按'推送状态'排序 + // if (widget.todayListLx == 'hycs' || + // widget.todayListLx == 'hyfh' || + // widget.todayListLx == 'tsjj') { + // itemList.add('抓拍次数'); + // } + // if (widget.todayListLx == 'hyfh' || widget.todayListLx == 'tsjj') { + // itemList.add('推送状态'); + // } + + Map mapTodayListDataText = { + "林格曼黑度": "lgmzs", + "抓拍时间": "zpsj", + "抓拍地点": "dwip", + "车牌颜色": "plate_color", + "车辆类型": "cplx", + "推送状态": "tszt", + }; + + //已加上tszt 推送状态字段,0->未推送,1->推送失败,2->推送成功,3->不推送(规定时间段内已有处罚记录的) + Map mpaTszt = { + 0: '未推送', + 1: '推送失败', + 2: '推送成功', + 3: '不推送(规定时间段内已有处罚记录)', + }; + + //App.Car_Statis.GetStaYjxx,获取抓拍统计数据 + //{ + // "ret": 200, + // "data": { + // "today": { + // "total": 8, + // "data": [ + // { + // "id": 428560, + // "car_number": "川Q31796", + // "cpys": "黄色", + // "zpsj": "2021-04-05 09:30:14", + // "dwip": "172.16.3.2", + // "dwms": "宜飞路石马溪大桥附近", + // "cplx": "其它", + // "lgmzs": 3, + // "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/553778bd21d80dfb073aec3b0d436140.jpg", + // "video_url": "video/2_6063_20210405_093014_川Q31796.mp4" + // }, + // { + // "id": 428561, + // "car_number": "川QL9963", + // "cpys": "蓝色", + // "zpsj": "2021-04-05 10:35:06", + // "dwip": "172.16.3.2", + // "dwms": "宜飞路石马溪大桥附近", + // "cplx": "其它", + // "lgmzs": 3, + // "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/4c5ddfe98daee099721a398d9eb35ba1.jpg", + // "video_url": "video/2_6063_20210405_103506_川Q37902.mp4" + // }, + // { + // "id": 428562, + // "car_number": "川QL9963", + // "cpys": "蓝色", + // "zpsj": "2021-04-05 10:35:06", + // "dwip": "172.16.3.2", + // "dwms": "宜飞路石马溪大桥附近", + // "cplx": "其它", + // "lgmzs": 3, + // "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/8b9c1a4b494dc8e41346874380d9a80d.jpg", + // "video_url": "video/2_6063_20210405_103506_川QL9963.mp4" + // }, + // { + // "id": 428566, + // "car_number": "川QB6360", + // "cpys": "蓝色", + // "zpsj": "2021-04-05 11:40:36", + // "dwip": "172.16.3.2", + // "dwms": "宜飞路石马溪大桥附近", + // "cplx": "其它", + // "lgmzs": 3, + // "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/e8ff7137d8a066d956e2cdf1999a976f.jpg", + // "video_url": "video/2_6063_20210405_114036_川QB6360.mp4" + // }, + // { + // "id": 428567, + // "car_number": "川QL9963", + // "cpys": "蓝色", + // "zpsj": "2021-04-05 11:48:54", + // "dwip": "172.16.3.2", + // "dwms": "宜飞路石马溪大桥附近", + // "cplx": "其它", + // "lgmzs": 3, + // "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/91b718a110f55d26c08980597cb45c2b.jpg", + // "video_url": "video/2_6063_20210405_114854_川QL9963.mp4" + // }, + // { + // "id": 428570, + // "car_number": "川QET268", + // "cpys": "蓝色", + // "zpsj": "2021-04-05 12:38:29", + // "dwip": "172.16.3.2", + // "dwms": "宜飞路石马溪大桥附近", + // "cplx": "其它", + // "lgmzs": 3, + // "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/9802f31038f8704efa2d0026c9716ba4.jpg", + // "video_url": "video/2_6063_20210405_123829_川QET268.mp4" + // }, + // { + // "id": 428575, + // "car_number": "川Q8N899", + // "cpys": "蓝色", + // "zpsj": "2021-04-05 13:18:08", + // "dwip": "172.16.3.2", + // "dwms": "宜飞路石马溪大桥附近", + // "cplx": "其它", + // "lgmzs": 3, + // "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/784d03dd6e392d6ba067b1d168fd3a95.jpg", + // "video_url": "video/2_6063_20210405_131808_川Q8N899.mp4" + // }, + // { + // "id": 428577, + // "car_number": "川QL9963", + // "cpys": "蓝色", + // "zpsj": "2021-04-05 13:31:19", + // "dwip": "172.16.3.2", + // "dwms": "宜飞路石马溪大桥附近", + // "cplx": "其它", + // "lgmzs": 3, + // "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/b4bbf23ade7e1cb403446afafbb84bde.jpg", + // "video_url": "video/2_6063_20210405_133119_川QL9963.mp4" + // } + // ] + // }, + // "all": 91 + // }, + // "msg": "" + // } + + //App.Car_Statis.GetStaHyc,获取已审核黑烟车统计数据 + //{ + // "ret": 200, + // "data": { + // "total": 88, + // "sends": { + // "total": 3, + // "data": [ + // { + // "id": 4545, + // "plate_id": "川15A2539", + // "plate_color": "绿色", + // "zpsj": "2021-04-04 12:33:20", + // "dwip": "172.16.3.3", + // "dwms": "岷江南路森林小区附近", + // "cplx": "农用车", + // "lgmzs": 3, + // "cs_username": "肖曦", + // "cs_time": "2021-04-04 12:40:47", + // "cs_tile": "黑烟车", + // "cs_shuoming": "黑烟超标,交由交警处罚", + // "fs_username": "肖曦", + // "fs_time": "2021-04-04 12:40:54", + // "fs_tile": "黑烟车", + // "fs_shuoming": "黑烟超标,交由交警处罚", + // "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/49a44411b3ab6856bf2c80a9690a662b.jpg", + // "video_url": "video/3_6063_20210404_123320_川15A2539.mp4", + // "ts_time": 1617514823, + // "tszt": 2 + // }, + // { + // "id": 4546, + // "plate_id": "川15A3661", + // "plate_color": "绿色", + // "zpsj": "2021-04-04 12:45:14", + // "dwip": "172.16.3.3", + // "dwms": "岷江南路森林小区附近", + // "cplx": "农用车", + // "lgmzs": 3, + // "cs_username": "肖曦", + // "cs_time": "2021-04-04 12:57:38", + // "cs_tile": "黑烟车", + // "cs_shuoming": "黑烟超标,交由交警处罚", + // "fs_username": "肖曦", + // "fs_time": "2021-04-04 12:57:45", + // "fs_tile": "黑烟车", + // "fs_shuoming": "黑烟超标,交由交警处罚", + // "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/28821cb7e55ca451ebebd6c303454873.jpg", + // "video_url": "video/3_6063_20210404_124514_川15A3661.mp4", + // "ts_time": 1617515902, + // "tszt": 2 + // }, + // { + // "id": 4547, + // "plate_id": "川F3B6P5", + // "plate_color": "蓝色", + // "zpsj": "2021-04-04 13:04:56", + // "dwip": "172.16.3.3", + // "dwms": "岷江南路森林小区附近", + // "cplx": "其它", + // "lgmzs": 3, + // "cs_username": "肖曦", + // "cs_time": "2021-04-04 13:11:21", + // "cs_tile": "黑烟车", + // "cs_shuoming": "黑烟超标,交由交警处罚", + // "fs_username": "肖曦", + // "fs_time": "2021-04-04 13:11:58", + // "fs_tile": "黑烟车", + // "fs_shuoming": "黑烟超标,交由交警处罚", + // "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/9510551a8eb22d176917e57f99d59cff.jpg", + // "video_url": "video/3_6063_20210404_130456_川F3B6P5.mp4", + // "ts_time": 1617516742, + // "tszt": 2 + // } + // ] + // }, + // "csnum": { + // "total": 4, + // "data": [ + // { + // "id": 4544, + // "plate_id": "川15A2563", + // "plate_color": "绿色", + // "zpsj": "2021-04-04 11:31:11", + // "dwip": "172.16.3.3", + // "dwms": "岷江南路森林小区附近", + // "cplx": "农用车", + // "lgmzs": 3, + // "cs_username": "肖曦", + // "cs_time": "2021-04-04 12:05:16", + // "cs_tile": "非黑烟车", + // "cs_shuoming": "误报,或肉眼无法识别", + // "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/e936524e92f90541cbaeca3b092c95be.jpg", + // "video_url": "video/3_6063_20210404_113111_川15A2563.mp4", + // "tszt": 0 + // }, + // { + // "id": 4545, + // "plate_id": "川15A2539", + // "plate_color": "绿色", + // "zpsj": "2021-04-04 12:33:20", + // "dwip": "172.16.3.3", + // "dwms": "岷江南路森林小区附近", + // "cplx": "农用车", + // "lgmzs": 3, + // "cs_username": "肖曦", + // "cs_time": "2021-04-04 12:40:47", + // "cs_tile": "黑烟车", + // "cs_shuoming": "黑烟超标,交由交警处罚", + // "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/49a44411b3ab6856bf2c80a9690a662b.jpg", + // "video_url": "video/3_6063_20210404_123320_川15A2539.mp4", + // "tszt": 2 + // }, + // { + // "id": 4546, + // "plate_id": "川15A3661", + // "plate_color": "绿色", + // "zpsj": "2021-04-04 12:45:14", + // "dwip": "172.16.3.3", + // "dwms": "岷江南路森林小区附近", + // "cplx": "农用车", + // "lgmzs": 3, + // "cs_username": "肖曦", + // "cs_time": "2021-04-04 12:57:38", + // "cs_tile": "黑烟车", + // "cs_shuoming": "黑烟超标,交由交警处罚", + // "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/28821cb7e55ca451ebebd6c303454873.jpg", + // "video_url": "video/3_6063_20210404_124514_川15A3661.mp4", + // "tszt": 2 + // }, + // { + // "id": 4547, + // "plate_id": "川F3B6P5", + // "plate_color": "蓝色", + // "zpsj": "2021-04-04 13:04:56", + // "dwip": "172.16.3.3", + // "dwms": "岷江南路森林小区附近", + // "cplx": "其它", + // "lgmzs": 3, + // "cs_username": "肖曦", + // "cs_time": "2021-04-04 13:11:21", + // "cs_tile": "黑烟车", + // "cs_shuoming": "黑烟超标,交由交警处罚", + // "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/9510551a8eb22d176917e57f99d59cff.jpg", + // "video_url": "video/3_6063_20210404_130456_川F3B6P5.mp4", + // "tszt": 2 + // } + // ] + // }, + // "fsnum": { + // "total": 4, + // "data": [ + // { + // "id": 4544, + // "plate_id": "川15A2563", + // "plate_color": "绿色", + // "zpsj": "2021-04-04 11:31:11", + // "dwip": "172.16.3.3", + // "dwms": "岷江南路森林小区附近", + // "cplx": "农用车", + // "lgmzs": 3, + // "cs_username": "肖曦", + // "cs_time": "2021-04-04 12:05:16", + // "cs_tile": "非黑烟车", + // "cs_shuoming": "误报,或肉眼无法识别", + // "fs_username": "肖曦", + // "fs_time": "2021-04-04 12:05:54", + // "fs_tile": "非黑烟车", + // "fs_shuoming": "误报,或肉眼无法识别", + // "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/e936524e92f90541cbaeca3b092c95be.jpg", + // "video_url": "video/3_6063_20210404_113111_川15A2563.mp4", + // "tszt": 0 + // }, + // { + // "id": 4545, + // "plate_id": "川15A2539", + // "plate_color": "绿色", + // "zpsj": "2021-04-04 12:33:20", + // "dwip": "172.16.3.3", + // "dwms": "岷江南路森林小区附近", + // "cplx": "农用车", + // "lgmzs": 3, + // "cs_username": "肖曦", + // "cs_time": "2021-04-04 12:40:47", + // "cs_tile": "黑烟车", + // "cs_shuoming": "黑烟超标,交由交警处罚", + // "fs_username": "肖曦", + // "fs_time": "2021-04-04 12:40:54", + // "fs_tile": "黑烟车", + // "fs_shuoming": "黑烟超标,交由交警处罚", + // "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/49a44411b3ab6856bf2c80a9690a662b.jpg", + // "video_url": "video/3_6063_20210404_123320_川15A2539.mp4", + // "tszt": 2 + // }, + // { + // "id": 4546, + // "plate_id": "川15A3661", + // "plate_color": "绿色", + // "zpsj": "2021-04-04 12:45:14", + // "dwip": "172.16.3.3", + // "dwms": "岷江南路森林小区附近", + // "cplx": "农用车", + // "lgmzs": 3, + // "cs_username": "肖曦", + // "cs_time": "2021-04-04 12:57:38", + // "cs_tile": "黑烟车", + // "cs_shuoming": "黑烟超标,交由交警处罚", + // "fs_username": "肖曦", + // "fs_time": "2021-04-04 12:57:45", + // "fs_tile": "黑烟车", + // "fs_shuoming": "黑烟超标,交由交警处罚", + // "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/28821cb7e55ca451ebebd6c303454873.jpg", + // "video_url": "video/3_6063_20210404_124514_川15A3661.mp4", + // "tszt": 2 + // }, + // { + // "id": 4547, + // "plate_id": "川F3B6P5", + // "plate_color": "蓝色", + // "zpsj": "2021-04-04 13:04:56", + // "dwip": "172.16.3.3", + // "dwms": "岷江南路森林小区附近", + // "cplx": "其它", + // "lgmzs": 3, + // "cs_username": "肖曦", + // "cs_time": "2021-04-04 13:11:21", + // "cs_tile": "黑烟车", + // "cs_shuoming": "黑烟超标,交由交警处罚", + // "fs_username": "肖曦", + // "fs_time": "2021-04-04 13:11:58", + // "fs_tile": "黑烟车", + // "fs_shuoming": "黑烟超标,交由交警处罚", + // "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/9510551a8eb22d176917e57f99d59cff.jpg", + // "video_url": "video/3_6063_20210404_130456_川F3B6P5.mp4", + // "tszt": 2 + // } + // ] + // } + // }, + // "msg": "" + // } + + String wipeYear(String date) { + date = date.trim(); + return date.substring(date.indexOf('-') + 1); + } + + Map mapColor = {'黑烟车': Colors.red, '非黑烟车': Colors.green}; + + Widget _getListTile(BuildContext context, indexRecord) { + double _width = 160; + //todayListLx为今日记录列表类型,'jrzp'今日抓拍、'hycs'今日初审、'hyfh'今日复审、'tsjj'今日推送 + switch (widget.todayListLx) { + case 'jrzp': + return Column( + children: [ + ListTile( + //"today": { + // "total": 8, + // "data": [ + // { + // "id": 428560, + // "car_number": "川Q31796", + // "cpys": "黄色", + // "zpsj": "2021-04-05 09:30:14", + // "dwip": "172.16.3.2", + // "dwms": "宜飞路石马溪大桥附近", + // "cplx": "其它", + // "lgmzs": 3, + // "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/553778bd21d80dfb073aec3b0d436140.jpg", + // "video_url": "video/2_6063_20210405_093014_川Q31796.mp4" + // }, + + /* + map['dwbh'] = listDwinfoGetList2[i]['dwbh']; + map['dwmc'] = listDwinfoGetList2[i]['dwmc']; + map['dwip'] = listDwinfoGetList2[i]['dwip']; + + listAllStatisData = + [ + { + "id": 1, + "dwip": "172.16.3.1", + "dwmc": "锦绣花园", + "zp_all": 54, + "hyc_all": 52, + "zp_today": 1, + "sends": 0, + "send_all": 33, + "csnum": 1, + "fsnum": 0, + "cll_today": 6420, + "cll_all": 3225733 + }, + ... + ]; + */ + contentPadding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 0), + enabled: true, + title: Text( + "${(indexRecord + 1).toString()}. ${listTodayList[indexRecord]['car_number']}" + + '(${listTodayList[indexRecord]['cpys']}),${listTodayList[indexRecord]['cplx']},' + + '黑度 ${listTodayList[indexRecord]['lgmzs'].toString()}', + style: TextStyle(fontSize: 14)), + subtitle: Text(getDate(listTodayList[indexRecord]['zpsj']), + style: TextStyle( + fontSize: 14, + color: isToday(listTodayList[indexRecord]['zpsj']) + ? Colors.lightBlue + : Colors.black, + fontWeight: FontWeight.bold)), + trailing: Container( + width: _width - 10, + child: Text( + getDwmc(listTodayList[indexRecord]['dwip']) + + ',${listTodayList[indexRecord]['dwms']}', + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 14), + ), + ), + onTap: () async { + int ret = -1; + if (widget.todayListLx == 'jrzp') { + ret = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => TodayListZpjlContent( + hyshlx: widget.todayListLx, + text: mapTodayListLx[widget.todayListLx]['text0'], + num: '(${indexRecord + 1}/${listTodayList.length})', + mapZpjl: listTodayList[indexRecord]), + ), + ); + } + + // int ret = -1; + // if (widget.todayListLx == 'fhycx') { + // ret = await Navigator.of(context).push( + // MaterialPageRoute( + // builder: (context) => FhycxContentNew( + // hyshlx: widget.todayListLx, + // title: '非黑烟车', + // indexRecord: indexRecord, + // id: listTodayList[indexRecord]['id']), + // ), + // ); + // print('hyshContentFirstAudit整型返回值:-1 表示出现异常,其余为更新结果,1表示成功,0表示无更新,false表示失败'); + // } else if (widget.todayListLx == 'tsjj') { + // ret = await Navigator.of(context).push( + // MaterialPageRoute( + // builder: (context) => TsjjContentNew( + // hyshlx: widget.todayListLx, + // title: '推送交警详情', + // indexRecord: indexRecord, + // id: listTodayList[indexRecord]['id']), + // ), + // ); + // print('hyshContentFirstAudit整型返回值:-1 表示出现异常,其余为更新结果,1表示成功,0表示无更新,false表示失败'); + // } else { + // ret = await Navigator.of(context).push( + // MaterialPageRoute( + // builder: (context) => HyshContentNew( + // hyshlx: widget.todayListLx, + // title: '违章审核详情', + // indexRecord: indexRecord, + // id: listTodayList[indexRecord]['id']), + // ), + // ); + // print('hyshContentFirstAudit整型返回值:-1 表示出现异常,其余为更新结果,1表示成功,0表示无更新,false表示失败'); + // } + // print('ret = $ret'); + }, + ), + Divider( + height: 1.0, + ), + ], + ); + break; + case 'hycs': + return Column( + children: [ + ListTile( + //"csnum": { + // "total": 4, + // "data": [ + // { + // "id": 4544, + // "plate_id": "川15A2563", + // "plate_color": "绿色", + // "zpsj": "2021-04-04 11:31:11", + // "dwip": "172.16.3.3", + // "dwms": "岷江南路森林小区附近", + // "cplx": "农用车", + // "lgmzs": 3, + // "cs_username": "肖曦", + // "cs_time": "2021-04-04 12:05:16", + // "cs_tile": "非黑烟车", + // "cs_shuoming": "误报,或肉眼无法识别", + // "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/e936524e92f90541cbaeca3b092c95be.jpg", + // "video_url": "video/3_6063_20210404_113111_川15A2563.mp4", + // "tszt": 0 + // }, + contentPadding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 0), + enabled: true, + onTap: () async { + int ret = -1; + if (widget.todayListLx == 'hycs') { + ret = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => TodayListZpjlContent( + hyshlx: widget.todayListLx, + text: mapTodayListLx[widget.todayListLx]['text0'], + num: '(${indexRecord + 1}/${listTodayList.length})', + mapZpjl: listTodayList[indexRecord]), + ), + ); + } + }, + title: RichText( + overflow: TextOverflow.ellipsis, + maxLines: 2, + text: TextSpan( + text: + "${(indexRecord + 1).toString()}. ${listTodayList[indexRecord]['plate_id']}" + + '(${listTodayList[indexRecord]['plate_color']}),', + style: TextStyle(fontSize: 14, color: Colors.black), + children: [ + TextSpan( + text: '${listTodayList[indexRecord]['cplx']},' + + getDwmc(listTodayList[indexRecord]['dwip']) + + ',黑度 ${listTodayList[indexRecord]['lgmzs']}', + style: TextStyle(fontSize: 12, color: Colors.black)), + ]), + ), + subtitle: RichText( + overflow: TextOverflow.ellipsis, + maxLines: 2, + text: TextSpan( + text: getDate(listTodayList[indexRecord]['zpsj']), + style: TextStyle( + fontSize: 14, + color: isToday(listTodayList[indexRecord]['zpsj']) + ? Colors.lightBlue + : Colors.black, + fontWeight: FontWeight.bold), + children: [ + // TextSpan( + // text: ',黑度 ${listTodayList[indexRecord]['lgmzs'].toString()}', + // style: DefaultTextStyle.of(context).style), + ]), + ), + trailing: Container( + width: _width - 20, + child: RichText( + overflow: TextOverflow.ellipsis, + maxLines: 3, + text: TextSpan( + style: TextStyle( + fontSize: 14, + color: mapColor[listTodayList[indexRecord]['cs_tile'].trim()]), + text: '初审:${listTodayList[indexRecord]['cs_tile']}', + children: [ + TextSpan( + text: ',${listTodayList[indexRecord]['cs_username']},', + style: TextStyle(fontSize: 14, color: Colors.black)), + TextSpan( + text: '${listTodayList[indexRecord]['cs_time']}', + style: TextStyle(fontSize: 14, color: Colors.black)), + // TextSpan( + // text: ',黑度 ${listTodayList[indexRecord]['lgmzs'].toString()}', + // style: DefaultTextStyle.of(context).style), + ]), + ), + ), + ), + Divider( + height: 1.0, + ), + ], + ); + break; + case 'hyfh': + return Column( + children: [ + ListTile( + //"fsnum": { + // "total": 4, + // "data": [ + // { + // "id": 4544, + // "plate_id": "川15A2563", + // "plate_color": "绿色", + // "zpsj": "2021-04-04 11:31:11", + // "dwip": "172.16.3.3", + // "dwms": "岷江南路森林小区附近", + // "cplx": "农用车", + // "lgmzs": 3, + // "cs_username": "肖曦", + // "cs_time": "2021-04-04 12:05:16", + // "cs_tile": "非黑烟车", + // "cs_shuoming": "误报,或肉眼无法识别", + // "fs_username": "肖曦", + // "fs_time": "2021-04-04 12:05:54", + // "fs_tile": "非黑烟车", + // "fs_shuoming": "误报,或肉眼无法识别", + // "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/e936524e92f90541cbaeca3b092c95be.jpg", + // "video_url": "video/3_6063_20210404_113111_川15A2563.mp4", + // "tszt": 0 + // }, + + //已加上tszt 推送状态字段,0->未推送,1->推送失败,2->推送成功,3->不推送(规定时间段内已有处罚记录的) + contentPadding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 0), + enabled: true, + onTap: () async { + int ret = -1; + if (widget.todayListLx == 'hyfh') { + ret = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => TodayListZpjlContent( + hyshlx: widget.todayListLx, + text: mapTodayListLx[widget.todayListLx]['text0'], + num: '(${indexRecord + 1}/${listTodayList.length})', + mapZpjl: listTodayList[indexRecord]), + ), + ); + } + }, + title: RichText( + overflow: TextOverflow.ellipsis, + maxLines: 2, + text: TextSpan( + text: + "${(indexRecord + 1).toString()}. ${listTodayList[indexRecord]['plate_id']}" + + '(${listTodayList[indexRecord]['plate_color']}),', + style: TextStyle(fontSize: 14, color: Colors.black), + children: [ + TextSpan( + text: '${listTodayList[indexRecord]['cplx']},' + + getDwmc(listTodayList[indexRecord]['dwip']) + + ',黑度 ${listTodayList[indexRecord]['lgmzs']}', + style: TextStyle(fontSize: 12, color: Colors.black)), + ]), + ), + subtitle: RichText( + overflow: TextOverflow.ellipsis, + maxLines: 2, + text: TextSpan( + text: getDate(listTodayList[indexRecord]['zpsj']), + style: TextStyle( + fontSize: 14, + color: isToday(listTodayList[indexRecord]['zpsj']) + ? Colors.lightBlue + : Colors.black, + fontWeight: FontWeight.bold), + children: [ + // TextSpan( + // text: ',黑度 ${listTodayList[indexRecord]['lgmzs'].toString()}', + // style: DefaultTextStyle.of(context).style), + ]), + ), + trailing: Container( + width: _width + 5, + child: RichText( + overflow: TextOverflow.ellipsis, + maxLines: 3, + text: TextSpan( + style: TextStyle( + fontSize: 13, + color: mapColor[listTodayList[indexRecord]['cs_tile'].trim()]), + text: '初审:${listTodayList[indexRecord]['cs_tile']}', + children: [ + TextSpan( + text: ',${listTodayList[indexRecord]['cs_username']},', + style: TextStyle(fontSize: 13, color: Colors.black)), + TextSpan( + text: '${wipeYear(listTodayList[indexRecord]['cs_time'])};', + style: TextStyle(fontSize: 11, color: Colors.black)), + TextSpan( + text: '复审:${listTodayList[indexRecord]['fs_tile']}', + style: TextStyle( + fontSize: 13, + color: mapColor[listTodayList[indexRecord]['fs_tile'].trim()])), + TextSpan( + text: ',${listTodayList[indexRecord]['fs_username']},', + style: TextStyle(fontSize: 13, color: Colors.black)), + TextSpan( + text: '${wipeYear(listTodayList[indexRecord]['fs_time'])}', + style: TextStyle(fontSize: 11, color: Colors.black)), + // TextSpan( + // text: ',黑度 ${listTodayList[indexRecord]['lgmzs'].toString()}', + // style: DefaultTextStyle.of(context).style), + ]), + ), + ), + ), + Divider( + height: 1.0, + ), + ], + ); + break; + case 'tsjj': + return Column( + children: [ + ListTile( + //"sends": { + // "total": 3, + // "data": [ + // { + // "id": 4545, + // "plate_id": "川15A2539", + // "plate_color": "绿色", + // "zpsj": "2021-04-04 12:33:20", + // "dwip": "172.16.3.3", + // "dwms": "岷江南路森林小区附近", + // "cplx": "农用车", + // "lgmzs": 3, + // "cs_username": "肖曦", + // "cs_time": "2021-04-04 12:40:47", + // "cs_tile": "黑烟车", + // "cs_shuoming": "黑烟超标,交由交警处罚", + // "fs_username": "肖曦", + // "fs_time": "2021-04-04 12:40:54", + // "fs_tile": "黑烟车", + // "fs_shuoming": "黑烟超标,交由交警处罚", + // "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/49a44411b3ab6856bf2c80a9690a662b.jpg", + // "video_url": "video/3_6063_20210404_123320_川15A2539.mp4", + // "ts_time": 1617514823, + // "tszt": 2 + // }, + contentPadding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 0), + enabled: true, + onTap: () async { + int ret = -1; + if (widget.todayListLx == 'tsjj') { + ret = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => TodayListZpjlContent( + hyshlx: widget.todayListLx, + text: mapTodayListLx[widget.todayListLx]['text0'], + num: '(${indexRecord + 1}/${listTodayList.length})', + mapZpjl: listTodayList[indexRecord], + tsztText: mapTsztText[getInt(listTodayList[indexRecord]['tszt'])], + ), + ), + ); + } + }, + title: RichText( + overflow: TextOverflow.ellipsis, + maxLines: 2, + text: TextSpan( + text: + "${(indexRecord + 1).toString()}. ${listTodayList[indexRecord]['plate_id']}" + + '(${listTodayList[indexRecord]['plate_color']}),', + style: TextStyle(fontSize: 14, color: Colors.black), + children: [ + TextSpan( + text: '${listTodayList[indexRecord]['cplx']},' + + getDwmc(listTodayList[indexRecord]['dwip']) + + ',黑度 ${listTodayList[indexRecord]['lgmzs']}', + style: TextStyle(fontSize: 12, color: Colors.black)), + ]), + ), + subtitle: RichText( + overflow: TextOverflow.ellipsis, + maxLines: 2, + text: TextSpan( + text: getDate(listTodayList[indexRecord]['zpsj']), + style: TextStyle( + fontSize: 14, + color: isToday(listTodayList[indexRecord]['zpsj']) + ? Colors.lightBlue + : Colors.black, + fontWeight: FontWeight.bold), + children: [ + // TextSpan( + // text: ',黑度 ${listTodayList[indexRecord]['lgmzs'].toString()}', + // style: DefaultTextStyle.of(context).style), + ]), + ), + trailing: Container( + width: _width + 5, + child: RichText( + overflow: TextOverflow.ellipsis, + maxLines: 3, + text: TextSpan( + style: TextStyle( + fontSize: 13, + color: mapColor[listTodayList[indexRecord]['cs_tile'].trim()]), + text: '初审:${listTodayList[indexRecord]['cs_tile']}', + children: [ + TextSpan( + text: ',${listTodayList[indexRecord]['cs_username']},', + style: TextStyle(fontSize: 13, color: Colors.black)), + TextSpan( + text: '${wipeYear(listTodayList[indexRecord]['cs_time'])};', + style: TextStyle(fontSize: 10, color: Colors.black)), + TextSpan( + text: '复审:${listTodayList[indexRecord]['fs_tile']}', + style: TextStyle( + fontSize: 13, + color: mapColor[listTodayList[indexRecord]['fs_tile'].trim()])), + TextSpan( + text: ',${listTodayList[indexRecord]['fs_username']},', + style: TextStyle(fontSize: 13, color: Colors.black)), + TextSpan( + text: '${wipeYear(listTodayList[indexRecord]['fs_time'])}', + style: TextStyle(fontSize: 10, color: Colors.black)), + TextSpan( + text: ',${mapTsztText[getInt(listTodayList[indexRecord]['tszt'])]}', + style: TextStyle(fontSize: 11, color: Colors.blueAccent)), + // TextSpan( + // text: ',黑度 ${listTodayList[indexRecord]['lgmzs'].toString()}', + // style: DefaultTextStyle.of(context).style), + ]), + ), + ), + ), + Divider( + height: 1.0, + ), + ], + ); + break; + default: + break; + } + } + + int getInt(var num) { + if (num is int) { + return num; + } else if (num is String) { + return int.parse(num); + } else { + return -1; + } + } + + ScrollController _controller = ScrollController(); //ListView控制器 + bool isLoading = false; //正在处理下载数据、跳转到首项、跳转到尾项等操作 + int firstIndex = 0; //ListView当前显示页面首项0基序号 + int lastIndex = 0; //ListView当前显示页面末项0基序号 + int itemOnPage = 8; //估计一屏显示的项目数量 + + Widget getIconButton({IconData iconData, var onPressed, double iconSize = 22}) { + return SizedBox( + height: iconSize, + width: iconSize + 10, + child: IconButton( + padding: EdgeInsets.all(0.0), + icon: Icon(iconData, size: iconSize, color: Colors.white), + onPressed: onPressed, + ), + ); + } + + //获取 listTodayList 中今日抓拍记录总数 + int getTodaySum() { + int i = 0; + for (Map record in listTodayList) { + if (isToday(record['zpsj'])) { + i++; + } + } + return i; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: PreferredSize( + preferredSize: Size.fromHeight(ScreenUtil().setHeight(173)), // here the desired height + child: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + //leading: Text(''), + titleSpacing: 0.0, + //设置title的左边距 + flexibleSpace: Container( + //SizedBox(height: ScreenUtil().statusBarHeight), //显示顶部状态栏 + // SizedBox(height: ScreenUtil().setHeight(10)), //显示顶部状态栏 + padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight), //留出顶部状态栏高度 + child: Container( + //height: ScreenUtil().setHeight(173), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + // decoration: BoxDecoration( + // gradient: LinearGradient(colors: [ + // Color(0xFF0018EB), + // Color(0xFF01C1D9), + // ], begin: Alignment.bottomCenter, end: Alignment.topCenter), + // ), + ), + ), + //1、第1行组件,工具按钮 + title: Padding( + padding: EdgeInsets.only(left: 0, right: 0), + child: Row( + //mainAxisAlignment: MainAxisAlignment.start, + children: [ + //1.1、返回按钮 + getIconAndTextButton( + iconColor: Colors.white, + iconData: Icons.chevron_left_outlined, + onPress: () { + Navigator.pop(context); + }, + ), + //1.2、title 显示控制 + Expanded( + child: RichText( + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + text: TextSpan( + style: TextStyle(color: Colors.white, fontSize: 20), + children: [ + TextSpan(text: "${mapTodayListLx[widget.todayListLx]['text']}("), + TextSpan( + text: "${getTodaySum()}", + style: TextStyle( + fontSize: 20, + color: Colors.redAccent, + fontWeight: FontWeight.w900)), + TextSpan( + text: (widget.todayListLx == 'jrzp') + ? '' + : "/${mapTodayListLx[widget.todayListLx]['data'].toStringAsFixed(0)}"), + TextSpan(text: ")"), + ], + ), + ), + ), + SizedBox(width: 5), + ], + ), + ), + //1.3、尾部工具按钮组 + actions: [ + getIconButton( + iconData: Icons.vertical_align_top_outlined, + onPressed: (!isLoading && (firstIndex > 0)) //未到顶部 + ? () async { + if (isLoading) { + return; + } + isLoading = true; + try_setState(); + + //必须延时执行,否则不能及时完成按钮状态更新 + Timer( + Duration(milliseconds: 500), + () { + _controller.jumpTo(_controller.position.minScrollExtent); + }, + ); + + Timer( + Duration(milliseconds: 1000), + () { + // Fluttertoast.showToast( + // msg: '已经跳转到开头记录!', + // toastLength: Toast.LENGTH_SHORT, + // gravity: ToastGravity.CENTER, + // ); + firstIndex = 0; + lastIndex = itemOnPage - 1; //0基序号,所以需要减1 + isLoading = false; + try_setState(); + }, + ); + } + : null, + ), + SizedBox(width: ScreenUtil().setWidth(32)), + getIconButton( + iconData: Icons.vertical_align_bottom_outlined, + onPressed: (!isLoading && (lastIndex < listTodayList.length)) //未到尾部 + ? () async { + if (isLoading) { + return; + } + isLoading = true; + try_setState(); + + //必须延时执行,否则不能及时完成按钮状态更新 + Timer( + Duration(milliseconds: 500), + () { + _controller.jumpTo(_controller.position.maxScrollExtent); + }, + ); + + Timer( + Duration(milliseconds: 1000), + () { + // Fluttertoast.showToast( + // msg: '已经跳转到末尾记录!', + // toastLength: Toast.LENGTH_SHORT, + // gravity: ToastGravity.CENTER, + // ); + //0基序号需要减1,再加上底部有一组显示信息,所以需要减2 + firstIndex = listTodayList.length - itemOnPage - 2; + lastIndex = listTodayList.length; + isLoading = false; + try_setState(); + }, + ); + } + : null, + ), + SizedBox(width: ScreenUtil().setWidth(32)), + ], + ), + ), + body: Column( + children: [ + //2、第2行排序按钮 + Container( + height: ScreenUtil().setHeight(142), + decoration: new BoxDecoration(border: new Border.all(color: Colors.red)), + child: Row( + children: [ + SizedBox(width: ScreenUtil().setWidth(50)), + Text('排序', style: TextStyle(fontSize: 18)), + Expanded(child: SizedBox.shrink()), + getDropdownButton(), + SizedBox(width: ScreenUtil().setWidth(20)), + ], + ), + ), + //3、第3行组件,数据显示区域 + _empty.isNotEmpty + ? Text(_empty, style: TextStyle(fontSize: 20)) + : (0 == listTodayList.length) + ? getMoreWidget(color: Colors.black38) + : Expanded( + child: ListView.custom( + controller: _controller, + cacheExtent: 1.0, // 只有设置了1.0 才能够准确的标记position 位置 + childrenDelegate: MyChildrenDelegate( + _getListTile, + childCount: listTodayList.length, + ), + ), + ) + ], + ), + ); + } + + // Widget getDropdownButton() { + // return DropdownButton( + // value: _selectedValue, + // onChanged: (String newValue) { + // setState(() { + // _selectedValue = newValue; + // }); + // }, + // items: ['One', 'Two', 'Free', 'Four'].map>((String value) { + // return DropdownMenuItem( + // value: value, + // child: Text(value), + // ); + // }).toList(), + // ); + // } + + //DropdownButton需要设置初始值的时候,初始值必须是显示列表里面的值,否则会导致弹出框异常。 + // 比如说:你的DropdownButton的items属性使用的是list这个列表里面的值,那么你的初始值应该在list[index]里面取,要不就会报错。 + // + // There should be exactly one item with [DropdownButton]'s value: 0.0. + // Either zero or 2 or more [DropdownMenuItem]s were detected with the same value + // 'package:flutter/src/material/dropdown.dart': + // Failed assertion: line 834 pos 15: 'items == null || items.isEmpty || value == null || + // items.where((DropdownMenuItem item) { + // return item.value == value; + // }).length == 1' + String _selectedValue = '抓拍时间'; + bool _descending = true; + + Widget _getImage(String _image) { + return Container( + margin: EdgeInsets.only(), + height: ScreenUtil().setWidth(48), + width: ScreenUtil().setWidth(48), + //child: Image.asset('assets/images/ybsthbj.png', fit: BoxFit.fitHeight), + child: Image.asset(_image, + fit: BoxFit.cover, color: isLoading ? Theme.of(context).disabledColor : null)); + } + + // //统计今日抓拍次数 today + // Future getToday() async { + // today = 0; + // int _yesterDayStamp = getEndDayOfYesterdayStamp(); + // for (var item in listTodayList) { + // //统计今日抓拍次数 + // if (item['zpsj'] > _yesterDayStamp) { + // today++; + // } + // } + // } + + //按照用户选择的_selectedValue、_descending对listHycsGetList2进行排序,并延时更新 + Future _listSort({bool bShowToast = false}) { + if (!isLoading && listTodayList.length > 0) { + isLoading = true; + try_setState(); + sum = listTodayList.length; //当前记录总数 + //getToday(); //统计今日抓拍次数 today + + switch (_selectedValue) { + case '推送状态': + if (_descending) { + // Unhandled Exception: NoSuchMethodError: The method 'compareTo' was called on null. + //按_selectedValue排序,降序 + listTodayList.sort((a, b) => (b[mapTodayListDataText[_selectedValue]]) + .compareTo(a[mapTodayListDataText[_selectedValue]])); + } else { + //按_selectedValue排序,升序 + listTodayList.sort((a, b) => (a[mapTodayListDataText[_selectedValue]]) + .compareTo(b[mapTodayListDataText[_selectedValue]])); + } + break; + default: + if (_descending) { + //按_selectedValue排序,降序 + listTodayList.sort((a, b) => (b[mapTodayListDataText[_selectedValue]]) + .compareTo(a[mapTodayListDataText[_selectedValue]])); + } else { + //按_selectedValue排序,升序 + listTodayList.sort((a, b) => (a[mapTodayListDataText[_selectedValue]]) + .compareTo(b[mapTodayListDataText[_selectedValue]])); + } + break; + } + + Future.delayed(const Duration(milliseconds: 1000), () { + if (bShowToast) { + Fluttertoast.showToast( + msg: '按“${_selectedValue}”${_descending ? '降序' : '升序'}排列完成!', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + isLoading = false; + try_setState(); //避免如下异常报错 + }); + } + } + + Widget getDropdownButton() { + //DropdownMenuItem项目文本list + List itemList = [ + '林格曼黑度', + '抓拍时间', + '抓拍地点', + '车牌颜色', + '车辆类型', + ]; + + //todayListLx为今日记录列表类型,'jrzp'今日抓拍、'hycs'今日初审、'hyfh'今日复审、'tsjj'今日推送 + //添加按'推送状态'排序 + if (widget.todayListLx == 'tsjj') { + itemList.add('推送状态'); + } + + //获取DropdownMenuItem项目组件list + List> _dropDownMenuItems = + itemList.map>((String item) { + return DropdownMenuItem( + value: item, + child: getDropdownButtonItemText(item), + ); + }).toList(); + + return Padding( + padding: EdgeInsets.only(top: 0, bottom: 0), + child: Container( + alignment: Alignment(0, 0), + width: 145, + margin: EdgeInsets.only(bottom: 0), + padding: EdgeInsets.only(left: 0, bottom: 0), + // decoration: BoxDecoration( + // border: Border.all(width: 0), + // //边框圆角设置 + // borderRadius: + // BorderRadius.vertical(top: Radius.elliptical(2, 2), bottom: Radius.elliptical(2, 2)), + // ), + //DropdownButton默认有一条下划线,DropdownButtonHideUnderline去除下划线 + child: DropdownButtonHideUnderline( + child: DropdownButton( + iconSize: ScreenUtil().setHeight(100), + //itemHeight: ScreenUtil().setHeight(372), + isDense: true, + value: _selectedValue, + items: _dropDownMenuItems, + onChanged: (String selectedValue) { + if (isLoading) { + return; + } + + if (_selectedValue == selectedValue) { + _descending = !_descending; + } else { + _descending = true; + } + _selectedValue = selectedValue; + print('_selectedValue = $_selectedValue'); + + //按抓拍次数排序,降序 + // listHycsGetList2.sort((a, b) => (b["yjxx_id"].split(',').length.toString()) + // .compareTo(a["yjxx_id"].split(',').length.toString())); + + //按照用户选择的_selectedValue、_descending对listHycsGetList2进行排序,并延时更新 + _listSort(); + }, + ), + ), + ), + ); + + //没有padding: ,会报错Failed assertion: line 1644 pos 15: 'padding != null': is + // return Padding( + // // child: OutlineButton( + // // borderSide: BorderSide(width: 4.0), + // // child: Text('Hi'), + // // onPressed: () {}, + // // padding: EdgeInsets.all(0), + // // ), + // ); + + // return SizedBox( + // height: 30, + // width: 30, + // child: Container( + // color: Colors.black45, + // ), + // ); + + // return Container( + // decoration: new BoxDecoration( + // border: new Border.all(color: Color(0xFFFF0000), width: 0.5), + // color: Color(0xFF9E9E9E), + // borderRadius: new BorderRadius.circular((20.0))), + // height: 28.0, + // width: 130, + // alignment: Alignment.center, + // child: Text('sdsd'), + // ); + + // ConstrainedBox( + // constraints: BoxConstraints( + // //minWidth: double.infinity, //宽度尽可能大 + // //minHeight: _listTileHeight, //最小高度 + // maxHeight: 20, //最大高度 + // ), + // ); + } + + Widget getDropdownButtonItemText(String item) { + return Padding( + padding: EdgeInsets.only(bottom: ScreenUtil().setHeight(3)), + child: Row( + //crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + alignment: Alignment(0, 0), + padding: EdgeInsets.only(top: ScreenUtil().setHeight(10)), + child: item == _selectedValue + ? _descending + ? _getImage('assets/images/descending.png') + : _getImage('assets/images/ascending.png') + // ? Icon(Icons.arrow_downward_outlined, size: 20) + // : Icon(Icons.arrow_upward_outlined, size: 20) + : SizedBox(), + ), + SizedBox( + width: ScreenUtil().setWidth(10), + ), + Container( + //alignment: Alignment(0, -1), + child: Text(item, + style: TextStyle( + fontSize: 18, + color: isLoading + ? Theme.of(context).disabledColor + : item == _selectedValue + ? Colors.blue + : null)), + // style: isLoading + // ? TextStyle(color: Theme.of(context).disabledColor) + // : (item == _selectedValue ? TextStyle(color: Colors.blue) : null)), + ), + ], + ), + ); + } + +// Widget getDropdownButtonItemText(String item) { +// return Row( +// //crossAxisAlignment: CrossAxisAlignment.center, +// children: [ +// Container( +// alignment: Alignment(0, 0.28), +// child: item == _selectedValue +// ? _descending +// ? _getImage('assets/images/descending.png') +// : _getImage('assets/images/ascending.png') +// // ? Icon(Icons.arrow_downward_outlined, size: 20) +// // : Icon(Icons.arrow_upward_outlined, size: 20) +// : SizedBox(), +// ), +// SizedBox( +// width: 3, +// ), +// Container( +// //alignment: Alignment(0, -1), +// child: Text(item, +// style: isLoading +// ? TextStyle(color: Theme.of(context).disabledColor) +// : (item == _selectedValue ? TextStyle(color: Colors.blue) : null)), +// ), +// ], +// ); +// } +// +// Widget getDropdownButton() { +// //DropdownMenuItem项目文本list +// List itemList = [ +// '抓拍次数', +// '林格曼黑度', +// '抓拍时间', +// '抓拍地点', +// '车牌颜色', +// '车牌号码', +// '车辆类型', +// '主键ID', +// ]; +// +// //添加按'推送状态'排序 +// if (todayListLx != 'hycs') { +// itemList.removeLast(); +// itemList.addAll(['推送状态', '主键ID']); +// } +// +// //获取DropdownMenuItem项目组件list +// List> _dropDownMenuItems = +// itemList.map>((String item) { +// return DropdownMenuItem( +// value: item, +// child: getDropdownButtonItemText(item), +// ); +// }).toList(); +// +// return Padding( +// padding: EdgeInsets.only(top: 10, bottom: 10), +// child: Container( +// alignment: Alignment(0.7, 0), +// width: 125, +// margin: EdgeInsets.only(bottom: 0), +// padding: EdgeInsets.only(left: 0, bottom: 0), +// decoration: BoxDecoration( +// border: Border.all(width: 0), +// //边框圆角设置 +// borderRadius: +// BorderRadius.vertical(top: Radius.elliptical(2, 2), bottom: Radius.elliptical(2, 2)), +// ), +// //DropdownButton默认有一条下划线,DropdownButtonHideUnderline去除下划线 +// child: DropdownButtonHideUnderline( +// child: DropdownButton( +// isDense: true, +// value: _selectedValue, +// items: _dropDownMenuItems, +// onChanged: (String selectedValue) { +// if (isLoading) { +// return; +// } +// +// if (_selectedValue == selectedValue) { +// _descending = !_descending; +// } else { +// _descending = true; +// } +// _selectedValue = selectedValue; +// print('_selectedValue = $_selectedValue'); +// +// //按抓拍次数排序,降序 +// // listHycsGetList2.sort((a, b) => (b["yjxx_id"].split(',').length.toString()) +// // .compareTo(a["yjxx_id"].split(',').length.toString())); +// +// //按照用户选择的_selectedValue、_descending对listHycsGetList2进行排序,并延时更新 +// _listSort(); +// }, +// ), +// ), +// ), +// ); +// +// //没有padding: ,会报错Failed assertion: line 1644 pos 15: 'padding != null': is +// // return Padding( +// // // child: OutlineButton( +// // // borderSide: BorderSide(width: 4.0), +// // // child: Text('Hi'), +// // // onPressed: () {}, +// // // padding: EdgeInsets.all(0), +// // // ), +// // ); +// +// // return SizedBox( +// // height: 30, +// // width: 30, +// // child: Container( +// // color: Colors.black45, +// // ), +// // ); +// +// // return Container( +// // decoration: new BoxDecoration( +// // border: new Border.all(color: Color(0xFFFF0000), width: 0.5), +// // color: Color(0xFF9E9E9E), +// // borderRadius: new BorderRadius.circular((20.0))), +// // height: 28.0, +// // width: 130, +// // alignment: Alignment.center, +// // child: Text('sdsd'), +// // ); +// +// // ConstrainedBox( +// // constraints: BoxConstraints( +// // //minWidth: double.infinity, //宽度尽可能大 +// // //minHeight: _listTileHeight, //最小高度 +// // maxHeight: 20, //最大高度 +// // ), +// // ); +// } +} + +//https://blog.csdn.net/u014803467/article/details/103750018 +//Flutter 使用SliverChildBuilderDelegate获取ListView的第一个和最后一个可见Item序号 +// 秋名山交警X 2019-12-28 23:52:52 +class _SaltedValueKey extends ValueKey { + const _SaltedValueKey(Key key) + : assert(key != null), + super(key); +} + +class MyChildrenDelegate extends SliverChildBuilderDelegate { + MyChildrenDelegate( + Widget Function(BuildContext, int) builder, { + int childCount, + bool addAutomaticKeepAlive = true, + bool addRepaintBoundaries = true, + }) : super(builder, + childCount: childCount, + addAutomaticKeepAlives: addAutomaticKeepAlive, + addRepaintBoundaries: addRepaintBoundaries); + + // Return a Widget for the given Exception + Widget _createErrorWidget(dynamic exception, StackTrace stackTrace) { + final FlutterErrorDetails details = FlutterErrorDetails( + exception: exception, + stack: stackTrace, + library: 'widgets library', + context: ErrorDescription('building'), + ); + FlutterError.reportError(details); + return ErrorWidget.builder(details); + } + + @override + Widget build(BuildContext context, int index) { + assert(builder != null); + if (index < 0 || (childCount != null && index >= childCount)) return null; + Widget child; + try { + child = builder(context, index); + } catch (exception, stackTrace) { + child = _createErrorWidget(exception, stackTrace); + } + if (child == null) return null; + final Key key = child.key != null ? _SaltedValueKey(child.key) : null; + if (addRepaintBoundaries) child = RepaintBoundary(child: child); + if (addSemanticIndexes) { + final int semanticIndex = semanticIndexCallback(child, index); + if (semanticIndex != null) + child = IndexedSemantics(index: semanticIndex + semanticIndexOffset, child: child); + } + if (addAutomaticKeepAlives) child = AutomaticKeepAlive(child: child); + return KeyedSubtree(child: child, key: key); + } + + @override + void didFinishLayout(int _firstIndex, int _lastIndex) { + // TODO: implement didFinishLayout + super.didFinishLayout(_firstIndex, _lastIndex); + } + + ///监听 在可见的列表中 显示的第一个位置和最后一个位置 + @override + double estimateMaxScrollOffset( + int _firstIndex, int _lastIndex, double _leadingScrollOffset, double _trailingScrollOffset) { + // print( + // 'firstIndex = $_firstIndex, lastIndex = $_lastIndex, leadingScrollOffset = $_leadingScrollOffset,' + // 'trailingScrollOffset : $_trailingScrollOffset '); + + //违章信息Listview滚动广播 + eventBus.fire(WzxxDataScrollEvent(_firstIndex, _lastIndex)); + + return super.estimateMaxScrollOffset( + _firstIndex, _lastIndex, _leadingScrollOffset, _trailingScrollOffset); + } +} diff --git a/lib/pages/Works/TJXX/today_list_zpjl_content.dart b/lib/pages/Works/TJXX/today_list_zpjl_content.dart new file mode 100644 index 0000000..eaab13b --- /dev/null +++ b/lib/pages/Works/TJXX/today_list_zpjl_content.dart @@ -0,0 +1,778 @@ +//import '../../../widget/player_pro.dart'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_drag_scale/flutter_drag_scale.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hyzp_ybqx/widget/my_superplayer.dart'; +import 'package:keyboard_avoider/keyboard_avoider.dart'; + +// +import '../../../components/commonFun.dart'; +import '../../../components/doJSON.dart'; +import '../../../components/hyxx_data_handle.dart'; + +// "today": { +// "total": 4, +// "data": [ +// { +// "id": 428549, +// "car_number": "川15A2563", +// "cpys": "绿色", +// "zpsj": "2021-04-04 11:31:11", +// "dwip": "172.16.3.3", +// "dwms": "岷江南路森林小区附近", +// "cplx": "农用车", +// "lgmzs": 3, +// "pic_url": "/wwwroot/admin/Api/wwwroot/public/uploads/e936524e92f90541cbaeca3b092c95be.jpg", +// "video_url": "video/3_6063_20210404_113111_川15A2563.mp4" +// }, + +class TodayListZpjlContent extends StatefulWidget { + TodayListZpjlContent({ + @required this.hyshlx, + @required this.num, + @required this.text, + @required this.mapZpjl, + this.tsztText = '', + Key key, + }) : super(key: key); + String hyshlx; + String num; + String text; + Map mapZpjl; + String tsztText; + + _TodayListZpjlPageState createState() => _TodayListZpjlPageState(); +} + +//用TabController实现顶部tab切换 +class _TodayListZpjlPageState extends State { + //try_setState(); //避免如下异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + dispose() { + super.dispose(); + } + + BuildContext _context; + + //flutter_screenUtil 4.x 用法,ScreenUtil.screenWidth (sdk>=2.6 : 1.sw) //设备宽度 + double _screenWidth = 1.sw; + + double _marginLeft = 25; + double _marginCenter = 35; + double _fontSize = 16; + double _widthLeft = 40; // = _screenWidth / 3; + double _iconSize = 18; + double _stampSize = 100; + double _listTileHeight = 30; + Color _iconColor = Colors.blue; + double _marginVer = 10; + + Map _mapTsjjGetTsStatus = {}; + + Map mapName = {}; + + void initState() { + _context = context; + _widthLeft = _screenWidth / 2.6; + //getListFlields(); + + //得到字段名称 + mapName['plate_id'] = widget.hyshlx == 'jrzp' ? 'car_number' : 'plate_id'; + mapName['plate_color'] = widget.hyshlx == 'jrzp' ? 'cpys' : 'plate_color'; + + imageWztp = getWztp(); //得到违章图片 + super.initState(); + } + + double _radioImage = 9 / 16; + + // 使用 cached_network_image 插件实现网络图片缓存 + // 使用 flutter_drag_scale 实现可缩放可拖拽双击放大的图片功能。PhotoView插件不好用,有问题 + Widget getNetworkImage(String url) { + return CachedNetworkImage( + imageUrl: url, + alignment: Alignment.topCenter, + imageBuilder: (context, imageProvider) => DragScaleContainer( + doubleTapStillScale: true, child: Image(image: imageProvider) + // child: Image( + // image: NetworkImage( + // 'http://h.hiphotos.baidu.com/zhidao/wh%3D450%2C600/sign=0d023672312ac65c67506e77cec29e27/9f2f070828381f30dea167bbad014c086e06f06c.jpg'), + // ), + ), + // imageBuilder: (context, imageProvider) => PhotoView( + // imageProvider: imageProvider, + // ), + //placeholder: (context, url) => CircularProgressIndicator(), + placeholder: (context, url) => + getMoreWidget(color: Colors.black38, size: 20.0, strokeWidth: 2.0), + errorWidget: (context, url, error) => Icon(Icons.error), + ); + } + + // 167 50 3.34 + Widget getLgmzs(int lgmzs, {double width = 127, double height = 127}) { + int _rgb = (255 * (5 - lgmzs)) ~/ 5; + return Stack( + children: [ + Container( + width: ScreenUtil().setWidth(width), + height: ScreenUtil().setHeight(height), + padding: EdgeInsets.only( + right: ScreenUtil().setWidth(6), left: ScreenUtil().setWidth(6), top: 0, bottom: 4), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text('$lgmzs级${lgmzs * 20}%', style: TextStyle(fontSize: 10)), + //SizedBox(height: 0), + Container( + width: ScreenUtil().setWidth(66), + height: ScreenUtil().setHeight(66), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + color: Color.fromRGBO(_rgb, _rgb, _rgb, 1.0), + borderRadius: new BorderRadius.circular(0), + ), + ) + ], + ), + ), + Positioned( + top: ScreenUtil().setHeight(4), + child: Container( + width: ScreenUtil().setWidth(width), + height: ScreenUtil().setHeight(height - 8), + padding: EdgeInsets.only( + right: ScreenUtil().setWidth(6), left: ScreenUtil().setWidth(6), top: 0, bottom: 0), + decoration: BoxDecoration( + border: Border.all( + color: (lgmzs == widget.mapZpjl['lgmzs']) + ? Colors.red + : Color.fromRGBO(244, 244, 244, 1), + width: 2), + //color: Colors.lightBlue, + borderRadius: new BorderRadius.circular(3.0), + ), + ), + ) + ], + ); + } + + //得到tsjj页面组件 + //1、得到格林曼黑度标准和视频播放按钮组件 + Widget getHdAndPlay() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + getLgmzs(0), + getLgmzs(1), + getLgmzs(2), + getLgmzs(3), + getLgmzs(4), + getLgmzs(5, width: 153), + getIconBtnSizeX( + height: 104, + //getIconBtnSizeX 中已经使用ScreenUtil().setHeight(126),此处不能传 ScreenUtil().setHeight(126) ,否则严重错位 + width: 168, + text: "视频", + textSize: 12, + circular: 4, + color: Color.fromRGBO(52, 157, 237, 1), + onTop: () async { + if (Playing) { + //禁止同时启动两次播放器 + return; + } + + Playing = true; //禁止同时启动两次播放器 + urlnew = getMediaUrl(widget.mapZpjl['video_url']); + + //获取视频地址失败 + if (!isVideoUrl(urlnew)) { + return; + } + + Navigator.of(_context).push(MaterialPageRoute( + builder: (context) => SuperPlayerPage( + loop: 0, //设置播放循环,默认播放器的循环次数是1, 即不循环播放。如果设置循环次数0,表示无限循环。 + url: urlnew, + title: + '${widget.text}视频${widget.num}\n${widget.mapZpjl[mapName['plate_id']]}(${getDwmc(widget.mapZpjl['dwip'])})'))); + + // Navigator.of(_context).push(MaterialPageRoute( + // builder: (context) => PlayerProNew( + // loop: 0, //设置播放循环,默认播放器的循环次数是1, 即不循环播放。如果设置循环次数0,表示无限循环。 + // url: urlnew, + // title: + // '${widget.text}视频${widget.num}\n${widget.mapZpjl[mapName['plate_id']]}(${getDwmc(widget.mapZpjl['dwip'])})'))); + }, + ), + SizedBox(width: ScreenUtil().setWidth(15)), + ], + ); + } + + Widget imageWztp; + + //2、得到违章图片组件 + Widget getWztp() { + //ratioList[index] = 0.5714285714285714 + return Stack( + children: [ + Container( + width: ScreenUtil().setWidth(1022), + //height: ScreenUtil().setHeight(639), + //height: ScreenUtil().setHeight(22 + 1022 * _radioImage), + height: ScreenUtil().setHeight(30 + 1022 * _radioImage), + decoration: BoxDecoration( + //color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12), + ), + ), + ), + Positioned( + //left: ScreenUtil().setWidth(_marginLeft), + top: ScreenUtil().setHeight(_marginLeft), + child: Container( + width: ScreenUtil().setWidth(1022), + height: ScreenUtil().setHeight(30 + 1022 * _radioImage), + child: getNetworkImage(getMediaUrl(widget.mapZpjl['pic_url'])), + ), + ) + ], + ); + } + + // void printScreenInformation() { + // print('Device width dp:${1.sw}dp'); + // print('Device height dp:${1.sh}dp'); + // print('Device pixel density:${ScreenUtil().pixelRatio}'); + // print('Bottom safe zone distance dp:${ScreenUtil().bottomBarHeight}dp'); + // print('Status bar height dp:${ScreenUtil().statusBarHeight}dp'); + // print('The ratio of actual width to UI design:${ScreenUtil().scaleWidth}'); + // print( + // 'The ratio of actual height to UI design:${ScreenUtil().scaleHeight}'); + // print('System font scaling:${ScreenUtil().textScaleFactor}'); + // print('0.5 times the screen width:${0.5.sw}dp'); + // print('0.5 times the screen height:${0.5.sh}dp'); + // } + + void printScreenInformation() { + print('ScreenUtil().screenWidth = ${ScreenUtil().screenWidth}'); + print('设备宽度:${1.sw}dp'); + print('"1.w" = ${1.w}'); + print('"1.sw" = ${1.sw}'); + print('设备高度:${1.sh}dp'); + print('设备的像素密度:${ScreenUtil().pixelRatio}'); + print('底部安全区距离:${ScreenUtil().bottomBarHeight}dp'); + print('状态栏高度:${ScreenUtil().statusBarHeight}dp'); + print('实际宽度的dp与设计稿px的比例:${ScreenUtil().scaleWidth}'); + print('实际高度的dp与设计稿px的比例:${ScreenUtil().scaleHeight}'); + print('宽度和字体相对于设计稿放大的比例:${ScreenUtil().scaleWidth * ScreenUtil().pixelRatio}'); + print('高度相对于设计稿放大的比例:${ScreenUtil().scaleHeight * ScreenUtil().pixelRatio}'); + print('系统的字体缩放比例:${ScreenUtil().textScaleFactor}'); + print('屏幕宽度的0.5:${0.5.sw}dp'); + print('屏幕高度的0.5:${0.5.sh}dp'); + } + + //3、得到违章图片说明信息组件 + Widget getWztpSmxx() { + return Container( + width: ScreenUtil().setWidth(1022), + height: ScreenUtil().setHeight(390), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12), + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + //违章信息组件1:车牌号码、车牌颜色 + getWzxxPart1(), + //违章信息组件2:抓拍时间组件 + getWzxxText('抓拍时间:' + widget.mapZpjl['zpsj']), + //违章信息组件3:车牌类型 + 黑度 + getWzxxPart3(), + //违章信息组件4:抓拍地点(简称) + getWzxxText('抓拍地点:' + getDwmc(widget.mapZpjl['dwip']) + ' (简称)'), + //违章信息组件5:抓拍地点 + getWzxxText('抓拍地点:' + widget.mapZpjl['dwms']), + ], + ), + ); + } + + // I/flutter (22989): ScreenUtil().screenWidth = 360.0 + // I/flutter (22989): 设备宽度:360.0dp + // I/flutter (22989): "1.w" = 0.3333333333333333 + // I/flutter (22989): "1.sw" = 360.0 + // I/flutter (22989): 设备高度:640.0dp + // I/flutter (22989): 设备的像素密度:3.0 + // I/flutter (22989): 底部安全区距离:0.0dp + // I/flutter (22989): 状态栏高度:24.0dp + // I/flutter (22989): 实际宽度的dp与设计稿px的比例:0.3333333333333333 + // I/flutter (22989): 实际高度的dp与设计稿px的比例:0.3333333333333333 + // I/flutter (22989): 宽度和字体相对于设计稿放大的比例:1.0 + // I/flutter (22989): 高度相对于设计稿放大的比例:1.0 + // I/flutter (22989): 系统的字体缩放比例:1.0 + // I/flutter (22989): 屏幕宽度的0.5:180.0dp + // I/flutter (22989): 屏幕高度的0.5:320.0dp + + //3、得到违章信息组件1:车牌号码、车牌颜色 + //车牌颜色Map cpysMap = { + // '蓝色': cpysItem( + // cpysText: '蓝色', + // cpysBackground: Colors.blue, + // cpysFont: Colors.white, + // cpysBorder: Colors.orange), + // } + Widget getWzxxPart1() { + //printScreenInformation(); + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + getTitleText('车牌号码:'), + getBoderText(widget.mapZpjl[mapName['plate_id']].toString(), + width: ScreenUtil().setWidth(1022 / 3.2)), + Expanded(child: SizedBox.shrink()), + getTitleText('颜色:'), + getBoderText(widget.mapZpjl[mapName['plate_color']], + width: ScreenUtil().setWidth(1022 / 4.8)), + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + ], + ); + } + + Widget getTitleRichText(String text1, {String text2 = '', double fontSize = 16}) { + return RichText( + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.left, + text: TextSpan( + text: text1, + style: TextStyle(fontSize: fontSize, color: Colors.black), + children: [ + TextSpan( + text: text2, + style: TextStyle(fontSize: fontSize, color: Colors.blue, fontWeight: FontWeight.w500), + ), + ], + ), + ); + } + + Widget getTitleText(String text, {double fontSize = 16}) { + return Text(text, + style: TextStyle(fontSize: fontSize), + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis); + } + + Widget getTrailText(String text, {double fontSize = 16, double off = 0}) { + return Container( + width: _screenWidth - _widthLeft - off - (2 * ScreenUtil().setWidth(_marginLeft)), + child: Text(text, + style: TextStyle(fontSize: fontSize), + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis), + ); + } + + Widget getBoderText(String text, {double width = 40}) { + cpysItem _cpysItem = cpysMap[widget.mapZpjl[mapName['plate_color']]]; + + return Container( + //color: _cpysItem.cpysBackground, + alignment: Alignment(0, -1), + width: width, + decoration: BoxDecoration( + border: Border.all(color: _cpysItem.cpysBorder, width: 2), + color: _cpysItem.cpysBackground, + borderRadius: BorderRadius.circular(3), + ), + child: Padding( + padding: EdgeInsets.only(bottom: 3), + child: Text(text, + style: TextStyle(fontSize: _fontSize, color: _cpysItem.cpysFont), + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis), + ), + ); + } + + //I/flutter (17555): mapZpjl = { + // id: 1222, plate_id: 川Q736X2, plate_color: 蓝色, zpsj: 1612857077, yjxx_id: 1399, workflow: 999, + // video_url: video/9_6063_20210209_155117_川Q736X2.mp4, + // pic_url: /wwwroot/admin/Api/wwwroot/public/uploads/9d2f45fd24b41f2b94abe42b30970d75.jpg, + // clfl: 集装箱卡车, dwip: 172.16.3.9, dwms: 宜长路出城方向, lgmzs: 3, jczxd: 994, sfhy: 黑烟车 + // } + + Widget getIcon(IconData _iconData) { + return Container( + width: _iconSize - 2, + height: _iconSize, + child: Padding( + padding: EdgeInsets.only(top: 2), + child: Icon(_iconData, size: _iconSize, color: _iconColor), + ), + ); + } + + Widget getWzxxText(String _text, {double fontSize = 16}) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + Container( + alignment: Alignment(-1, 0), + width: ScreenUtil().setWidth(1022) - ScreenUtil().setWidth(_marginLeft), + child: Text(_text, + style: TextStyle(fontSize: fontSize), + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis), + ), + ], + ); + } + + Widget getWzxxPart3() { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + Container( + alignment: Alignment(-1, 0), + //width: ScreenUtil().setWidth(1022) - ScreenUtil().setWidth(_marginLeft), + child: getTitleText('车牌类型:' + widget.mapZpjl['cplx']), + ), + Expanded(child: SizedBox.shrink()), + //getTitleText('黑度: ${widget.mapZpjl['lgmzs']}'), + getTitleRichText('黑度:', text2: '${widget.mapZpjl['lgmzs']} '), + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + ], + ); + } + + Widget getText(String text, {Color color}) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + alignment: Alignment(-1, 0), + width: ScreenUtil().setWidth(1022 - 2 * _marginLeft), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + Text(text, + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis, + style: TextStyle(color: color)) + ], + ), + ), + ], + ); + } + + Widget getText2(String text1, String text2, String text3, {Color color}) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + alignment: Alignment(-1, 0), + width: ScreenUtil().setWidth(1022 - 2 * _marginLeft), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + Text(text1, textAlign: TextAlign.left), + Text(text2, + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis, + style: TextStyle(color: color, fontWeight: FontWeight.w500)), + Text(text3, textAlign: TextAlign.left), + ], + ), + ), + ], + ); + } + + //5、得到黑烟初审'hycsInfo'、或者黑烟'hyfhInfo'複核信息组件 + //style: TextStyle(fontSize: _fontSize), + Widget getHyshInfo(String _hyshInfo) { + String _hyshLx = (_hyshInfo == 'hycsInfo' ? '初审' : '复审'); //黑烟审核类型 + String _hyshYh = (_hyshInfo == 'hycsInfo' ? 'cs_username' : 'fs_username'); //黑烟审核用户 + String _hyshSj = (_hyshInfo == 'hycsInfo' ? 'cs_time' : 'fs_time'); //黑烟审核时间 + String _hyshJg = (_hyshInfo == 'hycsInfo' ? 'cs_tile' : 'fs_tile'); //黑烟审核结果 + String _hyshYj = (_hyshInfo == 'hycsInfo' ? 'cs_shuoming' : 'fs_shuoming'); //黑烟审核意见 + + double _height = _hyshInfo == 'hyfhInfo' && widget.hyshlx == 'tsjj' ? 315 : 265; + return Column( + children: [ + Container( + width: ScreenUtil().setWidth(1022), + height: ScreenUtil().setHeight(_height), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular(12), + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + alignment: Alignment(-1, 0), + width: ScreenUtil().setWidth(1022 - 2 * _marginLeft), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + Text('${_hyshLx}结果:' + widget.mapZpjl[_hyshJg], + textAlign: TextAlign.left, overflow: TextOverflow.ellipsis), + SizedBox(width: ScreenUtil().setWidth(20)), + Container( + width: my_iconSize, + height: my_iconSize, + decoration: widget.mapZpjl[_hyshJg] == '' + ? null + : BoxDecoration( + //color: Colors.white, + image: DecorationImage( + image: AssetImage(widget.mapZpjl[_hyshJg] == "黑烟车" + ? "assets/images/hyc.png" + : "assets/images/fhyc.png"), + fit: BoxFit.contain), + ), + alignment: Alignment.center, + //child: + ), + ], + ), + ), + ], + ), + getText('${_hyshLx}意见:' + widget.mapZpjl[_hyshYj]), + getText('${_hyshLx}用户:' + widget.mapZpjl[_hyshYh]), + getText('${_hyshLx}时间:' + widget.mapZpjl[_hyshSj]), + _hyshInfo == 'hyfhInfo' && widget.hyshlx == 'tsjj' + ? (getText2('推送状态:', widget.tsztText, ' (${getDate(widget.mapZpjl['ts_time'])})', + color: + widget.tsztText.indexOf('成功') >= 0 ? Colors.blueAccent : Colors.black26)) + : SizedBox.shrink(), + ], + ), + ), + SizedBox(height: ScreenUtil().setHeight(_marginVer)), + ], + ); + } + + //9、得到推送交警状态信息组件7:推送状态 + // tszt 整型 推送状态:0-未推送 | 1-推送失败 | 3-推送成功 + //_mapTsjjGetTsStatus['tszt'] + Widget getWzxxPart7() { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: ScreenUtil().setWidth(_marginLeft)), + Container( + alignment: Alignment(-1, 0), + width: _screenWidth - ScreenUtil().setWidth(_marginLeft), + child: getTitleText('推送状态:' + mapTsztText[_mapTsjjGetTsStatus['tszt']]), + ), + ], + ); + } + + //10、得到推送交警确认组件 + Widget getTsjjQr() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + getBtnSizeX( + text: "返回", + onPressedFun: () async { + Navigator.pop(context); + }, + width: 90.0), + ], + ); + } + + bool showMoreWidget = false; + + @override + Widget build(BuildContext context) { + return Scaffold( + //resizeToAvoidBottomPadding: false, + appBar: PreferredSize( + preferredSize: Size.fromHeight(ScreenUtil().setHeight(173)), // 设置appBar高度 + // 设置appBar高度 + child: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + titleSpacing: 0.0, + //设置title的左边距 + flexibleSpace: Container( + //SizedBox(height: ScreenUtil().statusBarHeight), //显示顶部状态栏 + // SizedBox(height: ScreenUtil().setHeight(10)), //显示顶部状态栏 + padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight), //留出顶部状态栏高度 + child: Container( + //height: ScreenUtil().setHeight(173), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + // decoration: BoxDecoration( + // gradient: LinearGradient(colors: [ + // Color(0xFF0018EB), + // Color(0xFF01C1D9), + // ], begin: Alignment.bottomCenter, end: Alignment.topCenter), + // ), + ), + ), + title: Padding( + padding: EdgeInsets.only(left: 0, right: 0), + child: Row( + //mainAxisAlignment: MainAxisAlignment.start, + children: [ + getIconAndTextButton( + iconColor: Colors.white, + iconData: Icons.chevron_left_outlined, + onPress: showMoreWidget + ? null + : () { + Navigator.pop(context); + }, + ), + Expanded( + child: Text(widget.text + '记录' + widget.num, + style: TextStyle(color: Colors.white, fontSize: 20), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis), + ), + SizedBox(width: 30), + ], + ), + ), + ), + ), + body: null == imageWztp + // 显示加载中的圈圈 + ? getMoreWidget(color: Colors.black38, size: 20.0, strokeWidth: 2.0) + : Stack( + children: [ + //SizedBox.shrink() 创建父类允许最小尺寸的约束Box + showMoreWidget + ? Align( + alignment: Alignment(0, 0.8), + child: Container( + height: 200, + width: 200, + child: getMoreWidget2( + text: '加载中...', + color: Colors.red, + size: 40.0, + strokeWidth: 3.0), //显示加载中的圈圈, + ), + ) + : SizedBox.shrink(), + KeyboardAvoider( + autoScroll: true, + child: Container( + color: Color.fromRGBO(244, 244, 244, 1), + child: Column( + children: [ + //1、得到格林曼黑度标准和视频播放按钮组件 + getHdAndPlay(), + //2、得到违章图片组件 + imageWztp, + SizedBox(height: ScreenUtil().setHeight(_marginVer)), + //3、得到违章图片说明信息组件 + getWztpSmxx(), + SizedBox(height: ScreenUtil().setHeight(_marginVer)), + //7、得到黑烟初审信息组件 + widget.hyshlx == 'hycs' || + widget.hyshlx == 'hyfh' || + widget.hyshlx == 'tsjj' + ? getHyshInfo('hycsInfo') + : SizedBox.shrink(), + //8、得到黑烟复审信息组件 + widget.hyshlx == 'hyfh' || widget.hyshlx == 'tsjj' + ? getHyshInfo('hyfhInfo') + : SizedBox.shrink(), + SizedBox(height: widget.hyshlx == 'tsjj' ? 0 : 15), + //9、得到推送交警确认组件 + getTsjjQr(), + //SizedBox(height: 10), + ], + ), + ), + ), + widget.hyshlx == 'tsjj' + ? Positioned( + //alignment: Alignment(0.9, 0.35), + //alignment: Alignment(0.8, 0.45), + right: ScreenUtil().setWidth(50), + top: ScreenUtil().setHeight(1423), + child: Container( + //alignment: Alignment(0.5, -0.5), + width: _stampSize, + height: _stampSize, + //color: Colors.black12, + decoration: BoxDecoration( + //color: Colors.white, + image: DecorationImage( + //image: AssetImage("assets/images/jkzx_stamp.png"), fit: BoxFit.contain), + image: AssetImage(widget.mapZpjl['fs_tile'] == '黑烟车' + ? "assets/images/hyc.png" + : "assets/images/fhyc.png"), + fit: BoxFit.contain), + ), + //child: + ), + ) + : SizedBox.shrink(), + ], + ), + ); + } + + Widget getBtnSizeX({@required text, width = 70.0, height = 35.0, onPressedFun}) { + return Container( + color: Colors.white12, //onPressedFun为null时无效 + width: width, + height: height, + child: RaisedButton( + padding: EdgeInsets.all(0), + textColor: Colors.black, + child: Text(text), + onPressed: onPressedFun, + ), + ); + } +} diff --git a/lib/pages/Works/TJXX/zptj_bar_chart.dart b/lib/pages/Works/TJXX/zptj_bar_chart.dart new file mode 100644 index 0000000..43ac37e --- /dev/null +++ b/lib/pages/Works/TJXX/zptj_bar_chart.dart @@ -0,0 +1,629 @@ +import 'package:fl_chart/fl_chart.dart'; +//import 'package:flustars/flustars.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hyzp_ybqx/components/commonFun.dart'; +import 'package:hyzp_ybqx/components/dioFun.dart'; +import 'package:hyzp_ybqx/components/hyxx_data_handle.dart'; +import 'package:hyzp_ybqx/pages/Works/TJXX/tj_data.dart'; + +class ZptjBarChart extends StatefulWidget { + ZptjBarChart({this.statisType, this.data_ok = false, Key key}) : super(key: key); + String statisType; //统计类型 + bool data_ok; //listZptjStatis 中的数据是否准备好 + + @override + State createState() => ZptjBarChartState(); +} + +class ZptjBarChartState extends State { + static const Color red = const Color(0xffff5182); + static const Color blue = const Color(0xff0000ff); + static const Color leftBarColor = const Color(0xffff5182); + static const Color rightBarColor = const Color(0xff0000ff); + static const Color bottomBarColor = const Color(0xff939393); + static const Color gridColor = const Color(0xffe7e8ec); + static const double width = 7; + + String _textBottom1; + String _textBottom2; + String _textTop1; + String _textTop2; + int _rate = 10; // today 字段放大倍率 + int _rateCoord = 10000; // 坐标压缩倍率 + int _interval = 20; // 坐标间隔 + int _maxY = -1; + + List _listBarData = []; + + //try_setState(); //避免异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + @override + void initState() { + super.initState(); + + getDataNew().then((value) { + _listBarData = value; + try_setState(); + }); + + // ///获取点位信息数据 + // if (!widget.data_ok) { + // //listZptjStatis 中的数据未准备好 + // listZptjStatis.clear(); //最后需要显示的数据 + // } + // + // if (listZptjStatis.isEmpty) { + // if (listDwinfoGetList2.isEmpty) { + // //若没有读取了点位数据,便需要先读取 + // getThePageList(theHyshlx: 'dwxx').then((value) { + // listDwinfoGetList2 = value; + // print('listDwinfoGetList2 = \n$listDwinfoGetList2'); + // getZptjStatis(widget.statisType).then((value) { + // //按 sortField 升序排序 + // listZptjStatis.sort((a, b) => (a['dwbh']).compareTo(b['dwbh'])); + // + // getData().then((value) { + // _listBarData = value; + // try_setState(); + // }); + // }); + // }); + // } else { + // //若已经读取了点位数据,便直接使用 + // print('listDwinfoGetList2 = \n$listDwinfoGetList2'); + // getZptjStatis(widget.statisType).then((value) { + // //按 sortField 升序排序 + // listZptjStatis.sort((a, b) => (a['dwbh']).compareTo(b['dwbh'])); + // + // getData().then((value) { + // _listBarData = value; + // try_setState(); + // }); + // }); + // } + // } else { + // getData().then((value) { + // _listBarData = value; + // try_setState(); + // }); + // } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + // appBar: AppBar( + // title: Text(mapStatisType[widget.statisType]['text']), + // centerTitle: true, + // ), + appBar: PreferredSize( + preferredSize: Size.fromHeight(ScreenUtil().setHeight(173)), // 设置appBar高度 + // 设置appBar高度 + child: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + titleSpacing: 0.0, + flexibleSpace: Container( + //SizedBox(height: ScreenUtil().statusBarHeight), //显示顶部状态栏 + // SizedBox(height: ScreenUtil().setHeight(10)), //显示顶部状态栏 + padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight), //留出顶部状态栏高度 + child: Container( + //height: ScreenUtil().setHeight(173), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + // decoration: BoxDecoration( + // gradient: LinearGradient(colors: [ + // Color(0xFF0018EB), + // Color(0xFF01C1D9), + // ], begin: Alignment.bottomCenter, end: Alignment.topCenter), + // ), + ), + ), + title: Padding( + padding: EdgeInsets.only(left: 0, right: 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + getIconAndTextButton( + iconColor: Colors.white, + iconData: Icons.chevron_left_outlined, + onPress: () { + Navigator.pop(context); + }, + ), + Expanded( + child: Text(mapStatisType[widget.statisType]['text'], + style: TextStyle(color: Colors.white, fontSize: 20), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis), + ), + SizedBox(width: 50), + ], + ), + ), + ), + ), + + body: Container( + alignment: Alignment(0, -0.85), + child: AspectRatio( + aspectRatio: 0.94, //宽高比 + child: Card( + elevation: 4, //阴影高度 + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)), + color: Colors.white, + child: Container( + padding: const EdgeInsets.only(top: 20), + child: (listAllStatisData.isEmpty || _listBarData.isEmpty) + ? getMoreWidget(color: Colors.black38) + : Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: 3), + Text(_textTop1, style: TextStyle(color: leftBarColor, fontSize: 12)), + Expanded( + child: Text(''), + ), + Text(_textTop2, style: TextStyle(color: rightBarColor, fontSize: 12)), + SizedBox(width: 3), + ], + ), + SizedBox( + height: 10, + ), + BarChart( + BarChartData( + maxY: _maxY.toDouble() * 1.1, + groupsSpace: 6, + alignment: BarChartAlignment.spaceEvenly, + barTouchData: BarTouchData( + enabled: false, + ), + //坐标数据 + titlesData: FlTitlesData( + show: true, + bottomTitles: SideTitles( + rotateAngle: 45, + showTitles: true, + getTextStyles: (value) => + const TextStyle(color: bottomBarColor, fontSize: 10), + margin: 10, + getTitles: (double value) { + int i = value.toInt() - 1; + return '${listAllStatisData[i]['id']}. ${listAllStatisData[i]['dwmc']}'; + }, + ), + leftTitles: SideTitles( + showTitles: true, + getTextStyles: (value) => + const TextStyle(color: leftBarColor, fontSize: 10), + margin: 4, //边距 + getTitles: (double value) { + value = value / _rateCoord; //坐标压缩别率 + if (value.toInt() % _interval == 0) { + return '${(value / _rate).toInt()}'; //将左侧坐标放大 _rate 倍 + } else { + return ''; + } + }, + ), + rightTitles: SideTitles( + showTitles: true, + getTextStyles: (value) => + const TextStyle(color: rightBarColor, fontSize: 10), + margin: 4, + getTitles: (double value) { + value = value / _rateCoord; //坐标压缩别率 + if (value.toInt() % _interval == 0) { + return '${value.toInt()}'; + } else { + return ''; + } + }, + ), + ), + //栅格线 + gridData: FlGridData( + show: true, + checkToShowHorizontalLine: (value) => value % _rate == 0, + getDrawingHorizontalLine: (value) => FlLine( + color: gridColor, + strokeWidth: 1, + ), + ), + //边线 + borderData: FlBorderData( + show: true, + border: const Border( + bottom: BorderSide( + color: gridColor, + width: 2, + ), + left: BorderSide( + color: leftBarColor, + width: 2, + ), + right: BorderSide( + color: rightBarColor, + width: 2, + ), + top: BorderSide( + color: Colors.transparent, + ), + ), + ), + //图标数据 + barGroups: _listBarData, + //barGroups: showingBarGroups, + ), + ), + SizedBox(height: 30), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: 3), + Text(_textBottom1, + style: TextStyle(color: leftBarColor, fontSize: 12)), + Expanded( + child: Container( + alignment: Alignment.center, + child: Text('点位编号', + style: TextStyle(color: bottomBarColor, fontSize: 12)), + ), + ), + Text(_textBottom2, + style: TextStyle(color: rightBarColor, fontSize: 12)), + SizedBox(width: 3), + ], + ), + ]), + ), + ), + ), + ), + ); + } + + List bar_getAllSumNew(String field) { + int items = 0; + int sum = 0; + for (var item in listAllStatisData) { + if (item[field] > 0) { + items++; + sum += item[field]; + } + } + return [items, sum]; + } + + Future> getDataNew() async { + String _field1 = ''; + String _field2 = ''; + _maxY = -1; + int len = listAllStatisData.length; + List listBarData = []; + + switch (widget.statisType) { + case 'zptj': + _field1 = 'zp_today'; + _field2 = 'zp_all'; + _rate = 10; // today 字段放大倍率 + _rateCoord = 1; //坐标压缩别率 + _interval = 20; // 坐标间隔 + _textBottom1 = '今日合计 ${bar_getAllSumNew(_field1)[1]}'; + _textBottom2 = '总共 ${bar_getAllSumNew(_field2)[1]}'; // 'all' 字段没有记录详情,所以用bar_getAllSumCll() + _textTop1 = '今日(次)'; + _textTop2 = '(次)合计'; + _rateCoord = 1; + + for (int i = 0; i < len; i++) { + //将 today 字段放大 _rate 倍 + listBarData.add( + makeGroupData( + listAllStatisData[i]['id'], + (listAllStatisData[i][_field1] * _rate).toDouble(), + listAllStatisData[i][_field2].toDouble()), + ); + //得到最大值 + _maxY = listAllStatisData[i][_field1] * _rate > _maxY + ? listAllStatisData[i][_field1] * _rate + : _maxY; + _maxY = listAllStatisData[i][_field2] > _maxY ? listAllStatisData[i][_field2] : _maxY; + } + + break; + case 'sh_hyc_tj': + _field1 = 'hyc_all'; + _field2 = 'send_all'; + _rate = 1; // today 字段放大倍率 + _rateCoord = 1; //坐标压缩倍率 + _interval = 2; // 坐标间隔 + _textBottom1 = '已审核 ${bar_getAllSumNew(_field1)[1]}'; + _textBottom2 = '已推送 ${bar_getAllSumNew(_field2)[1]}'; + _textTop1 = '审核(次)'; + _textTop2 = '(次)推送'; + + for (int i = 0; i < len; i++) { + //将 today 字段放大 _rate 倍 + listBarData.add( + makeGroupData( + listAllStatisData[i]['id'], + (listAllStatisData[i][_field1] * _rate).toDouble(), + listAllStatisData[i][_field2].toDouble()), + ); + //得到最大值 + _maxY = listAllStatisData[i][_field1] * _rate > _maxY + ? listAllStatisData[i][_field1] * _rate + : _maxY; + _maxY = listAllStatisData[i][_field2] > _maxY ? listAllStatisData[i][_field2] : _maxY; + } + + break; + /* + listAllStatisData = + [ + { + "id": 1, + "dwip": "172.16.3.1", + "dwmc": "锦绣花园", + "zp_all": 54, + "hyc_all": 53, + "zp_today": 1, + "sends": 1, + "send_all": 34, + "csnum": 1, + "fsnum": 1, + "cll_today": 23200, + "cll_all": 3242513 + }, + ... + ] + */ + case 'clltj': + _field1 = 'cll_today'; + _field2 = 'cll_all'; + _rate = 10; // today 字段放大倍率 + _rateCoord = 10000; //坐标压缩别率 + _interval = 20; // 坐标间隔 + _textBottom1 = '今日合计 ${bar_getAllSumNew(_field1)[1] ~/ _rateCoord} 万辆'; + _textBottom2 = '总共 ${bar_getAllSumNew(_field2)[1] ~/ _rateCoord} 万辆'; + _textTop1 = '今日(万辆)'; + _textTop2 = '(万辆)合计'; + + for (int i = 0; i < len; i++) { + //将 today 字段放大 _rate 倍 + listBarData.add( + makeGroupData( + listAllStatisData[i]['id'], + (listAllStatisData[i][_field1] * _rate).toDouble(), + listAllStatisData[i][_field2].toDouble()), + ); + //得到最大值 + _maxY = listAllStatisData[i][_field1] * _rate > _maxY + ? listAllStatisData[i][_field1] * _rate + : _maxY; + _maxY = listAllStatisData[i][_field2] > _maxY ? listAllStatisData[i][_field2] : _maxY; + } + + break; + default: + break; + } + + return listBarData; + } + + Future> getData() async { + String _field1 = ''; + String _field2 = ''; + _maxY = -1; + int len = listZptjStatis.length; + List listBarData = []; + + switch (widget.statisType) { + case 'zptj': + _field1 = 'today'; + _field2 = 'all'; + _rate = 10; // today 字段放大倍率 + _rateCoord = 1; //坐标压缩别率 + _interval = 20; // 坐标间隔 + _textBottom1 = '今日合计 ${bar_getAllSum('today')[1]}'; + _textBottom2 = '总共 ${bar_getAllSumCll('all')[1]}'; // 'all' 字段没有记录详情,所以用bar_getAllSumCll() + _textTop1 = '今日(次)'; + _textTop2 = '(次)合计'; + _rateCoord = 1; + + for (int i = 0; i < len; i++) { + //将 today 字段放大 _rate 倍 + listBarData.add( + makeGroupData( + listZptjStatis[i]['dwbh'], + (listZptjStatis[i][_field1]["total"] * _rate).toDouble(), + listZptjStatis[i][_field2].toDouble()), + ); + //得到最大值 + _maxY = listZptjStatis[i][_field1]["total"] * _rate > _maxY + ? listZptjStatis[i][_field1]["total"] * _rate + : _maxY; + _maxY = listZptjStatis[i][_field2] > _maxY ? listZptjStatis[i][_field2] : _maxY; + } + + break; + case 'sh_hyc_tj': + _field1 = 'total'; + _field2 = 'sends'; + _rate = 1; // today 字段放大倍率 + _rateCoord = 1; //坐标压缩别率 + _interval = 2; // 坐标间隔 + _textBottom1 = + '已审核 ${bar_getAllSumCll('total')[1]}'; // 'total' 字段没有记录详情,所以用bar_getAllSumCll() + _textBottom2 = '已推送 ${bar_getAllSum('sends')[1]}'; + _textTop1 = '审核(次)'; + _textTop2 = '(次)推送'; + + for (int i = 0; i < len; i++) { + //将 today 字段放大 _rate 倍 + listBarData.add( + makeGroupData( + listZptjStatis[i]['dwbh'], + (listZptjStatis[i][_field1] * _rate).toDouble(), + listZptjStatis[i][_field2]["total"].toDouble()), + ); + //得到最大值 + _maxY = listZptjStatis[i][_field1] * _rate > _maxY + ? listZptjStatis[i][_field1] * _rate + : _maxY; + _maxY = listZptjStatis[i][_field2]["total"] > _maxY + ? listZptjStatis[i][_field2]["total"] + : _maxY; + } + + break; + case 'clltj': + _field1 = 'today'; + _field2 = 'all'; + _rate = 10; // today 字段放大倍率 + _rateCoord = 10000; //坐标压缩别率 + _interval = 20; // 坐标间隔 + _textBottom1 = '今日合计 ${bar_getAllSumCll('today')[1] ~/ _rateCoord} 万辆'; + _textBottom2 = '总共 ${bar_getAllSumCll('all')[1] ~/ _rateCoord} 万辆'; + _textTop1 = '今日(万辆)'; + _textTop2 = '(万辆)合计'; + + for (int i = 0; i < len; i++) { + //将 today 字段放大 _rate 倍 + listBarData.add( + makeGroupData( + listZptjStatis[i]['dwbh'], + (listZptjStatis[i][_field1] * _rate).toDouble(), + listZptjStatis[i][_field2].toDouble()), + ); + //得到最大值 + _maxY = listZptjStatis[i][_field1] * _rate > _maxY + ? listZptjStatis[i][_field1] * _rate + : _maxY; + _maxY = listZptjStatis[i][_field2] > _maxY ? listZptjStatis[i][_field2] : _maxY; + } + + break; + default: + break; + } + + return listBarData; + } + + BarChartGroupData makeGroupData(int x, double y1, double y2) { + return BarChartGroupData(barsSpace: 0, x: x, barRods: [ + BarChartRodData( + y: y1, + colors: [leftBarColor], + width: width, + borderRadius: const BorderRadius.all(Radius.zero), + ), + BarChartRodData( + y: y2, + colors: [rightBarColor], + width: width, + borderRadius: const BorderRadius.all(Radius.zero), + ), + ]); + } + + //获取已审核黑烟车统计数据:App.Car_Statis.GetStaHyc + //{ + // "ret": 200, + // "data": { + // "total": 40, + // "sends": { + // "total": 0, + // "data": [] + // }, + // "csnum": { + // "total": 0, + // "data": [] + // }, + // "fsnum": { + // "total": 0, + // "data": [] + // } + // }, + // "msg": "" + // } + + //获取抓拍统计数据:App.Car_Statis.GetStaYjxx + //{ + // "ret": 200, + // "data": { + // "today": { + // "total": 0, + // "data": [] + // }, + // "all": 40 + // }, + // "msg": "" + // } + + //得到 listZptjStatis[field] 的统计数据, + //适用的字段:抓拍统计的'today'、all;审核统计的"total"、"sends"; + List bar_getAllSum(String field) { + int items = 0; + int sum = 0; + for (var item in listZptjStatis) { + if (item[field]["total"] > 0) { + items++; + sum += item[field]["total"]; + } + } + return [items, sum]; + } + + //获取车流量统计数据:App.Car_Statis.GetStaCll + //{ + // "ret": 200, + // "data": { + // "today": 7010, + // "all": 1640350 + // }, + // "msg": "" + //} + + //得到 listZptjStatis[field] 的统计数据, + //适用的字段:车流量统计的'today'、all + List bar_getAllSumCll(String field) { + int items = 0; + int sum = 0; + for (var item in listZptjStatis) { + if (item[field] > 0) { + items++; + sum += item[field]; + } + } + return [items, sum]; + } +} diff --git a/lib/pages/Works/TJXX/zptj_bar_chart_one.dart b/lib/pages/Works/TJXX/zptj_bar_chart_one.dart new file mode 100644 index 0000000..97e8490 --- /dev/null +++ b/lib/pages/Works/TJXX/zptj_bar_chart_one.dart @@ -0,0 +1,508 @@ +import 'package:fl_chart/fl_chart.dart'; +//import 'package:flustars/flustars.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hyzp_ybqx/components/commonFun.dart'; +import 'package:hyzp_ybqx/components/dioFun.dart'; +import 'package:hyzp_ybqx/components/hyxx_data_handle.dart'; +import 'package:hyzp_ybqx/pages/Works/TJXX/tj_data.dart'; + +class ZptjBarChartOne extends StatefulWidget { + ZptjBarChartOne({this.statisType, this.data_ok = false, Key key}) : super(key: key); + String statisType; //统计类型 + bool data_ok; //listZptjStatis 中的数据是否准备好 + + @override + State createState() => ZptjBarChartOneState(); +} + +class ZptjBarChartOneState extends State { + static const Color red = const Color(0xffff5182); + static const Color blue = const Color(0xff0000ff); + static const Color leftBarColor = const Color(0xffff5182); + static const Color rightBarColor = const Color(0xff0000ff); + static const Color bottomBarColor = const Color(0xff939393); + static const Color gridColor = const Color(0xffe7e8ec); + static const double width = 7; + + String _textBottom1; + String _textBottom2; + String _textTop1; + String _textTop2; + int _rate = 10; // today 字段放大倍率 + int _rateCoord = 10000; // 坐标压缩倍率 + int _interval = 20; // 坐标间隔 + int _maxY = -1; + + List _listBarData = []; + + //try_setState(); //避免异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + @override + void initState() { + super.initState(); + + print('ZptjBarChartOne'); + + getDataNew().then((value) { + _listBarData = value; + try_setState(); + }); + + // ///获取点位信息数据 + // if (!widget.data_ok) { + // //listZptjStatis 中的数据未准备好 + // listZptjStatis.clear(); //最后需要显示的数据 + // } + // + // if (listZptjStatis.isEmpty) { + // if (listDwinfoGetList2.isEmpty) { + // //若没有读取了点位数据,便需要先读取 + // getThePageList(theHyshlx: 'dwxx').then((value) { + // listDwinfoGetList2 = value; + // print('listDwinfoGetList2 = \n$listDwinfoGetList2'); + // getZptjStatis(widget.statisType).then((value) { + // //按 sortField 升序排序 + // listZptjStatis.sort((a, b) => (a['dwbh']).compareTo(b['dwbh'])); + // + // getData().then((value) { + // _listBarData = value; + // try_setState(); + // }); + // }); + // }); + // } else { + // //若已经读取了点位数据,便直接使用 + // print('listDwinfoGetList2 = \n$listDwinfoGetList2'); + // getZptjStatis(widget.statisType).then((value) { + // //按 sortField 升序排序 + // listZptjStatis.sort((a, b) => (a['dwbh']).compareTo(b['dwbh'])); + // + // getData().then((value) { + // _listBarData = value; + // try_setState(); + // }); + // }); + // } + // } else { + // getData().then((value) { + // _listBarData = value; + // try_setState(); + // }); + // } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + // appBar: AppBar( + // title: Text(mapStatisType[widget.statisType]['text']), + // centerTitle: true, + // ), + appBar: PreferredSize( + preferredSize: Size.fromHeight(ScreenUtil().setHeight(173)), + // 设置appBar高度 + // 设置appBar高度 + child: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + titleSpacing: 0.0, + flexibleSpace: Container( + //SizedBox(height: ScreenUtil().statusBarHeight), //显示顶部状态栏 + // SizedBox(height: ScreenUtil().setHeight(10)), //显示顶部状态栏 + padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight), + //留出顶部状态栏高度 + child: Container( + //height: ScreenUtil().setHeight(173), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + // decoration: BoxDecoration( + // gradient: LinearGradient(colors: [ + // Color(0xFF0018EB), + // Color(0xFF01C1D9), + // ], begin: Alignment.bottomCenter, end: Alignment.topCenter), + // ), + ), + ), + title: Padding( + padding: EdgeInsets.only(left: 0, right: 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + getIconAndTextButton( + iconColor: Colors.white, + iconData: Icons.chevron_left_outlined, + onPress: () { + Navigator.pop(context); + }, + ), + Expanded( + child: Text(mapStatisType[widget.statisType]['text'], + style: TextStyle(color: Colors.white, fontSize: 20), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis), + ), + SizedBox(width: 50), + ], + ), + ), + ), + ), + + body: Container( + alignment: Alignment(0, -0.85), + child: AspectRatio( + aspectRatio: 0.94, //宽高比 + child: Card( + elevation: 4, //阴影高度 + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)), + color: Colors.white, + child: Container( + padding: const EdgeInsets.only(top: 20), + child: (listAllStatisData.isEmpty || _listBarData.isEmpty) + ? getMoreWidget(color: Colors.black38) + : Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: 3), + Text(_textTop1, style: TextStyle(color: leftBarColor, fontSize: 12)), + Expanded( + child: Text(''), + ), + Text(_textTop2, style: TextStyle(color: rightBarColor, fontSize: 12)), + SizedBox(width: 3), + ], + ), + SizedBox( + height: 10, + ), + BarChart( + BarChartData( + maxY: _maxY.toDouble() * 1.1, + groupsSpace: 6, + alignment: BarChartAlignment.spaceEvenly, + barTouchData: BarTouchData( + enabled: false, + ), + //坐标数据 + titlesData: FlTitlesData( + show: true, + bottomTitles: SideTitles( + rotateAngle: 45, + showTitles: true, + getTextStyles: (value) => + const TextStyle(color: bottomBarColor, fontSize: 10), + margin: 10, + getTitles: (double value) { + int i = value.toInt() - 1; + return '${listAllStatisData[i]['id']}. ${listAllStatisData[i]['dwmc']}'; + }, + ), + leftTitles: SideTitles( + showTitles: true, + getTextStyles: (value) => + const TextStyle(color: leftBarColor, fontSize: 10), + margin: 4, //边距 + getTitles: (double value) { + //value = value / 1000; //坐标压缩别率 + if (value.toInt() % 20000 == 0) { + return '${value / 100000}'; //将左侧坐标放大 _rate 倍 + } else { + return ''; + } + // if (value.toInt() % _interval == 0) { + // return '${(value / _rate).toInt()}'; //将左侧坐标放大 _rate 倍 + // } else { + // return ''; + // } + }, + ), + rightTitles: SideTitles( + showTitles: true, + // getTextStyles: (value) => const TextStyle(color: rightBarColor, fontSize: 10), + getTextStyles: (value) => + const TextStyle(color: leftBarColor, fontSize: 10), + margin: 4, + getTitles: (double value) { + if (value.toInt() % 20000 == 0) { + return '${value / 100000}'; //将左侧坐标放大 _rate 倍 + //return ''; + } else { + return ''; + } + }, + ), + ), + //栅格线 + gridData: FlGridData( + show: true, + checkToShowHorizontalLine: (value) => value % _rate == 0, + getDrawingHorizontalLine: (value) => FlLine( + color: gridColor, + strokeWidth: 1, + ), + ), + //边线 + borderData: FlBorderData( + show: true, + border: const Border( + bottom: BorderSide( + color: gridColor, + width: 2, + ), + left: BorderSide( + color: leftBarColor, + width: 2, + ), + right: BorderSide( + // color: rightBarColor, + color: leftBarColor, + width: 2, + ), + top: BorderSide( + color: Colors.transparent, + ), + ), + ), + //图表数据 + barGroups: _listBarData, + //barGroups: showingBarGroups, + ), + ), + SizedBox(height: 30), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: 3), + Text(_textBottom1, + style: TextStyle(color: leftBarColor, fontSize: 12)), + SizedBox(width: 60), + Text('点位编号', style: TextStyle(color: bottomBarColor, fontSize: 12)), + // Text(_textBottom2, + // style: TextStyle(color: rightBarColor, fontSize: 12)), + //SizedBox(width: 73), + ], + ), + ]), + ), + ), + ), + ), + ); + } + + List bar_getAllSumNew(String field) { + int items = 0; + int sum = 0; + for (var item in listAllStatisData) { + if (item[field] > 0) { + items++; + sum += item[field]; + } + } + return [items, sum]; + } + + Future> getDataNew() async { + String _field1 = ''; + String _field2 = ''; + + switch (widget.statisType) { + case 'zptj': + _field1 = 'zp_today'; + _field2 = 'zp_all'; + _rate = 10; // today 字段放大倍率 + _rateCoord = 1; //坐标压缩别率 + _interval = 20; // 坐标间隔 + _textBottom1 = '今日合计 ${bar_getAllSumNew(_field1)[1]}'; + _textBottom2 = '总共 ${bar_getAllSumNew(_field2)[1]}'; // 'all' 字段没有记录详情,所以用bar_getAllSumCll() + _textTop1 = '今日(次)'; + _textTop2 = '(次)合计'; + _rateCoord = 1; + break; + case 'sh_hyc_tj': + _field1 = 'hyc_all'; + _field2 = 'send_all'; + _rate = 1; // today 字段放大倍率 + _rateCoord = 1; //坐标压缩倍率 + _interval = 2; // 坐标间隔 + _textBottom1 = '已审核 ${bar_getAllSumNew(_field1)[1]}'; + _textBottom2 = '已推送 ${bar_getAllSumNew(_field2)[1]}'; + _textTop1 = '审核(次)'; + _textTop2 = '(次)推送'; + break; + /* + listAllStatisData = + [ + { + "id": 1, + "dwip": "172.16.3.1", + "dwmc": "锦绣花园", + "zp_all": 54, + "hyc_all": 53, + "zp_today": 1, + "sends": 1, + "send_all": 34, + "csnum": 1, + "fsnum": 1, + "cll_today": 23200, + "cll_all": 3242513 + }, + ... + ] + */ + case 'clltj': + _field1 = 'cll_today'; + _field2 = 'cll_all'; + _rate = 10; // today 字段放大倍率 + _rateCoord = 10000; //坐标压缩别率 + _interval = 5; // 坐标间隔 + _textBottom1 = '今日合计 ${bar_getAllSumNew(_field1)[1] ~/ _rateCoord} 万辆'; + //_textBottom2 = '总共 ${bar_getAllSumNew(_field2)[1] ~/ _rateCoord} 万辆'; + _textBottom2 = ''; + _textTop1 = '今日(万辆)'; + //_textTop2 = '(万辆)合计'; + _textTop2 = ''; + break; + default: + break; + } + + _maxY = -1; + int len = listAllStatisData.length; + List listBarData = []; + for (int i = 0; i < len; i++) { + //将 today 字段放大 _rate 倍 + listBarData.add( + makeGroupData( + listAllStatisData[i]['id'], + (listAllStatisData[i][_field1] * _rate).toDouble(), + listAllStatisData[i][_field2].toDouble()), + ); + //得到最大值,只显示今日车流量 + _maxY = listAllStatisData[i][_field1] * _rate > _maxY + ? listAllStatisData[i][_field1] * _rate + : _maxY; + // _maxY = listZptjStatis[i][_field2] > _maxY ? listZptjStatis[i][_field2] : _maxY; + } + + return listBarData; + } + + Future> getData() async { + String _field1 = ''; + String _field2 = ''; + switch (widget.statisType) { + case 'zptj': + _field1 = 'today'; + _field2 = 'all'; + _rate = 10; // today 字段放大倍率 + _rateCoord = 1; //坐标压缩别率 + _interval = 20; // 坐标间隔 + _textBottom1 = '今日合计 ${getAllSum('today')[1]}'; + _textBottom2 = '总共 ${getAllSum('all')[1]}'; + _textTop1 = '今日(次)'; + _textTop2 = '(次)合计'; + _rateCoord = 1; + break; + case 'sh_hyc_tj': + _field1 = 'total'; + _field2 = 'sends'; + _rate = 1; // today 字段放大倍率 + _rateCoord = 1; //坐标压缩别率 + _interval = 2; // 坐标间隔 + _textBottom1 = '今日审核 ${getAllSum('total')[1]}'; + _textBottom2 = '今日推送 ${getAllSum('sends')[1]}'; + _textTop1 = '审核(次)'; + _textTop2 = '(次)推送'; + break; + case 'clltj': + _field1 = 'today'; + _field2 = 'all'; + _rate = 10; // today 字段放大倍率 + _rateCoord = 10000; //坐标压缩别率 + _interval = 5; // 坐标间隔 + _textBottom1 = '今日合计 ${getAllSum('today')[1] ~/ (_rateCoord)} 万辆'; + //_textBottom2 = '总共 ${getAllSum('all')[1] ~/ _rateCoord} 千辆'; + _textBottom2 = ''; + _textTop1 = '今日(万辆)'; + //_textTop2 = '(千辆)合计'; + _textTop2 = ''; + break; + default: + break; + } + + _maxY = -1; + int len = listZptjStatis.length; + List listBarData = []; + for (int i = 0; i < len; i++) { + //将 today 字段放大 _rate 倍 + listBarData.add( + makeGroupData(listZptjStatis[i]['dwbh'], (listZptjStatis[i][_field1] * _rate).toDouble(), + listZptjStatis[i][_field2].toDouble()), + ); + //得到最大值,只显示今日车流量 + _maxY = + listZptjStatis[i][_field1] * _rate > _maxY ? listZptjStatis[i][_field1] * _rate : _maxY; + // _maxY = listZptjStatis[i][_field2] > _maxY ? listZptjStatis[i][_field2] : _maxY; + } + + return listBarData; + } + + BarChartGroupData makeGroupData(int x, double y1, double y2) { + return BarChartGroupData(barsSpace: 0, x: x, barRods: [ + BarChartRodData( + y: y1, + colors: [leftBarColor], + width: width, + borderRadius: const BorderRadius.all(Radius.zero), + ), + // BarChartRodData( + // y: y2, + // colors: [rightBarColor], + // width: width, + // borderRadius: const BorderRadius.all(Radius.zero), + // ), + ]); + } + + //得到 listZptjStatis[field] 的统计数据, + //适用的字段:抓拍统计和车流量统计的'today'、all;审核统计的"total"、"sends"; + List getAllSum(String field) { + int items = 0; + int sum = 0; + for (var item in listZptjStatis) { + if (item[field] > 0) { + items++; + sum += item[field]; + } + } + return [items, sum]; + } +} diff --git a/lib/pages/Works/TJXX/zptj_page.dart b/lib/pages/Works/TJXX/zptj_page.dart new file mode 100644 index 0000000..c7a3639 --- /dev/null +++ b/lib/pages/Works/TJXX/zptj_page.dart @@ -0,0 +1,835 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hyzp_ybqx/components/save_data_to_file.dart'; +import 'package:hyzp_ybqx/pages/Works/TJXX/zptj_bar_chart.dart'; +import 'package:hyzp_ybqx/pages/Works/TJXX/zptj_bar_chart_one.dart'; + +import '../../../components/commonFun.dart'; +import '../../../components/hyxx_data_handle.dart'; +import 'tj_data.dart'; + +//zptj 是本项目中“抓拍统计”的统一缩写 +//sh_hyc_tj 是本项目中“审核黑烟车统计”的统一缩写 +//clltj 是本项目中“车流量统计”的统一缩写 +class ZptjPage extends StatefulWidget { + //mapStatisType 为统计数据类型数据结构。用于在同一套代码中,处理相似类型的多种统计数据 + //statisType 为统计类型。mapStatisType[statisType] 为各种相似类型的设置数据 + ZptjPage({@required this.statisType, Key key}) : super(key: key); + String statisType; + + _ZptjPageState createState() => _ZptjPageState(); +} + +class _ZptjPageState extends State { + //try_setState(); //避免异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + @override + void dispose() { + super.dispose(); + } + + @override + void initState() { + // ///获取点位信息数据 + // listZptjStatis.clear(); //最后需要显示的数据 + // + // if (listDwinfoGetList2.isEmpty) { + // //若没有读取了点位数据,便需要先读取 + // getThePageList(theHyshlx: 'dwxx').then((value) { + // listDwinfoGetList2 = value; + // print('listDwinfoGetList2 = \n$listDwinfoGetList2'); + // getZptjStatis(widget.statisType).then((value) { + // _listSort(sortField: 'dwbh'); + // }); + // }); + // } else { + // //若已经读取了点位数据,便直接使用 + // print('listDwinfoGetList2 = \n$listDwinfoGetList2'); + // getZptjStatis(widget.statisType).then((value) { + // _listSort(sortField: 'dwbh'); + // }); + // } + + super.initState(); + } + + ///获取点位信息数据 + // List listDwinfoGetList2 = [ + // { + // "id": 1, + // "dwip": "172.16.3.1", + // "dwmc": "江北振兴大道", + // "dwbh": 1, + // "dwinfo": "江北振兴大道入城方向", + // "dwzb": "104.607091|28.807061", + // "dwms": "江北振兴大道入城方向,识别孜岩、红坝路入城排放黑烟车辆" + // }, + // ]; + + //listZptjStatis = [ + // {today: 832, all: 4479, dwip: 172.16.3.1}, + // {today: 0, all: 185, dwip: 172.16.3.2}, + // {today: 0, all: 131, dwip: 172.16.3.3}, + // {today: 0, all: 72, dwip: 172.16.3.4}, + // {today: 0, all: 30, dwip: 172.16.3.5}, + // {today: 0, all: 26, dwip: 172.16.3.6}, + // {today: 0, all: 0, dwip: 172.16.3.7}, + // {today: 0, all: 135, dwip: 172.16.3.8}, + // {today: 0, all: 65, dwip: 172.16.3.9}, + // {today: 0, all: 36, dwip: 172.16.3.10}, + // {today: 0, all: 44, dwip: 172.16.3.11}, + // {today: 0, all: 76, dwip: 172.16.3.12}, + // {today: 0, all: 83, dwip: 172.16.3.13} + //] + + Widget _getListTile(BuildContext context, int indexRecord, {double width = 520}) { + switch (widget.statisType) { + case 'zptj': + return _getZptjListTile(context, indexRecord, 520); + break; + case 'clltj': + return _getClltjListTile(context, indexRecord, 520); + break; + case 'sh_hyc_tj': + return _getSh_hyc_tjListTile(context, indexRecord, 510); + break; + default: + return Container(); + break; + } + } + + //抓拍统计数据 + //{ + // "today": 0, + // "all": 72 + // "dwbh": 1, + // "dwmc": "江北振兴大道", + // "dwip": "172.16.3.1" + //} + Widget _getSh_hyc_tjListTile(BuildContext context, int indexRecord, double width) { + return Column( + children: [ + ListTile( + //leading: new Icon(Icons.phone), + title: getDwmcField(indexRecord, 40), + // subtitle: + // Text('今日:${listZptjStatis[indexRecord]['today']}', style: TextStyle(fontSize: 10)), + trailing: Container( + width: ScreenUtil().setWidth(width), + child: Row( + children: [ + getText( + text: listAllStatisData[indexRecord]['hyc_all'].toString(), + width: 260, + fontSize: 16), + getText( + text: listAllStatisData[indexRecord]['send_all'].toString(), + width: 240, + fontSize: 16), + ], + ), + ), + contentPadding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 0), + enabled: true, + onTap: () async {}, + ), + Divider(height: 1.0), + ], + ); + } + + Widget getText({String text = '', double width = 100, double fontSize = 16}) { + return SizedBox( + width: ScreenUtil().setWidth(width), + child: Text( + text, + maxLines: 1, + overflow: TextOverflow.ellipsis, + //textAlign: TextAlign.right, + style: TextStyle(fontSize: fontSize), + ), + ); + } + + ///获取点位信息数据 + // List listDwinfoGetList2 = [ + // { + // "id": 1, + // "dwip": "172.16.3.1", + // "dwmc": "江北振兴大道", + // "dwbh": 1, + // "dwinfo": "江北振兴大道入城方向", + // "dwzb": "104.607091|28.807061", + // "dwms": "江北振兴大道入城方向,识别孜岩、红坝路入城排放黑烟车辆" + // }, + // ]; + + Widget getDwmcField(int indexRecord, double width) { + return SizedBox( + width: ScreenUtil().setWidth(width), + child: Text( + '${listAllStatisData[indexRecord]["id"].toString()}. ${listAllStatisData[indexRecord]["dwmc"]}', + style: TextStyle(fontSize: 16), + ), + ); + } + + /* + listAllStatisData = + [ + { + "id": 1, + "dwip": "172.16.3.1", + "dwmc": "锦绣花园", + "zp_all": 54, + "hyc_all": 53, + "zp_today": 1, + "sends": 1, + "send_all": 34, + "csnum": 1, + "fsnum": 1, + "cll_today": 23200, + "cll_all": 3242513 + }, + ... + ] + */ + Widget _getZptjListTile(BuildContext context, int indexRecord, double width) { + return Column( + children: [ + ListTile( + //leading: new Icon(Icons.phone), + title: getDwmcField(indexRecord, 40), + // subtitle: + // Text('今日:${listAllStatisData[indexRecord]['today']}', style: TextStyle(fontSize: 10)), + trailing: Container( + width: ScreenUtil().setWidth(width), + child: Row( + children: [ + getText( + text: listAllStatisData[indexRecord]['zp_today'].toString(), + width: 260, + fontSize: 16), + getText( + text: listAllStatisData[indexRecord]['zp_all'].toString(), + width: 260, + fontSize: 16), + ], + ), + ), + contentPadding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 0), + enabled: true, + onTap: () async {}, + ), + Divider(height: 1.0), + ], + ); + } + + Widget _getClltjListTile(BuildContext context, int indexRecord, double width) { + return Column( + children: [ + ListTile( + //leading: new Icon(Icons.phone), + title: getDwmcField(indexRecord, 40), + // subtitle: + // Text('今日:${listAllStatisData[indexRecord]['today']}', style: TextStyle(fontSize: 10)), + trailing: Container( + width: ScreenUtil().setWidth(width), + child: Row( + children: [ + // getText( + // text: listAllStatisData[indexRecord]['today'].toString(), + // width: 260, + // fontSize: 16), + // getText( + // text: (listAllStatisData[indexRecord]['all'] / 10000).toString(), + // width: 260, + // fontSize: 16), + getText(text: '', width: 260, fontSize: 16), + getText( + text: listAllStatisData[indexRecord]['cll_today'].toString(), + width: 260, + fontSize: 16), + ], + ), + ), + contentPadding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 0), + enabled: true, + onTap: () async {}, + ), + Divider(height: 1.0), + ], + ); + } + + bool isLoading = false; //正在处理下载数据、跳转到首项、跳转到尾项等操作 + + //Map mapTjDataText = { + // "dwip": '点位IP', + // "today": '今日', + // "all": '总共', + // "total": '总共2', + // "sends": '推送' + // }; + + //抓拍统计数据 + //{ + // "today": 5, + // "all": 77 + //}, + + //审核统计数据 + //{ + // "total": 6, + // "sends": 5 + // }, + + //车流量统计数据 + //{ + // "today": 0, + // "all": 675338 + //} + + //得到表头行 + Widget getHeadRow() { + Widget _headRow; + switch (widget.statisType) { + case 'zptj': + _headRow = Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox( + width: ScreenUtil().setWidth(475), + child: _getSortBtn(sortField: 'dwmc', left: 78), + ), + SizedBox( + width: ScreenUtil().setWidth(285), + child: _getSortBtn(sortField: 'today', left: 10), + ), + SizedBox( + width: ScreenUtil().setWidth(280), + child: _getSortBtn(sortField: 'all', left: 10), + ), + ], + ); + break; + case 'sh_hyc_tj': + _headRow = Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox( + width: ScreenUtil().setWidth(495), + child: _getSortBtn(sortField: 'dwmc', left: 78), + ), + SizedBox( + width: ScreenUtil().setWidth(265), + child: _getSortBtn(sortField: 'total', left: 10), + ), + SizedBox( + width: ScreenUtil().setWidth(300), + child: _getSortBtn(sortField: 'sends', left: 20), + ), + ], + ); + break; + case 'clltj': + _headRow = Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox( + width: ScreenUtil().setWidth(465), + child: _getSortBtn(sortField: 'dwmc', left: 78), + ), + // SizedBox( + // width: ScreenUtil().setWidth(265), + // child: _getSortBtn(sortField: 'today', left: 20), + // ), + // SizedBox( + // width: ScreenUtil().setWidth(300), + // child: _getSortBtn(sortField: 'all', left: 50), + // ), + SizedBox( + width: ScreenUtil().setWidth(265), + child: Text(''), + ), + SizedBox( + width: ScreenUtil().setWidth(300), + child: _getSortBtn(sortField: 'today', left: 20), + ), + ], + ); + break; + default: + return Container(); + break; + } + + return _headRow; + } + + /* + listAllStatisData = + [ + { + "id": 1, + "dwip": "172.16.3.1", + "dwmc": "锦绣花园", + "zp_all": 54, + "hyc_all": 53, + "zp_today": 1, + "sends": 1, + "send_all": 34, + "csnum": 1, + "fsnum": 1, + "cll_today": 23200, + "cll_all": 3242513 + }, + ... + ] + */ + //得到统计行 + Widget getStatisRow() { + Widget _headRow; + switch (widget.statisType) { + case 'zptj': + List _listSum = tj_getAllSum('zp_today'); + List _listAll = tj_getAllSum('zp_all'); + _headRow = Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox( + width: ScreenUtil().setWidth(455), + //xx个点位正常\n(共xx个点位) + child: _getStatisText( + //text: '${getOKdw().toString()}个点位正常\n(共${listDwinfoGetList2.length.toString()}个点位)', + text: '共 ${listDwinfoGetList2.length.toString()} 个点位', + left: ScreenUtil().setWidth(170)), + ), + SizedBox( + width: ScreenUtil().setWidth(270), + //xx个点位,今日共抓拍xx + child: _getStatisText( + //text: '${_listSum[0].toString()}个点位,今日共抓拍${_listSum[1].toString()}', + text: '今日 ${_listSum[1].toString()} 次', + width: ScreenUtil().setWidth(245)), + ), + SizedBox( + width: ScreenUtil().setWidth(300), + //xx个点位,总共抓拍xx + child: _getStatisText( + //text: '${_listAll[0].toString()}个点位,总共抓拍${_listAll[1].toString()}', + text: '总共 ${_listAll[1].toString()} 次', + width: ScreenUtil().setWidth(260)), + ), + ], + ); + break; + case 'sh_hyc_tj': + List _listSum = tj_getAllSum('send_all'); + //List _listAll = tj_getAllSumCll('total'); // 'total' 字段没有记录详情,所以用tj_getAllSumCll() + List _listAll = tj_getAllSum('hyc_all'); + _headRow = Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox( + width: ScreenUtil().setWidth(475), + //xx正常(共13) + child: _getStatisText( + //text: '${getOKdw().toString()}个点位正常\n(共${listDwinfoGetList2.length.toString()}个点位)', + text: '共 ${listDwinfoGetList2.length.toString()} 个点位', + left: ScreenUtil().setWidth(170)), + ), + SizedBox( + width: ScreenUtil().setWidth(260), + //xx个点位,总共审核xx + child: _getStatisText( + //text: '${_listAll[0].toString()}个点位,总共审核${_listAll[1].toString()}', + text: '共审核 ${_listAll[1].toString()} 次', + width: ScreenUtil().setWidth(225)), + ), + SizedBox( + //xx个点位,总共推送xx + child: _getStatisText( + //text: '${_listSum[0].toString()}个点位,总共推送${_listSum[1].toString()}', + text: '共推送 ${_listSum[1].toString()} 次', + width: ScreenUtil().setWidth(260)), + ), + ], + ); + break; + case 'clltj': + List _listSum = tj_getAllSum('cll_today'); + List _listAll = tj_getAllSum('cll_all'); + _headRow = Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox( + width: ScreenUtil().setWidth(445), + //xx个点位正常\n(共xx个点位) + child: _getStatisText( + //text: '${getOKdw().toString()}个点位正常\n(共${listDwinfoGetList2.length.toString()}个点位)', + text: '共 ${listDwinfoGetList2.length.toString()} 个点位', + left: ScreenUtil().setWidth(150)), + ), + // SizedBox( + // width: ScreenUtil().setWidth(280), + // //xx个点位,今日共抓拍xx + // child: _getStatisText( + // //text: '${_listSum[0].toString()}个点位,今日车流量${_listSum[1].toString()}', + // text: '今日 ${_listSum[1].toString()}', + // width: ScreenUtil().setWidth(265)), + // ), + // SizedBox( + // width: ScreenUtil().setWidth(280), + // //xx个点位,总共抓拍xx + // child: _getStatisText( + // //text: '${_listAll[0].toString()}个点位,总共车流量${_listAll[1].toString()}', + // text: '总共 ${(_listAll[1] ~/ 10000).toString()} 万', + // width: ScreenUtil().setWidth(280)), + // ), + SizedBox( + width: ScreenUtil().setWidth(280), + //xx个点位,今日共抓拍xx + child: _getStatisText( + //text: '${_listSum[0].toString()}个点位,今日车流量${_listSum[1].toString()}', + text: '', + width: ScreenUtil().setWidth(265)), + ), + SizedBox( + width: ScreenUtil().setWidth(280), + //xx个点位,今日共抓拍xx + child: _getStatisText( + //text: '${_listSum[0].toString()}个点位,今日车流量${_listSum[1].toString()}', + text: '今日 ${_listSum[1].toString()}', + width: ScreenUtil().setWidth(205)), + ), + ], + ); + break; + default: + return Container(); + break; + } + + return _headRow; + } + + //获取已审核黑烟车统计数据:App.Car_Statis.GetStaHyc + //{ + // "ret": 200, + // "data": { + // "total": 40, + // "sends": { + // "total": 0, + // "data": [] + // }, + // "csnum": { + // "total": 0, + // "data": [] + // }, + // "fsnum": { + // "total": 0, + // "data": [] + // } + // }, + // "msg": "" + // } + + //获取抓拍统计数据:App.Car_Statis.GetStaYjxx + //{ + // "ret": 200, + // "data": { + // "today": { + // "total": 0, + // "data": [] + // }, + // "all": 40 + // }, + // "msg": "" + // } + + //得到 listAllStatisData[field] 的统计数据, + //适用的字段:抓拍统计的'today'、all;审核统计的"total"、"sends"; + List tj_getAllSum(String field) { + int items = 0; + int sum = 0; + for (var item in listAllStatisData) { + if (item[field] > 0) { + items++; + sum += item[field]; + } + } + return [items, sum]; + } + + //获取车流量统计数据:App.Car_Statis.GetStaCll + //{ + // "ret": 200, + // "data": { + // "today": 7010, + // "all": 1640350 + // }, + // "msg": "" + //} + + //得到 listZptjStatis[field] 的统计数据, + //适用的字段:车流量统计的'today'、all + List tj_getAllSumCll(String field) { + int items = 0; + int sum = 0; + for (var item in listZptjStatis) { + if (item[field] > 0) { + items++; + sum += item[field]; + } + } + return [items, sum]; + } + + Widget _getStatisText({@required String text, double left = 0, double width = 100}) { + return Row( + children: [ + SizedBox(width: ScreenUtil().setWidth(left)), + Container( + width: width, + child: + Text(text, style: TextStyle(fontSize: 12), maxLines: 2, textAlign: TextAlign.center), + ), + //SizedBox(width: ScreenUtil().setWidth(20)), + ], + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: PreferredSize( + preferredSize: Size.fromHeight(ScreenUtil().setHeight(173)), // 设置appBar高度 + // 设置appBar高度 + child: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + titleSpacing: 0.0, + flexibleSpace: Container( + //SizedBox(height: ScreenUtil().statusBarHeight), //显示顶部状态栏 + // SizedBox(height: ScreenUtil().setHeight(10)), //显示顶部状态栏 + padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight), //留出顶部状态栏高度 + child: Container( + //height: ScreenUtil().setHeight(173), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + // decoration: BoxDecoration( + // gradient: LinearGradient(colors: [ + // Color(0xFF0018EB), + // Color(0xFF01C1D9), + // ], begin: Alignment.bottomCenter, end: Alignment.topCenter), + // ), + ), + ), + title: Padding( + padding: EdgeInsets.only(left: 0, right: 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + getIconAndTextButton( + iconColor: Colors.white, + iconData: Icons.chevron_left_outlined, + onPress: () { + Navigator.pop(context); + }, + ), + Expanded( + child: Text(mapStatisType[widget.statisType]['text'], + style: TextStyle(color: Colors.white, fontSize: 20), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis), + ), + SizedBox(width: 10), + ], + ), + ), + actions: [ + InkWell( + child: Image.asset( + widget.statisType == 'zptj' + ? 'assets/images/statis_blue.png' + : widget.statisType == 'sh_hyc_tj' + ? 'assets/images/statis_red.png' + : 'assets/images/statis_green.png', + width: 32, + height: 32, + color: Colors.white), + onTap: () { + Navigator.of(context).push( + MaterialPageRoute(builder: (context) { + if (widget.statisType == 'clltj') { + return ZptjBarChartOne( + data_ok: true, //listAllStatisData 中的数据已经准备好 + statisType: widget.statisType, + ); + } else { + return ZptjBarChart( + data_ok: true, //listAllStatisData 中的数据已经准备好 + statisType: widget.statisType, + ); + } + }), + ); + }, + ), + SizedBox(width: 15), + ], + ), + ), + body: (listAllStatisData.isEmpty) + ? getMoreWidget(color: Colors.black38) + : Column( + children: [ + SizedBox(height: ScreenUtil().setHeight(20)), + getHeadRow(), //得到表头行 + SizedBox(height: ScreenUtil().setHeight(20)), + Divider(height: 1.0, color: Colors.blue), + SizedBox(height: ScreenUtil().setHeight(10)), + getStatisRow(), //得到统计行 + SizedBox(height: ScreenUtil().setHeight(10)), + Divider(height: 1.0, color: Colors.blue), + Expanded( + child: ListView.builder( + itemCount: listAllStatisData.length, + itemBuilder: (BuildContext context, index) { + return Column( + children: [ + _getListTile(context, index), + Divider(height: 1.0), + ], + ); + }, + ), + ), + ], + ), + ); + } + + Widget _getImage(String sortField) { + sortField = (sortField == 'dwmc' ? 'id' : sortField); + String _image = _sortField != sortField + ? 'assets/images/sort.png' + : _descending + ? 'assets/images/descending.png' + : 'assets/images/ascending.png'; + + return Container( + margin: EdgeInsets.only(top: 4), + height: ScreenUtil().setWidth(38), + width: ScreenUtil().setWidth(38), + //child: Image.asset('assets/images/ybsthbj.png', fit: BoxFit.fitHeight), + child: Image.asset(_image, + fit: BoxFit.cover, + color: isLoading + ? Theme.of(context).disabledColor + : (_sortField == sortField ? Colors.blue : null))); + } + + String _sortField = 'dwmc'; + bool _descending = false; //默认升序排列 + + Widget _getSortBtn({@required String sortField, double left = 0}) { + return InkWell( + // color: Colors.white, + // padding: EdgeInsets.all(0), + //iconSize: this.iconSize, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: ScreenUtil().setWidth(left)), + Text(mapTjDataText[sortField], + style: TextStyle( + fontSize: 18, + color: Colors.blue, + fontWeight: _sortField == sortField || (_sortField == 'id' && sortField == 'dwmc') + ? FontWeight.bold + : null)), + SizedBox(width: ScreenUtil().setWidth(20)), + _getImage(sortField), + ], + ), + onTap: () { + if (isLoading) { + return; + } + print('sortField = $sortField,_descending = $_descending'); + //按照用户选择的 sortField、_descending对listSbbjGetList2进行排序,并延时更新 + _listSort(sortField: sortField == 'dwmc' ? 'id' : sortField); + }, + ); + } + + // Map mapTjDataText = { + // "dwip": '点位IP', + // "today": '今日', + // "all": '总共', + // "total": '总共2', + // "sends": '推送' + // }; + + //按照用户选择的 sortField、_descending对listZptjStatis进行排序,并延时更新 + Future _listSort({@required String sortField, bool bShowToast = false}) { + if (!isLoading && listAllStatisData.length > 0) { + isLoading = true; + try_setState(); + + if (_sortField == sortField) { + _descending = !_descending; //若是与上一次按同一个自动排序,则切换升序降序 + } else { + _descending = false; //默认升序排列 + } + + switch (sortField) { + default: + if (_descending) { + //按 sortField 降序排序 + listAllStatisData.sort((a, b) => (b[sortField]).compareTo(a[sortField])); + } else { + //按 sortField 升序排序 + listAllStatisData.sort((a, b) => (a[sortField]).compareTo(b[sortField])); + } + break; + } + + Future.delayed(const Duration(milliseconds: 1000), () { + if (bShowToast) { + Fluttertoast.showToast( + msg: + '按“${mapTjDataText[sortField == 'id' ? 'dwmc' : sortField]}”${_descending ? '降序' : '升序'}排列完成!', + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.CENTER, + ); + } + _sortField = sortField; + isLoading = false; + try_setState(); //避免如下异常报错 + }); + } + } +} diff --git a/lib/pages/tabs/Home.dart b/lib/pages/tabs/Home.dart new file mode 100644 index 0000000..49e8884 --- /dev/null +++ b/lib/pages/tabs/Home.dart @@ -0,0 +1,280 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import '../../model/ProductModel.dart'; +import 'package:flutter_swiper/flutter_swiper.dart'; + +import '../../config/Config.dart'; +import 'package:dio/dio.dart'; +// import '../../services/SignServices.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + + + +//轮播图类模型 +import '../../model/FocusModel.dart'; + +class HomePage extends StatefulWidget { + HomePage({Key key}) : super(key: key); + + _HomePageState createState() => _HomePageState(); +} + +class _HomePageState extends State + with AutomaticKeepAliveClientMixin { + List _focusData = []; + List _hotProductList = []; + List _bestProductList = []; + + @override + // TODO: implement wantKeepAlive + bool get wantKeepAlive => true; + + @override + void initState() { + super.initState(); + _getFocusData(); + _getHotProductData(); + _getBestProductData(); + // SignServices.getSign(); + } + + //获取轮播图数据 + _getFocusData() async { + var api = '${Config.domain}api/focus'; + var result = await Dio().get(api); + var focusList = FocusModel.fromJson(result.data); + setState(() { + this._focusData = focusList.result; + }); + } + + //获取猜你喜欢的数据 + _getHotProductData() async { + var api = '${Config.domain}api/plist?is_hot=1'; + var result = await Dio().get(api); + var hotProductList = ProductModel.fromJson(result.data); + setState(() { + this._hotProductList = hotProductList.result; + }); + } + + //获取热门推荐的数据 + _getBestProductData() async { + var api = '${Config.domain}api/plist?is_best=1'; + var result = await Dio().get(api); + var bestProductList = ProductModel.fromJson(result.data); + setState(() { + this._bestProductList = bestProductList.result; + }); + } + + //轮播图 + Widget _swiperWidget() { + if (this._focusData.length > 0) { + return Container( + child: AspectRatio( + aspectRatio: 2 / 1, + child: Swiper( + itemBuilder: (BuildContext context, int index) { + String pic = this._focusData[index].pic; + pic = Config.domain + pic.replaceAll('\\', '/'); + return new Image.network( + "${pic}", + fit: BoxFit.fill, + ); + }, + itemCount: this._focusData.length, + pagination: new SwiperPagination(), + autoplay: true), + ), + ); + } else { + return Text('加载中...'); + } + } + + Widget _titleWidget(value) { + return Container( + height: ScreenUtil().setHeight(60), + margin: EdgeInsets.only(left: ScreenUtil().setWidth(20)), + padding: EdgeInsets.only(left: ScreenUtil().setWidth(20)), + decoration: BoxDecoration( + border: Border( + left: BorderSide( + color: Colors.red, + width: ScreenUtil().setWidth(10), + ))), + child: Text( + value, + style: TextStyle(color: Colors.black54), + ), + ); + } + //热门商品 + + Widget _hotProductListWidget() { + if (this._hotProductList.length > 0) { + return Container( + height: ScreenUtil().setHeight(234), + padding: EdgeInsets.all(ScreenUtil().setWidth(20)), + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemBuilder: (contxt, index) { + //处理图片 + String sPic = this._hotProductList[index].sPic; + sPic = Config.domain + sPic.replaceAll('\\', '/'); + + return Column( + children: [ + Container( + height: ScreenUtil().setHeight(140), + width: ScreenUtil().setWidth(140), + margin: EdgeInsets.only(right: ScreenUtil().setWidth(21)), + child: Image.network(sPic, fit: BoxFit.cover), + ), + Container( + padding: EdgeInsets.only(top: ScreenUtil().setHeight(10)), + height: ScreenUtil().setHeight(50), + child: Text( + "¥${this._hotProductList[index].price}", + style: TextStyle(color: Colors.red), + ), + ) + ], + ); + }, + itemCount: this._hotProductList.length, + ), + ); + } else { + return Text(""); + } + } + + //推荐商品 + Widget _recProductListWidget() { + var itemWidth = (ScreenUtil().screenWidth - 30) / 2; + return Container( + padding: EdgeInsets.all(10), + child: Wrap( + runSpacing: 10, + spacing: 10, + children: this._bestProductList.map((value) { + //图片 + String sPic = value.sPic; + sPic = Config.domain + sPic.replaceAll('\\', '/'); + + return InkWell( + onTap: () { + Navigator.pushNamed(context, '/productContent', + arguments: {"id": value.sId}); + }, + child: Container( + padding: EdgeInsets.all(10), + width: itemWidth, + decoration: BoxDecoration( + border: Border.all( + color: Color.fromRGBO(233, 233, 233, 0.9), width: 1)), + child: Column( + children: [ + Container( + width: double.infinity, + child: AspectRatio( + //防止服务器返回的图片大小不一致导致高度不一致问题 + aspectRatio: 1 / 1, + child: Image.network( + "${sPic}", + fit: BoxFit.cover, + ), + ), + ), + Padding( + padding: EdgeInsets.only(top: ScreenUtil().setHeight(20)), + child: Text( + "${value.num}", + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: TextStyle(color: Colors.black54), + ), + ), + Padding( + padding: EdgeInsets.only(top: ScreenUtil().setHeight(20)), + child: Stack( + children: [ + Align( + alignment: Alignment.centerLeft, + child: Text( + "¥${value.price}", + style: TextStyle(color: Colors.red, fontSize: 16), + ), + ), + Align( + alignment: Alignment.centerRight, + child: Text("¥${value.oldPrice}", + style: TextStyle( + color: Colors.black54, + fontSize: 14, + decoration: TextDecoration.lineThrough)), + ) + ], + ), + ) + ], + ), + ), + ); + }).toList(), + ), + ); + } + + @override + Widget build(BuildContext context) { + + + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon(Icons.center_focus_weak, size: 28, color: Colors.black87), + onPressed: null, + ), + title: InkWell( + child: Container( + height: ScreenUtil().setHeight(68), + decoration: BoxDecoration( + color: Color.fromRGBO(233, 233, 233, 0.8), + borderRadius: BorderRadius.circular(30)), + padding: EdgeInsets.only(left: 10), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon(Icons.search), + Text("笔记本", style: TextStyle(fontSize: ScreenUtil().setSp(28))) + ], + ), + ), + onTap: () { + Navigator.pushNamed(context, '/search'); + }, + ), + actions: [ + IconButton( + icon: Icon(Icons.message, size: 28, color: Colors.black87), + onPressed: null, + ) + ], + ), + body: ListView( + children: [ + _swiperWidget(), + SizedBox(height: ScreenUtil().setHeight(20)), + _titleWidget("猜你喜欢"), + SizedBox(height: ScreenUtil().setHeight(20)), + _hotProductListWidget(), + _titleWidget("热门推荐"), + _recProductListWidget() + ], + ), + ); + } +} diff --git a/lib/pages/tabs/Tabs.dart b/lib/pages/tabs/Tabs.dart new file mode 100644 index 0000000..c2b6b00 --- /dev/null +++ b/lib/pages/tabs/Tabs.dart @@ -0,0 +1,186 @@ +import 'package:badges/badges.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:hyzp_ybqx/pages/tabs/page4_myMsics_new.dart'; + +//import '../../widget/player_pro.dart'; +import '../../components/commonFun.dart'; +//import 'package:fijkplayer/fijkplayer.dart'; +import '../../services/ServiceLocator.dart'; +import '../../services/Storage.dart'; +import 'page1_work.dart'; + +class Tabs extends StatefulWidget { + Tabs({Key key, this.arguments = 0}) : super(key: key); + int arguments; + + _TabsState createState() => _TabsState(); +} + +class _TabsState extends State { + //try_setState(); //避免如下异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + int _currentIndex = 0; + String sAppBar0 = 'Flutter Demo'; + String sAppBar = 'Flutter Demo'; + PageController _pageController; + + @override + void initState() { + getlistItems().then((value) => try_setState()); + print('widget.arguments = ${widget.arguments}'); + //_currentIndex = 3 == widget.arguments ? 0 : widget.arguments; //解决"我的"页面根据用户所属组及时刷新问题 + _currentIndex = widget.arguments; + this._pageController = PageController(initialPage: _currentIndex); + // 注册服务 + setupLocator(); + + // 解决登录按钮再次变换文字的问题 + Future.delayed(const Duration(milliseconds: 1000), () { + //重新初始化处理延时登录的变量 + //bMayLogin = false; //这句必须注释掉,否则“退出登录”后,无法再次进行用户名登录 + bPreLoading = false; + bLoginVerify = false; //处理延时登录,判断用户名登录是否验证通过 + }); + + super.initState(); + } + + double _activeIconWidth = 68; + + Future getlistItems() async { + listItems.addAll([ + BottomNavigationBarItem( + icon: getImageItem('assets/images/矢量智能对象.png'), + label: "首页", + activeIcon: getImageItem('assets/images/矢量智能对象.png', + width: _activeIconWidth, color: Colors.blue)), + BottomNavigationBarItem( + icon: getImageItem('assets/images/矢量智能对象(1).png'), + label: "统计", + activeIcon: getImageItem('assets/images/矢量智能对象(1).png', + width: _activeIconWidth, color: Colors.blue)), + BottomNavigationBarItem( + icon: getImageItem('assets/images/矩形 1 拷贝 39.png'), + label: "设备", + activeIcon: getImageItem('assets/images/矩形 1 拷贝 39.png', + width: _activeIconWidth, color: Colors.blue)), + //bNewVer:是否发现新版本 + BottomNavigationBarItem( + icon: getImageItem('assets/images/我的.png', bBadge: bNewVer), + label: "我的", + activeIcon: getImageItem('assets/images/我的.png', + width: _activeIconWidth, color: Colors.blue, bBadge: bNewVer)), + ]); + } + + // 添加底部导航栏图标的小红点 + Widget getImageItem(String imagePath, + {double width = 56, + Color color = const Color.fromRGBO(131, 131, 131, 1), + bool bBadge = false}) { + return bBadge + ? Badge( + position: BadgePosition.topEnd(top: -4, end: -9), + badgeContent: null, + child: Image.asset(imagePath, + width: ScreenUtil().setWidth(width), fit: BoxFit.cover, color: color)) + : Image.asset(imagePath, + width: ScreenUtil().setWidth(width), fit: BoxFit.cover, color: color); + } + + List listItems = []; + + //该美工优化的页面 Page1_Works,是供多个页面共享的代码框架。不同的页面以 PageType 字段进行区分 + //String pageType = ''; //'home_page'、'statis_page'、'device_page' + List _pageList = [ + Page1_Works(pageType: 'home_page', title: '黑烟车抓拍系统'), + Page1_Works(pageType: 'statis_page', title: ' 统计信息'), + Page1_Works(pageType: 'device_page', title: ' 设备管理'), + //Page2_StatisticsNew(), + //Page3_Device(), + //Page4_MyMsics(), + Page4_MyMsicsNew(pageType: 'my_page', title: ' 我的'), + ]; + + Text _getName(index) { + return Text(this.sAppBar0 + ' - ' + this.listItems[_currentIndex].label); + } + + @override + Widget build(BuildContext context) { + sizeWindowPhysicalSize = MediaQuery.of(context).size; + + return Scaffold( + // appBar: _currentIndex!=3?:AppBar( + // title: Text("用户中心"), + // ), + resizeToAvoidBottomPadding: false, //解决输入法键盘弹出越界问题-OK + appBar: PreferredSize( + child: AppBar( + //title: Text("Flutter Demo"), + title: listItems.isEmpty ? Text('') : _getName(this._currentIndex), + ), + preferredSize: Size.fromHeight(0) //Flutter——设置appBar的高度 + ), + //底部导航栏,使用PageView方式,在App启动时只加载显示页面,启动时没有警告报错。可以配置每个页面的是否保持状态,更为灵活,也更复杂一些 + body: PageView( + controller: this._pageController, + children: this._pageList, + onPageChanged: (index) { + setState(() { + this._currentIndex = index; + //label: "我的" + //eventBus.fire(GroupIdUpdateEvent('g_userInfo.userGroupIDlist 数据已更新')); //这样刷新有效 + // if (3 == index) { + // } + }); + }, + physics: NeverScrollableScrollPhysics(), //禁止pageView滑动 + ), + //底部导航栏,使用IndexedStack方式,是在App启动时便一次性加载所有页面,启动时有警告报错。所有页面的状态都会保持 +// body: IndexedStack( +// index: _currentIndex, +// children: this._pageList, +// ), + bottomNavigationBar: listItems.isEmpty + ? Text('') + : BottomNavigationBar( + currentIndex: _currentIndex, + onTap: (index) { + //eventBus.fire(GroupIdUpdateEvent('g_userInfo.userGroupIDlist 数据已更新')); //这样刷新有效 + g_iIndex = index; + Storage.setString('tabs_index', g_iIndex.toString()); + setState(() { + this._currentIndex = index; + this._pageController.jumpToPage(index); + + // if (1 == index && bPlaying) { + // if (player.value.videoRenderStart && player.state == FijkState.paused) { + // player.start(); + // } + // } else { + // //若player并未初始化播放,调用会报错:The method 'changePlayerState' was called on null. + // if (player.value.videoRenderStart && player.state == FijkState.started) { + // bPlaying = true; + // player.pause(); + // } + // } + }); + }, + iconSize: ScreenUtil().setSp(70), + //icon的大小 + fixedColor: Colors.blueAccent, + type: BottomNavigationBarType.fixed, + items: listItems, + ), + ); + } +} diff --git a/lib/pages/tabs/User.dart b/lib/pages/tabs/User.dart new file mode 100644 index 0000000..6179f0c --- /dev/null +++ b/lib/pages/tabs/User.dart @@ -0,0 +1,144 @@ +//https://material.io/tools/icons/?icon=favorite&style=baseline + +import 'package:flutter/material.dart'; +import '../../widget/JdButton.dart'; + + +import '../../services/UserServices.dart'; + +import '../../services/EventBus.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + + + +class UserPage extends StatefulWidget { + UserPage({Key key}) : super(key: key); + + _UserPageState createState() => _UserPageState(); +} + +class _UserPageState extends State { + bool isLogin = false; + List userInfo = []; + + @override + void initState() { + // TODO: implement initState + super.initState(); + this._getUserinfo(); + + //监听登录页面改变的事件 + eventBus.on().listen((event) { + print(event.str); + this._getUserinfo(); + }); + } + + _getUserinfo() async { + var isLogin = await UserServices.getUserLoginState(); + var userInfo = await UserServices.getUserInfo(); + + setState(() { + this.userInfo = userInfo; + this.isLogin = isLogin; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + // appBar: AppBar( + // title: Text("用户中心"), + // ), + body: ListView( + children: [ + Container( + height: ScreenUtil().setHeight(220), + width: double.infinity, + decoration: + BoxDecoration(image: DecorationImage(image: AssetImage('assets/images/user_bg.jpg'), fit: BoxFit.cover)), + child: Row( + children: [ + Container( + margin: EdgeInsets.fromLTRB(10, 0, 10, 0), + child: ClipOval( + child: Image.asset( + 'assets/images/user.png', + fit: BoxFit.cover, + width: ScreenUtil().setWidth(100), + height: ScreenUtil().setWidth(100), + ), + ), + ), + !this.isLogin + ? Expanded( + flex: 1, + child: InkWell( + onTap: () { + Navigator.pushNamed(context, '/login'); + }, + child: Text("登录/注册", style: TextStyle(color: Colors.white)), + ), + ) + : Expanded( + flex: 1, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("用户名:${this.userInfo[0]["username"]}", + style: TextStyle(color: Colors.white, fontSize: ScreenUtil().setSp(32))), + Text("普通会员", style: TextStyle(color: Colors.white, fontSize: ScreenUtil().setSp(24))), + ], + ), + ) + ], + ), + ), + ListTile( + leading: Icon(Icons.assignment, color: Colors.red), + title: Text("全部订单"), + onTap: () { + Navigator.pushNamed(context, '/order'); + }, + ), + Divider(), + ListTile( + leading: Icon(Icons.payment, color: Colors.green), + title: Text("待付款"), + ), + Divider(), + ListTile( + leading: Icon(Icons.local_car_wash, color: Colors.orange), + title: Text("待收货"), + ), + Container(width: double.infinity, height: 10, color: Color.fromRGBO(242, 242, 242, 0.9)), + ListTile( + leading: Icon(Icons.favorite, color: Colors.lightGreen), + title: Text("我的收藏"), + ), + Divider(), + ListTile( + leading: Icon(Icons.people, color: Colors.black54), + title: Text("在线客服"), + ), + Divider(), + this.isLogin + ? Container( + padding: EdgeInsets.all(20), + child: JdButton( + height: ScreenUtil().setSp(300), + color: Colors.red, + text: "退出登录", + onTop: () { + UserServices.loginOut(); + this._getUserinfo(); + }, + ), + ) + : Text("") + ], + )); + } +} diff --git a/lib/pages/tabs/page1_work.dart b/lib/pages/tabs/page1_work.dart new file mode 100644 index 0000000..c1ee19c --- /dev/null +++ b/lib/pages/tabs/page1_work.dart @@ -0,0 +1,990 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hyzp_ybqx/components/commonFun.dart'; +// import 'package:hyzp_ybqx/pages/Works/DWSP/dw_list_sound.dart'; +// import 'package:hyzp_ybqx/pages/Works/DWSP/dw_sound.dart'; +import 'package:hyzp_ybqx/pages/Works/DWSP/dwsp_getList.dart'; +import 'package:hyzp_ybqx/pages/Works/HYSH/hysh_getList_fliter.dart'; +import 'package:hyzp_ybqx/pages/Works/HYSH/hysh_getList_new.dart'; +import 'package:hyzp_ybqx/pages/Works/SBBJ/sbbj_getList.dart'; +import 'package:hyzp_ybqx/pages/Works/SBGL/dwxx_getList.dart'; +import 'package:hyzp_ybqx/pages/Works/TJXX/tj_data.dart'; +import 'package:hyzp_ybqx/pages/Works/TJXX/today_list.dart'; +import 'package:hyzp_ybqx/pages/Works/TJXX/zptj_bar_chart.dart'; +import 'package:hyzp_ybqx/pages/Works/TJXX/zptj_bar_chart_one.dart'; +import 'package:hyzp_ybqx/pages/Works/TJXX/zptj_page.dart'; +import 'package:hyzp_ybqx/services/EventBus.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../../components/hyxx_data_handle.dart'; +import '../Works/DWDT/basic_map.dart'; +import '../Works/LED_XSXX/led_xsxx_content.dart'; + +///flutter中如何获取子类Widget并调用它的方法 萤火虫离别的礼物 2019.08.07 15:46:08 https://www.jianshu.com/p/b16f70dd692c +//在flutter中开发中,会发现当子类Widget是StatefulWidget类型的时候,想要获取它的State并调用State中的方法,感觉无从下手。 +// 不像是在iOS中,可以直接调用一个类的公开的方法,flutter可以通过key来实现。每个Widget都是唯一标识的。此唯一标识对应于可选的Key参数。 +// 如果省略,Flutter将为您生成一个。key主要分为四种:GlobalKey,LocalKey,UniqueKey或ObjectKey,GlobalKey确保key是在整个应用程序唯一的, +// 这次我们就要使用它来实现。我们需要给子Widget定义一个唯一的GlobalKey,然后根据这个key获取到这个Widget,进行相关的操作,下面是相关的代码: +//这里就是关键的代码,定义一个key +//GlobalKey _myFijkPanelWidgetBuilderStateKey = new GlobalKey(); + +class Page1_Works extends StatefulWidget { + Page1_Works({@required this.pageType, this.title, Key key}) : super(key: key); + + //该美工优化的页面 Page1_Works,是供多个页面共享的代码框架。不同的页面以 PageType 字段进行区分 + String pageType = ''; //'home_page'、'statis_page'、'device_page' + String title = ''; + + @override + _Page1_WorksState createState() => _Page1_WorksState(); +} + +//class _Page1WorkState extends State with WidgetsBindingObserver, AutomaticKeepAliveClientMixin { +class _Page1_WorksState extends State + with WidgetsBindingObserver, AutomaticKeepAliveClientMixin { + //Begin:底部导航栏,使用PageView方式,配置每页面的保持状态。必须添加继承:with AutomaticKeepAliveClientMixin + + //try_setState(); //避免异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + @override + // TODO: implement wantKeepAlive + bool get wantKeepAlive => true; + + //End:底部导航栏,使用PageView方式,配置每页面的保持状态 + + // final FijkPlayer player = FijkPlayer(); + // bool bFirstPlay; + + @override + void initState() { + // getDataListFun().then((list) { + // _listBtnItems = list; + // try_setState(); + // }); + + //startGetStatisData(); //已经在 _MyAppState 中提前开始获取统计数据 + updateStatisData(); + + //监听统计数据改变事件 + eventBus.on().listen((event) { + print(event.str); + updateStatisData(); + }); + + //监听 选择LED点位 更新事件 + eventBus.on().listen((event) async { + print(event.str); + try_setState(); + }); + + super.initState(); + } + + // 更新工作页面的今日统计数据 + Future updateStatisData() async { + if (-1 == mapStatisInfo['今日抓拍']) { + getAllSumNew().then((_) { + try_setState(); + }); + } else { + try_setState(); + } + + // if (listZptjStatisAlone.length >= dwSum && -1 == mapStatisInfo['今日抓拍']) { + // getAllSum('today', listZptjStatisAlone).then((value) { + // //mapStatisInfo['今日抓拍'] = value[1]; + // listTodayZpjl = value[2]; + // //try_setState(); + // }); + // } + // + // if (listTodayShtj.length >= dwSum && -1 == mapStatisInfo['今日初审']) { + // getAllSum('csnum', listTodayShtj).then((value) { + // //mapStatisInfo['今日初审'] = value[1]; + // listTodayChjl = value[2]; + // //try_setState(); + // }); + // getAllSum('fsnum', listTodayShtj).then((value) { + // //mapStatisInfo['今日复审'] = value[1]; + // listTodayFhjl = value[2]; + // //try_setState(); + // }); + // getAllSum('sends', listTodayShtj).then((value) { + // //mapStatisInfo['今日推送'] = value[1]; + // listTodayTsjl = value[2]; + // //try_setState(); + // }); + // } + + //车流量统计数据用 getAllSumCll() 单独处理 + // if (listClltjStatisAlone.length >= dwSum && -1 == mapStatisInfo['今日车流']) { + // getAllSumCll('today', listClltjStatisAlone).then((value) { + // mapStatisInfo['今日车流'] = value[1] ~/ 10000; + // try_setState(); + // }); + // } + } + + Future sysPop() async { + await SystemChannels.platform.invokeMethod('SystemNavigator.pop'); + } + + _Page1_WorksState(); + + @override + void dispose() { + super.dispose(); + } + + // double getHeight() { + // double _height = 0; + // for (double h in listHeight) { + // _height += h; + // } + // print("_height1 = $_height"); // 767 + // _height += ScreenUtil().statusBarHeight * ScreenUtil().pixelRatio; // 系统顶部状态栏高度 + // _height += ScreenUtil().bottomBarHeight * ScreenUtil().pixelRatio; // 系统底部工具栏高度 + // //R:\FlutterProject\FlutterProject33\hyzp_ybqx\lib\pages\tabs\Tabs.dart中,iconSize: ScreenUtil().setSp(70), + // _height += (kBottomNavigationBarHeight * 3 + 8); // 底部导航栏的高度,this.elevation = 8.0,高度 默认8.0 + // //_height += (68 * 3); // 底部导航栏的高度 + // print("_height2 = $_height"); // 1015 + // + // _height = ScreenUtil().screenHeight * ScreenUtil().pixelRatio - _height; + // // print("ScreenUtil().screenHeight = ${ScreenUtil().screenHeight}"); //640.0 + // // print("ScreenUtil().pixelRatio = ${ScreenUtil().pixelRatio}"); // 3.0 + // print("_height3 = $_height"); // S7:905 / 3 = 301 OK; + // // S7 OK: + // // I/flutter (22790): _height1 = 767.0 + // // I/flutter (22790): _height2 = 1015.0 + // // I/flutter (22790): _height3 = 905.0 + // + // // S10 OK: + // // I/flutter (23478): _height1 = 767.0 + // // I/flutter (23478): _height2 = 1055.0 + // // I/flutter (23478): _height3 = 1081.0 + // + // // ARS AL00 OK: + // // I/flutter ( 9979): _height1 = 767.0 + // // I/flutter ( 9979): _height2 = 1039.0 + // // I/flutter ( 9979): _height3 = 1101.0 + // + // // AS ADV OK: + // // I/flutter ( 5464): _height1 = 767.0 + // // I/flutter ( 5464): _height2 = 1006.0 + // // I/flutter ( 5464): _height3 = 788.0 + // + // return _height; + // } + + List listHeight = [ + 484, + 46, + 168, + 69, + ]; + + @override + Widget build(BuildContext context) { + // double btnHeight1 = 80; //第一按钮行高度 + // double btnHeight2 = 160; //第二按钮行高度 + // double btnHeight3 = 302; //350、303 S7越界。第二按钮行高度,370越界,365 ARS AL00不越界 + // int btnCount = 4; //每行按钮个数 + // var mediaSize = MediaQuery.of(context).size; + // double ratio2 = (mediaSize.width / btnCount) / (btnHeight2 / 2); + + //注意:必须在返回Widget的覆盖构造函数中初始化,在其他地方初始化会报错失败 + //在使用之前请设置好设计稿的宽度和高度,传入设计稿的宽度和高度(单位px) + //一定在MaterialApp的home中的页面设置(即入口文件,只需设置一次),以保证在每次使用之前设置好了适配尺寸: + //默认 width : 1080px , height:1920px , allowFontScaling:false + //ScreenUtil.init(context); + + // double myWidth = mediaSize.width; + // double myHeight = mediaSize.width * 9 / 16; + // print('My_viewSize: Width-$myWidth, Height-$myHeight'); + + // double ratio = 1.0; + //getHeight(); + return WillPopScope( + child: Container( + decoration: new BoxDecoration( + color: Color.fromRGBO(244, 244, 244, 1), //设置背景色 + ), + child: Column( + children: [ + Container( + height: ScreenUtil().setHeight(listHeight[0]), //484, 530 - 46 + child: Stack( + children: [ + //1、第1行文字 + Positioned( + child: Container( + height: ScreenUtil().setHeight(324), //181 + alignment: Alignment.topCenter, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + //crossAxisAlignment: CrossAxisAlignment.start, //用的比较少 + children: [ + FlatButton( + child: Container( + child: Row( + children: [ + Padding( + padding: EdgeInsets.only(top: ScreenUtil().setHeight(10)), + child: Image.asset( + 'assets/images/形状 2.png', + height: ScreenUtil().setHeight(45), + ), + ), + Text(" 客服热线", + style: TextStyle(fontSize: 16, color: Colors.white)), + ], + ), + ), + onPressed: () => launch("tel://18784678300"), + ), + SizedBox( + width: ScreenUtil().setWidth(45), + ), + Expanded( + child: Text(widget.title, + style: TextStyle(fontSize: 20.0, color: Colors.white)), + ), + Container( + child: InkWell( + child: Image.asset( + 'assets/images/刷新.png', + height: ScreenUtil().setHeight(45), + color: Colors.white, + ), + onTap: () { + //刷新统计数据 + mapStatisInfo.forEach((key, value) { + mapStatisInfo[key] = -1; + }); + listDwinfoGetList2.clear(); + startGetStatisDataNew(); + Fluttertoast.showToast( + msg: '正在刷新统计数据...', + toastLength: Toast.LENGTH_LONG, + gravity: ToastGravity.BOTTOM, + ); + try_setState(); + }, + ), + ), + SizedBox( + width: ScreenUtil().setWidth(60), + ), + ], + ), + ), + ), + //2、第2行装饰 + Align( + alignment: Alignment.bottomLeft, + child: Container( + alignment: Alignment(0, 1), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + //crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Container( + padding: EdgeInsets.only(bottom: ScreenUtil().setHeight(18)), + height: ScreenUtil().setHeight(310), + decoration: BoxDecoration( + color: Color.fromRGBO(62, 88, 231, 1), + borderRadius: BorderRadius.horizontal(right: Radius.circular(20)), + ), + //color: Colors.pinkAccent, + width: ScreenUtil().setWidth(34), + alignment: Alignment.centerRight, + ), + getImageWidget(), + Container( + height: ScreenUtil().setHeight(310), + decoration: BoxDecoration( + color: Color.fromRGBO(113, 39, 203, 1), + borderRadius: BorderRadius.horizontal(left: Radius.circular(20)), + ), + //color: Colors.pinkAccent, + width: ScreenUtil().setWidth(34), + alignment: Alignment.centerRight, + ), + ], + ), + ), + ), + ], + ), + ), + //3、第3行统计信息 + SizedBox(height: ScreenUtil().setHeight(listHeight[1])), // 46 + Container( + padding: EdgeInsets.only(bottom: ScreenUtil().setHeight(18)), + alignment: Alignment.center, + width: ScreenUtil().setWidth(1022), + height: ScreenUtil().setHeight(listHeight[2]), + //168 + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + getStatisInfo( + '今日抓拍', listAllStatisData.length < dwSum ? -1 : mapStatisInfo['今日抓拍']), + getVerticalDivider(), + getStatisInfo( + '今日初审', listAllStatisData.length < dwSum ? -1 : mapStatisInfo['今日初审']), + getVerticalDivider(), + getStatisInfo( + '今日复审', listAllStatisData.length < dwSum ? -1 : mapStatisInfo['今日复审']), + getVerticalDivider(), + getStatisInfo( + '今日推送', listAllStatisData.length < dwSum ? -1 : mapStatisInfo['今日推送']), + getVerticalDivider(), + getStatisInfo( + '今日车流', listAllStatisData.length < dwSum ? -1 : mapStatisInfo['今日车流']), + // getStatisInfo( + // '今日抓拍', listZptjStatisAlone.length < dwSum ? -1 : mapStatisInfo['今日抓拍']), + // getVerticalDivider(), + // getStatisInfo('今日初审', listTodayShtj.length < dwSum ? -1 : mapStatisInfo['今日初审']), + // getVerticalDivider(), + // getStatisInfo('今日复审', listTodayShtj.length < dwSum ? -1 : mapStatisInfo['今日复审']), + // getVerticalDivider(), + // getStatisInfo('今日推送', listTodayShtj.length < dwSum ? -1 : mapStatisInfo['今日推送']), + // getVerticalDivider(), + // getStatisInfo( + // '今日车流', listClltjStatisAlone.length < dwSum ? -1 : mapStatisInfo['今日车流']), + ], + ), + ), + SizedBox(height: ScreenUtil().setHeight(listHeight[3])), // 69 + //4、第4行圆角按钮 + Expanded( + //color: Colors.black, + //height: ScreenUtil().setHeight(getHeight()), + // S7:905 + //height: ScreenUtil().setHeight(908), + //btnHeight3, // 302 + //padding: EdgeInsets.all(10), + //padding: EdgeInsets.fromLTRB(ScreenUtil().setWidth(76), 0, ScreenUtil().setWidth(76), 0), + //padding: EdgeInsets.fromLTRB(0, 0, 0, 0), + //color: Color.fromRGBO(224, 224, 224, 1), + //alignment: const Alignment(0, -1), + // GridView 控制太麻烦,动态生成必须用 GridView ,静态组件可以不用 GridView + // child: GridView.custom( + // gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + // crossAxisCount: btnCount, + // mainAxisSpacing: ScreenUtil().setWidth(0), + // crossAxisSpacing: ScreenUtil().setHeight(0), + // childAspectRatio: 1, + // ), + // childrenDelegate: SliverChildBuilderDelegate((context, position) { + // //圆角按钮 155 * 155 px + // return getItemContainer(listData4[position]); + // //listData4[position]; + // }, childCount: listData4.length), + // ), + child: getBtnGroup(), + ), + ], + ), + ), + onWillPop: () { + // if (player.state == FijkState.started) { + // player.pause(); + // } + //解决在Page1_Work.dart页面,按系统返回键总是报错flutter keeps stopping的问题 + //player.stop(); + sysPop(); + }, + ); + } + + double _left = 30; + double _intervalHor = 75; + double _intervalVer = 58; + + //Size _itemSize = Size(211, 230); + Size _itemSize = Size(211, 230); + List _listBtnItems = []; + + Widget getBtnGroup() { + return GridView.count( + mainAxisSpacing: ScreenUtil().setWidth(_intervalVer), //垂直间距 + //crossAxisSpacing: ScreenUtil().setHeight(_intervalHor), //水平间距 + //childAspectRatio: _itemSize.width / _itemSize.height, + padding: + EdgeInsets.only(left: ScreenUtil().setWidth(_left), right: ScreenUtil().setWidth(_left)), + crossAxisCount: 4, //一行的 Widget 数量 + children: getDataListFun(), + ); + } + + Future getBtnGroup0() async { + List listData4 = getDataListFun(); + + return Column( + children: [ + Row( + //mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SizedBox(width: ScreenUtil().setWidth(_left)), + listData4[0], + //SizedBox(width: ScreenUtil().setWidth(101)), + SizedBox(width: ScreenUtil().setWidth(_intervalHor)), + listData4[1], + SizedBox(width: ScreenUtil().setWidth(_intervalHor)), + listData4[2], + SizedBox(width: ScreenUtil().setWidth(_intervalHor)), + listData4[3], + ], + ), + SizedBox(height: ScreenUtil().setHeight(78)), + Row( + //mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + SizedBox(width: ScreenUtil().setWidth(_left)), + listData4[4], + SizedBox(width: ScreenUtil().setWidth(_intervalHor)), + listData4[5], + SizedBox(width: ScreenUtil().setWidth(_intervalHor)), + listData4[6], + SizedBox(width: ScreenUtil().setWidth(_intervalHor)), + listData4[7], + ], + ), + ], + ); + } + + Widget getVerticalDivider() { + return SizedBox( + width: 1, + height: ScreenUtil().setHeight(125), + child: DecoratedBox( + decoration: BoxDecoration(color: Colors.black26), + ), + ); + } + + Widget getStatisInfo(String name, double data) { + return InkWell( + child: Container( + alignment: Alignment(0, 0), + //padding: EdgeInsets.only(top: ScreenUtil().setHeight(15)), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(height: ScreenUtil().setHeight(10)), + Text(name, + style: TextStyle(fontSize: 12, color: Colors.black), textAlign: TextAlign.center), + SizedBox(height: ScreenUtil().setHeight(4)), + Row( + children: [ + Text( + data < 0 ? '...' : '${data.toStringAsFixed('今日车流' == name ? 2 : 0)}', + style: TextStyle( + fontSize: 20, + color: Color.fromRGBO(48, 135, 255, 1), + fontWeight: FontWeight.bold), + ), + '今日车流' == name + ? Container( + padding: EdgeInsets.only(top: ScreenUtil().setHeight(8)), + child: Text(' 万', style: TextStyle(fontSize: 10, color: Colors.black)), + ) + : SizedBox.shrink(), + ], + ), + ], + ), + ), + onTap: data < 0 + ? null + : () { + switch (name) { + case '今日抓拍': + // Navigator.of(context) + // .push(MaterialPageRoute(builder: (context) => ZptjBarChart(statisType: 'zptj'))); + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => TodayList(todayListLx: 'jrzp'))); + break; + case '今日初审': + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => TodayList(todayListLx: 'hycs'))); + break; + case '今日复审': + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => TodayList(todayListLx: 'hyfh'))); + break; + case '今日推送': + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => TodayList(todayListLx: 'tsjj'))); + break; + case "今日车流": + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => ZptjBarChartOne(statisType: 'clltj'))); + break; + default: + break; + } + }, + onLongPress: data < 0 + ? null + : () { + switch (name) { + case '今日抓拍': + Navigator.of(context) + .push(MaterialPageRoute(builder: (context) => ZptjPage(statisType: 'zptj'))); + break; + case '今日初审': + case '今日复审': + case '今日推送': + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => ZptjPage(statisType: 'sh_hyc_tj'))); + break; + case "今日车流": + Navigator.of(context) + .push(MaterialPageRoute(builder: (context) => ZptjPage(statisType: 'clltj'))); + break; + default: + break; + } + }, + ); + } + + //自定义带说明图标按钮函数。点击说明文字有反应 + Widget _getIconAndTextButton( + {String text, IconData icon, Color iconColor = Colors.blueAccent, var onPress = null}) { + return Container( + width: 60, + height: 60, + alignment: const Alignment(0, 1), + child: FlatButton( + padding: EdgeInsets.all(0), + onPressed: onPress, + color: Colors.transparent, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(height: 2), + Container( + alignment: Alignment(0, -1), + height: 36, + width: 36, + decoration: BoxDecoration( + color: Colors.white, + ), + child: Icon( + icon, + size: 32, + color: iconColor, + ), + ), + //SizedBox(height: 2), + Text(text, + //textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12.0, + )), + ], + ), + ), + ); + } + + //自定义带说明图片按钮函数。点击说明文字有反应 + //Flutter按钮添加背景图片及文字的一种方法,记录下,上代码 + //原文链接:https://blog.csdn.net/WC270607563/article/details/103148574 + Widget _getPicAndTextButton(String text, String imgpath, var onPress, + {Size imageSize, IconData icon, Color iconColor = Colors.white}) { + if (null == imageSize) { + imageSize = Size(ScreenUtil().setWidth(75), ScreenUtil().setHeight(75)); + } + + Color _bkgColor; + switch (text) { + case "黑烟复审": + case "视频播放": + case "审核图表": + case "审核统计": + case "今日抓拍": + case "点位喊话": + _bkgColor = Color.fromRGBO(36, 206, 192, 1); //绿色 + break; + case "抓拍图表": + case "抓拍统计": + case "推送交警": + case "报警信息": + case "今日推送": + case "LED字幕": + _bkgColor = Color.fromRGBO(79, 118, 230, 1); //深蓝 + break; + case "点位视频": + case "车流量图表": + case "今日初审": + _bkgColor = Color.fromRGBO(116, 139, 161, 1); //深灰 + break; + default: + _bkgColor = Color.fromRGBO(80, 159, 245, 1); //亮蓝 + break; + } + + //155 + 35 + 12 + 6 = 208 + return InkWell( + child: Container( + width: ScreenUtil().setWidth(_itemSize.width), + height: ScreenUtil().setHeight(_itemSize.height), + alignment: Alignment.center, + //color: Colors.red, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: ScreenUtil().setWidth(155), + //S10 2280*1080,在S10正方形变长,是正常的。因为在 ScreenUtilInit 是按 1080, 1920 + //ScreenUtilInit(designSize: Size(1080, 1920), //安卓手机宽高尺寸 + //通过 sizeWindowPhysicalSize = window.physicalSize; 自动适应安卓手机系统分辨率,解决 S10 手机正方形变形问题 + height: ScreenUtil().setHeight(155), + //color: Colors.blue, + decoration: BoxDecoration( + color: '车流量日统计' == text && cllRStatisDataGeting ? Colors.grey : _bkgColor, + borderRadius: BorderRadius.all(Radius.circular(15)), + ), + alignment: Alignment.center, + child: imgpath.isNotEmpty + ? Image.asset(imgpath, + fit: BoxFit.scaleDown, + color: Colors.white, + width: imageSize.width, + height: imageSize.height) + : Icon( + icon, + size: imageSize.width, + color: iconColor, + ), + ), + SizedBox(height: ScreenUtil().setWidth(0)), + Text(text, style: TextStyle(fontSize: 14)), + ], + ), + ), + //cllRStatisDataGeting = true; //正在获取车流量日统计数据,禁止重入 + onTap: '车流量日统计' == text && cllRStatisDataGeting ? null : onPress, + ); + } + + Widget _getPicAndTextButtonTest(String text, String imgpath, var onPress, {Size imageSize}) { + if (null == imageSize) { + imageSize = Size(ScreenUtil().setWidth(75), ScreenUtil().setHeight(75)); + } + //155 + 35 + 12 = 202 + return InkWell( + child: Container( + width: ScreenUtil().setWidth(155), + height: ScreenUtil().setHeight(155), + alignment: Alignment.center, + color: Colors.red, + ), + onTap: onPress, + ); + } + + //该美工优化的页面 Page1_Works,是供多个页面共享的代码框架。不同的页面以 PageType 字段进行区分 + //String pageType = ''; //'home_page'、'statis_page'、'device_page' + + //生成功能区按钮List + List getDataListFun() { + switch (widget.pageType) { + case 'home_page': + return getDataListFun_home_page(); + break; + case 'statis_page': + return getDataListFun_statis_page(); + break; + case 'device_page': + return getDataListFun_device_page(); + break; + } + } + + //生成功能区按钮List + List getDataListFun_device_page() { + List list = []; + list.add( + _getPicAndTextButton("点位信息", "assets/images/1 (194).png", () { + Navigator.of(context).push(MaterialPageRoute(builder: (context) => DwxxGetList())); + }), + ); + list.add( + _getPicAndTextButton("报警信息", "assets/images/1 (219).png", () { + Navigator.of(context) + .push(MaterialPageRoute(builder: (context) => SbbjGetList(hyshlx: 'sbbj'))); + }), + ); + list.add( + _getPicAndTextButton("点位视频", "assets/images/monitor2.png", () { + Navigator.of(context) + .push(MaterialPageRoute(builder: (context) => DwspGetList(hyshlx: 'dwsp'))); + }), + ); + return list; + } + + //生成功能区按钮List + List getDataListFun_statis_page() { + List list = []; + list.add( + _getPicAndTextButton("抓拍图表", "assets/images/statis_blue.png", () { + Navigator.of(context) + .push(MaterialPageRoute(builder: (context) => ZptjBarChart(statisType: 'zptj'))); + }), + ); + list.add( + _getPicAndTextButton("审核图表", "assets/images/statis_red.png", () { + Navigator.of(context) + .push(MaterialPageRoute(builder: (context) => ZptjBarChart(statisType: 'sh_hyc_tj'))); + }), + ); + list.add( + _getPicAndTextButton("车流量图表", "assets/images/statis_green.png", () { + Navigator.of(context) + .push(MaterialPageRoute(builder: (context) => ZptjBarChartOne(statisType: 'clltj'))); + }), + ); + list.add( + _getPicAndTextButton("抓拍统计", "assets/images/图层 11.png", () { + Navigator.of(context) + .push(MaterialPageRoute(builder: (context) => ZptjPage(statisType: 'zptj'))); + }), + ); + list.add( + _getPicAndTextButton("审核统计", "assets/images/1 (15).png", () { + Navigator.of(context) + .push(MaterialPageRoute(builder: (context) => ZptjPage(statisType: 'sh_hyc_tj'))); + }), + ); + list.add( + _getPicAndTextButton("车流量统计", "assets/images/1 (84).png", () { + Navigator.of(context) + .push(MaterialPageRoute(builder: (context) => ZptjPage(statisType: 'clltj'))); + }), + ); + list.add( + _getPicAndTextButton( + "今日抓拍", + '', + listZptjStatisAlone.length < dwSum + ? null + : () { + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => TodayList(todayListLx: 'jrzp'))); + }, + icon: Icons.camera_alt_outlined), + ); + list.add( + _getPicAndTextButton( + "今日初审", + '', + listTodayShtj.length < dwSum + ? null + : () { + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => TodayList(todayListLx: 'hycs'))); + }, + icon: Icons.preview_outlined), + ); + list.add( + _getPicAndTextButton( + "今日复审", + '', + listTodayShtj.length < dwSum + ? null + : () { + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => TodayList(todayListLx: 'hyfh'))); + }, + icon: Icons.rate_review_outlined), + ); + list.add( + _getPicAndTextButton( + "今日推送", + '', + listTodayShtj.length < dwSum + ? null + : () { + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => TodayList(todayListLx: 'tsjj'))); + }, + icon: Icons.format_list_numbered_outlined), + ); + // list.add( + // _getPicAndTextButton("车流量日统计", "assets/images/车流量日统计.png", () { + // Navigator.of(context).push(MaterialPageRoute( + // builder: (context) => ZptjBarEchartsTrinityNew(statisType: 'cllrtj'))); + // }), + // ); + return list; + } + + //生成功能区按钮List + List getDataListFun_home_page() { + List list = []; + list.add( + _getPicAndTextButton("黑烟初审", "assets/images/聚焦.png", () { + print('Icons.videocam'); + //hyshlx为黑烟审核类型,用于在同一套代码中,处理'hycs'黑烟初审、'hyfh'黑烟复审 + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => HyshGetListNew(hyshlx: 'hycs', title: '黑烟初审'))); + }), + ); + list.add( + _getPicAndTextButton("黑烟复审", "assets/images/盾 密码 安全.png", () { + print('Icons.videocam'); + //hyshlx为黑烟审核类型,用于在同一套代码中,处理'hycs'黑烟初审、'hyfh'黑烟复审 + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => HyshGetListNew(hyshlx: 'hyfh', title: '黑烟复审'))); + }), + ); + list.add( + _getPicAndTextButton("推送交警", "assets/images/警察.png", () { + print('Icons.videocam'); + // Navigator.of(context).push(MaterialPageRoute( + // builder: (context) => TsjjGetList( + // tsjjlx: 'tsjj', + // ))); + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => HyshGetListFliter(hyshlx: 'tsjj', title: '推送交警'))); + }), + ); + // list.add( + // _getPicAndTextButton("复审查询", "assets/fun_icons/fun_icon_4.png", () { + // print('Icons.videocam'); + // Navigator.of(context).push(MaterialPageRoute(builder: (context) => TsjjGetList(tsjjlx: 'fhcx',))); + // }), + // ); + list.add( + _getPicAndTextButton("非黑烟查询", "assets/images/1 (104).png", () { + print('Icons.videocam'); + //Navigator.of(context).push(MaterialPageRoute(builder: (context) => FhycxGetList())); + Navigator.of(context).push(MaterialPageRoute( + //builder: (context) => WzxxGetList(hyshlx: 'fhycx'))); + builder: (context) => HyshGetListFliter(hyshlx: 'fhycx', title: '非黑烟查询'))); + }), + ); + // list.add( + // _getPicAndTextButton("违章信息", "assets/fun_icons/fun_icon_5.png", () { + // //Navigator.of(context).push(MaterialPageRoute(builder: (context) => WzxxGetList())); + // Navigator.of(context) + // .push(MaterialPageRoute(builder: (context) => WzxxGetList(hyshlx: 'wzxx'))); + // }), + // ); + list.add( + _getPicAndTextButton("LED字幕", "assets/images/LED.png", () { + print('LED显示信息'); + // Navigator.of(context) + // .push(MaterialPageRoute(builder: (context) => LedXsxxGetList(hyshlx: 'led_xsxx'))); + //应公司要求改为:打开LED字幕后,直接显示点位选择那个界面,用户可以手动切换进行设置 + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => LedXsxxContent( + title: 'LED显示信息', + sbgllx: 'led_xsxx', //设备管理类型:LED显示信息 + id: 1, + ))); + }), + ); + list.add( + _getPicAndTextButton("点位地图", 'assets/images/1 (177).png', () { + print('点位地图'); + Navigator.of(context) + .push(MaterialPageRoute(builder: (context) => BasicMap(hyshlx: 'dwdt', title: "点位地图"))); + }), + ); + list.add( + _getPicAndTextButton("点位视频", "assets/images/monitor2.png", () { + print('点位视频'); + Navigator.of(context) + .push(MaterialPageRoute(builder: (context) => DwspGetList(hyshlx: 'dwsp'))); + }), + ); + // list.add( + // _getPicAndTextButton("点位喊话", "assets/images/点位喊话.png", () { + // print('点位喊话'); + // Navigator.of(context).push(MaterialPageRoute(builder: (context) => DwListSound())); + // }), + // ); + // list.add( + // _getPicAndTextButton("视频播放", 'assets/images/播放 (1).png', () { + // print('视频播放'); + // urlnew = + // "http://www.yibinu.edu.cn/__local/5/35/DF/264049B7E978EEE2F5849688986_05D4A6FE_152CDB8C.mp4?e=.mp4"; + // Navigator.of(context).push(MaterialPageRoute(builder: (context) => PlayerProNew())); + // }), + // ); + //填充空白 + //list.add(null); + + // list.add( + // _getPicAndTextButton("X5视频", "assets/images/monitor2.png", () { + // print('X5视频'); + // Navigator.of(context) + // .push(MaterialPageRoute(builder: (context) => X5WebviewPage())); + // }), + // ); + + return list; + } + + //生成容器部件 + Widget getItemContainer0(Widget item) { + return Container( + width: 5.0, + height: 5.0, + alignment: Alignment.center, + child: item, + color: Colors.white, + ); + } + + //生成容器部件 + Widget getItemContainer(Widget item) { + return Container( + decoration: BoxDecoration( + //color: Colors.blue, + borderRadius: BorderRadius.all(Radius.circular(20)), + ), + // width: 3.0, + // height: 3.0, + alignment: Alignment.center, + child: item, + //color: Colors.blue, + ); + } +} diff --git a/lib/pages/tabs/page4_myMsics_new.dart b/lib/pages/tabs/page4_myMsics_new.dart new file mode 100644 index 0000000..ac684b9 --- /dev/null +++ b/lib/pages/tabs/page4_myMsics_new.dart @@ -0,0 +1,744 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:badges/badges.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hyzp_ybqx/components/commonFun.dart'; +import 'package:hyzp_ybqx/pages/Login/FaceLogin.dart'; +import 'package:hyzp_ybqx/pages/Login/FaceReg.dart'; +import 'package:hyzp_ybqx/pages/MyMsics/05_updated/MyUpdatedNew.dart'; +import 'package:hyzp_ybqx/pages/Works/TJXX/tj_data.dart'; +import 'package:hyzp_ybqx/services/EventBus.dart'; +import 'package:hyzp_ybqx/services/EventBus.dart'; +import 'package:hyzp_ybqx/widget/JdButton.dart'; +import 'package:package_info/package_info.dart'; +import 'package:package_info/package_info.dart'; +import 'package:path_provider/path_provider.dart'; +//import 'package:hyzp_ybqx/widget/player_pro.dart'; +import 'package:scroll_to_index/util.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../../components/commonFun.dart'; +import '../../components/commonFun.dart'; +import '../../components/customDialogF.dart'; +import '../../services/Storage.dart'; +//import 'package:hyzp_ybqx/widget/player_pro.dart'; +import '../Login/ModifyPassword.dart'; +import '../MyMsics/03_personal/PersonalData.dart'; +import '../MyMsics/04_MyFeedback/MyFeedback.dart'; +import '../MyMsics/05_updated/MyUpdated.dart'; +import '../MyMsics/07_myAbout/MyAbout.dart'; + +///flutter中如何获取子类Widget并调用它的方法 萤火虫离别的礼物 2019.08.07 15:46:08 https://www.jianshu.com/p/b16f70dd692c +//在flutter中开发中,会发现当子类Widget是StatefulWidget类型的时候,想要获取它的State并调用State中的方法,感觉无从下手。 +// 不像是在iOS中,可以直接调用一个类的公开的方法,flutter可以通过key来实现。每个Widget都是唯一标识的。此唯一标识对应于可选的Key参数。 +// 如果省略,Flutter将为您生成一个。key主要分为四种:GlobalKey,LocalKey,UniqueKey或ObjectKey,GlobalKey确保key是在整个应用程序唯一的, +// 这次我们就要使用它来实现。我们需要给子Widget定义一个唯一的GlobalKey,然后根据这个key获取到这个Widget,进行相关的操作,下面是相关的代码: +//这里就是关键的代码,定义一个key +//GlobalKey _myFijkPanelWidgetBuilderStateKey = new GlobalKey(); + +class Page4_MyMsicsNew extends StatefulWidget { + Page4_MyMsicsNew({@required this.pageType, this.title, Key key}) : super(key: key); + + //该美工优化的页面 Page4_MyMsicsNew,是供多个页面共享的代码框架。不同的页面以 PageType 字段进行区分 + String pageType = ''; //'my_page' + String title = ''; + + @override + _Page4_MyMsicsNewState createState() => _Page4_MyMsicsNewState(); +} + +//class _Page1WorkState extends State with WidgetsBindingObserver, AutomaticKeepAliveClientMixin { +class _Page4_MyMsicsNewState extends State + with WidgetsBindingObserver, AutomaticKeepAliveClientMixin { + //Begin:底部导航栏,使用PageView方式,配置每页面的保持状态。必须添加继承:with AutomaticKeepAliveClientMixin + + //try_setState(); //避免异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + @override + // TODO: implement wantKeepAlive + bool get wantKeepAlive => true; + + //End:底部导航栏,使用PageView方式,配置每页面的保持状态 + + // final FijkPlayer player = FijkPlayer(); + // bool bFirstPlay; + + @override + void initState() { + //监听 g_userInfo.userGroupIDlist 更新事件 + // eventBus.on().listen((event) async { + // print(event.str); + // getAdminItem(); + // }); + + getListView().then((value) { + Future.delayed(Duration(milliseconds: 500), () { + getAdminItem(); + }); + }); + + super.initState(); + } + + Future updateStatisData() async { + if (listZptjStatisAlone.length >= dwSum && -1 == mapStatisInfo['今日抓拍']) { + getAllSum('today', listZptjStatisAlone).then((value) { + mapStatisInfo['今日抓拍'] = value[1]; + try_setState(); + }); + } + + if (listShtjStatisAlone.length >= dwSum && -1 == mapStatisInfo['今日初审']) { + getAllSum('total', listShtjStatisAlone).then((value) { + mapStatisInfo['今日初审'] = value[1]; + mapStatisInfo['今日复审'] = value[1]; + try_setState(); + }); + getAllSum('sends', listShtjStatisAlone).then((value) { + mapStatisInfo['今日推送'] = value[1]; + try_setState(); + }); + } + + if (listClltjStatisAlone.length >= dwSum && -1 == mapStatisInfo['今日车流']) { + getAllSum('today', listClltjStatisAlone).then((value) { + mapStatisInfo['今日车流'] = value[1] ~/ 10000; + try_setState(); + }); + } + } + + Future sysPop() async { + await SystemChannels.platform.invokeMethod('SystemNavigator.pop'); + } + + _Page4_MyMsicsNewState(); + + @override + void dispose() { + super.dispose(); + } + + //自定义方法 + static onNullFun() {} + + Widget _getListTile(title, + {String leadPath = '', + Color leadColor, + onTapFun = onNullFun, + onLongPressFun = onNullFun, + size = 16.0, + bool bBadge = false}) { + return Column( + children: [ + ListTile( + leading: bBadge + ? Badge( + position: BadgePosition.topEnd(top: -7, end: -12), + badgeContent: null, + child: Image.asset( + leadPath, + height: ScreenUtil().setHeight(78), + fit: BoxFit.fitHeight, + ), + ) + : Image.asset( + leadPath, + height: ScreenUtil().setHeight(78), + fit: BoxFit.fitHeight, + ), + title: new Text(title, style: TextStyle(fontSize: size)), + trailing: new Icon(Icons.arrow_forward_ios), + contentPadding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 0), + enabled: true, + onTap: onTapFun, + onLongPress: onLongPressFun, + ), + Divider( + height: 1.0, + ), + ], + ); + } + + List _listViewUser = []; + List _listViewUser_user = []; + + Color _greenColor = Color.fromRGBO(36, 206, 192, 1); //绿色 + Color _deepBlueColor = Color.fromRGBO(79, 118, 230, 1); //深蓝 + Color _deepGreyColor = Color.fromRGBO(116, 139, 161, 1); //深灰 + Color _ligthBlueColor = Color.fromRGBO(80, 159, 245, 1); //亮蓝 + + Future getListView() async { + _listViewUser_user.clear(); + // _listViewUser.add(_getListTile('个人资料', + // leadPath: 'assets/images/我的.png', + // leadColor: _ligthBlueColor, + // onTapFun: OnTap_personal_data)); + // _listViewUser.add(_getListTile('意见反馈', + // leadPath: 'assets/images/意见反馈.png', + // leadColor: _ligthBlueColor, + // onTapFun: OnTap_MyFeedback)); + // _listViewUser.add(_getListTile('版本更新', + // leadPath: 'assets/images/版本更新.png', leadColor: _greenColor, onTapFun: OnTap_MyUpdate)); + // _listViewUser.add(_getListTile('清除缓存', + // leadPath: 'assets/images/清除缓存.png', + // leadColor: _deepBlueColor, + // onTapFun: OnTap_ClearCache)); + // _listViewUser.add(_getListTile('关于', + // leadPath: 'assets/images/关于.png', leadColor: _deepBlueColor, onTapFun: OnTap_MyAbout)); + + _listViewUser_user = [ + _getListTile('清除缓存', + leadPath: 'assets/images/清除缓存.png', + leadColor: _deepBlueColor, + onTapFun: OnTap_ClearCache), + //用户资料修改、版本更新、意见反馈都需要后台支持才行,现在后台都没有提供支持,标书里面也没有要求,建议先去掉 + // _getListTile('个人资料', + // leadPath: 'assets/images/我的.png', + // leadColor: _ligthBlueColor, + // onTapFun: OnTap_personal_data), + // _getListTile('意见反馈', + // leadPath: 'assets/images/意见反馈.png', + // leadColor: _ligthBlueColor, + // onTapFun: OnTap_MyFeedback), + _getListTile('修改密码', + leadPath: 'assets/images/修改密码.png', + leadColor: _deepBlueColor, + onTapFun: OnTap_modify_password), + //bNewVer:是否发现新版本 + _getListTile('版本更新', + leadPath: 'assets/images/版本更新.png', + leadColor: _greenColor, + onTapFun: OnTap_MyUpdate, + bBadge: bNewVer), + + // _getListTile('关于', + // leadPath: 'assets/images/关于.png', leadColor: _deepBlueColor, onTapFun: OnTap_MyAbout), + // _getListTile('权限测试', + // leadPath: 'assets/images/权限.png', + // leadColor: _deepGreyColor, + // onTapFun: OnTap_UserAuthority), + ]; + } + + //已添加管理员记录的标志,0 未添加, 1 已添加 1 次 + //该标志也作为是否是管理员的标志,若为 0 便不是、只是当前还不是, 1 则是管理员 + //int alreadyFlag = 0; + + Future getAdminItem() async { + _listViewUser.addAll(_listViewUser_user); + for (int group_id in g_userInfo.userGroupIDlist) { + print('group_id = $group_id'); + if (26 == group_id || 31 == group_id) { + Widget _item = _getListTile('人脸注册', + leadPath: 'assets/images/人脸注册.png', + leadColor: _ligthBlueColor, + onTapFun: OnTap_FaceReg); + print('_listViewUser.length = ${_listViewUser.length}'); + _listViewUser.add(_item); + break; //添加后便跳出循环,避免重复添加 + } + } + + _listViewUser.add(_getListTile('关于', + leadPath: 'assets/images/关于.png', leadColor: _deepBlueColor, onTapFun: OnTap_MyAbout)); + print('_listViewUser.length = ${_listViewUser.length}'); + Future.delayed(Duration(milliseconds: 500), () { + try_setState(); + }); + } + + @override + Widget build(BuildContext context) { + return WillPopScope( + child: Container( + decoration: new BoxDecoration( + color: Color.fromRGBO(244, 244, 244, 1), //设置背景色 + ), + child: Column( + children: [ + Container( + height: ScreenUtil().setHeight(484), //530 - 46 + child: Stack( + children: [ + //1、第1行文字 + Positioned( + child: Container( + height: ScreenUtil().setHeight(324), //181 + alignment: Alignment.topCenter, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + //crossAxisAlignment: CrossAxisAlignment.start, //用的比较少 + children: [ + FlatButton( + child: Container( + child: Row( + children: [ + Padding( + padding: EdgeInsets.only(top: ScreenUtil().setHeight(10)), + child: Image.asset( + 'assets/images/形状 2.png', + height: ScreenUtil().setHeight(45), + ), + ), + Text(" 客服热线", + style: TextStyle(fontSize: 16, color: Colors.white)), + ], + ), + ), + onPressed: () => launch("tel://18784678300"), + ), + SizedBox( + width: ScreenUtil().setWidth(45), + ), + Expanded( + child: Text(widget.title, + style: TextStyle(fontSize: 20.0, color: Colors.white)), + ), + ], + ), + ), + ), + //2、第2行装饰 + Align( + alignment: Alignment.bottomLeft, + child: Container( + alignment: Alignment(0, 1), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + //crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Container( + padding: EdgeInsets.only(bottom: ScreenUtil().setHeight(18)), + height: ScreenUtil().setHeight(310), + decoration: BoxDecoration( + color: Color.fromRGBO(62, 88, 231, 1), + borderRadius: BorderRadius.horizontal(right: Radius.circular(20)), + ), + //color: Colors.pinkAccent, + width: ScreenUtil().setWidth(34), + alignment: Alignment.centerRight, + ), + getImageWidget(), + // Container( + // alignment: Alignment(0, 0), + // height: ScreenUtil().setHeight(346), + // width: ScreenUtil().setWidth(942), + // child: Image.asset( + // 'assets/images/装饰图片10.png', + // fit: BoxFit.cover, + // ), + // ), + Container( + height: ScreenUtil().setHeight(310), + decoration: BoxDecoration( + color: Color.fromRGBO(113, 39, 203, 1), + borderRadius: BorderRadius.horizontal(left: Radius.circular(20)), + ), + //color: Colors.pinkAccent, + width: ScreenUtil().setWidth(34), + alignment: Alignment.centerRight, + ), + ], + ), + ), + ), + ], + ), + ), + //3、第3行统计信息 + SizedBox(height: ScreenUtil().setHeight(46)), + Expanded( + //Flutter Column套ListView不显示,可将ListView用Expanded包裹起来。 + //用 ListView.builder 不好区别处理响应函数的动态参数传递,所以使用基本 ListView + // child: ListView.builder( + // itemCount: listContacts.length, + // itemBuilder: this._getlistContacts), + child: _listViewUser.isEmpty + ? getMoreWidget(color: Colors.black26) + : ListView( + padding: EdgeInsets.all(10), + children: _listViewUser, + ), + ), + Divider( + height: 20.0, + indent: 0.0, + thickness: 1.0, + color: Color.fromRGBO(80, 159, 245, 1), + ), + // Center( + // child: RaisedButton( + // //padding: EdgeInsets.all(0), + // onPressed: () { + // Navigator.pushNamed(context, '/', arguments: 0); + // }, + // //color: Colors.transparent, + // child: Text('退出登录'), + // ), + // ), + JdButton( + height: 126, + //JdText中已经使用ScreenUtil().setHeight(126),此处不能传 ScreenUtil().setHeight(126) ,否则严重错位 + width: 350, + text: "退出登录", + color: Color.fromRGBO(80, 159, 245, 1), + onTop: () { + Navigator.pushNamed(context, '/', arguments: 0); + }, + ), + SizedBox( + height: 20.0, //防止误触,所以设大一些 + ), + ], + ), + ), + onWillPop: () { + sysPop(); + }, + ); + } + + OnTap_MyAbout() { + PackageInfo.fromPlatform().then((PackageInfo packageInfo) { + String appName = packageInfo.appName; + String packageName = packageInfo.packageName; + String version = packageInfo.version; + String buildNumber = packageInfo.buildNumber; + String buildDate = + '${buildNumber.substring(0, 4)}.${buildNumber.substring(4, 6)}.${buildNumber.substring(6, 8)}'; + Navigator.of(context) + .push(MaterialPageRoute(builder: (context) => MyAbout(ver: version, date: buildDate))); + }); + } + + OnTap_MyUpdate() { + PackageInfo.fromPlatform().then((PackageInfo packageInfo) async { + String appName = packageInfo.appName; + String packageName = packageInfo.packageName; + String version = packageInfo.version; + String buildNumber = packageInfo.buildNumber; + String buildDate = + '${buildNumber.substring(0, 4)}.${buildNumber.substring(4, 6)}.${buildNumber.substring(6, 8)}'; + + print('appName = $appName'); + print('packageName = $packageName'); + print('version = $version'); + print('buildNumber = $buildNumber'); + print('buildDate = $buildDate'); + // I/flutter (30820): appName = 宜宾黑烟抓拍 + // I/flutter (30820): packageName = com.flutter.hyzp_ybqx + // I/flutter (30820): version = 1.3.1 + // I/flutter (30820): buildNumber = 20210508 + // I/flutter (30820): buildDate = 2021.05.08 + + //Fluttertoast.showToast(msg: '当前版本 v$version。暂无更新', gravity: ToastGravity.CENTER); + // Navigator.of(context).push(MaterialPageRoute( + // builder: (context) => MyUpdated(ver: version, date: buildDate, theContext: context))); + + MyUpdatedNew m = await MyUpdatedNew( + ver: version, + date: buildDate, + theContext: context, + bStartUpdated: true, + bShowNoNewVersion: true); + }); + } + + Future _getTotalSizeOfFilesInDir(final FileSystemEntity file) async { + if (file is File) { + int length = await file.length(); + return double.parse(length.toString()); + } + if (file is Directory) { + final List children = file.listSync(); + double total = 0; + if (children != null) + for (final FileSystemEntity child in children) + total += await _getTotalSizeOfFilesInDir(child); + return total; + } + return 0; + } + + OnTap_FaceLogin() async { + Navigator.of(context).push(MaterialPageRoute(builder: (context) => FaceLogin())); + } + + OnTap_FaceReg() async { + Navigator.of(context).push(MaterialPageRoute(builder: (context) => FaceReg())); + } + + OnTap_modify_password() { + Navigator.of(context).push(MaterialPageRoute(builder: (context) => ModifyPassword())); + } + + OnTap_personal_data() { + Navigator.of(context).push(MaterialPageRoute(builder: (context) => PersonalData())); + } + + OnTap_MyFeedback() { + Navigator.of(context).push(MaterialPageRoute(builder: (context) => MyFeedback())); + } + + OnTap_UserAuthority() async { + //1、根据用户ID获取用户所属角色(用户组) + //getUserAccess(user_id: 136); + + //2.2、获取后台用户全部角色分组数据 + //I/flutter (15540): g_userInfo.userGroupIDlist = [32, 33] + // g_userInfo.userRulesMap.clear(); + // //getUserGroup(group_id: 27); + // getUserGroupAll(user_id: 136); + // + // Future.delayed(const Duration(milliseconds: 3500), () { + // print('g_userInfo.userRulesMap = ${g_userInfo.userRulesMap.toString()}'); + // }); + //I/flutter (15540): g_userInfo.userRulesMap = {32: [1968, 1972, 1973, 1969, 1976, 1977, 2008, 2009, 2011, 2014, 2015, 2018, 2029, 2030, 2031, 2054, 2055, 2035, 2036, 2037, 204 + // 1, 2042, 2043, 2047, 2048, 2049, 2053, 1970, 1980, 1981, 1971, 1984, 1985, 1992, 1993, 2000, 2001, 2020, 2022], 33: [1968, 1972, 1973, 1969, 1976, 1977, 2008, 2009, 2011, 201 + // 4, 2015, 2018, 2029, 2030, 2031, 2054, 2055, 2035, 2036, 2037, 2041, 2042, 2043, 2047, 2048, 2049, 2053, 1970, 1980, 1981, 1971, 1984, 1985, 1992, 1993, 2000, 2001, 2019, 202 + // 0, 2022]} + + // getUserGroup(group_id: g_userInfo.userGroupIDlist[0]); + // print('g_userInfo.userRulesMap = ${g_userInfo.userRulesMap.toString()}'); + //I/flutter (15540): g_userInfo.userRulesMap = {32: [1968, 1972, 1973, 1969, 1976, 1977, 2008, 2009, 2011, + // 2014, 2015, 2018, 2029, 2030, 2031, 2054, 2055, 2035, 2036, 2037, 2041, 2042, 2043, 2047, + // 2048, 2049, 2053, 1970, 1980, 1981, 1971, 1984, 1985, 1992, 1993, 2000, 2001, 2020, 2022]} + + // g_userInfo.userRulesMap.clear(); + // getUserGroupAll(); + // print('g_userInfo.userGroupIDlist = ${g_userInfo.userGroupIDlist}'); + // print('g_userInfo.userGroupIDlist[0] = ${g_userInfo.userGroupIDlist[0]}'); + // getUserGroup(group_id: g_userInfo.userGroupIDlist[0]); + //I/flutter (15540): g_userInfo.userGroupIDlist = [31, 27] + //getUserGroup(group_id: g_userInfo.userGroupIDlist[1]); + + ///3、获取后台全部 (All) 用户角色分组分页列表数据 + // getRecordList(api: ServicePath.getUserGroupListUrl).then((map) { + // mapUserGroupList = map; + // }); + //I/flutter ( 1422): http://125.64.218.67:9904/?s=App.User_User.GetGroupList + // I/flutter ( 1422): 开始处理登录请求... + // I/flutter ( 1422): response = {"ret":200,"data":{"items":[{"id":35,"jgid":2,"type":0,"title":"局领导","level":0,"pid":0,"sort":1,"status":1,"rules":""},{"id":34,"jgid":2,"typ + // e":0,"title":"系统管理","level":0,"pid":0,"sort":1,"status":1,"rules":""},{"id":33,"jgid":2,"type":1,"title":"参观者","level":0,"pid":0,"sort":4,"status":1,"rules":"1968,1972 + // ,1973,1969,1976,1977,2008,2009,2011,2014,2015,2018,2029,2030,2031,2054,2055,2035,2036,2037,2041,2042,2043,2047,2048,2049,2053,1970,1980,1981,1971,1984,1985,1992,1993,2000,200 + // 1,2019,2020,2022"},{"id":32,"jgid":2,"type":0,"title":"演示账户","level":0,"pid":0,"sort":3,"status":1,"rules":"1968,1972,1973,1969,1976,1977,2008,2009,2011,2014,2015,2018,20 + // 29,2030,2031,2054,2055,2035,2036,2037,2041,2042,2043,2047,2048,2049,2053,1970,1980,1981,1971,1984,1985,1992,1993,2000,2001,2020,2022"},{"id":31,"jgid":2,"type":0,"title":"监 + // 控室","level":0,"pid":0,"sort":2,"status":1,"rules":""},{"id":30,"jgid":2,"type":0,"title":"中心领导","level":0,"pid":0,"sort":1,"status":1,"rules":"196 + // I/flutter ( 1422): mapRecordList['mapRecordListRet'] = {ret: 200, data: {items: [{id: 35, jgid: 2, type: 0, title: 局领导, level: 0, pid: 0, sort: 1, status: 1, rules: }, {id + // : 34, jgid: 2, type: 0, title: 系统管理, level: 0, pid: 0, sort: 1, status: 1, rules: }, {id: 33, jgid: 2, type: 1, title: 参观者, level: 0, pid: 0, sort: 4, status: 1, rules + // : 1968,1972,1973,1969,1976,1977,2008,2009,2011,2014,2015,2018,2029,2030,2031,2054,2055,2035,2036,2037,2041,2042,2043,2047,2048,2049,2053,1970,1980,1981,1971,1984,1985,1992,19 + // 93,2000,2001,2019,2020,2022}, {id: 32, jgid: 2, type: 0, title: 演示账户, level: 0, pid: 0, sort: 3, status: 1, rules: 1968,1972,1973,1969,1976,1977,2008,2009,2011,2014,2015, + // 2018,2029,2030,2031,2054,2055,2035,2036,2037,2041,2042,2043,2047,2048,2049,2053,1970,1980,1981,1971,1984,1985,1992,1993,2000,2001,2020,2022}, {id: 31, jgid: 2, type: 0, title + // : 监控室, level: 0, pid: 0, sort: 2, status: 1, rules: }, {id: 30, jgid: 2, type: 0, title: 中心领导, level: 0, pid: 0, sort: 1, status: 1, rules: 1968 + // I/flutter ( 1422): mapRecordList['listRecordList'] = [] + // I/flutter ( 1422): _list1 = [{id: 35, jgid: 2, type: 0, title: 局领导, level: 0, pid: 0, sort: 1, status: 1, rules: }, {id: 34, jgid: 2, type: 0, title: 系统管理, level: 0, p + // id: 0, sort: 1, status: 1, rules: }, {id: 33, jgid: 2, type: 1, title: 参观者, level: 0, pid: 0, sort: 4, status: 1, rules: 1968,1972,1973,1969,1976,1977,2008,2009,2011,2014, + // 2015,2018,2029,2030,2031,2054,2055,2035,2036,2037,2041,2042,2043,2047,2048,2049,2053,1970,1980,1981,1971,1984,1985,1992,1993,2000,2001,2019,2020,2022}, {id: 32, jgid: 2, type + // : 0, title: 演示账户, level: 0, pid: 0, sort: 3, status: 1, rules: 1968,1972,1973,1969,1976,1977,2008,2009,2011,2014,2015,2018,2029,2030,2031,2054,2055,2035,2036,2037,2041,20 + // 42,2043,2047,2048,2049,2053,1970,1980,1981,1971,1984,1985,1992,1993,2000,2001,2020,2022}, {id: 31, jgid: 2, type: 0, title: 监控室, level: 0, pid: 0, sort: 2, status: 1, rule + // s: }, {id: 30, jgid: 2, type: 0, title: 中心领导, level: 0, pid: 0, sort: 1, status: 1, rules: 1968,1972,1973,1974,1975,1969,1976,1977,1978,1979,1970,1 + // I/flutter ( 1422): mapRecordList['listRecordList'] = [{id: 35, jgid: 2, type: 0, title: 局领导, level: 0, pid: 0, sort: 1, status: 1, rules: }, {id: 34, jgid: 2, type: 0, tit + // le: 系统管理, level: 0, pid: 0, sort: 1, status: 1, rules: }, {id: 33, jgid: 2, type: 1, title: 参观者, level: 0, pid: 0, sort: 4, status: 1, rules: 1968,1972,1973,1969,1976, + // 1977,2008,2009,2011,2014,2015,2018,2029,2030,2031,2054,2055,2035,2036,2037,2041,2042,2043,2047,2048,2049,2053,1970,1980,1981,1971,1984,1985,1992,1993,2000,2001,2019,2020,2022 + // }, {id: 32, jgid: 2, type: 0, title: 演示账户, level: 0, pid: 0, sort: 3, status: 1, rules: 1968,1972,1973,1969,1976,1977,2008,2009,2011,2014,2015,2018,2029,2030,2031,2054,20 + // 55,2035,2036,2037,2041,2042,2043,2047,2048,2049,2053,1970,1980,1981,1971,1984,1985,1992,1993,2000,2001,2020,2022}, {id: 31, jgid: 2, type: 0, title: 监控室, level: 0, pid: 0, + // sort: 2, status: 1, rules: }, {id: 30, jgid: 2, type: 0, title: 中心领导, level: 0, pid: 0, sort: 1, status: 1, rules: 1968,1972,1973,1974,1975,1969,1 + // I/flutter ( 1422): map['page'] = 1 + // I/flutter ( 1422): _counter = 8 + // I/flutter ( 1422): _total = 8 + + ///5、获取后台功能分类分页列表数据 + // getRecordList(api: ServicePath.getUserAuthListUrl).then((map) { + // mapUserAuthList = map; + // }); + //I/flutter ( 3512): http://125.64.218.67:9904/?s=App.User_User.GetAuthList + // I/flutter ( 3512): 开始处理登录请求... + // I/flutter ( 3512): response = {"ret":200,"data":{"items":[{"id":2069,"jgid":2,"level":2,"pid":2067,"name":"blacksmoke2/b2tj/fenxicll/fenxi","title":"分析","type":1,"status":1 + // ,"condition":"","sort":9},{"id":2068,"jgid":2,"level":2,"pid":2067,"name":"blacksmoke2/b2tj/fenxicll/view","title":"查看","type":1,"status":1,"condition":"","sort":0},{"id":2 + // 067,"jgid":2,"level":1,"pid":2029,"name":"blacksmoke2/b2tj/fenxicll","title":"车流量统计","type":1,"status":1,"condition":"","sort":7},{"id":2066,"jgid":2,"level":2,"pid":206 + // 4,"name":"blacksmoke2/b2tj/dwinfoview/fenxi","title":"分析","type":1,"status":1,"condition":"","sort":9},{"id":2065,"jgid":2,"level":2,"pid":2064,"name":"blacksmoke2/b2tj/dwi + // nfoview/view","title":"查看","type":1,"status":1,"condition":"","sort":0},{"id":2064,"jgid":2,"level":1,"pid":2029,"name":"blacksmoke2/b2tj/dwinfoview","title":"监测点位状态 + // 详情","type":1,"status":1,"condition":"","sort":6},{"id":2063,"jgid":2,"level":2,"pid":2061,"name":"blacksmoke2/b2tj/dwinfo/fenxi","title":"分析","typ + // I/flutter ( 3512): mapRecordList['mapRecordListRet'] = {ret: 200, data: {items: [{id: 2069, jgid: 2, level: 2, pid: 2067, name: blacksmoke2/b2tj/fenxicll/fenxi, title: 分析, + // type: 1, status: 1, condition: , sort: 9}, {id: 2068, jgid: 2, level: 2, pid: 2067, name: blacksmoke2/b2tj/fenxicll/view, title: 查看, type: 1, status: 1, condition: , sort: + // 0}, {id: 2067, jgid: 2, level: 1, pid: 2029, name: blacksmoke2/b2tj/fenxicll, title: 车流量统计, type: 1, status: 1, condition: , sort: 7}, {id: 2066, jgid: 2, level: 2, pid: + // 2064, name: blacksmoke2/b2tj/dwinfoview/fenxi, title: 分析, type: 1, status: 1, condition: , sort: 9}, {id: 2065, jgid: 2, level: 2, pid: 2064, name: blacksmoke2/b2tj/dwinfo + // view/view, title: 查看, type: 1, status: 1, condition: , sort: 0}, {id: 2064, jgid: 2, level: 1, pid: 2029, name: blacksmoke2/b2tj/dwinfoview, title: 监测点位状态详情, type: + // 1, status: 1, condition: , sort: 6}, {id: 2063, jgid: 2, level: 2, pid: 2061, name: blacksmoke2/b2tj/dwinfo/fenxi, title: 分析, type: 1, status: 1, c + // I/flutter ( 3512): map['page'] = 1 + // I/flutter ( 3512): _counter = 20 + // I/flutter ( 3512): _total = 78 + // I/flutter ( 3512): 第 1 次网络请求过程正常完成 + // I/flutter ( 3512): response = {"ret":200,"data":{"items":[{"id":2042,"jgid":2,"level":1,"pid":2029,"name":"blacksmoke2/b2tj/fenxicartime","title":"车辆轨迹查询","type":1,"sta + // tus":1,"condition":"","sort":3},{"id":2041,"jgid":2,"level":2,"pid":2036,"name":"blacksmoke2/b2tj/fenxicar/fenxi","title":"分析","type":1,"status":1,"condition":"","sort":9}, + // {"id":2037,"jgid":2,"level":2,"pid":2036,"name":"blacksmoke2/b2tj/fenxicar/view","title":"查看","type":1,"status":1,"condition":"","sort":0},{"id":2036,"jgid":2,"level":1,"pi + // d":2029,"name":"blacksmoke2/b2tj/fenxicar","title":"车辆点位频率分析","type":1,"status":1,"condition":"","sort":2},{"id":2035,"jgid":2,"level":2,"pid":2030,"name":"blacksmoke + // 2/b2tj/fenxi","title":"分析","type":1,"status":1,"condition":"","sort":9},{"id":2034,"jgid":2,"level":2,"pid":2030,"name":"blacksmoke2/b2tj/outxls","title":"导出","type":1,"s + // tatus":1,"condition":"","sort":7},{"id":2031,"jgid":2,"level":2,"pid":2030,"name":"blacksmoke2/b2tj/view","title":"查看","type":1,"status":1,"condit + // I/flutter ( 3512): mapRecordList['mapRecordListRet'] = {ret: 200, data: {items: [{id: 2042, jgid: 2, level: 1, pid: 2029, name: blacksmoke2/b2tj/fenxicartime, title: 车辆轨迹 + // 查询, type: 1, status: 1, condition: , sort: 3}, {id: 2041, jgid: 2, level: 2, pid: 2036, name: blacksmoke2/b2tj/fenxicar/fenxi, title: 分析, type: 1, status: 1, condition: , + // sort: 9}, {id: 2037, jgid: 2, level: 2, pid: 2036, name: blacksmoke2/b2tj/fenxicar/view, title: 查看, type: 1, status: 1, condition: , sort: 0}, {id: 2036, jgid: 2, level: 1 + // , pid: 2029, name: blacksmoke2/b2tj/fenxicar, title: 车辆点位频率分析, type: 1, status: 1, condition: , sort: 2}, {id: 2035, jgid: 2, level: 2, pid: 2030, name: blacksmoke2/b + // 2tj/fenxi, title: 分析, type: 1, status: 1, condition: , sort: 9}, {id: 2034, jgid: 2, level: 2, pid: 2030, name: blacksmoke2/b2tj/outxls, title: 导出, type: 1, status: 1, co + // ndition: , sort: 7}, {id: 2031, jgid: 2, level: 2, pid: 2030, name: blacksmoke2/b2tj/view, title: 查看, type: 1, status: 1, condition: , sort: 0}, { + // I/flutter ( 3512): map['page'] = 2 + // I/flutter ( 3512): _counter = 40 + // I/flutter ( 3512): _total = 78 + // I/flutter ( 3512): 第 2 次网络请求过程正常完成 + // I/flutter ( 3512): response = {"ret":200,"data":{"items":[{"id":2008,"jgid":2,"level":1,"pid":1969,"name":"blacksmoke2/b2yjfsls/index","title":"历史数据","type":1,"status":1, + // "condition":"","sort":2},{"id":2007,"jgid":2,"level":2,"pid":2000,"name":"blacksmoke2/b2dwinfo/inxls","title":"导入","type":1,"status":1,"condition":"","sort":8},{"id":2006," + // jgid":2,"level":2,"pid":2000,"name":"blacksmoke2/b2dwinfo/outxls","title":"导出","type":1,"status":1,"condition":"","sort":7},{"id":2005,"jgid":2,"level":2,"pid":2000,"name": + // "blacksmoke2/b2dwinfo/del","title":"删除","type":1,"status":1,"condition":"","sort":4},{"id":2004,"jgid":2,"level":2,"pid":2000,"name":"blacksmoke2/b2dwinfo/lock","title":"锁 + // 定","type":1,"status":1,"condition":"","sort":3},{"id":2003,"jgid":2,"level":2,"pid":2000,"name":"blacksmoke2/b2dwinfo/edit","title":"编辑","type":1,"status":1,"condition":"" + // ,"sort":2},{"id":2002,"jgid":2,"level":2,"pid":2000,"name":"blacksmoke2/b2dwinfo/add","title":"新增","type":1,"status":1,"condition":"","sort":1},{"id":2001 + // I/flutter ( 3512): mapRecordList['mapRecordListRet'] = {ret: 200, data: {items: [{id: 2008, jgid: 2, level: 1, pid: 1969, name: blacksmoke2/b2yjfsls/index, title: 历史数据, t + // ype: 1, status: 1, condition: , sort: 2}, {id: 2007, jgid: 2, level: 2, pid: 2000, name: blacksmoke2/b2dwinfo/inxls, title: 导入, type: 1, status: 1, condition: , sort: 8}, { + // id: 2006, jgid: 2, level: 2, pid: 2000, name: blacksmoke2/b2dwinfo/outxls, title: 导出, type: 1, status: 1, condition: , sort: 7}, {id: 2005, jgid: 2, level: 2, pid: 2000, na + // me: blacksmoke2/b2dwinfo/del, title: 删除, type: 1, status: 1, condition: , sort: 4}, {id: 2004, jgid: 2, level: 2, pid: 2000, name: blacksmoke2/b2dwinfo/lock, title: 锁定, t + // ype: 1, status: 1, condition: , sort: 3}, {id: 2003, jgid: 2, level: 2, pid: 2000, name: blacksmoke2/b2dwinfo/edit, title: 编辑, type: 1, status: 1, condition: , sort: 2}, {i + // d: 2002, jgid: 2, level: 2, pid: 2000, name: blacksmoke2/b2dwinfo/add, title: 新增, type: 1, status: 1, condition: , sort: 1}, {id: 2001, jgid: 2, level: 2, + // I/flutter ( 3512): map['page'] = 3 + // I/flutter ( 3512): _counter = 60 + // I/flutter ( 3512): _total = 78 + // I/flutter ( 3512): 第 3 次网络请求过程正常完成 + // I/flutter ( 3512): response = {"ret":200,"data":{"items":[{"id":1988,"jgid":2,"level":2,"pid":1984,"name":"blacksmoke2/b2ledxs/lock","title":"锁定","type":1,"status":1,"condi + // tion":"","sort":3},{"id":1987,"jgid":2,"level":2,"pid":1984,"name":"blacksmoke2/b2ledxs/edit","title":"编辑","type":1,"status":1,"condition":"","sort":2},{"id":1986,"jgid":2, + // "level":2,"pid":1984,"name":"blacksmoke2/b2ledxs/add","title":"新增","type":1,"status":1,"condition":"","sort":1},{"id":1985,"jgid":2,"level":2,"pid":1984,"name":"blacksmoke2 + // /b2ledxs/view","title":"查看","type":1,"status":1,"condition":"","sort":0},{"id":1984,"jgid":2,"level":1,"pid":1971,"name":"blacksmoke2/b2ledxs/index","title":"LED显示设置"," + // type":1,"status":1,"condition":"","sort":1},{"id":1983,"jgid":2,"level":2,"pid":1980,"name":"blacksmoke2/b2ts/shenhe","title":"审核","type":1,"status":1,"condition":"","sort" + // :5},{"id":1981,"jgid":2,"level":2,"pid":1980,"name":"blacksmoke2/b2ts/view","title":"查看","type":1,"status":1,"condition":"","sort":0},{"id":1980,"jgid":2, + // I/flutter ( 3512): mapRecordList['mapRecordListRet'] = {ret: 200, data: {items: [{id: 1988, jgid: 2, level: 2, pid: 1984, name: blacksmoke2/b2ledxs/lock, title: 锁定, type: 1 + // , status: 1, condition: , sort: 3}, {id: 1987, jgid: 2, level: 2, pid: 1984, name: blacksmoke2/b2ledxs/edit, title: 编辑, type: 1, status: 1, condition: , sort: 2}, {id: 1986 + // , jgid: 2, level: 2, pid: 1984, name: blacksmoke2/b2ledxs/add, title: 新增, type: 1, status: 1, condition: , sort: 1}, {id: 1985, jgid: 2, level: 2, pid: 1984, name: blacksmo + // ke2/b2ledxs/view, title: 查看, type: 1, status: 1, condition: , sort: 0}, {id: 1984, jgid: 2, level: 1, pid: 1971, name: blacksmoke2/b2ledxs/index, title: LED显示设置, type: + // 1, status: 1, condition: , sort: 1}, {id: 1983, jgid: 2, level: 2, pid: 1980, name: blacksmoke2/b2ts/shenhe, title: 审核, type: 1, status: 1, condition: , sort: 5}, {id: 1981 + // , jgid: 2, level: 2, pid: 1980, name: blacksmoke2/b2ts/view, title: 查看, type: 1, status: 1, condition: , sort: 0}, {id: 1980, jgid: 2, level: 1, pid: 1970 + // I/flutter ( 3512): map['page'] = 4 + // I/flutter ( 3512): _counter = 78 + // I/flutter ( 3512): _total = 78 + + ///6、获取后台功能分类分页列表数据,然后获取用户功能权限索引map,便于直观理解和处理 + // getRecordList(api: ServicePath.getUserAuthListUrl).then((map) { + // mapUserAuthList = map; + // getUserAuth(); + // }); + + ///7、获取后台功能分类分页列表数据,然后获取用户功能路径索引map,便于直观理解和处理 + // getRecordList(api: ServicePath.getUserAuthListUrl).then((map) { + // mapUserAuthList = map; + // getUserAuthMap(value: 'name'); + // }); + + ///8、测试新的视频地址 rtsp://125.64.218.67:9901/rtp/gb_play_34020000001320013016_34020000001320013016 + // urlnew = 'rtsp://125.64.218.67:9901/rtp/gb_play_34020000001320013016_34020000001320013016'; + // Navigator.of(context).push(MaterialPageRoute( + // builder: (context) => PlayerPro( + // url: urlnew, + // title: '点位视频测试', + // ))); + + ///9、测试新的视频地址 rtmp://125.64.218.67:9901/rtp/gb_play_34020000001320013016_34020000001320013016 + // urlnew = 'rtmp://125.64.218.67:9901/rtp/gb_play_34020000001320013016_34020000001320013016'; + // Navigator.of(context).push(MaterialPageRoute( + // builder: (context) => PlayerPro( + // url: urlnew, + // title: '点位视频测试', + // ))); + + // getRecordList(api: ServicePath.getUserAuthListUrl).then((map) { + // mapUserAuthList = map; + // // var _jsonStr = json.encode(getUserAuthMap(value: 'name')); + // // List _list = json.decode(_jsonStr); + // + // Map map1 = {"name": "AllenSu", "area": "郑州", "sex": "男", "age": 18}; + // String _jsonStr = json.encode(map1); + // //print('_jsonStr = $_jsonStr'); + // //List _list = json.decode(_jsonStr); + // // Unhandled Exception: type '_InternalLinkedHashMap' is not a subtype of type 'List' + // Map map2 = json.decode(_jsonStr); + // //print('_list = ${_list}'); + // + // String str = json_print(map2, 1); + // List list = ['test', 'dsaf', 'swer']; + // //segmentPrint(str); + // //print('_jsonStr = ${json_print(map2, 1)}'); + // + // print('str = ${str}'); + // my_segmentPrint(str); + // + // }); + } + + Future loadCache() async { + Directory tempDir = await getTemporaryDirectory(); + double value = await _getTotalSizeOfFilesInDir(tempDir); + print('临时目录大小: ' + value.toString()); + //清除缓存 + delDir(tempDir); + } + + //递归方式删除目录 + Future delDir(FileSystemEntity file) async { + if (file is Directory) { + final List children = file.listSync(); + for (final FileSystemEntity child in children) { + await delDir(child); + } + } + await file.delete(); + } + + OnTap_ClearCache() async { + Directory tempDir = await getTemporaryDirectory(); + print('tempDir: ' + tempDir.path); + double SizeOfFiles = await _getTotalSizeOfFilesInDir(tempDir) / 1000000; + print('临时目录大小: ${SizeOfFiles.toString()} MB'); + + bool ret = await showDialog( + context: context, + builder: (context) { + myController.text = ''; + return CustomDialogF( + title: "选择操作", + content: '缓存大小:${SizeOfFiles.toString()} MB,是否清除', + ); + }); + + print('ret: $ret'); + if (ret) { + print('清除缓存...'); + //清除内存 + PaintingBinding.instance.imageCache.clear(); + //清除缓存 + delDir(tempDir); + //清空SharedPreferences + Storage.clear(); + } + } +} diff --git a/lib/pages/tabs/page_details.dart b/lib/pages/tabs/page_details.dart new file mode 100644 index 0000000..f993143 --- /dev/null +++ b/lib/pages/tabs/page_details.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import '../../custom_icons/icons_data.dart'; +import '../../custom_icons/icons_name.dart'; + +class DetailsPage extends StatelessWidget { + final String arguments; //id of icon + + DetailsPage({Key key, this.arguments = 'text'}) : super(key: key); + + @override + Widget build(BuildContext context) { + String s = '图标ID :$arguments' + '\n图标名称:' + iconNameList[int.parse(arguments)]; + + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: Icon(Icons.arrow_back), + onPressed: () { + print('返回上一页'); + Navigator.pop(context); + }, + ), + title: Text('联系人详情页'), + ), + body: FutureBuilder( + future: null, + builder: (context, snapshot) { + if (snapshot.hasData) { + return Stack( + children: [ + ListView( + children: [ + Text(arguments), + ], + ), + Positioned( + bottom: 0, + left: 0, + child: null, + ) + ], + ); + } else { + //return Text('加载中........'); + return Container( + alignment: Alignment(0, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + iconList[int.parse(arguments)], + size: 200, + color: Colors.black, + ), + SizedBox(height: 10,), + Text(s), + ], + ), + ); + } + }, + ), + ); + } +} diff --git a/lib/provider/CheckOut.dart b/lib/provider/CheckOut.dart new file mode 100644 index 0000000..8325d5a --- /dev/null +++ b/lib/provider/CheckOut.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +class CheckOut with ChangeNotifier { + List _checkOutListData = []; //购物车数据 + List get checkOutListData => this._checkOutListData; + + changeCheckOutListData(data){ + this._checkOutListData=data; + notifyListeners(); + } + +} diff --git a/lib/provider/player_ratio.dart b/lib/provider/player_ratio.dart new file mode 100644 index 0000000..5280d17 --- /dev/null +++ b/lib/provider/player_ratio.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +class PlayerRatioProvide with ChangeNotifier { + double scale = 1.0; //播放区域缩放比例 + Offset offset = Offset.zero; + double deltaX = 0; + double deltaY = 0; + + //改变播放区域缩放比例 + changeScale(double scale) { + this.scale = scale; + notifyListeners(); + } + + //改变播放区域偏移位置 + changeOffset(Offset offset) { + this.offset = offset; + notifyListeners(); + } + + changeDeltaX(double deltaX) { + this.deltaX = deltaX; + notifyListeners(); + } + + changeDeltaY(double deltaY) { + this.deltaY = deltaY; + notifyListeners(); + } +} diff --git a/lib/provider/player_region.dart b/lib/provider/player_region.dart new file mode 100644 index 0000000..7cf1025 --- /dev/null +++ b/lib/provider/player_region.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; + +class PlayerRegionProvide with ChangeNotifier { + String playerText = '播放'; + IconData playerIcon = Icons.play_arrow; + + //改变图标 + changePlayerState(bool bPlaying) { + playerText = (bPlaying) ? '暂停' : '播放'; + playerIcon = (bPlaying) ? Icons.pause : Icons.play_arrow; + notifyListeners(); + } + +} diff --git a/lib/res/listContacts.dart b/lib/res/listContacts.dart new file mode 100644 index 0000000..42188c0 --- /dev/null +++ b/lib/res/listContacts.dart @@ -0,0 +1,103 @@ +Map mapUserInfoRet = { + "ret": 200, + "data": { + "profile": { + "id": 2, + "username": "test", + "nickname": "", + "reg_time": 1606653977, + "avatar": "", + "mobile": null, + "email": null + } + }, + "msg": "" +}; + +Map mapUserInfo = { + "id": 1, + "username": "dogstar", + "nickname": "", + "reg_time": 1585624262, + "avatar": "", + "mobile": "", + "sex": 0, + "email": "" +}; + +Map mapUserInfoText = { + "id": '用户ID', + "username": "用户名", + "nickname": "昵称", + "reg_time": "注册时间", + "avatar": "头像", + "mobile": "手机", + "email": "email", +}; + +Map mapUserInfoModifyable = { + "id": false, + "username": false, + "nickname": false, + "reg_time": false, + "avatar": false, + "mobile": false, + "email": false, +}; + +List listContacts = [ + { + "id": 15, + "username": '张三', + "nickname": null, + "avatar": null, + "reg_time": 1606653977, + "mobile": '133xxxxxxxx', + "email": '1234@qq.com', + }, + { + "id": 16, + "username": '李四', + "nickname": null, + "avatar": null, + "reg_time": 1606653977, + "mobile": '136xxxxxxxx', + "email": '1234@qq.com', + }, + { + "id": 16, + "username": '王五', + "nickname": null, + "avatar": null, + "reg_time": 1606653977, + "mobile": '137xxxxxxxx', + "email": '1234@qq.com', + }, + { + "id": 16, + "username": '朱六', + "nickname": null, + "avatar": null, + "reg_time": 1606653977, + "mobile": '139xxxxxxxx', + "email": '1234@qq.com', + }, + { + "id": 16, + "username": '江七', + "nickname": null, + "avatar": null, + "reg_time": 1606653977, + "mobile": '186xxxxxxxx', + "email": '1234@qq.com', + }, + { + "id": 16, + "username": '丁八', + "nickname": null, + "avatar": null, + "reg_time": 1606653977, + "mobile": '180xxxxxxxx', + "email": '1234@qq.com', + }, +]; diff --git a/lib/res/listData.dart b/lib/res/listData.dart new file mode 100644 index 0000000..5057b9c --- /dev/null +++ b/lib/res/listData.dart @@ -0,0 +1,38 @@ + List listData=[ + { + "title": 'Candy Shop', + "author": 'Mohamed Chahin', + "imageUrl": 'https://www.itying.com/images/flutter/1.png', + }, + { + "title": 'Childhood in a picture', + "author": 'Google', + "imageUrl": 'https://www.itying.com/images/flutter/2.png', + }, + { + "title": 'Alibaba Shop', + "author": 'Alibaba', + "imageUrl": 'https://www.itying.com/images/flutter/3.png', + }, + { + "title": 'Candy Shop', + "author": 'Mohamed Chahin', + "imageUrl": 'https://www.itying.com/images/flutter/4.png', + }, + { + "title": 'Tornado', + "author": 'Mohamed Chahin', + "imageUrl": 'https://www.itying.com/images/flutter/5.png', + }, + { + "title": 'Undo', + "author": 'Mohamed Chahin', + "imageUrl": 'https://www.itying.com/images/flutter/6.png', + }, + { + "title": 'white-dragon', + "author": 'Mohamed Chahin', + "imageUrl": 'https://www.itying.com/images/flutter/7.png', + } + + ]; diff --git a/lib/routers/router.dart b/lib/routers/router.dart new file mode 100644 index 0000000..3b8e4d1 --- /dev/null +++ b/lib/routers/router.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:hyzp_ybqx/pages/Login/FaceLogin.dart'; +import 'package:hyzp_ybqx/pages/Login/FaceReg.dart'; +import 'package:hyzp_ybqx/pages/Login/LoginTabs2.dart'; + +//import '../pages/CheckOut.dart'; +import '../pages/Address/AddressAdd.dart'; +import '../pages/Address/AddressEdit.dart'; +import '../pages/Address/AddressList.dart'; +import '../pages/Login/ForgotPassword.dart'; +//import '../pages/Login/TakePictuer.dart'; +import '../pages/Login/TakePictuer.dart'; +import '../pages/Order.dart'; +import '../pages/OrderInfo.dart'; +import '../pages/Pay.dart'; +import '../pages/ProductList.dart'; +import '../pages/RegisterFirst.dart'; +import '../pages/RegisterSecond.dart'; +import '../pages/RegisterThird.dart'; +import '../pages/Search.dart'; +// import '../pages/ProductContent.dart'; +// import '../pages/tabs/Cart.dart'; + +import '../pages/tabs/Tabs.dart'; +import '../pages/tabs/page_details.dart'; + +//配置路由 +final routes = { + '/': (context) => LoginTabs2(), + '/tabs': (context, {arguments}) => Tabs(arguments: arguments), + '/search': (context) => SearchPage(), + //'/cart': (context) => CartPage(), + + '/forgotPassword': (context) => ForgotPassword(), + '/faceReg_take_pictuer': (context, {arguments}) => TakePictuer(arguments: arguments), + '/fackReg': (context, {arguments}) => FaceReg(arguments: arguments), + + '/faceLogin_take_pictuer': (context, {arguments}) => TakePictuer(arguments: arguments), + '/fackLogin': (context, {arguments}) => FaceLogin(arguments: arguments), + + '/details': (context, {arguments}) => DetailsPage(arguments: arguments), + + '/registerFirst': (context) => RegisterFirstPage(), + '/registerSecond': (context, {arguments}) => RegisterSecondPage(arguments: arguments), + '/registerThird': (context, {arguments}) => RegisterThirdPage(arguments: arguments), + '/productList': (context, {arguments}) => ProductListPage(arguments: arguments), + //'/productContent': (context, {arguments}) => ProductContentPage(arguments: arguments), + //'/checkOut': (context) => CheckOutPage(), + '/addressAdd': (context) => AddressAddPage(), + '/addressEdit': (context, {arguments}) => AddressEditPage(arguments: arguments), + '/addressList': (context) => AddressListPage(), + '/pay': (context) => PayPage(), + '/order': (context) => OrderPage(), + '/orderinfo': (context) => OrderInfoPage(), + //'/page5player': (context, {arguments}) => PlayerPro(arguments: arguments), +}; + +//统一处理命名路由传参 +//var onGenerateRoute = (RouteSettings settings) { +Route onGenerateRoute(RouteSettings settings) { + String name = settings.name; + Function pageContentBuilder = routes[name]; + if (pageContentBuilder != null) { + if (settings.arguments != null) { + Route route = MaterialPageRoute( + builder: (context) => pageContentBuilder(context, arguments: settings.arguments)); + return route; + } else { + Route route = MaterialPageRoute(builder: (context) => pageContentBuilder(context)); + return route; + } + } +} diff --git a/lib/services/CartServices.dart b/lib/services/CartServices.dart new file mode 100644 index 0000000..203d3d0 --- /dev/null +++ b/lib/services/CartServices.dart @@ -0,0 +1,118 @@ +import 'dart:convert'; +import 'Storage.dart'; +import '../config/Config.dart'; + +class CartServices { + static addCart(item) async { + //把对象转换成Map类型的数据 + item = CartServices.formatCartData(item); + + /* + 1、获取本地存储的cartList数据 + 2、判断cartList是否有数据 + 有数据: + 1、判断购物车有没有当前数据: + 有当前数据: + 1、让购物车中的当前数据数量 等于以前的数量+现在的数量 + 2、重新写入本地存储 + + 没有当前数据: + 1、把购物车cartList的数据和当前数据拼接,拼接后重新写入本地存储。 + + 没有数据: + 1、把当前商品数据以及属性数据放在数组中然后写入本地存储 + + + + List list=[ + {"_id": "1", + "title": "磨砂牛皮男休闲鞋-有属性", + "price": 688, + "selectedAttr": "牛皮 ,系带,黄色", + "count": 4, + "pic":"public\upload\RinsvExKu7Ed-ocs_7W1DxYO.png", + "checked": true + }, + {"_id": "2", + "title": "磨xxxxxxxxxxxxx", + "price": 688, + "selectedAttr": "牛皮 ,系带,黄色", + "count": 2, + "pic":"public\upload\RinsvExKu7Ed-ocs_7W1DxYO.png", + "checked": true + } + + ]; + + + */ + + try { + List cartListData = json.decode(await Storage.getString('cartList')); + + //判断购物车有没有当前数据 + bool hasData = cartListData.any((value) { + return value['_id'] == item['_id'] && + value['selectedAttr'] == item['selectedAttr']; + }); + + if (hasData) { + for (var i = 0; i < cartListData.length; i++) { + if (cartListData[i]['_id'] == item['_id'] && + cartListData[i]['selectedAttr'] == item['selectedAttr']) { + cartListData[i]["count"] = cartListData[i]["count"] + 1; + } + } + await Storage.setString('cartList', json.encode(cartListData)); + } else { + cartListData.add(item); + await Storage.setString('cartList', json.encode(cartListData)); + } + } catch (e) { + List tempList = []; + tempList.add(item); + await Storage.setString('cartList', json.encode(tempList)); + } + } + + //过滤数据 + static formatCartData(item) { + //处理图片 + String pic = item.pic; + pic = Config.domain + pic.replaceAll('\\', '/'); + + final Map data = new Map(); + data['_id'] = item.sId; + data['title'] = item.num; + //处理 string 和int类型的价格 + if (item.price is int || item.price is double) { + data['price'] = item.price; + } else { + data['price'] = double.parse(item.price); + } + data['selectedAttr'] = item.selectedAttr; + data['count'] = item.count; + data['pic'] = pic; + //是否选中 + data['checked'] = true; + return data; + } + + //获取购物车选中的数据 + static getCheckOutData() async { + List cartListData = []; + List tempCheckOutData = []; + try { + cartListData = json.decode(await Storage.getString('cartList')); + } catch (e) { + cartListData = []; + } + for (var i = 0; i < cartListData.length; i++) { + if (cartListData[i]["checked"] == true) { + tempCheckOutData.add(cartListData[i]); + } + } + + return tempCheckOutData; + } +} diff --git a/lib/services/CheckOutServices.dart b/lib/services/CheckOutServices.dart new file mode 100644 index 0000000..c80c70c --- /dev/null +++ b/lib/services/CheckOutServices.dart @@ -0,0 +1,35 @@ +import 'dart:convert'; +import '../services/Storage.dart'; +class CheckOutServices{ + //计算总价 + static getAllPrice(checkOutListData) { + var tempAllPrice=0.0; + for (var i = 0; i < checkOutListData.length; i++) { + if (checkOutListData[i]["checked"] == true) { + tempAllPrice += checkOutListData[i]["price"] * checkOutListData[i]["count"]; + } + } + return tempAllPrice; + } + static removeUnSelectedCartItem() async{ + + List _cartList=[]; + List _tempList=[]; + //获取购物车的数据 + try { + List cartListData = json.decode(await Storage.getString('cartList')); + _cartList = cartListData; + } catch (e) { + _cartList = []; + } + + for (var i = 0; i < _cartList.length; i++) { + if (_cartList[i]["checked"] == false) { + _tempList.add(_cartList[i]); + } + } + + Storage.setString("cartList", json.encode(_tempList)); + + } +} \ No newline at end of file diff --git a/lib/services/EventBus.dart b/lib/services/EventBus.dart new file mode 100644 index 0000000..67192b9 --- /dev/null +++ b/lib/services/EventBus.dart @@ -0,0 +1,215 @@ +import 'package:event_bus/event_bus.dart'; + +//Bus 初始化 + +EventBus eventBus = EventBus(); + +//监听统计数据改变事件 +class StatisDataUpdate { + String str; + + StatisDataUpdate(String _str) { + this.str = _str; + } +} + +//监听 g_userInfo.userGroupIDlist 更新事件 +class GroupIdUpdateEvent { + String str; + + GroupIdUpdateEvent(String _str) { + this.str = _str; + } +} + +//监听人脸注册数据更新事件 +class FaceRegUpdateEvent { + String str; + + FaceRegUpdateEvent(String _str) { + this.str = _str; + } +} + +//监听 选择点位过滤 更新事件 +class SelectDwfliterUpdateEvent { + String str; + String selectedValue; + + SelectDwfliterUpdateEvent(String _str, String _selectedValue) { + this.str = _str; + this.selectedValue = _selectedValue; + } +} + +//监听 选择显示违章记录 更新事件 +class SelectWzjlUpdateEvent { + String str; + String selectedValue; + + SelectWzjlUpdateEvent(String _str, String _selectedValue) { + this.str = _str; + this.selectedValue = _selectedValue; + } +} + +//选择LED点位 更新事件 +class SelectLedDwUpdateEvent { + String str; + String selectedValue; + + SelectLedDwUpdateEvent(String _str, String _selectedValue) { + this.str = _str; + this.selectedValue = _selectedValue; + } +} + +//LED字幕添加广播 +class LedXsxxUpdateEvent { + String str; + + LedXsxxUpdateEvent(String str) { + this.str = str; + } +} + +//监听违章信息推送状态更新事件 +// class HycsTsztUpdateEvent { +// String str; +// HycsTsztUpdateEvent(String str) { +// this.str = str; +// } +// } + +//黑烟初审数据审核广播 +class HycsDataUpdateEvent { + String str; + + HycsDataUpdateEvent(String str) { + this.str = str; + } +} + +//监听点位视频信息数据更新事件 +class DwspUpdateEvent { + String str; + + DwspUpdateEvent(String str) { + this.str = str; + } +} + +//黑烟初审数据审核Radio选项改变广播 +class HycsDataAuditRadioEvent { + String str; + int selectedRadio; + + HycsDataAuditRadioEvent(String str, int selectedRadio) { + this.str = str; + this.selectedRadio = selectedRadio; + } +} + +//黑烟初审数据审核Dropdown选项改变广播 +class HycsDataAuditDropdownEvent { + String str; + String selectedValue; + + HycsDataAuditDropdownEvent(String str, String _selectedValse) { + this.str = str; + this.selectedValue = _selectedValse; + } +} + +//黑烟审核 sfyc 改变广播 +class HycsDataAuditSfyc { + String str; + + HycsDataAuditSfyc(String str) { + this.str = str; + } +} + +//黑烟审核推送交警Checkbox改变广播 +class HycsDataAuditCheckboxButton { + String str; + bool checkValue; + + HycsDataAuditCheckboxButton(String str, bool _checkValue) { + this.str = str; + this.checkValue = _checkValue; + } +} + +//违章信息数据审核广播 +class WzxxDataAuditEvent { + String str; + + WzxxDataAuditEvent(String str) { + this.str = str; + } +} + +//设备管理信息数据更新广播 +class SbglDataUpdateEvent { + String str; + + SbglDataUpdateEvent(String str) { + this.str = str; + } +} + +//违章信息数据更新广播 +class WzxxDataUpdateEvent { + String str; + + WzxxDataUpdateEvent(String str) { + this.str = str; + } +} + +//违章信息Listview滚动广播 +class WzxxDataScrollEvent { + int firstIndex = 0; //ListView当前显示页面首项0基序号 + int lastIndex = 0; //ListView当前显示页面末项0基序号 + WzxxDataScrollEvent(int _firstIndex, int _lastIndex) { + this.firstIndex = _firstIndex; + this.lastIndex = _lastIndex; + } +} + +//商品详情广播数据 +class ProductContentEvent { + String str; + + ProductContentEvent(String str) { + this.str = str; + } +} + +//用户中心广播 +class UserEvent { + String str; + + UserEvent(String str) { + this.str = str; + } +} + +//收货地址广播 +class AddressEvent { + String str; + + AddressEvent(String str) { + this.str = str; + } +} + +//结算页面 +class CheckOutEvent { + String str; + + CheckOutEvent(String str) { + this.str = str; + } +} diff --git a/lib/services/SearchServices.dart b/lib/services/SearchServices.dart new file mode 100644 index 0000000..6119f42 --- /dev/null +++ b/lib/services/SearchServices.dart @@ -0,0 +1,62 @@ +import 'dart:convert'; + +import 'Storage.dart'; + +class SearchServices { + static setHistoryData(keywords) async { + /* + 1、获取本地存储里面的数据 (searchList) + + 2、判断本地存储是否有数据 + + 2.1、如果有数据 + + 1、读取本地存储的数据 + 2、判断本地存储中有没有当前数据, + 如果有不做操作、 + 如果没有当前数据,本地存储的数据和当前数据拼接后重新写入 + + + 2.2、如果没有数据 + + 直接把当前数据放在数组中写入到本地存储 + + + */ + + try { + List searchListData = json.decode(await Storage.getString('searchList')); + + print(searchListData); + var hasData = searchListData.any((v) { + return v == keywords; + }); + if (!hasData) { + searchListData.add(keywords); + await Storage.setString('searchList', json.encode(searchListData)); + } + } catch (e) { + List tempList = new List(); + tempList.add(keywords); + await Storage.setString('searchList', json.encode(tempList)); + } + } + static getHistoryList() async{ + try { + List searchListData = json.decode(await Storage.getString('searchList')); + return searchListData; + } catch (e) { + return []; + } + } + + static clearHistoryList() async{ + await Storage.remove('searchList'); + } + static removeHistoryData(keywords) async{ + List searchListData = json.decode(await Storage.getString('searchList')); + searchListData.remove(keywords); + await Storage.setString('searchList', json.encode(searchListData)); + } + +} diff --git a/lib/services/ServiceLocator.dart b/lib/services/ServiceLocator.dart new file mode 100644 index 0000000..2597fa7 --- /dev/null +++ b/lib/services/ServiceLocator.dart @@ -0,0 +1,16 @@ +import 'package:get_it/get_it.dart'; +import './TelAndSmsService.dart'; + +GetIt locator = GetIt.instance; + +bool locatorIsRegistered = false; + +void setupLocator() { + //解决登录后、退出登录、再次登录时,导致 TelAndSmsService 重复注册红屏报错问题 + //I/flutter ( 6555): The following ArgumentError was thrown building Builder: + // I/flutter ( 6555): Invalid argument(s): Object/factory with type TelAndSmsService is already registered inside GetIt. + if (!locatorIsRegistered) { + locatorIsRegistered = true; + locator.registerSingleton(TelAndSmsService()); + } +} diff --git a/lib/services/SignServices.dart b/lib/services/SignServices.dart new file mode 100644 index 0000000..1a9c38d --- /dev/null +++ b/lib/services/SignServices.dart @@ -0,0 +1,17 @@ +import 'dart:convert'; +import 'package:crypto/crypto.dart'; + +class SignServices{ + + static getSign(json){ + + List attrKeys=json.keys.toList(); + attrKeys.sort(); //排序 ASCII 字符顺序进行升序排列 + String str=''; + for(var i=0;i setString(String key, String value) async { + SharedPreferences sp = await SharedPreferences.getInstance(); + sp.setString(key, value); + } + + static Future getString(String key) async { + SharedPreferences sp = await SharedPreferences.getInstance(); + return sp.getString(key); + } + + static Future setBool(String key, bool value) async { + SharedPreferences sp = await SharedPreferences.getInstance(); + sp.setBool(key, value); + } + + static Future getBool(String key) async { + SharedPreferences sp = await SharedPreferences.getInstance(); + return sp.getBool(key); + } + + static Future remove(String key) async { + SharedPreferences sp = await SharedPreferences.getInstance(); + sp.remove(key); + } + + static Future clear() async { + SharedPreferences sp = await SharedPreferences.getInstance(); + sp.clear(); + } +} diff --git a/lib/services/TelAndSmsService.dart b/lib/services/TelAndSmsService.dart new file mode 100644 index 0000000..c871282 --- /dev/null +++ b/lib/services/TelAndSmsService.dart @@ -0,0 +1,7 @@ +import 'package:url_launcher/url_launcher.dart'; + +class TelAndSmsService { + void call(String number) => launch("tel:$number"); + void sendSms(String number) => launch("sms:$number"); + void sendEmail(String email) => launch("mailto:$email"); +} diff --git a/lib/services/UserServices.dart b/lib/services/UserServices.dart new file mode 100644 index 0000000..0d32710 --- /dev/null +++ b/lib/services/UserServices.dart @@ -0,0 +1,26 @@ + +import '../services/Storage.dart'; +import 'dart:convert'; + +class UserServices{ + static getUserInfo() async{ + List userinfo; + try { + List userInfoData = json.decode(await Storage.getString('userInfo')); + userinfo = userInfoData; + } catch (e) { + userinfo = []; + } + return userinfo; + } + static getUserLoginState() async{ + var userInfo=await UserServices.getUserInfo(); + if(userInfo.length>0&&userInfo[0]["username"]!=""){ + return true; + } + return false; + } + static loginOut(){ + Storage.remove('userInfo'); + } +} \ No newline at end of file diff --git a/lib/widget/CarNumberAndCpysItems.dart b/lib/widget/CarNumberAndCpysItems.dart new file mode 100644 index 0000000..40c348a --- /dev/null +++ b/lib/widget/CarNumberAndCpysItems.dart @@ -0,0 +1,193 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/screen_util.dart'; +import '../components/hyxx_data_handle.dart'; +import '../services/EventBus.dart'; + +class CarNumberAndCpysItems extends StatefulWidget { + int index; + String initValue; + + CarNumberAndCpysItems( + this.index, this.initValue); //I don't know what is this index for but I will put it in anyway + @override + _CarNumberAndCpysItemsState createState() => _CarNumberAndCpysItemsState(); +} + +class _CarNumberAndCpysItemsState extends State { + List> _dropDownMenuItems; + String selectedValue; + + @override + void initState() { + super.initState(); + selectedValue = widget.initValue; + _dropDownMenuItems = getDropDownMenuItems(); + } + + List> getDropDownMenuItems() { + List> items = []; + int len = cpysList.length; + for (int i = 0; i < len; i++) { + items.add( + DropdownMenuItem( + value: cpysList[i].cpysText, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + cpysList[i].cpysText, + style: TextStyle( + color: cpysList[i].cpysFont, + background: Paint()..color = cpysList[i].cpysBackground), + ), + (selectedValue == cpysList[i].cpysText) + ? Icon( + Icons.check, + //color: cpysList[i].cpysBackground, + size: 16, + ) + : SizedBox( + width: 0, + ), + ], + ), + ), + ); + } + return items; + } + + Widget getDropdownButton() { + //DropdownButton默认有一条下划线,DropdownButtonHideUnderline去除下划线 + //更改图标亮度 + return Theme( + //data: Theme.of(context).copyWith(primaryColor: cpysList[getIndexOfCpysList(colorText: selectedValue)].cpysFont), + data: Theme.of(context).copyWith(brightness: Brightness.dark), + child: DropdownButtonHideUnderline( + child: DropdownButton( + iconEnabledColor: cpysList[getIndexOfCpysList(colorText: selectedValue)].cpysFont, + value: selectedValue, + items: _dropDownMenuItems, + onChanged: (String _selectedFruit) { + selectedValue = _selectedFruit; + _dropDownMenuItems = getDropDownMenuItems(); + //黑烟初审数据审核Dropdown选项改变广播 + eventBus.fire(HycsDataAuditDropdownEvent('黑烟初审数据审核Dropdown选项已改变', selectedValue)); + setState(() {}); + print('selectedValue = $selectedValue'); + }, + ), + ), + ); + } + + //Tab页面中的车牌号码、车牌颜色组件 + Widget getCarNumberAndCpys(int i) { + return Container( + width: ScreenUtil().setWidth(1022), + height: ScreenUtil().setHeight(my_listTileHeight2 * 2 + 15), + child: Column(children: [ + //1、车牌号码 + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: ScreenUtil().setWidth(my_marginLeft2)), + Text('${mapGetZpjlGetDataSpecial['car_number'].fieldText}: ', + style: TextStyle(fontSize: my_fontSize)), + Container( + alignment: Alignment(-1, 0), + //widthTrail = 400报错,360刚能显示,300换行,260 + height: ScreenUtil().setHeight(my_listTileHeight2), //最大高度 + width: ScreenUtil().setWidth(400), + child: TextField( + textAlign: TextAlign.center, + style: TextStyle( + fontSize: my_fontSize, + //color: cpysList[getIndexOfCpysList(colorText: listGetZpjl[i]['cpys'])].cpysFont, + //color: cpysList[getIndexOfCpysList(colorText: topTabs_map['cpysText_List'][i])].cpysFont, + color: cpysList[getIndexOfCpysList(colorText: selectedValue)].cpysFont, + ), + // background: Paint() + // ..color = cpysList[getIndexOfCpysList(colorText: listGetZpjl[i]['cpys'])] + // .cpysBackground), + decoration: InputDecoration( + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(width: 2.0), borderRadius: BorderRadius.circular(3.0)), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(width: 2.0), borderRadius: BorderRadius.circular(3.0)), + //prefixText: "pre", + filled: true, + //fillColor: cpysList[getIndexOfCpysList(colorText: listGetZpjl[i]['cpys'])].cpysBackground, + //fillColor: cpysList[getIndexOfCpysList(colorText: topTabs_map['cpysText_List'][i])].cpysBackground, + fillColor: cpysList[getIndexOfCpysList(colorText: selectedValue)].cpysBackground, + hintText: '车牌号码', + //border: InputBorder.none, //TextField去掉下划线 + //contentPadding: EdgeInsets.only(right: 0), + //contentPadding: const EdgeInsets.symmetric(vertical: _textFieldHeight), + //contentPadding: EdgeInsets.symmetric(vertical: _textFieldHeight), + contentPadding: EdgeInsets.all(0), + //contentPadding: EdgeInsets.only(top: 0), + + // border: OutlineInputBorder( + // //borderRadius: BorderRadius.circular(1.0), + // borderSide: BorderSide( + // //color: cpysList[getIndexOfCpysList(colorText: listGetZpjl[i]['cpys'])].cpysBorder, + // color: + // cpysList[getIndexOfCpysList(colorText: topTabs_map['cpysText_List'][i])] + // .cpysBorder, + // width: 2.0)), + ), + //controller: listZpljController[i][indexField], + controller: TextEditingController.fromValue(TextEditingValue( + text: listGetZpjl[i]['car_number'].toString(), + // 保持光标在最后 + selection: TextSelection.fromPosition(TextPosition( + affinity: TextAffinity.downstream, + offset: '${listGetZpjl[i]['car_number'].toString()}'.length)))), + enabled: true, + //利用控制器初始化文本 + onChanged: (value) { + listGetZpjl[i]['car_number'] = value; + }, + ), + ), + SizedBox(width: ScreenUtil().setWidth(my_marginLeft2)), + ], + ), + SizedBox(height: ScreenUtil().setHeight(15)), + //2、车牌颜色 + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: ScreenUtil().setWidth(my_marginLeft2)), + Text('车牌颜色: ', + style: TextStyle(fontSize: my_fontSize)), + Container( + alignment: Alignment(1, 0), + //widthTrail = 400报错,360刚能显示,300换行,260 + height: ScreenUtil().setHeight(my_listTileHeight2), //最大高度 + width: ScreenUtil().setWidth(400), + margin: EdgeInsets.only(bottom: 0), + padding: EdgeInsets.only(left: 0, bottom: 2), + decoration: BoxDecoration( + //color: cpysList[getIndexOfCpysList(colorText: listGetZpjl[i]['cpys'])].cpysBackground, + //color: cpysList[getIndexOfCpysList(colorText: topTabs_map['cpysText_List'][i])].cpysBackground, + color: cpysList[getIndexOfCpysList(colorText: selectedValue)].cpysBackground, + border: Border.all(color: Colors.black87, width: 2), + //边框圆角设置 + borderRadius: BorderRadius.vertical( + top: Radius.elliptical(3, 3), bottom: Radius.elliptical(3, 3)), + ), + child: getDropdownButton(), + ) + ], + ), + ],), + ); + } + + @override + Widget build(BuildContext context) { + return getCarNumberAndCpys(widget.index); + } +} diff --git a/lib/widget/CheckboxButtonItem.dart b/lib/widget/CheckboxButtonItem.dart new file mode 100644 index 0000000..3829752 --- /dev/null +++ b/lib/widget/CheckboxButtonItem.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/screen_util.dart'; +import '../services/EventBus.dart'; +import '../components/dioFun.dart'; + +//错误提示 +// This class (or a class which this class inherits from) is marked as '@immutable', but one or more of +// This class (or a class that this class inherits from) is marked as '@immutable', but one or more of +// +// 原本是自定义一个导航栏,需要传递一个参数(标题)过来。 +// class NavWidget extends StatelessWidget { +// String title; // +// NavWidget(this.title); +// 结果就提示了上面的错误。看了下有道翻译 +// 有道翻译:这个类(或该类继承自的一个类)被标记为“@不可变”,但是它的一个或多个实例字段不是final: NavWidget.title +// 意为StatelessWidget是一个不可变的widget,申明的title也应为不可变的。所以我就加了个申明的关键字final。然后警告就没了 +// +// 以下是修改后的代码,不再报错 +// class NavWidget extends StatelessWidget { +// final String title; // +// NavWidget(this.title); + +class CheckboxButtonItem extends StatefulWidget { + final int index; + + CheckboxButtonItem(this.index); //I don't know what is this index for but I will put it in anyway + @override + _CheckboxButtonItemState createState() => _CheckboxButtonItemState(); +} + +class _CheckboxButtonItemState extends State { + //try_setState(); //避免如下异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + @override + void initState() { + //黑烟审核推送交警Checkbox改变事件 + eventBus.on().listen((event) { + print(event.str); + try_setState(); //避免如下异常报错 + }); + + super.initState(); + } + + //自定义带说明图标按钮函数。点击说明文字有反应 + Widget _getCheckboxButton() { + return Container( + width: ScreenUtil().setWidth(330), + height: ScreenUtil().setWidth(110), + alignment: const Alignment(0, 1), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey[600], width: 1), + borderRadius: BorderRadius.circular(3), + ), + child: FlatButton( + padding: EdgeInsets.all(0), + onPressed: !fh_hyc + ? null + : () { + tsjj = !tsjj; + //黑烟审核推送交警Checkbox改变广播 + eventBus.fire(HycsDataAuditCheckboxButton('黑烟审核推送交警Checkbox已改变', tsjj)); + print('tsjj = $tsjj'); + setState(() {}); + }, + color: Colors.transparent, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text('同时推送交警', + style: TextStyle(color: !fh_hyc || 1 == sfyc ? Colors.grey : null, fontSize: 12)), + SizedBox(width: 1), + !fh_hyc || 1 == sfyc + ? Icon(Icons.check_box_outline_blank, color: Colors.grey) + : tsjj + ? Icon(Icons.check_box, color: Colors.blue) + : Icon(Icons.check_box_outline_blank, color: Colors.black), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return _getCheckboxButton(); + } +} diff --git a/lib/widget/ConfirmDialog01.dart b/lib/widget/ConfirmDialog01.dart new file mode 100644 index 0000000..da95325 --- /dev/null +++ b/lib/widget/ConfirmDialog01.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'dart:async'; + +enum Action { Ok, Cancel } + +class ConfirmDialog01 extends StatefulWidget { + ConfirmDialog01( + {this.title = '标题', this.msg = '提示信息', this.okText = '确定', this.cancelText = '取消'}); + + String title; + String msg; + String okText; + String cancelText; + + @override + _ConfirmDialog01State createState() => _ConfirmDialog01State(); +} + +class _ConfirmDialog01State extends State { + String _choice = 'Nothing'; + + Future _openAlertDialog() async { + final action = await showDialog( + context: context, + barrierDismissible: false, //// user must tap button! + builder: (BuildContext context) { + return AlertDialog( + title: Text(widget.title), + content: Text(widget.msg), + actions: [ + FlatButton( + child: Text(widget.cancelText), + onPressed: () { + Navigator.pop(context, Action.Cancel); + }, + ), + FlatButton( + child: Text(widget.okText), + onPressed: () { + Navigator.pop(context, Action.Ok); + }, + ), + ], + ); + }, + ); + + switch (action) { + case Action.Ok: + setState(() { + _choice = 'Ok'; + }); + break; + case Action.Cancel: + setState(() { + _choice = 'Cancel'; + }); + break; + default: + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('ConfirmDialog01'), + elevation: 0.0, + ), + body: Container( + padding: EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Your choice is: $_choice'), + SizedBox( + height: 16.0, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + RaisedButton( + child: Text('Open AlertDialog'), + onPressed: _openAlertDialog, + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/widget/DropdownItem.dart b/lib/widget/DropdownItem.dart new file mode 100644 index 0000000..f2e51c1 --- /dev/null +++ b/lib/widget/DropdownItem.dart @@ -0,0 +1,153 @@ +import 'package:flutter/material.dart'; +import '../services/EventBus.dart'; + +class DropdownItem extends StatefulWidget { + DropdownItem( + {@required this.listItems, + @required this.initValue, + @required this.dropdownEvent, + this.width = 180, + this.height = 35, + this.fontSize = 16}); + + List listItems; + String initValue; + double width; + double height; + double fontSize; + String dropdownEvent; + + //处理Dropdown选项改变广播 + //如果传递函数,将导致App崩溃 + //eventBus.fire(widget.dropdownEvent('Dropdown选项已改变', _selectedValue)); + //通过传字符串,用 switch 处理不会导致App崩溃 + //widget.dropdownEvent 取值: + //case 'SelectLedDwUpdateEvent': //监听 选择LED点位 更新事件 + //case 'SelectWzjlUpdateEvent': //监听 选择显示违章记录 更新事件 + + @override + _DropdownItemState createState() => _DropdownItemState(); +} + +class _DropdownItemState extends State { + List> _dropDownMenuItems; + String _selectedValue; + + //try_setState(); //避免异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + @override + void initState() { + _selectedValue = widget.initValue; + _dropDownMenuItems = getDropDownMenuItems(); + + if (widget.dropdownEvent == 'SelectWzjlUpdateEvent') { + //eventBus.fire(SelectWzjlUpdateEvent('external_SelectWzjlUpdateEvent', getWzjlString(_mapGetLedXsxxGetData["xsts"]))); + ///监听 选择显示违章记录 更新事件 + eventBus.on().listen((event) async { + print(event.str); + //只响应外部发送的'external_SelectWzjlUpdateEvent' + //不响应内部发送的'insider_SelectWzjlUpdateEvent' + if (event.str == 'external_SelectWzjlUpdateEvent') { + //刷新页面数据 + //print('external_SelectWzjlUpdateEvent:' '_selectedValue = $_selectedValue'); + _selectedValue = event.selectedValue; + _dropDownMenuItems = getDropDownMenuItems(); + try_setState(); //避免异常报错 + } + }); + } + + super.initState(); + } + + List> getDropDownMenuItems() { + List> items = []; + int len = widget.listItems.length; + for (int i = 0; i < len; i++) { + items.add( + DropdownMenuItem( + value: widget.listItems[i], + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + (_selectedValue == widget.listItems[i]) + ? Container( + color: Colors.black12, + child: Text(widget.listItems[i], style: TextStyle(fontSize: widget.fontSize)), + ) + : Text(widget.listItems[i], style: TextStyle(fontSize: widget.fontSize)), + SizedBox(width: 2), + (_selectedValue == widget.listItems[i]) + ? Icon(Icons.check, size: 18) + : SizedBox(width: 0), + ], + ), + ), + ); + } + return items; + } + + Widget getDropdownButton() { + //_selectedValue = initValue; + return DropdownButtonHideUnderline( + child: DropdownButton( + isExpanded: true, //让下拉图标始终位于末尾 + value: _selectedValue, + items: _dropDownMenuItems, + onChanged: (String _selValue) { + _selectedValue = _selValue; + print('_selectedValue = $_selectedValue'); + _dropDownMenuItems = getDropDownMenuItems(); + setState(() {}); + + ///处理Dropdown选项改变广播 + //如果传递函数,将导致App崩溃 + //eventBus.fire(widget.dropdownEvent('Dropdown选项已改变', _selectedValue)); + //通过传字符串,用 switch 处理不会导致App崩溃 + //widget.dropdownEvent 取值: + //case 'SelectLedDwUpdateEvent': //监听 选择LED点位 更新事件 + //case 'SelectWzjlUpdateEvent': //监听 选择显示违章记录 更新事件 + switch (widget.dropdownEvent) { + case 'SelectDwfliterUpdateEvent': //监听 选择点位 更新事件 + eventBus.fire(SelectDwfliterUpdateEvent('Dropdown选项已改变', _selectedValue)); + break; + case 'SelectLedDwUpdateEvent': //监听 选择LED点位 更新事件 + eventBus.fire(SelectLedDwUpdateEvent('Dropdown选项已改变', _selectedValue)); + break; + case 'SelectWzjlUpdateEvent': //监听 选择显示违章记录 更新事件 + eventBus.fire(SelectWzjlUpdateEvent('insider_SelectWzjlUpdateEvent', _selectedValue)); + break; + default: + break; + } + }, + ), + ); + } + + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment(-1, 0), + margin: EdgeInsets.only(bottom: 0), + padding: EdgeInsets.only(left: 5), + height: widget.height, + width: widget.width, + decoration: BoxDecoration( + //color: Colors.white, + border: Border.all(color: Colors.grey, width: 2), + borderRadius: BorderRadius.all(Radius.circular(3.0)), + ), + child: getDropdownButton(), + //child: Text('getDropdownButton'), + ); + } +} diff --git a/lib/widget/DropdownItems.dart b/lib/widget/DropdownItems.dart new file mode 100644 index 0000000..9e4dca2 --- /dev/null +++ b/lib/widget/DropdownItems.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import '../components/hyxx_data_handle.dart'; +import '../services/EventBus.dart'; + +class DropdownItems extends StatefulWidget { + int index; + String initValue; + + DropdownItems(this.index, this.initValue); //I don't know what is this index for but I will put it in anyway + @override + _DropdownItemsState createState() => _DropdownItemsState(); +} + +class _DropdownItemsState extends State { + List> _dropDownMenuItems; + String selectedValue; + + @override + void initState() { + super.initState(); + _dropDownMenuItems = getDropDownMenuItems(); + selectedValue = widget.initValue; + } + + List> getDropDownMenuItems() { + List> items = []; + int len = cpysList.length; + for (int i = 0; i < len; i++) { + items.add(DropdownMenuItem( + value: cpysList[i].cpysText, + child: Text(cpysList[i].cpysText, + style: TextStyle( + color: cpysList[i].cpysFont, + background: Paint()..color = cpysList[i].cpysBackground)))); + } + return items; + } + + Widget getDropdownButton() { + //DropdownButton默认有一条下划线,DropdownButtonHideUnderline去除下划线 + return DropdownButtonHideUnderline( + child: DropdownButton( + value: selectedValue, + items: _dropDownMenuItems, + onChanged: (String _selectedFruit) { + selectedValue = _selectedFruit; + //黑烟初审数据审核Dropdown选项改变广播 + eventBus.fire(HycsDataAuditDropdownEvent('黑烟初审数据审核Dropdown选项已改变', selectedValue)); + setState(() {}); + print('selectedValue = $selectedValue'); + }, + ), + ); + } + + @override + Widget build(BuildContext context) { + return getDropdownButton(); + } +} diff --git a/lib/widget/JdButton.dart b/lib/widget/JdButton.dart new file mode 100644 index 0000000..9e71edc --- /dev/null +++ b/lib/widget/JdButton.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +// +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class JdButton extends StatelessWidget { + final Color color; + final String text; + final double textSize; + final Object onTop; + final double height; + final double width; + final double circular; + + JdButton( + {Key key, + this.color = Colors.black, + this.text = "按钮", + this.textSize = 18, + this.onTop, + this.height = 68, + this.width = 130, + this.circular = 10}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: this.onTop, + child: Container( + alignment: Alignment(0, 0), + margin: EdgeInsets.all(5), + padding: EdgeInsets.all(5), + width: ScreenUtil().setWidth(this.width), + height: ScreenUtil().setHeight(this.height), + decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(circular)), + child: Text( + text, + style: TextStyle(color: Colors.white, fontSize: textSize), + ), + ), + ); + } +} diff --git a/lib/widget/JdText.dart b/lib/widget/JdText.dart new file mode 100644 index 0000000..3b78e4a --- /dev/null +++ b/lib/widget/JdText.dart @@ -0,0 +1,134 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class JdText extends StatefulWidget { + final String text; + final String title; + bool password; + final Object onChanged; + final int maxLines; + final double height; + TextEditingController controller; + final String endBtn; + + JdText( + {Key key, + this.text = "输入内容", + this.title = '标题', + this.password = false, + this.onChanged, + this.maxLines = 1, + this.height = 68, + this.controller, + this.endBtn}) + : super(key: key); + + _JdTextState createState() => _JdTextState(); +} + +class _JdTextState extends State { + Icon ShowHiddenIcon = Icon(Icons.more_horiz); + + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment(0, -1), + padding: EdgeInsets.only(top: 0), + height: ScreenUtil().setHeight(widget.height), + decoration: + BoxDecoration(border: Border(bottom: BorderSide(width: 1, color: Colors.black12))), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + alignment: Alignment(0, 0), + child: Container( + padding: EdgeInsets.only(top: 0, bottom: 0), + child: Text( + widget.title, + style: TextStyle( + fontSize: 17, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + //Flutter中Row中不能直接使用textfield控件 + Expanded( + child: TextField( + textAlignVertical: TextAlignVertical(y: 1.0), + controller: widget.controller, + maxLines: 1, + obscureText: widget.password, + decoration: InputDecoration( + //contentPadding: EdgeInsets.only(bottom: 16), + hintText: widget.text, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(30), borderSide: BorderSide.none), + ), + onChanged: widget.onChanged, + ), + ), + getBtn(), + ], + ), + ); + } + + Widget getBtn() { + Widget btn; + switch (widget.endBtn) { + case 'ClearBtn': + widget.controller = new TextEditingController(); + btn = Container( + // alignment: Alignment(0, -1), + // padding: EdgeInsets.only(bottom: 23), + child: IconButton( + icon: Icon(Icons.highlight_off), + onPressed: () { + setState(() { + widget.controller.clear(); + }); + }), + ); + break; + case 'ShowHiddenBtn': + btn = Container( + // alignment: Alignment(0, -1), + // padding: EdgeInsets.only(bottom: 23), + child: IconButton( + icon: ShowHiddenIcon, + onPressed: () { + widget.password = !widget.password; + setState(() { + ShowHiddenIcon = + widget.password ? Icon(Icons.more_horiz) : Icon(Icons.remove_red_eye); + }); + }), + ); + break; + case 'OutlineButton': + btn = Container( + child: OutlineButton( + borderSide: BorderSide(color: Colors.blue), + onPressed: () {}, + child: Container( + child: Text("获取验证码"), + ), + ), + ); + break; + default: + btn = SizedBox.shrink(); + break; + } + return btn; + } +} diff --git a/lib/widget/LoadingWidget.dart b/lib/widget/LoadingWidget.dart new file mode 100644 index 0000000..777d040 --- /dev/null +++ b/lib/widget/LoadingWidget.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +class LoadingWidget extends StatelessWidget { + const LoadingWidget({Key key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Center( + child: Padding( + padding: EdgeInsets.all(10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + CircularProgressIndicator( + strokeWidth: 1.0, + ),Text( + '加载中...', + style: TextStyle(fontSize: 16.0), + ) + ], + ), + ), + );; + } +} \ No newline at end of file diff --git a/lib/widget/app_bar.dart b/lib/widget/app_bar.dart new file mode 100644 index 0000000..5649cc0 --- /dev/null +++ b/lib/widget/app_bar.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; + +class SettingMenu extends StatelessWidget { + @override + Widget build(BuildContext context) { + return IconButton( + // action button + icon: Icon(Icons.settings), + onPressed: () { + debugPrint("Click Menu Setting"); + }, + ); + } +} + +class PeakAppBar extends StatelessWidget implements PreferredSizeWidget { + PeakAppBar({Key key, @required this.title, this.actions}) : super(key: key); + + final String title; + final List actions; + + PeakAppBar.defaultSetting({Key key, @required this.title}) : actions = null; + // todo settings page + //: actions=[SettingMenu()]; + + @override + Widget build(BuildContext context) { + return PreferredSize( + child: AppBar( + title: Text(this.title), + actions: this.actions, + ), + preferredSize: preferredSize, + ); + } + + @override + Size get preferredSize => Size.fromHeight(45.0); +} diff --git a/lib/widget/customRadioWidget.dart b/lib/widget/customRadioWidget.dart new file mode 100644 index 0000000..7f91210 --- /dev/null +++ b/lib/widget/customRadioWidget.dart @@ -0,0 +1,461 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hyzp_ybqx/components/customDialogHysh.dart'; +import 'package:hyzp_ybqx/components/dioFun.dart'; + +import '../components/hyxx_data_handle.dart'; +import '../services/EventBus.dart'; +import 'CheckboxButtonItem.dart'; + +class RadioListItems extends StatefulWidget { + RadioListItems( + {@required this.index, + @required this.hyshlx, + this.size = const Size(30, 30), + this.fontSize = 16, + this.selectedRadio = 0, + this.id = -1}); + + //I don't know what is this index for but I will put it in anyway + final int index; + Size size; + double fontSize; + String hyshlx; + int selectedRadio; + int id; + + @override + _RadioListItemsState createState() => _RadioListItemsState(); +} + +class _RadioListItemsState extends State { + //try_setState(); //避免如下异常报错 + try_setState() { + try { + setState(() {}); + } catch (e) { + print('setState(() {})异常:${e}'); + } + } + + int _selectedRadio = 0; + String _sfcyTextTrue = '当前时间 > 抓拍时间 + 审核间隔,只能复审,不推送交警'; + String _sfcyTextFalse = '当前时间 < 抓拍时间 + 审核间隔,复审后可以推送交警'; + + void initState() { + _selectedRadio = widget.selectedRadio; + + //黑烟审核推送交警Checkbox改变事件 + eventBus.on().listen((event) { + print(event.str); + try_setState(); //避免如下异常报错 + }); + + super.initState(); + } + + Widget getRadio(int index, Size size) { + return Container( + alignment: Alignment(-1, 1), + width: size.width, + height: size.height, + child: Radio( + value: index, + onChanged: (value) { + _selectedRadio = value; + //黑烟初审数据审核Radio选项改变广播 + eventBus.fire(HycsDataAuditRadioEvent('黑烟初审数据审核Radio选项已改变', _selectedRadio)); + setState(() {}); + print('selectedRadio = ${_selectedRadio.toString()}'); + }, + groupValue: _selectedRadio, + ), + ); + } + + //Couldn't infer type parameter 'T'. Tried to infer 'dynamic' for 'T' which doesn't work: + // Parameter 'onChanged' declared as 'void Function(T)' but argument is 'Null Function(String)'. + // The type 'dynamic' was inferred from: Parameter 'value' declared as 'T' but argument is 'String'. + // Parameter 'groupValue' declared as 'T' but argument is 'dynamic'. + // Consider passing explicit type argument(s) to the generic. + + //原因是selectedRadio的类型为int,示例中_radValue为String + //无法推断类型参数“t”。试图推断“T”的“dynamic”无效:参数“onChanged”声明为“void Function(T)”, + // 但参数为“Null Function(String)”。类型“dynamic”的推断依据:参数“value”声明为“T”, + // 但参数为“String”。参数“groupValue”声明为“T”,但参数为“dynamic”。考虑将显式类型参数传递给泛型。 + + @override + Widget build(BuildContext context) { + Widget qxButton = getBtnSizeX( + text: "取消", + onPressedFun: () async { + Navigator.pop(context); + }, + width: 60.0); + + return Column( + children: [ + getShyj(widget.index), + SizedBox(height: 10), + //8、审核结果 + Divider(height: 1.0, color: Colors.blue), + SizedBox(height: 5), + Container( + height: widget.size.height, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(width: my_marginLeft), + Text((widget.hyshlx == 'hyfh' ? '复审' : '初审') + '结果:', + style: TextStyle( + fontSize: widget.fontSize, + color: 0 == _selectedRadio ? Colors.red : Colors.green)), + CustomRadioWidget( + value: 0, + title: mapHyshlx[hyshlx]['nick_text'] + "为黑烟车", + fontSize: widget.fontSize, + width: 140, + groupValue: _selectedRadio, + onChanged: (int value) { + _selectedRadio = value; + fh_hyc = true; //复审为黑烟车 + tsjj = true; //同时推送交警 + //黑烟初审数据审核Radio选项改变广播 + eventBus.fire(HycsDataAuditRadioEvent('黑烟初审数据审核Radio选项已改变', _selectedRadio)); + setState(() {}); + print('selectedRadio = ${_selectedRadio.toString()}'); + }, + ), + SizedBox(width: 20), + CustomRadioWidget( + value: 1, + title: "非黑烟车", + fontSize: widget.fontSize, + width: 110, + groupValue: _selectedRadio, + onChanged: (int value) { + _selectedRadio = value; + fh_hyc = false; //复审为黑烟车 + tsjj = false; //同时推送交警 + //黑烟初审数据审核Radio选项改变广播 + eventBus.fire(HycsDataAuditRadioEvent('黑烟初审数据审核Radio选项已改变', _selectedRadio)); + setState(() {}); + print('selectedRadio = ${_selectedRadio.toString()}'); + }, + ), + ], + ), + ), + SizedBox(height: 6), + Divider(height: 1.0, color: Colors.blue), + SizedBox(height: widget.hyshlx == 'hyfh' ? 3 : 30), + //9、得到审核确认组件 + //getShqr(widget.index), + widget.hyshlx == 'hyfh' + ? Column( + children: [ + Container( + alignment: Alignment.centerLeft, + padding: EdgeInsets.only(left: 10), + child: Text( + 1 == sfyc ? _sfcyTextTrue : _sfcyTextFalse, + textAlign: TextAlign.left, + style: TextStyle(color: 1 == sfyc ? Colors.blue : null), + )), + SizedBox(height: 2), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + CheckboxButtonItem(widget.index), + //_getShjgImage(widget.tabController, _selectedRadio, 35), + Container( + width: 35, + height: 35, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(_selectedRadio == 0 + ? "assets/images/hyc.png" + : "assets/images/fhyc.png"), + fit: BoxFit.contain), + ), + ), + getBtnSizeX( + text: '复审提交', + fontColor: 0 == _selectedRadio ? Colors.red : Colors.green, + onPressedFun: () async { + int ret = -1; + print('等待复审提交确认'); + await Navigator.of(context) + .push( + PageRouteBuilder( + opaque: false, + pageBuilder: (context, animation, secondaryAnimation) => + CustomDialogHysh( + shjg: 0 == _selectedRadio ? hyc_text : fhyc_text, + title: '复审', + content: + '是否进行复审提交${tsjj && 0 == sfyc ? '、同时推送交警' : ''}?\n${1 == sfyc ? _sfcyTextTrue : ''}'), + ), + ) + .then((value) async { + print('value = $value'); + if (value) { + print('用户已确认,开始处理复审提交!'); + +//复审接口增加是否延迟字段 sfyc (是否延迟)整型 必须 是否延误,0-正常 1-延误。延误状态的不推送 +// 初审不用判断,sfyc 直接提交0即可。只有复审的时候才判断时间 +// A、若在规定时间内,则 int sfyc = 0,审核完毕后正常推送交警。 +// B、若超出规定时间,即当前时间>抓拍时间+间隔时间,则 sfyc = 1,不推送交警。 + + //设置 sfyc 和 tsjj + set_sfyc_tsjj(int.parse(listGetZpjl[widget.index]['zpsj'])) + .then((value) async { + hyshContentFirstAudit( + widget.id, + widget.index, + mapHyshlx[hyshlx]['audit_workflow'], + topTabs_map['auditShuoming_Controller_List'][widget.index].text, + topTabs_map['auditTitle'][widget.index], + sfyc: sfyc, + ).then((value) { + eventBus.fire( + HycsDataUpdateEvent('${mapHyshlx[hyshlx]['text']}数据已更新')); + //必须等待审核过程完成后,再处理同时推送交警,否则推送交警总是失败 + print('tsjj = $tsjj'); + if (tsjj) { + print('before tsjjFun(widget.id, _plateAndID)'); + + String _plateAndID = + topTabs_map['car_number_List'].toString() + + '(ID:${widget.id.toString()})'; + + tsjjFun(widget.id, _plateAndID); + + print('after tsjjFun(widget.id, _plateAndID)'); + + Fluttertoast.showToast( + msg: '$_plateAndID 已推送交警,请等待返回结果。', + gravity: ToastGravity.CENTER); + } + }); + }); + } else { + print('用户取消了复审提交'); + } + }); + Navigator.pop(context, ret); + }, + width: 90.0), //'复审提交' + qxButton, //'取消' + ], + ) + ], + ) + : Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + //_getShjgImage(widget.tabController, _selectedRadio, 35), + Container( + width: 35, + height: 35, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(_selectedRadio == 0 + ? "assets/images/hyc.png" + : "assets/images/fhyc.png"), + fit: BoxFit.contain), + ), + ), + getBtnSizeX( + text: '初审提交', + fontColor: 0 == _selectedRadio ? Colors.red : Colors.green, + onPressedFun: () async { + int ret = -1; + print('等待初审提交确认'); + await Navigator.of(context) + .push( + PageRouteBuilder( + opaque: false, + pageBuilder: (context, animation, secondaryAnimation) => + CustomDialogHysh( + shjg: 0 == _selectedRadio ? hyc_text : fhyc_text, + title: '初审', + content: '是否进行初审提交?'), + ), + ) + .then((value) async { + print('value = $value'); + if (value) { + print('用户已确认,开始处理初审提交!'); + //return; + + hyshContentFirstAudit( + widget.id, + widget.index, + mapHyshlx[hyshlx]['audit_workflow'], + topTabs_map['auditShuoming_Controller_List'][widget.index].text, + topTabs_map['auditTitle'][widget.index], + sfyc: 0, + ).then((value) { + eventBus + .fire(HycsDataUpdateEvent('${mapHyshlx[hyshlx]['text']}数据已更新')); + }); + } else { + print('用户取消了初审提交'); + } + }); + Navigator.pop(context, ret); + }, + width: 90.0), //'初审提交' + qxButton, //'取消' + ], + ), + ], + ); + } + + //7、得到审核意见组件 + Widget getShyj(int index) { + return ConstrainedBox( + constraints: BoxConstraints( + minWidth: double.infinity, //宽度尽可能大 + //minHeight: _listTileHeight, //最小高度 + maxHeight: my_listTileHeight, //最大高度 + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.baseline, + children: [ + SizedBox(width: my_marginLeft), + Text((widget.hyshlx == 'hyfh' ? '复审' : '初审') + '意见:', + style: TextStyle( + fontSize: my_fontSize, color: 0 == _selectedRadio ? Colors.red : Colors.green)), + Container( + alignment: Alignment(-1, 0), + height: my_listTileHeight, + //widthTrail = 400报错,360刚能显示,300换行,260 + width: 266, + child: TextField( + //textAlign: TextAlign.right, + //style: TextStyle(fontSize: _fontSize, color: cpysList[getIndexOfCpysList(colorText: topTabs_map['cpysText_List'][i])].cpysFont), + //style: TextStyle(fontSize: _fontSize, color: cpysList[getIndexOfCpysList(colorText: myCpys)].cpysFont), + style: TextStyle(fontSize: my_fontSize), + textAlign: TextAlign.left, + decoration: InputDecoration( + hintText: '請輸入审核意见', + //border: InputBorder.none, //TextField去掉下划线 + //contentPadding: EdgeInsets.only(right: 0), + //contentPadding: EdgeInsets.symmetric(vertical: my_textFieldHeight), + contentPadding: EdgeInsets.only(left: 4, right: 4), //这行代码是关键,设置这个之后,居中 + //contentPadding: EdgeInsets.zero, //这行代码是关键,设置这个之后,居中 + border: OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey[600]), + //borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(3), + ), + ), + controller: topTabs_map['auditShuoming_Controller_List'][index], + maxLines: 1, + minLines: 1, + //maxLengthEnforced: false, + //maxLength: 10, + enabled: true, + //利用控制器初始化文本 + onChanged: (value) { + topTabs_map['auditShuoming_Controller_List'][index].text = value; + }, + ), + ), + ], + ), + ); + } + + Widget getBtnSizeX( + {@required text, width = 70.0, height = 35.0, onPressedFun, fontColor = Colors.black}) { + return Container( + color: Colors.white12, //onPressedFun为null时无效 + width: width, + height: height, + child: RaisedButton( + padding: EdgeInsets.all(0), + textColor: Colors.black, + child: Text(text, style: TextStyle(color: fontColor)), + onPressed: onPressedFun, + ), + ); + } +} + +class CustomRadioWidget extends StatelessWidget { + final T value; + final String title; + final double fontSize; + final T groupValue; + final ValueChanged onChanged; + final double width; + final double height; + + CustomRadioWidget( + {this.value, + this.title = '', + this.fontSize = 16, + this.groupValue, + this.onChanged, + this.width = 25, + this.height = 25}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(0), + child: GestureDetector( + onTap: () { + onChanged(this.value); + }, + child: Container( + //alignment: Alignment(0, 0), + height: this.height, + width: this.width, + child: value == groupValue + ? Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.only(top: 2), + child: Icon(Icons.radio_button_checked_rounded, + color: onChanged == null ? Colors.grey : Colors.blue), + ), + SizedBox(width: title.isEmpty ? 0 : 2), + Text( + title, + style: TextStyle( + fontSize: fontSize, + color: onChanged == null ? Colors.grey : Colors.blue, + fontWeight: FontWeight.bold, + ), + ), + ], + ) + : Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.only(top: 2), + child: Icon(Icons.radio_button_unchecked_rounded, + color: onChanged == null ? Colors.grey : Colors.black), + ), + SizedBox(width: title.isEmpty ? 0 : 2), + Text(title, + style: TextStyle( + fontSize: fontSize, + color: onChanged == null ? Colors.grey : Colors.black)), + ], + ), + ), + ), + ); + } +} diff --git a/lib/widget/my_Tabs.dart b/lib/widget/my_Tabs.dart new file mode 100644 index 0000000..4e688e7 --- /dev/null +++ b/lib/widget/my_Tabs.dart @@ -0,0 +1,1537 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// @dart = 2.8 + +import 'dart:async'; +import 'dart:ui' show lerpDouble; + +import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter/gestures.dart' show DragStartBehavior; + +import 'package:flutter/material.dart'; + +// import 'app_bar.dart'; +// import 'colors.dart'; +// import 'constants.dart'; +// import 'debug.dart'; +// import 'ink_well.dart'; +// import 'material.dart'; +// import 'material_localizations.dart'; +// import 'tab_bar_theme.dart'; +// import 'tab_controller.dart'; +// import 'tab_indicator.dart'; +// import 'theme.dart'; + +const double _kTabHeight = 46.0; +const double _kTextAndIconTabHeight = 72.0; + +/// Defines how the bounds of the selected tab indicator are computed. +/// +/// See also: +/// +/// * [TabBar], which displays a row of tabs. +/// * [TabBarView], which displays a widget for the currently selected tab. +/// * [TabBar.indicator], which defines the appearance of the selected tab +/// indicator relative to the tab's bounds. +enum TabBarIndicatorSize { + /// The tab indicator's bounds are as wide as the space occupied by the tab + /// in the tab bar: from the right edge of the previous tab to the left edge + /// of the next tab. + tab, + + /// The tab's bounds are only as wide as the (centered) tab widget itself. + /// + /// This value is used to align the tab's label, typically a [MyTab] + /// widget's text or icon, with the selected tab indicator. + label, +} + +/// A material design [TabBar] tab. +/// +/// If both [icon] and [text] are provided, the text is displayed below +/// the icon. +/// +/// See also: +/// +/// * [TabBar], which displays a row of tabs. +/// * [TabBarView], which displays a widget for the currently selected tab. +/// * [TabController], which coordinates tab selection between a [TabBar] and a [TabBarView]. +/// * +class MyTab extends StatelessWidget { + /// Creates a material design [TabBar] tab. + /// + /// At least one of [text], [icon], and [child] must be non-null. The [text] + /// and [child] arguments must not be used at the same time. The + /// [iconMargin] is only useful when [icon] and either one of [text] or + /// [child] is non-null. + const MyTab({ + Key key, + this.text, + this.icon, + this.iconMargin = const EdgeInsets.only(bottom: 10.0), + this.child, + this.style, + }) : assert(text != null || child != null || icon != null), + assert(text == null || child == null), + super(key: key); + + /// The text to display as the tab's label. + /// + /// Must not be used in combination with [child]. + final String text; + + /// The widget to be used as the tab's label. + /// + /// Usually a [Text] widget, possibly wrapped in a [Semantics] widget. + /// + /// Must not be used in combination with [text]. + final Widget child; + + /// An icon to display as the tab's label. + final Widget icon; + + /// The margin added around the tab's icon. + /// + /// Only useful when used in combination with [icon], and either one of + /// [text] or [child] is non-null. + final EdgeInsetsGeometry iconMargin; + + final TextStyle style; + + Widget _buildLabelText0() { + return child ?? Text(text, softWrap: false, overflow: TextOverflow.fade); + } + + Widget _buildLabelText() { + return child ?? + Text( + text, + softWrap: false, + overflow: TextOverflow.fade, + style: style, + ); + } + + @override + Widget build(BuildContext context) { + assert(debugCheckHasMaterial(context)); + + double height; + Widget label; + if (icon == null) { + height = _kTabHeight; + label = _buildLabelText(); + } else if (text == null && child == null) { + height = _kTabHeight; + label = icon; + } else { + height = _kTextAndIconTabHeight; + label = Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + child: icon, + margin: iconMargin, + ), + _buildLabelText(), + ], + ); + } + + return SizedBox( + height: height, + child: Center( + child: label, + widthFactor: 1.0, + ), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(StringProperty('text', text, defaultValue: null)); + properties.add(DiagnosticsProperty('icon', icon, defaultValue: null)); + } +} + +class _TabStyle extends AnimatedWidget { + const _TabStyle({ + Key key, + Animation animation, + this.selected, + this.labelColor, + this.unselectedLabelColor, + this.labelStyle, + this.unselectedLabelStyle, + @required this.child, + }) : super(key: key, listenable: animation); + + final TextStyle labelStyle; + final TextStyle unselectedLabelStyle; + final bool selected; + final Color labelColor; + final Color unselectedLabelColor; + final Widget child; + + @override + Widget build(BuildContext context) { + final ThemeData themeData = Theme.of(context); + final TabBarTheme tabBarTheme = TabBarTheme.of(context); + final Animation animation = listenable as Animation; + + // To enable TextStyle.lerp(style1, style2, value), both styles must have + // the same value of inherit. Force that to be inherit=true here. + final TextStyle defaultStyle = + (labelStyle ?? tabBarTheme.labelStyle ?? themeData.primaryTextTheme.bodyText1) + .copyWith(inherit: true); + final TextStyle defaultUnselectedStyle = (unselectedLabelStyle ?? + tabBarTheme.unselectedLabelStyle ?? + labelStyle ?? + themeData.primaryTextTheme.bodyText1) + .copyWith(inherit: true); + final TextStyle textStyle = selected + ? TextStyle.lerp(defaultStyle, defaultUnselectedStyle, animation.value) + : TextStyle.lerp(defaultUnselectedStyle, defaultStyle, animation.value); + + final Color selectedColor = + labelColor ?? tabBarTheme.labelColor ?? themeData.primaryTextTheme.bodyText1.color; + final Color unselectedColor = unselectedLabelColor ?? + tabBarTheme.unselectedLabelColor ?? + selectedColor.withAlpha(0xB2); // 70% alpha + final Color color = selected + ? Color.lerp(selectedColor, unselectedColor, animation.value) + : Color.lerp(unselectedColor, selectedColor, animation.value); + + return DefaultTextStyle( + style: textStyle.copyWith(color: color), + child: IconTheme.merge( + data: IconThemeData( + size: 24.0, + color: color, + ), + child: child, + ), + ); + } +} + +typedef _LayoutCallback = void Function( + List xOffsets, TextDirection textDirection, double width); + +class _TabLabelBarRenderer extends RenderFlex { + _TabLabelBarRenderer({ + List children, + @required Axis direction, + @required MainAxisSize mainAxisSize, + @required MainAxisAlignment mainAxisAlignment, + @required CrossAxisAlignment crossAxisAlignment, + @required TextDirection textDirection, + @required VerticalDirection verticalDirection, + @required this.onPerformLayout, + }) : assert(onPerformLayout != null), + assert(textDirection != null), + super( + children: children, + direction: direction, + mainAxisSize: mainAxisSize, + mainAxisAlignment: mainAxisAlignment, + crossAxisAlignment: crossAxisAlignment, + textDirection: textDirection, + verticalDirection: verticalDirection, + ); + + _LayoutCallback onPerformLayout; + + @override + void performLayout() { + super.performLayout(); + // xOffsets will contain childCount+1 values, giving the offsets of the + // leading edge of the first tab as the first value, of the leading edge of + // the each subsequent tab as each subsequent value, and of the trailing + // edge of the last tab as the last value. + RenderBox child = firstChild; + final List xOffsets = []; + while (child != null) { + final FlexParentData childParentData = child.parentData as FlexParentData; + xOffsets.add(childParentData.offset.dx); + assert(child.parentData == childParentData); + child = childParentData.nextSibling; + } + assert(textDirection != null); + switch (textDirection) { + case TextDirection.rtl: + xOffsets.insert(0, size.width); + break; + case TextDirection.ltr: + xOffsets.add(size.width); + break; + } + onPerformLayout(xOffsets, textDirection, size.width); + } +} + +// This class and its renderer class only exist to report the widths of the tabs +// upon layout. The tab widths are only used at paint time (see _IndicatorPainter) +// or in response to input. +class _TabLabelBar extends Flex { + _TabLabelBar({ + Key key, + List children = const [], + this.onPerformLayout, + }) : super( + key: key, + children: children, + direction: Axis.horizontal, + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + verticalDirection: VerticalDirection.down, + ); + + final _LayoutCallback onPerformLayout; + + @override + RenderFlex createRenderObject(BuildContext context) { + return _TabLabelBarRenderer( + direction: direction, + mainAxisAlignment: mainAxisAlignment, + mainAxisSize: mainAxisSize, + crossAxisAlignment: crossAxisAlignment, + textDirection: getEffectiveTextDirection(context), + verticalDirection: verticalDirection, + onPerformLayout: onPerformLayout, + ); + } + + @override + void updateRenderObject(BuildContext context, _TabLabelBarRenderer renderObject) { + super.updateRenderObject(context, renderObject); + renderObject.onPerformLayout = onPerformLayout; + } +} + +double _indexChangeProgress(TabController controller) { + final double controllerValue = controller.animation.value; + final double previousIndex = controller.previousIndex.toDouble(); + final double currentIndex = controller.index.toDouble(); + + // The controller's offset is changing because the user is dragging the + // TabBarView's PageView to the left or right. + if (!controller.indexIsChanging) + return (currentIndex - controllerValue).abs().clamp(0.0, 1.0) as double; + + // The TabController animation's value is changing from previousIndex to currentIndex. + return (controllerValue - currentIndex).abs() / (currentIndex - previousIndex).abs(); +} + +class _IndicatorPainter extends CustomPainter { + _IndicatorPainter({ + @required this.controller, + @required this.indicator, + @required this.indicatorSize, + @required this.tabKeys, + _IndicatorPainter old, + }) : assert(controller != null), + assert(indicator != null), + super(repaint: controller.animation) { + if (old != null) saveTabOffsets(old._currentTabOffsets, old._currentTextDirection); + } + + final TabController controller; + final Decoration indicator; + final TabBarIndicatorSize indicatorSize; + final List tabKeys; + + List _currentTabOffsets; + TextDirection _currentTextDirection; + Rect _currentRect; + BoxPainter _painter; + bool _needsPaint = false; + + void markNeedsPaint() { + _needsPaint = true; + } + + void dispose() { + _painter?.dispose(); + } + + void saveTabOffsets(List tabOffsets, TextDirection textDirection) { + _currentTabOffsets = tabOffsets; + _currentTextDirection = textDirection; + } + + // _currentTabOffsets[index] is the offset of the start edge of the tab at index, and + // _currentTabOffsets[_currentTabOffsets.length] is the end edge of the last tab. + int get maxTabIndex => _currentTabOffsets.length - 2; + + double centerOf(int tabIndex) { + assert(_currentTabOffsets != null); + assert(_currentTabOffsets.isNotEmpty); + assert(tabIndex >= 0); + assert(tabIndex <= maxTabIndex); + return (_currentTabOffsets[tabIndex] + _currentTabOffsets[tabIndex + 1]) / 2.0; + } + + Rect indicatorRect(Size tabBarSize, int tabIndex) { + assert(_currentTabOffsets != null); + assert(_currentTextDirection != null); + assert(_currentTabOffsets.isNotEmpty); + assert(tabIndex >= 0); + assert(tabIndex <= maxTabIndex); + double tabLeft, tabRight; + switch (_currentTextDirection) { + case TextDirection.rtl: + tabLeft = _currentTabOffsets[tabIndex + 1]; + tabRight = _currentTabOffsets[tabIndex]; + break; + case TextDirection.ltr: + tabLeft = _currentTabOffsets[tabIndex]; + tabRight = _currentTabOffsets[tabIndex + 1]; + break; + } + + if (indicatorSize == TabBarIndicatorSize.label) { + final double tabWidth = tabKeys[tabIndex].currentContext.size.width; + final double delta = ((tabRight - tabLeft) - tabWidth) / 2.0; + tabLeft += delta; + tabRight -= delta; + } + + return Rect.fromLTWH(tabLeft, 0.0, tabRight - tabLeft, tabBarSize.height); + } + + @override + void paint(Canvas canvas, Size size) { + _needsPaint = false; + _painter ??= indicator.createBoxPainter(markNeedsPaint); + + if (controller.indexIsChanging) { + // The user tapped on a tab, the tab controller's animation is running. + final Rect targetRect = indicatorRect(size, controller.index); + _currentRect = + Rect.lerp(targetRect, _currentRect ?? targetRect, _indexChangeProgress(controller)); + } else { + // The user is dragging the TabBarView's PageView left or right. + final int currentIndex = controller.index; + final Rect previous = currentIndex > 0 ? indicatorRect(size, currentIndex - 1) : null; + final Rect middle = indicatorRect(size, currentIndex); + final Rect next = currentIndex < maxTabIndex ? indicatorRect(size, currentIndex + 1) : null; + final double index = controller.index.toDouble(); + final double value = controller.animation.value; + if (value == index - 1.0) + _currentRect = previous ?? middle; + else if (value == index + 1.0) + _currentRect = next ?? middle; + else if (value == index) + _currentRect = middle; + else if (value < index) + _currentRect = previous == null ? middle : Rect.lerp(middle, previous, index - value); + else + _currentRect = next == null ? middle : Rect.lerp(middle, next, value - index); + } + assert(_currentRect != null); + + final ImageConfiguration configuration = ImageConfiguration( + size: _currentRect.size, + textDirection: _currentTextDirection, + ); + _painter.paint(canvas, _currentRect.topLeft, configuration); + } + + static bool _tabOffsetsEqual(List a, List b) { + // TODO(shihaohong): The following null check should be replaced when a fix + // for https://github.com/flutter/flutter/issues/40014 is available. + if (a == null || b == null || a.length != b.length) return false; + for (int i = 0; i < a.length; i += 1) { + if (a[i] != b[i]) return false; + } + return true; + } + + @override + bool shouldRepaint(_IndicatorPainter old) { + return _needsPaint || + controller != old.controller || + indicator != old.indicator || + tabKeys.length != old.tabKeys.length || + (!_tabOffsetsEqual(_currentTabOffsets, old._currentTabOffsets)) || + _currentTextDirection != old._currentTextDirection; + } +} + +class _ChangeAnimation extends Animation with AnimationWithParentMixin { + _ChangeAnimation(this.controller); + + final TabController controller; + + @override + Animation get parent => controller.animation; + + @override + void removeStatusListener(AnimationStatusListener listener) { + if (parent != null) super.removeStatusListener(listener); + } + + @override + void removeListener(VoidCallback listener) { + if (parent != null) super.removeListener(listener); + } + + @override + double get value => _indexChangeProgress(controller); +} + +class _DragAnimation extends Animation with AnimationWithParentMixin { + _DragAnimation(this.controller, this.index); + + final TabController controller; + final int index; + + @override + Animation get parent => controller.animation; + + @override + void removeStatusListener(AnimationStatusListener listener) { + if (parent != null) super.removeStatusListener(listener); + } + + @override + void removeListener(VoidCallback listener) { + if (parent != null) super.removeListener(listener); + } + + @override + double get value { + assert(!controller.indexIsChanging); + final double controllerMaxValue = (controller.length - 1).toDouble(); + final double controllerValue = + controller.animation.value.clamp(0.0, controllerMaxValue) as double; + return (controllerValue - index.toDouble()).abs().clamp(0.0, 1.0) as double; + } +} + +// This class, and TabBarScrollController, only exist to handle the case +// where a scrollable TabBar has a non-zero initialIndex. In that case we can +// only compute the scroll position's initial scroll offset (the "correct" +// pixels value) after the TabBar viewport width and scroll limits are known. +class _TabBarScrollPosition extends ScrollPositionWithSingleContext { + _TabBarScrollPosition({ + ScrollPhysics physics, + ScrollContext context, + ScrollPosition oldPosition, + this.tabBar, + }) : super( + physics: physics, + context: context, + initialPixels: null, + oldPosition: oldPosition, + ); + + final _TabBarState tabBar; + + bool _initialViewportDimensionWasZero; + + @override + bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) { + bool result = true; + if (_initialViewportDimensionWasZero != true) { + // If the viewport never had a non-zero dimension, we just want to jump + // to the initial scroll position to avoid strange scrolling effects in + // release mode: In release mode, the viewport temporarily may have a + // dimension of zero before the actual dimension is calculated. In that + // scenario, setting the actual dimension would cause a strange scroll + // effect without this guard because the super call below would starts a + // ballistic scroll activity. + assert(viewportDimension != null); + _initialViewportDimensionWasZero = viewportDimension != 0.0; + correctPixels( + tabBar._initialScrollOffset(viewportDimension, minScrollExtent, maxScrollExtent)); + result = false; + } + return super.applyContentDimensions(minScrollExtent, maxScrollExtent) && result; + } +} + +// This class, and TabBarScrollPosition, only exist to handle the case +// where a scrollable TabBar has a non-zero initialIndex. +class _TabBarScrollController extends ScrollController { + _TabBarScrollController(this.tabBar); + + final _TabBarState tabBar; + + @override + ScrollPosition createScrollPosition( + ScrollPhysics physics, ScrollContext context, ScrollPosition oldPosition) { + return _TabBarScrollPosition( + physics: physics, + context: context, + oldPosition: oldPosition, + tabBar: tabBar, + ); + } +} + +/// A material design widget that displays a horizontal row of tabs. +/// +/// Typically created as the [AppBar.bottom] part of an [AppBar] and in +/// conjunction with a [TabBarView]. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=POtoEH-5l40} +/// +/// If a [TabController] is not provided, then a [DefaultTabController] ancestor +/// must be provided instead. The tab controller's [TabController.length] must +/// equal the length of the [tabs] list and the length of the +/// [TabBarView.children] list. +/// +/// Requires one of its ancestors to be a [Material] widget. +/// +/// Uses values from [TabBarTheme] if it is set in the current context. +/// +/// To see a sample implementation, visit the [TabController] documentation. +/// +/// See also: +/// +/// * [TabBarView], which displays page views that correspond to each tab. +class TabBar extends StatefulWidget implements PreferredSizeWidget { + /// Creates a material design tab bar. + /// + /// The [tabs] argument must not be null and its length must match the [controller]'s + /// [TabController.length]. + /// + /// If a [TabController] is not provided, then there must be a + /// [DefaultTabController] ancestor. + /// + /// The [indicatorWeight] parameter defaults to 2, and must not be null. + /// + /// The [indicatorPadding] parameter defaults to [EdgeInsets.zero], and must not be null. + /// + /// If [indicator] is not null or provided from [TabBarTheme], + /// then [indicatorWeight], [indicatorPadding], and [indicatorColor] are ignored. + const TabBar({ + Key key, + @required this.tabs, + this.controller, + this.isScrollable = false, + this.indicatorColor, + this.indicatorWeight = 2.0, + this.indicatorPadding = EdgeInsets.zero, + this.indicator, + this.indicatorSize, + this.labelColor, + this.labelStyle, + this.labelPadding, + this.unselectedLabelColor, + this.unselectedLabelStyle, + this.dragStartBehavior = DragStartBehavior.start, + this.mouseCursor, + this.onTap, + this.physics, + }) : assert(tabs != null), + assert(isScrollable != null), + assert(dragStartBehavior != null), + assert(indicator != null || (indicatorWeight != null && indicatorWeight > 0.0)), + assert(indicator != null || (indicatorPadding != null)), + super(key: key); + + /// Typically a list of two or more [MyTab] widgets. + /// + /// The length of this list must match the [controller]'s [TabController.length] + /// and the length of the [TabBarView.children] list. + final List tabs; + + /// This widget's selection and animation state. + /// + /// If [TabController] is not provided, then the value of [DefaultTabController.of] + /// will be used. + final TabController controller; + + /// Whether this tab bar can be scrolled horizontally. + /// + /// If [isScrollable] is true, then each tab is as wide as needed for its label + /// and the entire [TabBar] is scrollable. Otherwise each tab gets an equal + /// share of the available space. + final bool isScrollable; + + /// The color of the line that appears below the selected tab. + /// + /// If this parameter is null, then the value of the Theme's indicatorColor + /// property is used. + /// + /// If [indicator] is specified or provided from [TabBarTheme], + /// this property is ignored. + final Color indicatorColor; + + /// The thickness of the line that appears below the selected tab. + /// + /// The value of this parameter must be greater than zero and its default + /// value is 2.0. + /// + /// If [indicator] is specified or provided from [TabBarTheme], + /// this property is ignored. + final double indicatorWeight; + + /// The horizontal padding for the line that appears below the selected tab. + /// + /// For [isScrollable] tab bars, specifying [kTabLabelPadding] will align + /// the indicator with the tab's text for [MyTab] widgets and all but the + /// shortest [MyTab.text] values. + /// + /// The [EdgeInsets.top] and [EdgeInsets.bottom] values of the + /// [indicatorPadding] are ignored. + /// + /// The default value of [indicatorPadding] is [EdgeInsets.zero]. + /// + /// If [indicator] is specified or provided from [TabBarTheme], + /// this property is ignored. + final EdgeInsetsGeometry indicatorPadding; + + /// Defines the appearance of the selected tab indicator. + /// + /// If [indicator] is specified or provided from [TabBarTheme], + /// the [indicatorColor], [indicatorWeight], and [indicatorPadding] + /// properties are ignored. + /// + /// The default, underline-style, selected tab indicator can be defined with + /// [UnderlineTabIndicator]. + /// + /// The indicator's size is based on the tab's bounds. If [indicatorSize] + /// is [TabBarIndicatorSize.tab] the tab's bounds are as wide as the space + /// occupied by the tab in the tab bar. If [indicatorSize] is + /// [TabBarIndicatorSize.label], then the tab's bounds are only as wide as + /// the tab widget itself. + final Decoration indicator; + + /// Defines how the selected tab indicator's size is computed. + /// + /// The size of the selected tab indicator is defined relative to the + /// tab's overall bounds if [indicatorSize] is [TabBarIndicatorSize.tab] + /// (the default) or relative to the bounds of the tab's widget if + /// [indicatorSize] is [TabBarIndicatorSize.label]. + /// + /// The selected tab's location appearance can be refined further with + /// the [indicatorColor], [indicatorWeight], [indicatorPadding], and + /// [indicator] properties. + final TabBarIndicatorSize indicatorSize; + + /// The color of selected tab labels. + /// + /// Unselected tab labels are rendered with the same color rendered at 70% + /// opacity unless [unselectedLabelColor] is non-null. + /// + /// If this parameter is null, then the color of the [ThemeData.primaryTextTheme]'s + /// bodyText1 text color is used. + final Color labelColor; + + /// The color of unselected tab labels. + /// + /// If this property is null, unselected tab labels are rendered with the + /// [labelColor] with 70% opacity. + final Color unselectedLabelColor; + + /// The text style of the selected tab labels. + /// + /// If [unselectedLabelStyle] is null, then this text style will be used for + /// both selected and unselected label styles. + /// + /// If this property is null, then the text style of the + /// [ThemeData.primaryTextTheme]'s bodyText1 definition is used. + final TextStyle labelStyle; + + /// The padding added to each of the tab labels. + /// + /// If this property is null, then kTabLabelPadding is used. + final EdgeInsetsGeometry labelPadding; + + /// The text style of the unselected tab labels. + /// + /// If this property is null, then the [labelStyle] value is used. If [labelStyle] + /// is null, then the text style of the [ThemeData.primaryTextTheme]'s + /// bodyText1 definition is used. + final TextStyle unselectedLabelStyle; + + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + + /// The cursor for a mouse pointer when it enters or is hovering over the + /// individual tab widgets. + /// + /// If this property is null, [SystemMouseCursors.click] will be used. + final MouseCursor mouseCursor; + + /// An optional callback that's called when the [TabBar] is tapped. + /// + /// The callback is applied to the index of the tab where the tap occurred. + /// + /// This callback has no effect on the default handling of taps. It's for + /// applications that want to do a little extra work when a tab is tapped, + /// even if the tap doesn't change the TabController's index. TabBar [onTap] + /// callbacks should not make changes to the TabController since that would + /// interfere with the default tap handler. + final ValueChanged onTap; + + /// How the [TabBar]'s scroll view should respond to user input. + /// + /// For example, determines how the scroll view continues to animate after the + /// user stops dragging the scroll view. + /// + /// Defaults to matching platform conventions. + final ScrollPhysics physics; + + /// A size whose height depends on if the tabs have both icons and text. + /// + /// [AppBar] uses this size to compute its own preferred size. + @override + Size get preferredSize { + for (final Widget item in tabs) { + if (item is MyTab) { + final MyTab tab = item; + if ((tab.text != null || tab.child != null) && tab.icon != null) + return Size.fromHeight(_kTextAndIconTabHeight + indicatorWeight); + } + } + return Size.fromHeight(_kTabHeight + indicatorWeight); + } + + @override + _TabBarState createState() => _TabBarState(); +} + +class _TabBarState extends State { + ScrollController _scrollController; + TabController _controller; + _IndicatorPainter _indicatorPainter; + int _currentIndex; + double _tabStripWidth; + List _tabKeys; + + @override + void initState() { + super.initState(); + // If indicatorSize is TabIndicatorSize.label, _tabKeys[i] is used to find + // the width of tab widget i. See _IndicatorPainter.indicatorRect(). + _tabKeys = widget.tabs.map((Widget tab) => GlobalKey()).toList(); + } + + Decoration get _indicator { + if (widget.indicator != null) return widget.indicator; + final TabBarTheme tabBarTheme = TabBarTheme.of(context); + if (tabBarTheme.indicator != null) return tabBarTheme.indicator; + + Color color = widget.indicatorColor ?? Theme.of(context).indicatorColor; + // ThemeData tries to avoid this by having indicatorColor avoid being the + // primaryColor. However, it's possible that the tab bar is on a + // Material that isn't the primaryColor. In that case, if the indicator + // color ends up matching the material's color, then this overrides it. + // When that happens, automatic transitions of the theme will likely look + // ugly as the indicator color suddenly snaps to white at one end, but it's + // not clear how to avoid that any further. + // + // The material's color might be null (if it's a transparency). In that case + // there's no good way for us to find out what the color is so we don't. + if (color.value == Material.of(context).color?.value) color = Colors.white; + + return UnderlineTabIndicator( + insets: widget.indicatorPadding, + borderSide: BorderSide( + width: widget.indicatorWeight, + color: color, + ), + ); + } + + // If the TabBar is rebuilt with a new tab controller, the caller should + // dispose the old one. In that case the old controller's animation will be + // null and should not be accessed. + bool get _controllerIsValid => _controller?.animation != null; + + void _updateTabController() { + final TabController newController = widget.controller ?? DefaultTabController.of(context); + assert(() { + if (newController == null) { + throw FlutterError('No TabController for ${widget.runtimeType}.\n' + 'When creating a ${widget.runtimeType}, you must either provide an explicit ' + 'TabController using the "controller" property, or you must ensure that there ' + 'is a DefaultTabController above the ${widget.runtimeType}.\n' + 'In this case, there was neither an explicit controller nor a default controller.'); + } + return true; + }()); + + if (newController == _controller) return; + + if (_controllerIsValid) { + _controller.animation.removeListener(_handleTabControllerAnimationTick); + _controller.removeListener(_handleTabControllerTick); + } + _controller = newController; + if (_controller != null) { + _controller.animation.addListener(_handleTabControllerAnimationTick); + _controller.addListener(_handleTabControllerTick); + _currentIndex = _controller.index; + } + } + + void _initIndicatorPainter() { + _indicatorPainter = !_controllerIsValid + ? null + : _IndicatorPainter( + controller: _controller, + indicator: _indicator, + indicatorSize: widget.indicatorSize ?? TabBarTheme.of(context).indicatorSize, + tabKeys: _tabKeys, + old: _indicatorPainter, + ); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + assert(debugCheckHasMaterial(context)); + _updateTabController(); + _initIndicatorPainter(); + } + + @override + void didUpdateWidget(TabBar oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.controller != oldWidget.controller) { + _updateTabController(); + _initIndicatorPainter(); + } else if (widget.indicatorColor != oldWidget.indicatorColor || + widget.indicatorWeight != oldWidget.indicatorWeight || + widget.indicatorSize != oldWidget.indicatorSize || + widget.indicator != oldWidget.indicator) { + _initIndicatorPainter(); + } + + if (widget.tabs.length > oldWidget.tabs.length) { + final int delta = widget.tabs.length - oldWidget.tabs.length; + _tabKeys.addAll(List.generate(delta, (int n) => GlobalKey())); + } else if (widget.tabs.length < oldWidget.tabs.length) { + _tabKeys.removeRange(widget.tabs.length, oldWidget.tabs.length); + } + } + + @override + void dispose() { + _indicatorPainter.dispose(); + if (_controllerIsValid) { + _controller.animation.removeListener(_handleTabControllerAnimationTick); + _controller.removeListener(_handleTabControllerTick); + } + _controller = null; + // We don't own the _controller Animation, so it's not disposed here. + super.dispose(); + } + + int get maxTabIndex => _indicatorPainter.maxTabIndex; + + double _tabScrollOffset(int index, double viewportWidth, double minExtent, double maxExtent) { + if (!widget.isScrollable) return 0.0; + double tabCenter = _indicatorPainter.centerOf(index); + switch (Directionality.of(context)) { + case TextDirection.rtl: + tabCenter = _tabStripWidth - tabCenter; + break; + case TextDirection.ltr: + break; + } + return (tabCenter - viewportWidth / 2.0).clamp(minExtent, maxExtent) as double; + } + + double _tabCenteredScrollOffset(int index) { + final ScrollPosition position = _scrollController.position; + return _tabScrollOffset( + index, position.viewportDimension, position.minScrollExtent, position.maxScrollExtent); + } + + double _initialScrollOffset(double viewportWidth, double minExtent, double maxExtent) { + return _tabScrollOffset(_currentIndex, viewportWidth, minExtent, maxExtent); + } + + void _scrollToCurrentIndex() { + final double offset = _tabCenteredScrollOffset(_currentIndex); + _scrollController.animateTo(offset, duration: kTabScrollDuration, curve: Curves.ease); + } + + void _scrollToControllerValue() { + final double leadingPosition = + _currentIndex > 0 ? _tabCenteredScrollOffset(_currentIndex - 1) : null; + final double middlePosition = _tabCenteredScrollOffset(_currentIndex); + final double trailingPosition = + _currentIndex < maxTabIndex ? _tabCenteredScrollOffset(_currentIndex + 1) : null; + + final double index = _controller.index.toDouble(); + final double value = _controller.animation.value; + double offset; + if (value == index - 1.0) + offset = leadingPosition ?? middlePosition; + else if (value == index + 1.0) + offset = trailingPosition ?? middlePosition; + else if (value == index) + offset = middlePosition; + else if (value < index) + offset = leadingPosition == null + ? middlePosition + : lerpDouble(middlePosition, leadingPosition, index - value); + else + offset = trailingPosition == null + ? middlePosition + : lerpDouble(middlePosition, trailingPosition, value - index); + + _scrollController.jumpTo(offset); + } + + void _handleTabControllerAnimationTick() { + assert(mounted); + if (!_controller.indexIsChanging && widget.isScrollable) { + // Sync the TabBar's scroll position with the TabBarView's PageView. + _currentIndex = _controller.index; + _scrollToControllerValue(); + } + } + + void _handleTabControllerTick() { + if (_controller.index != _currentIndex) { + _currentIndex = _controller.index; + if (widget.isScrollable) _scrollToCurrentIndex(); + } + setState(() { + // Rebuild the tabs after a (potentially animated) index change + // has completed. + }); + } + + // Called each time layout completes. + void _saveTabOffsets(List tabOffsets, TextDirection textDirection, double width) { + _tabStripWidth = width; + _indicatorPainter?.saveTabOffsets(tabOffsets, textDirection); + } + + void _handleTap(int index) { + assert(index >= 0 && index < widget.tabs.length); + _controller.animateTo(index); + if (widget.onTap != null) { + widget.onTap(index); + } + } + + Widget _buildStyledTab(Widget child, bool selected, Animation animation) { + return _TabStyle( + animation: animation, + selected: selected, + labelColor: widget.labelColor, + unselectedLabelColor: widget.unselectedLabelColor, + labelStyle: widget.labelStyle, + unselectedLabelStyle: widget.unselectedLabelStyle, + child: child, + ); + } + + @override + Widget build(BuildContext context) { + assert(debugCheckHasMaterialLocalizations(context)); + assert(() { + if (_controller.length != widget.tabs.length) { + throw FlutterError( + "Controller's length property (${_controller.length}) does not match the " + "number of tabs (${widget.tabs.length}) present in TabBar's tabs property."); + } + return true; + }()); + final MaterialLocalizations localizations = MaterialLocalizations.of(context); + if (_controller.length == 0) { + return Container( + height: _kTabHeight + widget.indicatorWeight, + ); + } + + final TabBarTheme tabBarTheme = TabBarTheme.of(context); + + final List wrappedTabs = List(widget.tabs.length); + for (int i = 0; i < widget.tabs.length; i += 1) { + wrappedTabs[i] = Center( + heightFactor: 1.0, + child: Padding( + padding: widget.labelPadding ?? tabBarTheme.labelPadding ?? kTabLabelPadding, + child: KeyedSubtree( + key: _tabKeys[i], + child: widget.tabs[i], + ), + ), + ); + } + + // If the controller was provided by DefaultTabController and we're part + // of a Hero (typically the AppBar), then we will not be able to find the + // controller during a Hero transition. See https://github.com/flutter/flutter/issues/213. + if (_controller != null) { + final int previousIndex = _controller.previousIndex; + + if (_controller.indexIsChanging) { + // The user tapped on a tab, the tab controller's animation is running. + assert(_currentIndex != previousIndex); + final Animation animation = _ChangeAnimation(_controller); + wrappedTabs[_currentIndex] = _buildStyledTab(wrappedTabs[_currentIndex], true, animation); + wrappedTabs[previousIndex] = _buildStyledTab(wrappedTabs[previousIndex], false, animation); + } else { + // The user is dragging the TabBarView's PageView left or right. + final int tabIndex = _currentIndex; + final Animation centerAnimation = _DragAnimation(_controller, tabIndex); + wrappedTabs[tabIndex] = _buildStyledTab(wrappedTabs[tabIndex], true, centerAnimation); + if (_currentIndex > 0) { + final int tabIndex = _currentIndex - 1; + final Animation previousAnimation = + ReverseAnimation(_DragAnimation(_controller, tabIndex)); + wrappedTabs[tabIndex] = _buildStyledTab(wrappedTabs[tabIndex], false, previousAnimation); + } + if (_currentIndex < widget.tabs.length - 1) { + final int tabIndex = _currentIndex + 1; + final Animation nextAnimation = + ReverseAnimation(_DragAnimation(_controller, tabIndex)); + wrappedTabs[tabIndex] = _buildStyledTab(wrappedTabs[tabIndex], false, nextAnimation); + } + } + } + + // Add the tap handler to each tab. If the tab bar is not scrollable, + // then give all of the tabs equal flexibility so that they each occupy + // the same share of the tab bar's overall width. + final int tabCount = widget.tabs.length; + for (int index = 0; index < tabCount; index += 1) { + wrappedTabs[index] = InkWell( + mouseCursor: widget.mouseCursor ?? SystemMouseCursors.click, + onTap: () { + _handleTap(index); + }, + child: Padding( + padding: EdgeInsets.only(bottom: widget.indicatorWeight), + child: Stack( + children: [ + wrappedTabs[index], + Semantics( + selected: index == _currentIndex, + label: localizations.tabLabel(tabIndex: index + 1, tabCount: tabCount), + ), + ], + ), + ), + ); + if (!widget.isScrollable) wrappedTabs[index] = Expanded(child: wrappedTabs[index]); + } + + Widget tabBar = CustomPaint( + painter: _indicatorPainter, + child: _TabStyle( + animation: kAlwaysDismissedAnimation, + selected: false, + labelColor: widget.labelColor, + unselectedLabelColor: widget.unselectedLabelColor, + labelStyle: widget.labelStyle, + unselectedLabelStyle: widget.unselectedLabelStyle, + child: _TabLabelBar( + onPerformLayout: _saveTabOffsets, + children: wrappedTabs, + ), + ), + ); + + if (widget.isScrollable) { + _scrollController ??= _TabBarScrollController(this); + tabBar = SingleChildScrollView( + dragStartBehavior: widget.dragStartBehavior, + scrollDirection: Axis.horizontal, + controller: _scrollController, + physics: widget.physics, + child: tabBar, + ); + } + + return tabBar; + } +} + +/// A page view that displays the widget which corresponds to the currently +/// selected tab. +/// +/// This widget is typically used in conjunction with a [TabBar]. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=POtoEH-5l40} +/// +/// If a [TabController] is not provided, then there must be a [DefaultTabController] +/// ancestor. +/// +/// The tab controller's [TabController.length] must equal the length of the +/// [children] list and the length of the [TabBar.tabs] list. +/// +/// To see a sample implementation, visit the [TabController] documentation. +class TabBarView extends StatefulWidget { + /// Creates a page view with one child per tab. + /// + /// The length of [children] must be the same as the [controller]'s length. + const TabBarView({ + Key key, + @required this.children, + this.controller, + this.physics, + this.dragStartBehavior = DragStartBehavior.start, + }) : assert(children != null), + assert(dragStartBehavior != null), + super(key: key); + + /// This widget's selection and animation state. + /// + /// If [TabController] is not provided, then the value of [DefaultTabController.of] + /// will be used. + final TabController controller; + + /// One widget per tab. + /// + /// Its length must match the length of the [TabBar.tabs] + /// list, as well as the [controller]'s [TabController.length]. + final List children; + + /// How the page view should respond to user input. + /// + /// For example, determines how the page view continues to animate after the + /// user stops dragging the page view. + /// + /// The physics are modified to snap to page boundaries using + /// [PageScrollPhysics] prior to being used. + /// + /// Defaults to matching platform conventions. + final ScrollPhysics physics; + + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + + @override + _TabBarViewState createState() => _TabBarViewState(); +} + +class _TabBarViewState extends State { + TabController _controller; + PageController _pageController; + List _children; + List _childrenWithKey; + int _currentIndex; + int _warpUnderwayCount = 0; + + // If the TabBarView is rebuilt with a new tab controller, the caller should + // dispose the old one. In that case the old controller's animation will be + // null and should not be accessed. + bool get _controllerIsValid => _controller?.animation != null; + + void _updateTabController() { + final TabController newController = widget.controller ?? DefaultTabController.of(context); + assert(() { + if (newController == null) { + throw FlutterError('No TabController for ${widget.runtimeType}.\n' + 'When creating a ${widget.runtimeType}, you must either provide an explicit ' + 'TabController using the "controller" property, or you must ensure that there ' + 'is a DefaultTabController above the ${widget.runtimeType}.\n' + 'In this case, there was neither an explicit controller nor a default controller.'); + } + return true; + }()); + + if (newController == _controller) return; + + if (_controllerIsValid) _controller.animation.removeListener(_handleTabControllerAnimationTick); + _controller = newController; + if (_controller != null) _controller.animation.addListener(_handleTabControllerAnimationTick); + } + + @override + void initState() { + super.initState(); + _updateChildren(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _updateTabController(); + _currentIndex = _controller?.index; + _pageController = PageController(initialPage: _currentIndex ?? 0); + } + + @override + void didUpdateWidget(TabBarView oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.controller != oldWidget.controller) _updateTabController(); + if (widget.children != oldWidget.children && _warpUnderwayCount == 0) _updateChildren(); + } + + @override + void dispose() { + if (_controllerIsValid) _controller.animation.removeListener(_handleTabControllerAnimationTick); + _controller = null; + // We don't own the _controller Animation, so it's not disposed here. + super.dispose(); + } + + void _updateChildren() { + _children = widget.children; + _childrenWithKey = KeyedSubtree.ensureUniqueKeysForList(widget.children); + } + + void _handleTabControllerAnimationTick() { + if (_warpUnderwayCount > 0 || !_controller.indexIsChanging) + return; // This widget is driving the controller's animation. + + if (_controller.index != _currentIndex) { + _currentIndex = _controller.index; + _warpToCurrentIndex(); + } + } + + Future _warpToCurrentIndex() async { + if (!mounted) return Future.value(); + + if (_pageController.page == _currentIndex.toDouble()) return Future.value(); + + final int previousIndex = _controller.previousIndex; + if ((_currentIndex - previousIndex).abs() == 1) { + _warpUnderwayCount += 1; + await _pageController.animateToPage(_currentIndex, + duration: kTabScrollDuration, curve: Curves.ease); + _warpUnderwayCount -= 1; + return Future.value(); + } + + assert((_currentIndex - previousIndex).abs() > 1); + final int initialPage = _currentIndex > previousIndex ? _currentIndex - 1 : _currentIndex + 1; + final List originalChildren = _childrenWithKey; + setState(() { + _warpUnderwayCount += 1; + + _childrenWithKey = List.from(_childrenWithKey, growable: false); + final Widget temp = _childrenWithKey[initialPage]; + _childrenWithKey[initialPage] = _childrenWithKey[previousIndex]; + _childrenWithKey[previousIndex] = temp; + }); + _pageController.jumpToPage(initialPage); + + await _pageController.animateToPage(_currentIndex, + duration: kTabScrollDuration, curve: Curves.ease); + if (!mounted) return Future.value(); + setState(() { + _warpUnderwayCount -= 1; + if (widget.children != _children) { + _updateChildren(); + } else { + _childrenWithKey = originalChildren; + } + }); + } + + // Called when the PageView scrolls + bool _handleScrollNotification(ScrollNotification notification) { + if (_warpUnderwayCount > 0) return false; + + if (notification.depth != 0) return false; + + _warpUnderwayCount += 1; + if (notification is ScrollUpdateNotification && !_controller.indexIsChanging) { + if ((_pageController.page - _controller.index).abs() > 1.0) { + _controller.index = _pageController.page.floor(); + _currentIndex = _controller.index; + } + _controller.offset = (_pageController.page - _controller.index).clamp(-1.0, 1.0) as double; + } else if (notification is ScrollEndNotification) { + _controller.index = _pageController.page.round(); + _currentIndex = _controller.index; + if (!_controller.indexIsChanging) + _controller.offset = (_pageController.page - _controller.index).clamp(-1.0, 1.0) as double; + } + _warpUnderwayCount -= 1; + + return false; + } + + @override + Widget build(BuildContext context) { + assert(() { + if (_controller.length != widget.children.length) { + throw FlutterError( + "Controller's length property (${_controller.length}) does not match the " + "number of tabs (${widget.children.length}) present in TabBar's tabs property."); + } + return true; + }()); + return NotificationListener( + onNotification: _handleScrollNotification, + child: PageView( + dragStartBehavior: widget.dragStartBehavior, + controller: _pageController, + physics: widget.physics == null + ? const PageScrollPhysics().applyTo(const ClampingScrollPhysics()) + : const PageScrollPhysics().applyTo(widget.physics), + children: _childrenWithKey, + ), + ); + } +} + +/// Displays a single circle with the specified border and background colors. +/// +/// Used by [TabPageSelector] to indicate the selected page. +class TabPageSelectorIndicator extends StatelessWidget { + /// Creates an indicator used by [TabPageSelector]. + /// + /// The [backgroundColor], [borderColor], and [size] parameters must not be null. + const TabPageSelectorIndicator({ + Key key, + @required this.backgroundColor, + @required this.borderColor, + @required this.size, + }) : assert(backgroundColor != null), + assert(borderColor != null), + assert(size != null), + super(key: key); + + /// The indicator circle's background color. + final Color backgroundColor; + + /// The indicator circle's border color. + final Color borderColor; + + /// The indicator circle's diameter. + final double size; + + @override + Widget build(BuildContext context) { + return Container( + width: size, + height: size, + margin: const EdgeInsets.all(4.0), + decoration: BoxDecoration( + color: backgroundColor, + border: Border.all(color: borderColor), + shape: BoxShape.circle, + ), + ); + } +} + +/// Displays a row of small circular indicators, one per tab. +/// +/// The selected tab's indicator is highlighted. Often used in conjunction with +/// a [TabBarView]. +/// +/// If a [TabController] is not provided, then there must be a +/// [DefaultTabController] ancestor. +class TabPageSelector extends StatelessWidget { + /// Creates a compact widget that indicates which tab has been selected. + const TabPageSelector({ + Key key, + this.controller, + this.indicatorSize = 12.0, + this.color, + this.selectedColor, + }) : assert(indicatorSize != null && indicatorSize > 0.0), + super(key: key); + + /// This widget's selection and animation state. + /// + /// If [TabController] is not provided, then the value of + /// [DefaultTabController.of] will be used. + final TabController controller; + + /// The indicator circle's diameter (the default value is 12.0). + final double indicatorSize; + + /// The indicator circle's fill color for unselected pages. + /// + /// If this parameter is null, then the indicator is filled with [Colors.transparent]. + final Color color; + + /// The indicator circle's fill color for selected pages and border color + /// for all indicator circles. + /// + /// If this parameter is null, then the indicator is filled with the theme's + /// accent color, [ThemeData.accentColor]. + final Color selectedColor; + + Widget _buildTabIndicator( + int tabIndex, + TabController tabController, + ColorTween selectedColorTween, + ColorTween previousColorTween, + ) { + Color background; + if (tabController.indexIsChanging) { + // The selection's animation is animating from previousValue to value. + final double t = 1.0 - _indexChangeProgress(tabController); + if (tabController.index == tabIndex) + background = selectedColorTween.lerp(t); + else if (tabController.previousIndex == tabIndex) + background = previousColorTween.lerp(t); + else + background = selectedColorTween.begin; + } else { + // The selection's offset reflects how far the TabBarView has / been dragged + // to the previous page (-1.0 to 0.0) or the next page (0.0 to 1.0). + final double offset = tabController.offset; + if (tabController.index == tabIndex) { + background = selectedColorTween.lerp(1.0 - offset.abs()); + } else if (tabController.index == tabIndex - 1 && offset > 0.0) { + background = selectedColorTween.lerp(offset); + } else if (tabController.index == tabIndex + 1 && offset < 0.0) { + background = selectedColorTween.lerp(-offset); + } else { + background = selectedColorTween.begin; + } + } + return TabPageSelectorIndicator( + backgroundColor: background, + borderColor: selectedColorTween.end, + size: indicatorSize, + ); + } + + @override + Widget build(BuildContext context) { + final Color fixColor = color ?? Colors.transparent; + final Color fixSelectedColor = selectedColor ?? Theme.of(context).accentColor; + final ColorTween selectedColorTween = ColorTween(begin: fixColor, end: fixSelectedColor); + final ColorTween previousColorTween = ColorTween(begin: fixSelectedColor, end: fixColor); + final TabController tabController = controller ?? DefaultTabController.of(context); + assert(() { + if (tabController == null) { + throw FlutterError('No TabController for $runtimeType.\n' + 'When creating a $runtimeType, you must either provide an explicit TabController ' + 'using the "controller" property, or you must ensure that there is a ' + 'DefaultTabController above the $runtimeType.\n' + 'In this case, there was neither an explicit controller nor a default controller.'); + } + return true; + }()); + final Animation animation = CurvedAnimation( + parent: tabController.animation, + curve: Curves.fastOutSlowIn, + ); + return AnimatedBuilder( + animation: animation, + builder: (BuildContext context, Widget child) { + return Semantics( + label: 'Page ${tabController.index + 1} of ${tabController.length}', + child: Row( + mainAxisSize: MainAxisSize.min, + children: List.generate(tabController.length, (int tabIndex) { + return _buildTabIndicator( + tabIndex, tabController, selectedColorTween, previousColorTween); + }).toList(), + ), + ); + }, + ); + } +} diff --git a/lib/widget/my_delay_toast.dart b/lib/widget/my_delay_toast.dart new file mode 100644 index 0000000..9d7222b --- /dev/null +++ b/lib/widget/my_delay_toast.dart @@ -0,0 +1,142 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/screen_util.dart'; +import '../components/commonFun.dart'; + +//int my_count = 0; //重试次数 + +class MyDelayToast { + int showTime; // MyDelayToast显示时间 + double top; + double left; + double width; + double height; + + OverlayEntry _overlayEntry; // MyDelayToast靠它加到屏幕上 + Timer _timer; + double minEdge = 2.5; + + MyDelayToast( + {@required BuildContext context, + this.showTime = 5, + this.top = 100, + this.left = 20, + this.width = 80, + this.height = 50}) { + OverlayState overlayState = Overlay.of(context); + startTimer(); + if (_overlayEntry == null) { + _overlayEntry = OverlayEntry( + builder: (BuildContext context) { + return Positioned( + top: top, + left: left, + child: Draggable( + child: getView(context), + feedback: getView(context), + onDragStarted: () { + print('onDragStarted:'); + }, + // onDragEnd: (detail) { + // print('onDragEnd:${detail.offset}'); + // getView(top: detail.offset.dy, left: detail.offset.dx); + // }, + onDraggableCanceled: (Velocity velocity, Offset _offset) { + //ScreenUtil().statusBarHeight //Top Status bar height , Notch will be higher + //ScreenUtil().bottomBarHeight //Bottom safe zone distance, suitable for buttons with full screen + top = _offset.dy <= ScreenUtil().statusBarHeight + ? ScreenUtil().statusBarHeight + minEdge + : (_offset.dy < + ScreenUtil().screenHeight - height - ScreenUtil().bottomBarHeight) + ? _offset.dy + : (ScreenUtil().screenHeight - + height - + ScreenUtil().bottomBarHeight - + minEdge); + left = _offset.dx < minEdge + ? minEdge + : (_offset.dx < ScreenUtil().screenWidth - width - minEdge) + ? _offset.dx + : (ScreenUtil().screenWidth - width - minEdge); + getView(context); + _overlayEntry.markNeedsBuild(); + }, + childWhenDragging: Container(), + ), + ); + }, + ); + overlayState.insert(_overlayEntry); + } else { + //重新绘制UI,类似setState + _overlayEntry.markNeedsBuild(); + } + } + + Widget getView(BuildContext context, {double width = 80, double height = 50}) { + return GestureDetector( + child: Container( + width: width, + height: height, + padding: EdgeInsets.all(0), + color: Theme.of(context).buttonColor, + child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + Text('第 ${getCount} 次获取 ', + style: TextStyle( + fontFamily: Theme.of(context).accentTextTheme.caption.fontFamily, + fontSize: 13, + color: Colors.black, + decoration: TextDecoration.none, + fontWeight: FontWeight.normal, + ), + textAlign: TextAlign.center), + Text('(${showTime} 秒)', + style: TextStyle( + fontFamily: Theme.of(context).accentTextTheme.caption.fontFamily, + fontSize: 13, + color: Colors.black, + decoration: TextDecoration.none, + fontWeight: FontWeight.normal, + ), + textAlign: TextAlign.center), + ]), + ), + onTap: () {}, + onDoubleTap: () { + endMyDelayToast(); + }, + onLongPress: () { + endMyDelayToast(); + }, + ); + } + + endMyDelayToast() async { + _timer?.cancel(); + _overlayEntry.markNeedsBuild(); + await Future.delayed(Duration(milliseconds: 400)); + _overlayEntry.remove(); + _overlayEntry = null; + } + + void startTimer() { + _timer = Timer.periodic( + Duration(seconds: 1), + (Timer timer) { + if (showTime < 1) { + timer.cancel(); + endMyDelayToast(); + } else { + if (!getingDwVideo) { + endMyDelayToast(); + } + showTime--; + // if (showTime % 3 == 0) { + // getCount++; + // } + _overlayEntry.markNeedsBuild(); + } + }, + ); + } +} diff --git a/lib/widget/my_superplayer.dart b/lib/widget/my_superplayer.dart new file mode 100644 index 0000000..9002b1e --- /dev/null +++ b/lib/widget/my_superplayer.dart @@ -0,0 +1,620 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/screen_util.dart'; +import 'package:flutter_superplayer/flutter_superplayer.dart'; +import 'package:flutterptcontrol/flutterptcontrol.dart'; +import 'package:hyzp_ybqx/components/commonFun.dart'; +import 'package:hyzp_ybqx/components/dioFun.dart'; +import 'package:hyzp_ybqx/provider/player_ratio.dart'; +import 'package:hyzp_ybqx/provider/player_region.dart'; +import 'package:provider/provider.dart'; + +import '../components/commonFun.dart'; +import '../services/Storage.dart'; + +const _kControlViewTypes = [kControlViewTypeDefault, kControlViewTypeWithout]; + +class SuperPlayerPage extends StatefulWidget { + SuperPlayerPage( + {@required this.url, + this.id = -2, // 播放点位视频的点位编号,-2 表示播放违章视频 + this.loop = 1, + this.title = 'Tencent Player', + Key key}) + : super(key: key); + String url; + String title; + int loop; //设置播放循环,默认播放器的循环次数是1, 即不循环播放。如果设置循环次数0,表示无限循环。 + int id; + + @override + _SuperPlayerPageState createState() => _SuperPlayerPageState(); +} + +class _SuperPlayerPageState extends State with SuperPlayerListener { + SuperPlayerController _playerController = SuperPlayerController(); + + String _sdkVersion = 'Unknown'; + List _logs = []; + bool bFullScreen = false; + + String _controlViewType = _kControlViewTypes.first; + + @override + void dispose() { + Playing = false; + super.dispose(); + } + + @override + void initState() { + super.initState(); + //initPlatformState(); + // Future.delayed(const Duration(milliseconds: 1000), () { + // _playerController.playWithModel(SuperPlayerModel(url: widget.url)); + // setState(() { + // }); + // }); + init(); + } + + Future init() async { + await _playerController.addListener(this); + await initPlatformState(); + if (!mounted) return; + print('mounted = ${mounted}'); + // 开启调试日志 + //await FTXPlayerController.setConsoleEnabled(true); + // 初始化播放器 + //await _controller.initialize(onlyAudio: true); + + await _playerController.uiHideDanmu(); // 隐藏弹幕 + //设置播放循环,默认播放器的循环次数是1, 即不循环播放。如果设置循环次数0,表示无限循环。 + if (0 == widget.loop) { + await _playerController.setLoop(true); + } + + await _playerController.playWithModel(testSuperPlayerModel); + //_controller.play("http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"); + // _controller + // .play('rtmp://125.64.218.67:9901/rtp/gb_play_34020000001320003016_34020000001320003016'); + // 设置循环播放 + //await _controller.setLoop(true); + // 开始播放 + //await _controller.play("http://125.64.218.67:9908/video/2_6063_20210409_140608_川Q31715.mp4"); + } + + SuperPlayerModel get testSuperPlayerModel { + // int appId = 1252463788; + // String fileId = "5285890781763144364"; + + SuperPlayerModel superPlayerModel = SuperPlayerModel( + url: widget.url, + // appId: appId, + // videoId: SuperPlayerVideoId(fileId: fileId), + ); + return superPlayerModel; + } + + // Platform messages are asynchronous, so we initialize in an async method. + Future initPlatformState() async { + String sdkVersion; + // Platform messages may fail, so we use a try/catch PlatformException. + try { + sdkVersion = await FlutterSuperPlayer.sdkVersion; + } on PlatformException { + sdkVersion = 'Failed to get platform version.'; + } + print('sdkVersion = ${sdkVersion}'); + print('mounted = ${mounted}'); + + // If the widget was removed from the tree while the asynchronous platform + // message was in flight, we want to discard the reply rather than calling + // setState to update our non-existent appearance. + if (!mounted) return; + + setState(() { + _sdkVersion = sdkVersion; + }); + } + + void _addLog(String method, dynamic data) { + _logs.add('>>>$method'); + if (data != null) { + _logs.add(data is Map ? json.encode(data) : data); + } + _logs.add(' '); + + setState(() {}); + } + + PlayerRatioProvide playerRatioProvide; + + @override + Widget build(BuildContext context) { + playerRegionProvide = Provider.of(context); + playerRatioProvide = Provider.of(context); + List listData = getDataListControl2(); + double btnHeight1 = 70; //第一按钮行高度 + double btnHeight2 = 160; //第二按钮行高度 + int btnCount = 4; //每行按钮个数 + int btnCount3 = listData.length; //每行按钮个数 + var mediaSize = MediaQuery.of(context).size; + //远程控制球机方向按钮外半径和内半径 + double _outerRadius = 270; + double _innerRadius = _outerRadius / 2; + + //double barHeight = bFullScreen ? 0 : MediaQueryData.fromWindow(window).padding.top; + return Scaffold( + appBar: bFullScreen + ? null + : PreferredSize( + preferredSize: Size.fromHeight(ScreenUtil().setHeight(173)), // 设置appBar高度 + // 设置appBar高度 + child: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + titleSpacing: 0.0, + //设置title的左边距 + flexibleSpace: Container( + //SizedBox(height: ScreenUtil().statusBarHeight), //显示顶部状态栏 + // SizedBox(height: ScreenUtil().setHeight(10)), //显示顶部状态栏 + padding: EdgeInsets.only(top: ScreenUtil().statusBarHeight), //留出顶部状态栏高度 + child: Container( + //height: ScreenUtil().setHeight(173), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(12, 186, 156, 1), + Color.fromRGBO(39, 127, 235, 1), + ], + ), + ), + // decoration: BoxDecoration( + // gradient: LinearGradient(colors: [ + // Color(0xFF0018EB), + // Color(0xFF01C1D9), + // ], begin: Alignment.bottomCenter, end: Alignment.topCenter), + // ), + ), + ), + title: Padding( + padding: EdgeInsets.only(left: 0, right: 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + getIconAndTextButton( + iconColor: Colors.white, + iconData: Icons.chevron_left_outlined, + onPress: () { + getingDwVideo = false; + Navigator.pop(context); + }, + ), + Expanded( + child: Text(widget.title, + style: TextStyle(color: Colors.white, fontSize: 18), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis), + ), + SizedBox(width: 50), + ], + ), + ), + ), + ), + body: WillPopScope( + child: Container( + // height: ScreenUtil().screenHeight - + // ScreenUtil().statusBarHeight - + // ScreenUtil().bottomBarHeight, + color: Color.fromRGBO(224, 224, 224, 1), + child: Column( + children: [ + //第2行组件,视频播放区 + Center( + child: Container( + //padding: EdgeInsets.only(top: barHeight), + alignment: Alignment(0, -1), + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.width * (9 / 16), + color: Colors.black, + child: Stack( + children: [ + _playState <= 2 ? getMoreWidget(strokeWidth: 3.0) : SizedBox.shrink(), + SuperPlayerView( + controller: _playerController, + controlViewType: _kControlViewTypes[0], + ), + //_playState < 3 ? SizedBox.shrink() : getMoreWidget(strokeWidth: 3.0), + ], + ), + ), + ), + //第3.1行组件,控制按钮区 + bFullScreen ? SizedBox.shrink() : SizedBox(height: ScreenUtil().setHeight(69)), + bFullScreen + ? SizedBox.shrink() + : Row( + children: [ + SizedBox(width: ScreenUtil().setWidth(347)), + getRoundButton( + //(bPlaying) ? '暂停' : '播放', + text: playerRegionProvide.playerText, + icon: playerRegionProvide.playerIcon, + diameter: 130, + onPress: playOrPause, + ), + SizedBox(width: ScreenUtil().setWidth(104)), + getRoundButton( + text: '刷新', + icon: Icons.autorenew, + diameter: 130, + onPress: () { + restartPlay(urlnew); + }, + ), + ], + ), + bFullScreen ? SizedBox.shrink() : SizedBox(height: ScreenUtil().setHeight(79)), + bFullScreen + ? SizedBox.shrink() + : Container( + height: ScreenUtil().setHeight(2 * _outerRadius), + width: ScreenUtil().setWidth(2 * _outerRadius), + child: PtControlWidget( + // innerRadius: _innerRadius, + // outerRadius: _outerRadius, + // 进行像素单位转换,解决按钮响应错乱问题 + innerRadius: _innerRadius / ScreenUtil().pixelRatio, + outerRadius: _outerRadius / ScreenUtil().pixelRatio, + callback: -2 == widget.id + ? null + : (status) { + ///点击回调 + print('status = $status'); + switch (status) { + + /// status == -1 超出范围 + /// status == 0 右 + case 0: + setSphericalCameraDio(id: widget.id, cmdCode: 1); + break; + + /// status == 1 右上 + case 1: + setSphericalCameraDio(id: widget.id, cmdCode: 9); + break; + + ///三、球机位移接口方向代码说明: + // 上:8 下:4 左:2 右:1 左上:10 左下:6 右上:9 右下:5 + /// status == 2 上 + case 2: + setSphericalCameraDio(id: widget.id, cmdCode: 8); + break; + + /// status == 3 左上 + case 3: + setSphericalCameraDio(id: widget.id, cmdCode: 10); + break; + + /// status == 4 左 + case 4: + setSphericalCameraDio(id: widget.id, cmdCode: 2); + break; + + /// status == 5 左下 + case 5: + setSphericalCameraDio(id: widget.id, cmdCode: 6); + break; + + /// status == 6 下 + case 6: + setSphericalCameraDio(id: widget.id, cmdCode: 4); + break; + + /// status == 7 右下 + case 7: + setSphericalCameraDio(id: widget.id, cmdCode: 5); + break; + + /// status == 8 还原 + case 8: + break; + default: + break; + } + }, + ), + // child: GridView.custom( + // // padding: EdgeInsets.only( + // // left: ScreenUtil().setWidth(35), right: ScreenUtil().setWidth(35)), + // gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + // crossAxisCount: btnCount3, + // mainAxisSpacing: 0, + // crossAxisSpacing: 1, + // childAspectRatio: ratio3, + // ), + // childrenDelegate: SliverChildBuilderDelegate((context, position) { + // return getItemContainer(listData[position]); + // }, childCount: btnCount3)), + ), + //SizedBox(height: ScreenUtil().setHeight(49)), + //Divider(color: Colors.blue), + //第4行组件,分隔栏 + // Container( + // //height: 11, + // height: ScreenUtil().setHeight(28), + // color: Color.fromRGBO(224, 224, 224, 1), + // ), + ], + ), + ), + onWillPop: () { + Playing = false; + getingDwVideo = false; + Navigator.pop(context); //关闭弹框,播放输入视频地址 + }, + ), + ); + } + + @override + void onClickFloatCloseBtn() { + _addLog('onClickFloatCloseBtn', {}); + } + + @override + void onClickSmallReturnBtn() { + _addLog('onClickSmallReturnBtn', {}); + Navigator.maybePop(context); + } + + @override + void onFullScreenChange(bool isFullScreen) { + _addLog('onFullScreenChange', {'isFullScreen': isFullScreen}); + bFullScreen = !bFullScreen; + setState(() {}); + } + + @override + void onPlayProgressChange(int current, int duration) { + _addLog('onPlayProgressChange', {'current': current, 'duration': duration}); + } + + int _playState = 4; + int i = 0; + + @override + void onPlayStateChange(int playState) { + _playState = playState; + i++; + print('$i、playState = $playState'); + _addLog('onPlayStateChange', {'playState': playState}); + setPlayOrPauseIcon(playState); + } + + void setPlayOrPauseIcon(int state) { + //state : 1 播放状态,2 暂停状态 + print('state = $state'); + if (1 == state) { + bPlaying = true; + } else { + bPlaying = false; + } + playerRegionProvide.changePlayerState(bPlaying); + Storage.setString('bPlaying', bPlaying ? 'true' : 'false'); + setState(() {}); + } + + @override + void onStartFloatWindowPlay() { + _addLog('onStartFloatWindowPlay', {}); + } + + //生成圆形按钮部件 + Widget getRoundButton( + {double diameter = 144, + double marginVer = 10, + String text, + IconData icon, + double fontSize = 16, + double iconSize = 90, + Color color = const Color.fromRGBO(52, 157, 237, 1), + var onPress}) { + return InkWell( + onTap: onPress, + child: Column( + children: [ + Container( + width: ScreenUtil().setWidth(diameter), + height: ScreenUtil().setHeight(diameter), + alignment: Alignment.center, + child: Icon( + icon, + size: ScreenUtil().setWidth(iconSize), + color: Color.fromRGBO(52, 157, 237, 1), + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(200)), + border: Border.all(width: 0, style: BorderStyle.none), + ), + ), + SizedBox(height: ScreenUtil().setHeight(marginVer)), + Text(text, style: TextStyle(fontSize: fontSize, color: Color.fromRGBO(139, 139, 139, 1))), + ], + ), + ); + } + + bool bPlaying = true; + + void playOrPause() { + //state : 1 播放状态,2 暂停状态 + _playerController.getPlayState().then((state) { + print('state = $state'); + if (1 == state) { + bPlaying = false; + _playerController.pause(); + } else { + bPlaying = true; + _playerController.resume(); + } + playerRegionProvide.changePlayerState(bPlaying); + Storage.setString('bPlaying', bPlaying ? 'true' : 'false'); + setState(() {}); + }); + } + + void restartPlay(String url) async { + _playState = 4; + bPlaying = true; + _playerController.resetPlayer(); + _playerController.resume(); + // //writeCurrentPosFile(); + // await player.stop(); + // await player.reset(); + // await player.setOption(FijkOption.playerCategory, "mediacodec-all-videos", 1); + // await player.setOption(FijkOption.hostCategory, "enable-snapshot", 1); + // await player.setOption(FijkOption.hostCategory, "request-screen-on", 1); + // await player.setOption(FijkOption.hostCategory, "request-audio-focus", 1); + // await player.setOption(FijkOption.hostCategory, "enable-accurate-seek", 1); + // await player.setOption(FijkOption.hostCategory, "max-buffer-size", 500 * 1024); + // await player.setDataSource(url, autoPlay: true).catchError((e) { + // print("setDataSource error: $e"); + // }); + // await player.setLoop(widget.loop); //设置播放循环,默认播放器的循环次数是1, 即不循环播放。如果设置循环次数0,表示无限循环。 + // bPlaying = true; + // setState(() {}); + // playerRegionProvide.changePlayerState(bPlaying); + } + + //生成播放控制区第2行按钮List + List getDataListControl2() { + double _diameter = 100; + double _iconSize = 70; + double _fontSize = 14; + double _marginVer = 8; + + List list = [ + // getRoundButton( + // diameter: _diameter, + // iconSize: _iconSize, + // text: '快退', + // icon: Icons.fast_rewind, + // onPress: () { + // fastSeek(false); + // }, + // ), + // getRoundButton( + // diameter: _diameter, + // iconSize: _iconSize, + // text: '快进', + // icon: Icons.fast_forward, + // onPress: () { + // fastSeek(true); + // }, + // ), + getRoundButton( + diameter: _diameter, + iconSize: _iconSize, + fontSize: _fontSize, + marginVer: _marginVer, + text: '放大', + icon: Icons.zoom_in, + onPress: () { + //print('Icons.videocam'); + //_inputDialog(context2); + if (10 >= playerRatioProvide.scale) { + playerRatioProvide.changeScale(playerRatioProvide.scale + 0.5); + } + }, + ), + getRoundButton( + diameter: _diameter, + iconSize: _iconSize, + fontSize: _fontSize, + marginVer: _marginVer, + text: '缩小', + icon: Icons.zoom_out, + onPress: () { + //print('Icons.videocam'); + //_getFileDialog(context2); + if (1 < playerRatioProvide.scale) { + playerRatioProvide.changeScale(playerRatioProvide.scale - 0.5); + } + }, + ), +// _getIconAndTextButton( +// '还原', +// Icons.reply, +// Colors.orange, +// () { +// //print('Icons.videocam'); +// //_getFileDialog(context2); +// // 更新当前位置 +// playerRatioProvide.changeScale(1.0); +// playerRatioProvide.changeOffset(Offset(0, 0)); +// playerRatioProvide.changeDeltaX(0.0); +// playerRatioProvide.changeDeltaY(0.0); +// }, +// ), + getRoundButton( + diameter: _diameter, + iconSize: _iconSize, + fontSize: _fontSize, + marginVer: _marginVer, + text: '截图', + icon: Icons.camera_alt, + onPress: () { + //通过上面定义的key,才能准确调用该类型的该对象的方法 + //_myFijkPanelWidgetBuilderStateKey.currentState.takeSnapshot(); + //_fijkPanelWidgetBuilder.currentState..takeSnapshot(); + takeSnapshot(); + }, + ), + getRoundButton( + diameter: _diameter, + iconSize: _iconSize, + fontSize: _fontSize, + marginVer: _marginVer, + text: '全屏', + icon: Icons.fullscreen, + onPress: () { + //player.enterFullScreen(); + _playerController.toFullScreen(); + }, + ), + ]; + return list; + } + + void takeSnapshot() { + // player.takeSnapShot().then((v) { + // var provider = MemoryImage(v); + // precacheImage(provider, context).then((_) { + // setState(() { + // myImageProvider = provider; + // }); + // }); + // FijkLog.d("get snapshot succeed"); + // + // //Uint8List fileData; + // String fileFath; + // ImageSaver.save('extended_image_cropped_image.jpg', v).then((value) { + // fileFath = value; + // // var fileFath = await ImagePickerSaver.saveFile(fileData: fileData); + // print('my save fileFath : $fileFath'); + // }); + // }).catchError((e) { + // FijkLog.d("get snapshot failed"); + // }); + } +} diff --git a/lib/widget/table_scrollable/table_body.dart b/lib/widget/table_scrollable/table_body.dart new file mode 100644 index 0000000..0d09ddc --- /dev/null +++ b/lib/widget/table_scrollable/table_body.dart @@ -0,0 +1,134 @@ +import 'package:flutter/material.dart'; +import 'package:linked_scroll_controller/linked_scroll_controller.dart'; +import 'table_cell.dart'; + +class TableBody extends StatefulWidget { + // 所有点位 31 天的车流量日统计数据,每天有 3 个数据:车流量、早高峰、晚高峰 + Map mapTableData; // 有 31 个元素,每个元素又是一个 Map:包含车流量、早高峰、晚高峰 + final ScrollController scrollController; + + TableBody({ + @required this.scrollController, + @required this.mapTableData, + }); + + @override + _TableBodyState createState() => _TableBodyState(); +} + +class _TableBodyState extends State { + LinkedScrollControllerGroup _controllers; + ScrollController _firstColumnController; + ScrollController _restColumnsController; + + @override + void initState() { + super.initState(); + _controllers = LinkedScrollControllerGroup(); + _firstColumnController = _controllers.addAndGet(); + _restColumnsController = _controllers.addAndGet(); + } + + @override + void dispose() { + _firstColumnController.dispose(); + _restColumnsController.dispose(); + super.dispose(); + } + + List listWidth = [ + 100.0, + 80.0, + 80.0, + 80.0, + ]; + + @override + Widget build(BuildContext context) { + return ListView( + controller: _restColumnsController, + physics: const ClampingScrollPhysics(), + children: List.generate(widget.mapTableData.length, (index1) { + // 降序生成 + int rIndex1 = widget.mapTableData.length - 1 - index1; + String key1 = widget.mapTableData.keys.elementAt(rIndex1); + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate(4, (index2) { + String key2 = ''; + if (index2 > 0) { + key2 = widget.mapTableData[key1].keys.elementAt(index2 - 1); + } + + return MultiplicationTableCell( + width: listWidth[index2], + height: cellHeight, + fontsize: 0 == index2 ? 14 : 16, + edgeRight: ' ', + text: 0 == index2 + ? '${rIndex1 + 1}.${key1}' + : widget.mapTableData[key1][key2].toString(), + //text: _text, + ); + }), + ); + }), + // children: List.generate(31, (y) { + // return Row( + // mainAxisAlignment: MainAxisAlignment.center, + // children: List.generate(4, (x) { + // return MultiplicationTableCell( + // value: (x + 2) * (y + 2), + // width: listWidth[x], + // height: cellHeight, + // text: 0 == x ? '${y + 1}.${(x + 2) * (y + 2)}' : '', + // ); + // }), + // ); + // }), + ); + + //Row( + //children: + //[ + // SizedBox( + // width: cellWidth, + // child: ListView( + // controller: _firstColumnController, + // physics: ClampingScrollPhysics(), + // children: List.generate(maxNumber - 1, (index) { + // return MultiplicationTableCell( + // //color: Colors.yellow.withOpacity(0.3), + // value: index + 2, + // ); + // }), + // ), + // ), + + // Expanded( + // child: SingleChildScrollView( + // controller: widget.scrollController, + // scrollDirection: Axis.horizontal, + // physics: const ClampingScrollPhysics(), + // child: SizedBox( + // width: (maxNumber - 1) * cellWidth, + // child: ListView( + // controller: _restColumnsController, + // physics: const ClampingScrollPhysics(), + // children: List.generate(maxNumber - 1, (y) { + // return Row( + // children: List.generate(maxNumber - 1, (x) { + // return MultiplicationTableCell( + // value: (x + 2) * (y + 2), + // ); + // }), + // ); + // }), + // ), + // ), + // ), + // ), + //], + //); + } +} diff --git a/lib/widget/table_scrollable/table_cell.dart b/lib/widget/table_scrollable/table_cell.dart new file mode 100644 index 0000000..1cf30fc --- /dev/null +++ b/lib/widget/table_scrollable/table_cell.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; + +const double cellWidth = 50; +const double cellHeight = 25; +const double headHeight = 30; + +class MultiplicationTableCell extends StatelessWidget { + final String text; + final Color color; + double width; + double height; + double fontsize; + AlignmentGeometry alignment; + String edgeRight; + + MultiplicationTableCell({ + this.text = '', + this.color, + this.width = cellWidth, + this.height = cellWidth, + this.fontsize = 16, + this.alignment = Alignment.centerRight, + this.edgeRight = ' ', + }); + + @override + Widget build(BuildContext context) { + return Container( + width: width, + height: height, + decoration: BoxDecoration( + color: color, + border: Border.all( + color: Colors.black12, + width: 1.0, + ), + ), + alignment: alignment, + child: Text(text + edgeRight, style: TextStyle(fontSize: fontsize)), + ); + } +} diff --git a/lib/widget/table_scrollable/table_head.dart b/lib/widget/table_scrollable/table_head.dart new file mode 100644 index 0000000..93b4352 --- /dev/null +++ b/lib/widget/table_scrollable/table_head.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'table_cell.dart'; + +class TableHead extends StatelessWidget { + final ScrollController scrollController; + + TableHead({ + @required this.scrollController, + }); + + Widget getHeadText(String text, {double height = 30}) { + return Container( + alignment: Alignment.center, + height: height, + child: Text(text, style: TextStyle(fontWeight: FontWeight.bold)), + ); + } + + List listHead = [ + '统计时间', + '车流量', + '早高峰', + '晚高峰', + ]; + + List listWidth = [ + 100.0, + 80.0, + 80.0, + 80.0, + ]; + + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment(0, 0), + height: headHeight, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate(listHead.length, (index) { + return MultiplicationTableCell( + color: Colors.yellow.withOpacity(0.3), + text: listHead[index], + width: listWidth[index], + ); + }), + + // children: [ + // // getHeadText('统计时间'), + // // getHeadText('车流量'), + // // getHeadText('早高峰'), + // // getHeadText('晚高峰'), + // // MultiplicationTableCell( + // // color: Colors.yellow.withOpacity(0.3), + // // value: 1, + // // ), + // // Expanded( + // // child: ListView( + // // //controller: scrollController, //注释掉该行,便只能上下滚动 + // // physics: ClampingScrollPhysics(), + // // scrollDirection: Axis.horizontal, + // // children: List.generate(maxNumber - 1, (index) { + // // return MultiplicationTableCell( + // // color: Colors.yellow.withOpacity(0.3), + // // value: index + 2, + // // ); + // // }), + // // ), + // // ), + // ], + ), + ); + } +} diff --git a/lib/widget/table_scrollable/table_scrollable.dart b/lib/widget/table_scrollable/table_scrollable.dart new file mode 100644 index 0000000..92f069b --- /dev/null +++ b/lib/widget/table_scrollable/table_scrollable.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:linked_scroll_controller/linked_scroll_controller.dart'; + +import 'table_head.dart'; +import 'table_body.dart'; + +class TableScrollable extends StatefulWidget { + // 所有点位 31 天的车流量日统计数据,每天有 3 个数据:车流量、早高峰、晚高峰 + Map mapTableData; // 有 31 个元素,每个元素又是一个 Map:包含车流量、早高峰、晚高峰 + + TableScrollable({ + @required this.mapTableData, + }); + + @override + _TableScrollableState createState() => _TableScrollableState(); +} + +class _TableScrollableState extends State { + LinkedScrollControllerGroup _controllers; + ScrollController _headController; + ScrollController _bodyController; + + @override + void initState() { + super.initState(); + _controllers = LinkedScrollControllerGroup(); + _headController = _controllers.addAndGet(); + _bodyController = _controllers.addAndGet(); + } + + @override + void dispose() { + _headController.dispose(); + _bodyController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + TableHead( + scrollController: _headController, + ), + Expanded( + child: TableBody( + scrollController: _bodyController, + mapTableData: widget.mapTableData, + ), + ), + ], + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..4c14e98 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,910 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + archive: + dependency: transitive + description: + name: archive + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.13" + args: + dependency: transitive + description: + name: args + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.6.0" + asn1lib: + dependency: transitive + description: + name: asn1lib + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.8.1" + async: + dependency: transitive + description: + name: async + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.5.0-nullsafety.1" + audioplayers: + dependency: "direct main" + description: + name: audioplayers + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.16.2" + badges: + dependency: "direct main" + description: + name: badges + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.0-nullsafety.1" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.5.1" + camera: + dependency: "direct main" + description: + name: camera + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.5.8+17" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0-nullsafety.3" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.0-nullsafety.1" + city_pickers: + dependency: "direct main" + description: + name: city_pickers + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.30" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0-nullsafety.1" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.15.0-nullsafety.3" + common_utils: + dependency: transitive + description: + name: common_utils + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.4" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.1" + crypto: + dependency: "direct main" + description: + name: crypto + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.5" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.0" + decimal: + dependency: transitive + description: + name: decimal + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.3.5" + device_info: + dependency: "direct main" + description: + name: device_info + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.0" + device_info_platform_interface: + dependency: transitive + description: + name: device_info_platform_interface + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.1" + dio: + dependency: "direct main" + description: + name: dio + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.10" + encrypt: + dependency: "direct main" + description: + name: encrypt + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.1.0" + equatable: + dependency: transitive + description: + name: equatable + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.6" + event_bus: + dependency: "direct main" + description: + name: event_bus + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.1" + extended_image: + dependency: transitive + description: + name: extended_image + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0" + extended_image_library: + dependency: "direct main" + description: + name: extended_image_library + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.3" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.0-nullsafety.1" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.3" + file: + dependency: transitive + description: + name: file + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.2.1" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.12.3" + flustars: + dependency: "direct main" + description: + name: flustars + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.3.3" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_blurhash: + dependency: transitive + description: + name: flutter_blurhash + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.5.0" + flutter_bmfbase: + dependency: transitive + description: + name: flutter_bmfbase + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.2" + flutter_bmfmap: + dependency: "direct main" + description: + name: flutter_bmfmap + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.2" + flutter_bmfutils: + dependency: "direct main" + description: + name: flutter_bmfutils + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.2" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.2" + flutter_datetime_picker: + dependency: "direct main" + description: + name: flutter_datetime_picker + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.5.0" + flutter_downloader: + dependency: "direct main" + description: + name: flutter_downloader + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.5.2" + flutter_drag_scale: + dependency: "direct main" + description: + path: "." + ref: HEAD + resolved-ref: "562ac559370da547783c2c5b1c18c931adddb8a4" + url: "https://github.com/mjl0602/flutter_drag_scale.git" + source: git + version: "0.0.1" + flutter_easyrefresh: + dependency: "direct main" + description: + name: flutter_easyrefresh + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.8" + flutter_echarts: + dependency: "direct main" + description: + name: flutter_echarts + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.5.0" + flutter_inappbrowser: + dependency: "direct main" + description: + name: flutter_inappbrowser + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.2" + flutter_page_indicator: + dependency: transitive + description: + name: flutter_page_indicator + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.0.3" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.11" + flutter_screenutil: + dependency: "direct main" + description: + name: flutter_screenutil + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.4+1" + flutter_superplayer: + dependency: "direct main" + description: + path: "lib/my_flutter_superplayer" + relative: true + source: path + version: "0.0.2" + flutter_swiper: + dependency: "direct main" + description: + name: flutter_swiper + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.6" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutterptcontrol: + dependency: "direct main" + description: + path: "lib/my_flutterPtControl" + relative: true + source: path + version: "1.1.5" + fluttertoast: + dependency: "direct main" + description: + name: fluttertoast + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.1" + get_it: + dependency: "direct main" + description: + name: get_it + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.0.6" + http: + dependency: "direct main" + description: + name: http + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.12.2" + http_client_helper: + dependency: transitive + description: + name: http_client_helper + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.4" + image: + dependency: transitive + description: + name: image + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.19" + image_picker: + dependency: "direct main" + description: + name: image_picker + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.6.7+22" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.6" + intl: + dependency: transitive + description: + name: intl + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.16.1" + keyboard_avoider: + dependency: "direct main" + description: + name: keyboard_avoider + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.2" + linked_scroll_controller: + dependency: "direct main" + description: + name: linked_scroll_controller + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.2" + lpinyin: + dependency: transitive + description: + name: lpinyin + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.1" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.12.10-nullsafety.1" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.0-nullsafety.3" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.9.7" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.0.4" + octo_image: + dependency: transitive + description: + name: octo_image + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.3.0" + open_file: + dependency: "direct main" + description: + name: open_file + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.3" + package_info: + dependency: "direct main" + description: + name: package_info + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.4.3+4" + path: + dependency: transitive + description: + name: path + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.8.0-nullsafety.1" + path_drawing: + dependency: transitive + description: + name: path_drawing + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.4.1+1" + path_parsing: + dependency: transitive + description: + name: path_parsing + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.4" + path_provider: + dependency: "direct main" + description: + name: path_provider + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.6.28" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.0.1+2" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.0.4+8" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.4" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.0.4+3" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.9.2" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.1.0+2" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.2" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.0" + photo_manager: + dependency: transitive + description: + name: photo_manager + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.5.8" + photo_view: + dependency: "direct main" + description: + name: photo_view + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.10.3" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.1" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.3" + pointycastle: + dependency: transitive + description: + name: pointycastle + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.1" + process: + dependency: transitive + description: + name: process + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.13" + progress_dialog: + dependency: "direct main" + description: + name: progress_dialog + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.4" + provider: + dependency: "direct main" + description: + name: provider + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.3.3" + rational: + dependency: transitive + description: + name: rational + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.3.8" + rxdart: + dependency: transitive + description: + name: rxdart + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.25.0" + scroll_to_index: + dependency: "direct main" + description: + name: scroll_to_index + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.6" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.5.12+4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.0.2+4" + shared_preferences_macos: + dependency: transitive + description: + name: shared_preferences_macos + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.0.1+11" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.4" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.2+7" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.0.2+3" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.8.0-nullsafety.2" + sp_util: + dependency: transitive + description: + name: sp_util + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.2" + sprintf: + dependency: "direct main" + description: + name: sprintf + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.0.0" + sqflite: + dependency: transitive + description: + name: sqflite + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.2+4" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.3+3" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.10.0-nullsafety.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.0-nullsafety.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0-nullsafety.1" + synchronized: + dependency: transitive + description: + name: synchronized + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.0+2" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.0-nullsafety.1" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.19-nullsafety.2" + transformer_page_view: + dependency: transitive + description: + name: transformer_page_view + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.6" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.3.0-nullsafety.3" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + url: "https://pub.flutter-io.cn" + source: hosted + version: "5.7.10" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.0.1+4" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.0.1+9" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.9" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.5+3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.0.1+3" + uuid: + dependency: transitive + description: + name: uuid + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.2" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.1.0-nullsafety.3" + video_player: + dependency: transitive + description: + name: video_player + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.10.12+5" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.0" + video_player_web: + dependency: transitive + description: + name: video_player_web + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.4+1" + webview_flutter: + dependency: transitive + description: + name: webview_flutter + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.7" + wechat_assets_picker: + dependency: "direct main" + description: + name: wechat_assets_picker + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.2.1" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.7.4+1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.2" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.5.1" +sdks: + dart: ">=2.10.2 <2.11.0" + flutter: ">=1.22.2 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..17f5ce3 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,237 @@ +name: hyzp_ybqx +description: HeiYanZhuaPai_YiBin_QuXian Flutter application . + +# The following line prevents the package from being accidentally published to +# pub.dev using `pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# 开发人员信息: +# 技术人员: 闵军 +# By: ybmj@vip.163.com , +# QQ: 153248043 +# https://www.cnblogs.com/ybmj/ + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# 版本名称: 1.2.7 版本号:1 +# R:\FlutterProject\FlutterProject33\hyzp_ybqx\lib\components\commonFun.dart +# String dateAppCompile = '2021.05.18'; //1.2.7 +# String dateAppCompile = '2021.05.15'; //1.3.1 +# 版本号保存在该文件中:r:\FlutterProject\FlutterProject33\hyzp_ybqx\pubspec.yaml +# version: 1.3.1+20210508 +号前面是版本号,+号后面是时间(yyyymmdd 小版本号) 对应 hyzp_ybqx-Commit802-Branch.031 +# version: 1.3.0+20210526 +# version: 1.3.1+20210527 +# version: 1.3.2+20210528 +# version: 1.3.4+20210529 +# version: 1.3.5+20210530 +# version: 1.3.6+20210601 +# version: 1.3.7+20210604 +# version: 1.3.10+20210604 +# version: 1.3.11+20210729 +# 关键是在通过接口 App.Car_Upload.Apk 上传新版本 apk 时,ver参数的版本号必须为1.3.12,而不能为1.3.12+20210729 +# 否则老版本1.3.10+20210604无法更新。新版本1.3.12+20210729已经解决该问题 +# version: 1.3.12+20210729 +version: 1.3.13+20211026 + +environment: + sdk: ">=2.7.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.0 + + # hyzp_ybqx Project Adds + # fijkplayer: ^0.8.7 + path_provider: ^1.6.14 + #permission_handler: ^3.3.0 + permission_handler: ^5.0.1+1 + + flutter_swiper: ^1.1.6 + # flutter_screenutil: ^2.3.1 + flutter_screenutil: ^4.0.3+1 + dio: ^3.0.10 + shared_preferences: ^0.5.12+4 + flutter_inappbrowser: ^1.2.1 + provider: ^4.3.1 + event_bus: ^1.1.1 + fluttertoast: ^4.0.1 + city_pickers: ^0.1.22 + crypto: ^2.1.1 + + camera: ^0.5.8+11 + audioplayers: ^0.16.2 + + encrypt: ^4.1.0 + device_info: ^1.0.0 + + # 解决 wechat_assets_picker: ^3.0.0+1 直接依赖冲突报错,可以不用目录依赖方式-OK + #path_provider: ^0.4.1 + extended_image_library: ^0.2.3 + + # wechat_assets_picker: ^3.0.0+1 # 直接依赖有冲突报错,所以采用目录依赖方式。 + # 但是为了便于修改,还是直接采用目录依赖方式简单得多 + # wechat_assets_picker: + # path: ./lib/wechat_assets_picker-3.0.0+1/ + # wechat_assets_picker: + # path: ./lib/my_wechat_assets_picker-3.0.0+1/ + # wechat_assets_picker: + # path: ./lib/my_wechat_assets_picker-4.1.0+1/ + wechat_assets_picker: ^4.1.0+6 + + url_launcher: ^5.7.0 + get_it: ^5.0.1 + image_picker: ^0.6.7+22 + cached_network_image: ^2.4.1 + flutter_easyrefresh: ^2.1.8 + photo_view: ^0.10.3 + flustars: ^0.3.3 + keyboard_avoider: ^0.1.2 + scroll_to_index: ^1.0.6 + flutter_drag_scale: + #git: https://github.com/LiuC520/flutter_drag_scale.git + git: https://github.com/mjl0602/flutter_drag_scale.git + + flutter_bmfmap: ^1.0.2 + flutter_bmfutils: ^1.0.2 + http: ^0.12.2 + + #charts_flutter: ^0.9.0 + fl_chart: ^0.12.3 + + flutter_datetime_picker: ^1.5.0 + + linked_scroll_controller: ^0.1.2 + + #flutter_common_exports: ^0.1.0 + + #pinch_zoom_image_last: ^0.3.2 //不好用 + #extended_image: ^2.0.0 #版本冲突 + #Because extended_image >=2.0.0 depends on extended_image_library ^1.0.1 and hyzp_ybqx depends on extended_image_library ^0.2.3, extended_image >=2.0.0 is forbidden. + #So, because hyzp_ybqx depends on extended_image ^2.0.0, version solving failed. + #Running "flutter pub get" in hyzp_ybqx... + #pub get failed (1; So, because hyzp_ybqx depends on extended_image ^2.0.0, version solving failed.) + + # 经多次测试,是插件x5_webview与flutter_downloader冲突 + #x5_webview: ^0.2.4 + # x5_webview: + # path: ./lib/my_x5_webview/ + + + flutterptcontrol: + path: ./lib/my_flutterPtControl + + sprintf: ^5.0.0 + + ## 处理自动更新 + # permission_handler: ^5.0.0+hotfix.4 + # package_info: ^0.4.1 + # path_provider: ^1.6.11 + open_file: ^3.0.3 # 打开文件 + # 经多次测试,是插件x5_webview与flutter_downloader冲突 + flutter_downloader: ^1.5.2 # 下载文件 + package_info: ^0.4.3+2 # 检测版本号 + progress_dialog: ^1.2.0 # 显示进度对话框 + + #flutter_app_badger and flutter_app_icon_badge Not working on android devices of Samsung Galaxy S7 and Samsung Galaxy S10 + #flutter_app_badger: ^1.1.2 # 桌面App图标红点 + #flutter_app_icon_badge: ^1.0.1 # 替代 flutter_app_badger,无效 + badges: ^1.0.3 # App中的图标红点 + + flutter_echarts: ^1.5.0 + flutter_superplayer: + path: .\lib\my_flutter_superplayer + +dev_dependencies: + flutter_test: + sdk: flutter + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + assets: + - assets/images/ + - assets/fun_icons/ + - assets/files_icons/ + - assets/audio/ + - assets/images/背景图.png + - assets/images/形状 811.png + - assets/images/形状 810.png + - assets/images/形状 809.png + - assets/images/图层 5.png + - assets/images/矢量智能对象.png + - assets/images/矢量智能对象(1).png + - assets/images/矩形 1 拷贝 39.png + - assets/images/我的.png + - assets/images/形状 2.png + - assets/images/装饰图片10.png + - assets/images/聚焦.png + - assets/images/盾 密码 安全.png + - assets/images/警察.png + - assets/images/1 (104).png + - assets/images/1 (177).png + - assets/images/LED.png + - assets/images/monitor.png + - assets/images/播放 (1).png + - assets/images/图层 11.png + - assets/images/1 (15).png + - assets/images/1 (84).png + - assets/images/1 (194).png + - assets/images/1 (219).png + - assets/images/个人资料.png + - assets/images/意见反馈.png + - assets/images/版本更新.png + - assets/images/清除缓存.png + - assets/images/关于.png + - assets/images/刷新.png + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..dc61885 --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,29 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:hyzp_ybqx/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +}