From 750a1eead09151c675c7067e92c3ef10d46eab72 Mon Sep 17 00:00:00 2001 From: zzw <602129484@qq.com> Date: Tue, 15 Feb 2022 11:43:50 +0800 Subject: [PATCH 01/14] feat:local_auth update --- packages/integration_test/pubspec.yaml | 10 +++++----- packages/local_auth/example/pubspec.yaml | 4 +++- packages/local_auth/pubspec.yaml | 14 ++++++++------ 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/integration_test/pubspec.yaml b/packages/integration_test/pubspec.yaml index 6e041292..ee2b65e1 100644 --- a/packages/integration_test/pubspec.yaml +++ b/packages/integration_test/pubspec.yaml @@ -5,7 +5,7 @@ homepage: https://github.com/flutter/plugins/tree/master/packages/integration_te environment: sdk: ">=2.2.2 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + flutter: ">=1.12.13+hotfix.5 <2.10.0" dependencies: flutter: @@ -14,12 +14,12 @@ dependencies: sdk: flutter flutter_test: sdk: flutter - path: ^1.6.4 - vm_service: ^4.2.0 + path: ^1.8.0 + vm_service: ^7.3.0 dev_dependencies: - pedantic: ^1.8.0 - mockito: ^4.1.1 + pedantic: ^1.11.1 + mockito: ^5.1.0 flutter: plugin: diff --git a/packages/local_auth/example/pubspec.yaml b/packages/local_auth/example/pubspec.yaml index 9e674f7e..a815345b 100644 --- a/packages/local_auth/example/pubspec.yaml +++ b/packages/local_auth/example/pubspec.yaml @@ -1,7 +1,9 @@ name: local_auth_example version: 1.0.0 description: Demonstrates how to use the local_auth plugin. - +environment: + sdk: '>=2.10.0 <3.0.0' + flutter: ">=1.20.4 <2.10.0" dependencies: flutter: sdk: flutter diff --git a/packages/local_auth/pubspec.yaml b/packages/local_auth/pubspec.yaml index 57bf9fa3..54ccecdb 100644 --- a/packages/local_auth/pubspec.yaml +++ b/packages/local_auth/pubspec.yaml @@ -4,6 +4,10 @@ description: Flutter plugin for Android and iOS device authentication sensors homepage: https://github.com/flutter/plugins/tree/master/packages/local_auth version: 0.6.3+1 +environment: + sdk: '>=2.10.0 <3.0.0' + flutter: ">=1.20.4 <2.10.0" + flutter: plugin: platforms: @@ -16,11 +20,12 @@ flutter: dependencies: flutter: sdk: flutter - meta: ^1.0.5 + meta: ^1.7.0 + #meta: ^1.0.5 intl: ">=0.15.1 <0.17.0" platform: ">=2.0.0 <4.0.0" - flutter_plugin_android_lifecycle: ^1.0.2 - + #flutter_plugin_android_lifecycle: ^1.0.2 + flutter_plugin_android_lifecycle: ^2.0.5 dev_dependencies: integration_test: path: ../integration_test @@ -30,6 +35,3 @@ dev_dependencies: sdk: flutter pedantic: ^1.8.0 -environment: - sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" From a677252815981a71a84164e2aa8bc63bd34b2dcb Mon Sep 17 00:00:00 2001 From: zzw <602129484@qq.com> Date: Tue, 15 Feb 2022 13:57:42 +0800 Subject: [PATCH 02/14] feat:local_auth intl update --- packages/local_auth/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/local_auth/pubspec.yaml b/packages/local_auth/pubspec.yaml index 54ccecdb..a28def03 100644 --- a/packages/local_auth/pubspec.yaml +++ b/packages/local_auth/pubspec.yaml @@ -22,7 +22,7 @@ dependencies: sdk: flutter meta: ^1.7.0 #meta: ^1.0.5 - intl: ">=0.15.1 <0.17.0" + intl: "0.17.0" platform: ">=2.0.0 <4.0.0" #flutter_plugin_android_lifecycle: ^1.0.2 flutter_plugin_android_lifecycle: ^2.0.5 From d48ddfc40e9e10e08f02721e917dd3420d33d497 Mon Sep 17 00:00:00 2001 From: zzw <602129484@qq.com> Date: Mon, 21 Feb 2022 16:23:06 +0800 Subject: [PATCH 03/14] feat:add android install_plugin --- packages/flutter_install_plugin/.gitignore | 29 + packages/flutter_install_plugin/.metadata | 10 + packages/flutter_install_plugin/CHANGELOG.md | 3 + packages/flutter_install_plugin/LICENSE | 1 + packages/flutter_install_plugin/README.md | 15 + .../analysis_options.yaml | 4 + .../flutter_install_plugin/android/.gitignore | 8 + .../android/build.gradle | 50 ++ .../gradle/wrapper/gradle-wrapper.properties | 5 + .../android/settings.gradle | 1 + .../android/src/main/AndroidManifest.xml | 16 + .../flutter_install_plugin/FileProvider.kt | 3 + .../FlutterInstallPlugin.kt | 171 ++++++ .../main/res/xml/provider_install_paths.xml | 6 + .../flutter_install_plugin/example/.gitignore | 46 ++ .../flutter_install_plugin/example/.metadata | 10 + .../flutter_install_plugin/example/README.md | 16 + .../example/analysis_options.yaml | 29 + .../example/android/.gitignore | 13 + .../example/android/app/build.gradle | 68 +++ .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 36 ++ .../MainActivity.kt | 6 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + .../example/android/build.gradle | 31 + .../example/android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 6 + .../example/android/settings.gradle | 11 + .../example/ios/.gitignore | 34 ++ .../ios/Flutter/AppFrameworkInfo.plist | 26 + .../example/ios/Flutter/Debug.xcconfig | 2 + .../example/ios/Flutter/Release.xcconfig | 2 + .../ios/Runner.xcodeproj/project.pbxproj | 552 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 87 +++ .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../example/ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 122 ++++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 564 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 1588 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 1025 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 1716 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 1920 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 1895 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 3831 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 1888 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 3294 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 3612 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 ++ .../ios/Runner/Base.lproj/Main.storyboard | 26 + .../example/ios/Runner/Info.plist | 47 ++ .../ios/Runner/Runner-Bridging-Header.h | 1 + .../example/lib/main.dart | 78 +++ .../example/pubspec.yaml | 85 +++ .../example/test/widget_test.dart | 27 + .../flutter_install_plugin/ios/.gitignore | 38 ++ .../ios/Assets/.gitkeep | 0 .../ios/Classes/FlutterInstallPlugin.h | 4 + .../ios/Classes/FlutterInstallPlugin.m | 15 + .../Classes/SwiftFlutterInstallPlugin.swift | 35 ++ .../ios/flutter_install_plugin.podspec | 23 + .../lib/flutter_install_plugin.dart | 26 + packages/flutter_install_plugin/pubspec.yaml | 64 ++ .../test/flutter_install_plugin_test.dart | 23 + 87 files changed, 2115 insertions(+) create mode 100644 packages/flutter_install_plugin/.gitignore create mode 100644 packages/flutter_install_plugin/.metadata create mode 100644 packages/flutter_install_plugin/CHANGELOG.md create mode 100644 packages/flutter_install_plugin/LICENSE create mode 100644 packages/flutter_install_plugin/README.md create mode 100644 packages/flutter_install_plugin/analysis_options.yaml create mode 100644 packages/flutter_install_plugin/android/.gitignore create mode 100644 packages/flutter_install_plugin/android/build.gradle create mode 100644 packages/flutter_install_plugin/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 packages/flutter_install_plugin/android/settings.gradle create mode 100644 packages/flutter_install_plugin/android/src/main/AndroidManifest.xml create mode 100644 packages/flutter_install_plugin/android/src/main/kotlin/com/example/flutter_install_plugin/FileProvider.kt create mode 100644 packages/flutter_install_plugin/android/src/main/kotlin/com/example/flutter_install_plugin/FlutterInstallPlugin.kt create mode 100644 packages/flutter_install_plugin/android/src/main/res/xml/provider_install_paths.xml create mode 100644 packages/flutter_install_plugin/example/.gitignore create mode 100644 packages/flutter_install_plugin/example/.metadata create mode 100644 packages/flutter_install_plugin/example/README.md create mode 100644 packages/flutter_install_plugin/example/analysis_options.yaml create mode 100644 packages/flutter_install_plugin/example/android/.gitignore create mode 100644 packages/flutter_install_plugin/example/android/app/build.gradle create mode 100644 packages/flutter_install_plugin/example/android/app/src/debug/AndroidManifest.xml create mode 100644 packages/flutter_install_plugin/example/android/app/src/main/AndroidManifest.xml create mode 100644 packages/flutter_install_plugin/example/android/app/src/main/kotlin/com/example/flutter_install_plugin_example/MainActivity.kt create mode 100644 packages/flutter_install_plugin/example/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 packages/flutter_install_plugin/example/android/app/src/main/res/drawable/launch_background.xml create mode 100644 packages/flutter_install_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 packages/flutter_install_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 packages/flutter_install_plugin/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 packages/flutter_install_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 packages/flutter_install_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 packages/flutter_install_plugin/example/android/app/src/main/res/values-night/styles.xml create mode 100644 packages/flutter_install_plugin/example/android/app/src/main/res/values/styles.xml create mode 100644 packages/flutter_install_plugin/example/android/app/src/profile/AndroidManifest.xml create mode 100644 packages/flutter_install_plugin/example/android/build.gradle create mode 100644 packages/flutter_install_plugin/example/android/gradle.properties create mode 100644 packages/flutter_install_plugin/example/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 packages/flutter_install_plugin/example/android/settings.gradle create mode 100644 packages/flutter_install_plugin/example/ios/.gitignore create mode 100644 packages/flutter_install_plugin/example/ios/Flutter/AppFrameworkInfo.plist create mode 100644 packages/flutter_install_plugin/example/ios/Flutter/Debug.xcconfig create mode 100644 packages/flutter_install_plugin/example/ios/Flutter/Release.xcconfig create mode 100644 packages/flutter_install_plugin/example/ios/Runner.xcodeproj/project.pbxproj create mode 100644 packages/flutter_install_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 packages/flutter_install_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/flutter_install_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 packages/flutter_install_plugin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 packages/flutter_install_plugin/example/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 packages/flutter_install_plugin/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/flutter_install_plugin/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 packages/flutter_install_plugin/example/ios/Runner/AppDelegate.swift create mode 100644 packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 packages/flutter_install_plugin/example/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 packages/flutter_install_plugin/example/ios/Runner/Base.lproj/Main.storyboard create mode 100644 packages/flutter_install_plugin/example/ios/Runner/Info.plist create mode 100644 packages/flutter_install_plugin/example/ios/Runner/Runner-Bridging-Header.h create mode 100644 packages/flutter_install_plugin/example/lib/main.dart create mode 100644 packages/flutter_install_plugin/example/pubspec.yaml create mode 100644 packages/flutter_install_plugin/example/test/widget_test.dart create mode 100644 packages/flutter_install_plugin/ios/.gitignore create mode 100644 packages/flutter_install_plugin/ios/Assets/.gitkeep create mode 100644 packages/flutter_install_plugin/ios/Classes/FlutterInstallPlugin.h create mode 100644 packages/flutter_install_plugin/ios/Classes/FlutterInstallPlugin.m create mode 100644 packages/flutter_install_plugin/ios/Classes/SwiftFlutterInstallPlugin.swift create mode 100644 packages/flutter_install_plugin/ios/flutter_install_plugin.podspec create mode 100644 packages/flutter_install_plugin/lib/flutter_install_plugin.dart create mode 100644 packages/flutter_install_plugin/pubspec.yaml create mode 100644 packages/flutter_install_plugin/test/flutter_install_plugin_test.dart diff --git a/packages/flutter_install_plugin/.gitignore b/packages/flutter_install_plugin/.gitignore new file mode 100644 index 00000000..9be145fd --- /dev/null +++ b/packages/flutter_install_plugin/.gitignore @@ -0,0 +1,29 @@ +# 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 +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/packages/flutter_install_plugin/.metadata b/packages/flutter_install_plugin/.metadata new file mode 100644 index 00000000..8c15ad72 --- /dev/null +++ b/packages/flutter_install_plugin/.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: 77d935af4db863f6abd0b9c31c7e6df2a13de57b + channel: stable + +project_type: plugin diff --git a/packages/flutter_install_plugin/CHANGELOG.md b/packages/flutter_install_plugin/CHANGELOG.md new file mode 100644 index 00000000..41cc7d81 --- /dev/null +++ b/packages/flutter_install_plugin/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/packages/flutter_install_plugin/LICENSE b/packages/flutter_install_plugin/LICENSE new file mode 100644 index 00000000..ba75c69f --- /dev/null +++ b/packages/flutter_install_plugin/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/packages/flutter_install_plugin/README.md b/packages/flutter_install_plugin/README.md new file mode 100644 index 00000000..fc030310 --- /dev/null +++ b/packages/flutter_install_plugin/README.md @@ -0,0 +1,15 @@ +# flutter_install_plugin + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter +[plug-in package](https://flutter.dev/developing-packages/), +a specialized package that includes platform-specific implementation code for +Android and/or iOS. + +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/packages/flutter_install_plugin/analysis_options.yaml b/packages/flutter_install_plugin/analysis_options.yaml new file mode 100644 index 00000000..a5744c1c --- /dev/null +++ b/packages/flutter_install_plugin/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/flutter_install_plugin/android/.gitignore b/packages/flutter_install_plugin/android/.gitignore new file mode 100644 index 00000000..c6cbe562 --- /dev/null +++ b/packages/flutter_install_plugin/android/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures diff --git a/packages/flutter_install_plugin/android/build.gradle b/packages/flutter_install_plugin/android/build.gradle new file mode 100644 index 00000000..b9f61120 --- /dev/null +++ b/packages/flutter_install_plugin/android/build.gradle @@ -0,0 +1,50 @@ +group 'com.example.flutter_install_plugin' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '1.3.50' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.1.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +rootProject.allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion 30 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + minSdkVersion 16 + } +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/packages/flutter_install_plugin/android/gradle/wrapper/gradle-wrapper.properties b/packages/flutter_install_plugin/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..da9702f9 --- /dev/null +++ b/packages/flutter_install_plugin/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/packages/flutter_install_plugin/android/settings.gradle b/packages/flutter_install_plugin/android/settings.gradle new file mode 100644 index 00000000..9162c9e1 --- /dev/null +++ b/packages/flutter_install_plugin/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'flutter_install_plugin' diff --git a/packages/flutter_install_plugin/android/src/main/AndroidManifest.xml b/packages/flutter_install_plugin/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..f62a9ae4 --- /dev/null +++ b/packages/flutter_install_plugin/android/src/main/AndroidManifest.xml @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/packages/flutter_install_plugin/android/src/main/kotlin/com/example/flutter_install_plugin/FileProvider.kt b/packages/flutter_install_plugin/android/src/main/kotlin/com/example/flutter_install_plugin/FileProvider.kt new file mode 100644 index 00000000..ef057779 --- /dev/null +++ b/packages/flutter_install_plugin/android/src/main/kotlin/com/example/flutter_install_plugin/FileProvider.kt @@ -0,0 +1,3 @@ +package com.example.flutter_install_plugin + +class FileProvider : androidx.core.content.FileProvider() diff --git a/packages/flutter_install_plugin/android/src/main/kotlin/com/example/flutter_install_plugin/FlutterInstallPlugin.kt b/packages/flutter_install_plugin/android/src/main/kotlin/com/example/flutter_install_plugin/FlutterInstallPlugin.kt new file mode 100644 index 00000000..0ade6aa4 --- /dev/null +++ b/packages/flutter_install_plugin/android/src/main/kotlin/com/example/flutter_install_plugin/FlutterInstallPlugin.kt @@ -0,0 +1,171 @@ +package com.example.flutter_install_plugin + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.provider.Settings +import android.util.Log +import androidx.annotation.NonNull +import androidx.core.content.FileProvider +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.PluginRegistry.Registrar +import io.flutter.plugin.common.BinaryMessenger +import java.io.File +import java.io.FileNotFoundException + +/** FlutterInstallPlugin */ +class FlutterInstallPlugin: FlutterPlugin, MethodCallHandler , ActivityAware { + /// 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 lateinit var channel : MethodChannel + private var apkFile: String? = null + private var appId: String? = null + private val installRequestCode = 1234 + private var activity:Activity? = null + + companion object { + @JvmStatic + fun registerWith(registrar: Registrar) { + val plugin = FlutterInstallPlugin() + + plugin.onAttachedToEngine(registrar.messenger()) + + val activity = registrar.activity() + if (activity != null) { + plugin.onActivityChanged(activity) + } + } + } + + override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + onAttachedToEngine(flutterPluginBinding.binaryMessenger) + } + override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + } + + override fun onAttachedToActivity(binding: ActivityPluginBinding){ + onActivityChanged(binding.activity) + binding.addActivityResultListener { requestCode, resultCode, intent -> + Log.d("android plugin", "addActivityResultListener $resultCode") + if (resultCode == Activity.RESULT_OK && requestCode == installRequestCode) { + val file = File(apkFile) + install24(activity, file, appId) + true + } else + false + } + } + + fun onAttachedToEngine(messenger: BinaryMessenger) { + channel = MethodChannel(messenger, "flutter_install_plugin") + channel.setMethodCallHandler(this) + } + + override fun onDetachedFromActivityForConfigChanges() = onActivityChanged(null) + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) = + onActivityChanged(binding.activity) + + override fun onDetachedFromActivity() = onActivityChanged(null) + + fun onActivityChanged(activity: Activity?) { + this.activity = activity + } + + override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: MethodChannel.Result) { + when (call.method) { + "getPlatformVersion" -> { + result.success("Android ${android.os.Build.VERSION.RELEASE}") + } + "installApk" -> { + apkFile = call.argument("filePath") + appId = call.argument("appId") + Log.d("android plugin", "installApk $apkFile $appId") + try { + installApk(apkFile, appId) + result.success("Success") + } catch (e: Throwable) { + result.error(e.javaClass.simpleName, e.message, null) + } + } + else -> result.notImplemented() + } + } + + private fun installApk(filePath: String?, appId: String?) { + if (filePath == null) throw NullPointerException("fillPath is null!") + if (activity == null) throw NullPointerException("activity is null!") + + val file = File(filePath) + if (!file.exists()) throw FileNotFoundException("$filePath is not exist! or check permission") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + install24(activity, file, appId) + } else { + activity?.let { installBelow24(it, file) } + } + } + + + private fun showSettingPackageInstall(activity: Activity) { // todo to test with android 26 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Log.d("SettingPackageInstall", ">= Build.VERSION_CODES.O") + val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES) + intent.data = Uri.parse("package:" + activity.packageName) + activity.startActivityForResult(intent, installRequestCode) + } else { + throw RuntimeException("VERSION.SDK_INT < O") + } + + } + + private fun canRequestPackageInstalls(activity: Activity): Boolean { + return Build.VERSION.SDK_INT <= Build.VERSION_CODES.O || activity.packageManager.canRequestPackageInstalls() + } + + private fun installBelow24(context: Context, file: File?) { + val intent = Intent(Intent.ACTION_VIEW) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + val uri = Uri.fromFile(file) + intent.setDataAndType(uri, "application/vnd.android.package-archive") + context.startActivity(intent) + } + + /** + * android24及以上安装需要通过 ContentProvider 获取文件Uri, + * 需在应用中的AndroidManifest.xml 文件添加 provider 标签, + * 并新增文件路径配置文件 res/xml/provider_path.xml + * 在android 6.0 以上如果没有动态申请文件读写权限,会导致文件读取失败,你将会收到一个异常。 + * 插件中不封装申请权限逻辑,是为了使模块功能单一,调用者可以引入独立的权限申请插件 + */ + private fun install24(context: Context?, file: File?, appId: String?) { + if (context == null) throw NullPointerException("context is null!") + if (file == null) throw NullPointerException("file is null!") + if (appId == null) throw NullPointerException("appId is null!") + + try { + val intent = Intent(Intent.ACTION_VIEW) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + val uri: Uri = FileProvider.getUriForFile(context, "$appId.fileProvider.install", file) + intent.setDataAndType(uri, "application/vnd.android.package-archive") + context.startActivity(intent) + } catch (e: Throwable) { + Log.d("android plugin", "install24 error") + } + + } + + + + +} diff --git a/packages/flutter_install_plugin/android/src/main/res/xml/provider_install_paths.xml b/packages/flutter_install_plugin/android/src/main/res/xml/provider_install_paths.xml new file mode 100644 index 00000000..38298951 --- /dev/null +++ b/packages/flutter_install_plugin/android/src/main/res/xml/provider_install_paths.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/packages/flutter_install_plugin/example/.gitignore b/packages/flutter_install_plugin/example/.gitignore new file mode 100644 index 00000000..0fa6b675 --- /dev/null +++ b/packages/flutter_install_plugin/example/.gitignore @@ -0,0 +1,46 @@ +# 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 + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/packages/flutter_install_plugin/example/.metadata b/packages/flutter_install_plugin/example/.metadata new file mode 100644 index 00000000..fd70cabc --- /dev/null +++ b/packages/flutter_install_plugin/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: 77d935af4db863f6abd0b9c31c7e6df2a13de57b + channel: stable + +project_type: app diff --git a/packages/flutter_install_plugin/example/README.md b/packages/flutter_install_plugin/example/README.md new file mode 100644 index 00000000..6b9fd50b --- /dev/null +++ b/packages/flutter_install_plugin/example/README.md @@ -0,0 +1,16 @@ +# flutter_install_plugin_example + +Demonstrates how to use the flutter_install_plugin 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/packages/flutter_install_plugin/example/analysis_options.yaml b/packages/flutter_install_plugin/example/analysis_options.yaml new file mode 100644 index 00000000..61b6c4de --- /dev/null +++ b/packages/flutter_install_plugin/example/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/flutter_install_plugin/example/android/.gitignore b/packages/flutter_install_plugin/example/android/.gitignore new file mode 100644 index 00000000..6f568019 --- /dev/null +++ b/packages/flutter_install_plugin/example/android/.gitignore @@ -0,0 +1,13 @@ +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 +**/*.keystore +**/*.jks diff --git a/packages/flutter_install_plugin/example/android/app/build.gradle b/packages/flutter_install_plugin/example/android/app/build.gradle new file mode 100644 index 00000000..4c953bb5 --- /dev/null +++ b/packages/flutter_install_plugin/example/android/app/build.gradle @@ -0,0 +1,68 @@ +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 plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion flutter.compileSdkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.flutter_install_plugin_example" + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + 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 { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/packages/flutter_install_plugin/example/android/app/src/debug/AndroidManifest.xml b/packages/flutter_install_plugin/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..21311b61 --- /dev/null +++ b/packages/flutter_install_plugin/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/flutter_install_plugin/example/android/app/src/main/AndroidManifest.xml b/packages/flutter_install_plugin/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..67b87053 --- /dev/null +++ b/packages/flutter_install_plugin/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + diff --git a/packages/flutter_install_plugin/example/android/app/src/main/kotlin/com/example/flutter_install_plugin_example/MainActivity.kt b/packages/flutter_install_plugin/example/android/app/src/main/kotlin/com/example/flutter_install_plugin_example/MainActivity.kt new file mode 100644 index 00000000..3ecd4430 --- /dev/null +++ b/packages/flutter_install_plugin/example/android/app/src/main/kotlin/com/example/flutter_install_plugin_example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.flutter_install_plugin_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/packages/flutter_install_plugin/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/flutter_install_plugin/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000..f74085f3 --- /dev/null +++ b/packages/flutter_install_plugin/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/flutter_install_plugin/example/android/app/src/main/res/drawable/launch_background.xml b/packages/flutter_install_plugin/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000..304732f8 --- /dev/null +++ b/packages/flutter_install_plugin/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/flutter_install_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/flutter_install_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/packages/flutter_install_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/flutter_install_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/packages/flutter_install_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/flutter_install_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/packages/flutter_install_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/flutter_install_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/packages/flutter_install_plugin/example/android/app/src/main/res/values-night/styles.xml b/packages/flutter_install_plugin/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000..3db14bb5 --- /dev/null +++ b/packages/flutter_install_plugin/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/flutter_install_plugin/example/android/app/src/main/res/values/styles.xml b/packages/flutter_install_plugin/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..d460d1e9 --- /dev/null +++ b/packages/flutter_install_plugin/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/flutter_install_plugin/example/android/app/src/profile/AndroidManifest.xml b/packages/flutter_install_plugin/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000..21311b61 --- /dev/null +++ b/packages/flutter_install_plugin/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/flutter_install_plugin/example/android/build.gradle b/packages/flutter_install_plugin/example/android/build.gradle new file mode 100644 index 00000000..24047dce --- /dev/null +++ b/packages/flutter_install_plugin/example/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.3.50' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.1.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/packages/flutter_install_plugin/example/android/gradle.properties b/packages/flutter_install_plugin/example/android/gradle.properties new file mode 100644 index 00000000..94adc3a3 --- /dev/null +++ b/packages/flutter_install_plugin/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/flutter_install_plugin/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/flutter_install_plugin/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..bc6a58af --- /dev/null +++ b/packages/flutter_install_plugin/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-6.7-all.zip diff --git a/packages/flutter_install_plugin/example/android/settings.gradle b/packages/flutter_install_plugin/example/android/settings.gradle new file mode 100644 index 00000000..44e62bcf --- /dev/null +++ b/packages/flutter_install_plugin/example/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/packages/flutter_install_plugin/example/ios/.gitignore b/packages/flutter_install_plugin/example/ios/.gitignore new file mode 100644 index 00000000..7a7f9873 --- /dev/null +++ b/packages/flutter_install_plugin/example/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.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/ephemeral/ +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/packages/flutter_install_plugin/example/ios/Flutter/AppFrameworkInfo.plist b/packages/flutter_install_plugin/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000..8d4492f9 --- /dev/null +++ b/packages/flutter_install_plugin/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + 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/packages/flutter_install_plugin/example/ios/Flutter/Debug.xcconfig b/packages/flutter_install_plugin/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000..ec97fc6f --- /dev/null +++ b/packages/flutter_install_plugin/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/packages/flutter_install_plugin/example/ios/Flutter/Release.xcconfig b/packages/flutter_install_plugin/example/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000..c4855bfe --- /dev/null +++ b/packages/flutter_install_plugin/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/packages/flutter_install_plugin/example/ios/Runner.xcodeproj/project.pbxproj b/packages/flutter_install_plugin/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..0c944f62 --- /dev/null +++ b/packages/flutter_install_plugin/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,552 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + 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 */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 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 */; }; + B7A773E9A241F69C7B4DEE54 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CDA6780D5BBD54A514070203 /* Pods_Runner.framework */; }; +/* 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 = ""; }; + 1E7ADB245C786F2C23B8E16F /* 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 = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 4CF8AD6B985E3BFF788BD3F7 /* 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 = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 8E10FAE6FA3ECB3D55CE4794 /* 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 = ""; }; + 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; }; + 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 = ""; }; + CDA6780D5BBD54A514070203 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + B7A773E9A241F69C7B4DEE54 /* Pods_Runner.framework in Frameworks */, + ); + 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 */, + DAAB008EEEC88121645CACB0 /* Pods */, + F60971A1B98C72B626B2A5ED /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + DAAB008EEEC88121645CACB0 /* Pods */ = { + isa = PBXGroup; + children = ( + 8E10FAE6FA3ECB3D55CE4794 /* Pods-Runner.debug.xcconfig */, + 4CF8AD6B985E3BFF788BD3F7 /* Pods-Runner.release.xcconfig */, + 1E7ADB245C786F2C23B8E16F /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + F60971A1B98C72B626B2A5ED /* Frameworks */ = { + isa = PBXGroup; + children = ( + CDA6780D5BBD54A514070203 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 7A034FA0CB65052DED1E1A0F /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 258D4CE26C233C5838A2C6A3 /* [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 = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + 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 */ + 258D4CE26C233C5838A2C6A3 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 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"; + }; + 7A034FA0CB65052DED1E1A0F /* [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; + }; + 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 = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift 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; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 678MZVH5SX; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterInstallPluginExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + 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; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 678MZVH5SX; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterInstallPluginExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 678MZVH5SX; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterInstallPluginExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + 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/packages/flutter_install_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/flutter_install_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/packages/flutter_install_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/flutter_install_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/flutter_install_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/flutter_install_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/flutter_install_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/flutter_install_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/packages/flutter_install_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/flutter_install_plugin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/flutter_install_plugin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..c87d15a3 --- /dev/null +++ b/packages/flutter_install_plugin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/flutter_install_plugin/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/flutter_install_plugin/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..21a3cc14 --- /dev/null +++ b/packages/flutter_install_plugin/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/flutter_install_plugin/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/flutter_install_plugin/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/flutter_install_plugin/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/flutter_install_plugin/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/flutter_install_plugin/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/packages/flutter_install_plugin/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/flutter_install_plugin/example/ios/Runner/AppDelegate.swift b/packages/flutter_install_plugin/example/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000..70693e4a --- /dev/null +++ b/packages/flutter_install_plugin/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d36b1fab --- /dev/null +++ b/packages/flutter_install_plugin/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/packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 literal 0 HcmV?d00001 diff --git a/packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f091b6b0bca859a3f474b03065bef75ba58a9e4c GIT binary patch literal 1588 zcmV-42Fv-0P)C1SqPt}wig>|5Crh^=oyX$BK<}M8eLU3e2hGT;=G|!_SP)7zNI6fqUMB=)y zRAZ>eDe#*r`yDAVgB_R*LB*MAc)8(b{g{9McCXW!lq7r(btRoB9!8B-#AI6JMb~YFBEvdsV)`mEQO^&#eRKx@b&x- z5lZm*!WfD8oCLzfHGz#u7sT0^VLMI1MqGxF^v+`4YYnVYgk*=kU?HsSz{v({E3lb9 z>+xILjBN)t6`=g~IBOelGQ(O990@BfXf(DRI5I$qN$0Gkz-FSc$3a+2fX$AedL4u{ z4V+5Ong(9LiGcIKW?_352sR;LtDPmPJXI{YtT=O8=76o9;*n%_m|xo!i>7$IrZ-{l z-x3`7M}qzHsPV@$v#>H-TpjDh2UE$9g6sysUREDy_R(a)>=eHw-WAyfIN z*qb!_hW>G)Tu8nSw9yn#3wFMiLcfc4pY0ek1}8(NqkBR@t4{~oC>ryc-h_ByH(Cg5 z>ao-}771+xE3um9lWAY1FeQFxowa1(!J(;Jg*wrg!=6FdRX+t_<%z&d&?|Bn){>zm zZQj(aA_HeBY&OC^jj*)N`8fa^ePOU72VpInJoI1?`ty#lvlNzs(&MZX+R%2xS~5Kh zX*|AU4QE#~SgPzOXe9>tRj>hjU@c1k5Y_mW*Jp3fI;)1&g3j|zDgC+}2Q_v%YfDax z!?umcN^n}KYQ|a$Lr+51Nf9dkkYFSjZZjkma$0KOj+;aQ&721~t7QUKx61J3(P4P1 zstI~7-wOACnWP4=8oGOwz%vNDqD8w&Q`qcNGGrbbf&0s9L0De{4{mRS?o0MU+nR_! zrvshUau0G^DeMhM_v{5BuLjb#Hh@r23lDAk8oF(C+P0rsBpv85EP>4CVMx#04MOfG z;P%vktHcXwTj~+IE(~px)3*MY77e}p#|c>TD?sMatC0Tu4iKKJ0(X8jxQY*gYtxsC z(zYC$g|@+I+kY;dg_dE>scBf&bP1Nc@Hz<3R)V`=AGkc;8CXqdi=B4l2k|g;2%#m& z*jfX^%b!A8#bI!j9-0Fi0bOXl(-c^AB9|nQaE`*)Hw+o&jS9@7&Gov#HbD~#d{twV zXd^Tr^mWLfFh$@Dr$e;PBEz4(-2q1FF0}c;~B5sA}+Q>TOoP+t>wf)V9Iy=5ruQa;z)y zI9C9*oUga6=hxw6QasLPnee@3^Rr*M{CdaL5=R41nLs(AHk_=Y+A9$2&H(B7!_pURs&8aNw7?`&Z&xY_Ye z)~D5Bog^td-^QbUtkTirdyK^mTHAOuptDflut!#^lnKqU md>ggs(5nOWAqO?umG&QVYK#ibz}*4>0000U6E9hRK9^#O7(mu>ETqrXGsduA8$)?`v2seloOCza43C{NQ$$gAOH**MCn0Q?+L7dl7qnbRdqZ8LSVp1ItDxhxD?t@5_yHg6A8yI zC*%Wgg22K|8E#!~cTNYR~@Y9KepMPrrB8cABapAFa=`H+UGhkXUZV1GnwR1*lPyZ;*K(i~2gp|@bzp8}og7e*#% zEnr|^CWdVV!-4*Y_7rFvlww2Ze+>j*!Z!pQ?2l->4q#nqRu9`ELo6RMS5=br47g_X zRw}P9a7RRYQ%2Vsd0Me{_(EggTnuN6j=-?uFS6j^u69elMypu?t>op*wBx<=Wx8?( ztpe^(fwM6jJX7M-l*k3kEpWOl_Vk3@(_w4oc}4YF4|Rt=2V^XU?#Yz`8(e?aZ@#li0n*=g^qOcVpd-Wbok=@b#Yw zqn8u9a)z>l(1kEaPYZ6hwubN6i<8QHgsu0oE) ziJ(p;Wxm>sf!K+cw>R-(^Y2_bahB+&KI9y^);#0qt}t-$C|Bo71lHi{_+lg#f%RFy z0um=e3$K3i6K{U_4K!EX?F&rExl^W|G8Z8;`5z-k}OGNZ0#WVb$WCpQu-_YsiqKP?BB# vzVHS-CTUF4Ozn5G+mq_~Qqto~ahA+K`|lyv3(-e}00000NkvXXu0mjfd`9t{ literal 0 HcmV?d00001 diff --git a/packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d0ef06e7edb86cdfe0d15b4b0d98334a86163658 GIT binary patch literal 1716 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 literal 0 HcmV?d00001 diff --git a/packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c8f9ed8f5cee1c98386d13b17e89f719e83555b2 GIT binary patch literal 1895 zcmV-t2blPYP)FQtfgmafE#=YDCq`qUBt#QpG%*H6QHY765~R=q zZ6iudfM}q!Pz#~9JgOi8QJ|DSu?1-*(kSi1K4#~5?#|rh?sS)(-JQqX*}ciXJ56_H zdw=^s_srbAdqxlvGyrgGet#6T7_|j;95sL%MtM;q86vOxKM$f#puR)Bjv9Zvz9-di zXOTSsZkM83)E9PYBXC<$6(|>lNLVBb&&6y{NByFCp%6+^ALR@NCTse_wqvNmSWI-m z!$%KlHFH2omF!>#%1l3LTZg(s7eof$7*xB)ZQ0h?ejh?Ta9fDv59+u#MokW+1t8Zb zgHv%K(u9G^Lv`lh#f3<6!JVTL3(dCpxHbnbA;kKqQyd1~^Xe0VIaYBSWm6nsr;dFj z4;G-RyL?cYgsN1{L4ZFFNa;8)Rv0fM0C(~Tkit94 zz#~A)59?QjD&pAPSEQ)p8gP|DS{ng)j=2ux)_EzzJ773GmQ_Cic%3JJhC0t2cx>|v zJcVusIB!%F90{+}8hG3QU4KNeKmK%T>mN57NnCZ^56=0?&3@!j>a>B43pi{!u z7JyDj7`6d)qVp^R=%j>UIY6f+3`+qzIc!Y_=+uN^3BYV|o+$vGo-j-Wm<10%A=(Yk^beI{t%ld@yhKjq0iNjqN4XMGgQtbKubPM$JWBz}YA65k%dm*awtC^+f;a-x4+ddbH^7iDWGg&N0n#MW{kA|=8iMUiFYvMoDY@sPC#t$55gn6ykUTPAr`a@!(;np824>2xJthS z*ZdmT`g5-`BuJs`0LVhz+D9NNa3<=6m;cQLaF?tCv8)zcRSh66*Z|vXhG@$I%U~2l z?`Q zykI#*+rQ=z6Jm=Bui-SfpDYLA=|vzGE(dYm=OC8XM&MDo7ux4UF1~0J1+i%aCUpRe zt3L_uNyQ*cE(38Uy03H%I*)*Bh=Lb^Xj3?I^Hnbeq72(EOK^Y93CNp*uAA{5Lc=ky zx=~RKa4{iTm{_>_vSCm?$Ej=i6@=m%@VvAITnigVg{&@!7CDgs908761meDK5azA} z4?=NOH|PdvabgJ&fW2{Mo$Q0CcD8Qc84%{JPYt5EiG{MdLIAeX%T=D7NIP4%Hw}p9 zg)==!2Lbp#j{u_}hMiao9=!VSyx0gHbeCS`;q&vzeq|fs`y&^X-lso(Ls@-706qmA z7u*T5PMo_w3{se1t2`zWeO^hOvTsohG_;>J0wVqVe+n)AbQCx)yh9;w+J6?NF5Lmo zecS@ieAKL8%bVd@+-KT{yI|S}O>pYckUFs;ry9Ow$CD@ztz5K-*D$^{i(_1llhSh^ zEkL$}tsQt5>QA^;QgjgIfBDmcOgi5YDyu?t6vSnbp=1+@6D& z5MJ}B8q;bRlVoxasyhcUF1+)o`&3r0colr}QJ3hcSdLu;9;td>kf@Tcn<@9sIx&=m z;AD;SCh95=&p;$r{Xz3iWCO^MX83AGJ(yH&eTXgv|0=34#-&WAmw{)U7OU9!Wz^!7 zZ%jZFi@JR;>Mhi7S>V7wQ176|FdW2m?&`qa(ScO^CFPR80HucLHOTy%5s*HR0^8)i h0WYBP*#0Ks^FNSabJA*5${_#%002ovPDHLkV1oKhTl@e3 literal 0 HcmV?d00001 diff --git a/packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 diff --git a/packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 diff --git a/packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..75b2d164a5a98e212cca15ea7bf2ab5de5108680 GIT binary patch literal 3831 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 literal 0 HcmV?d00001 diff --git a/packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..c4df70d39da7941ef3f6dcb7f06a192d8dcb308d GIT binary patch literal 1888 zcmV-m2cP(fP)x~L`~4d)Rspd&<9kFh{hn*KP1LP0~$;u(LfAu zp%fx&qLBcRHx$G|3q(bv@+b;o0*D|jwD-Q9uQR(l*ST}s+uPgQ-MeFwZ#GS?b332? z&Tk$&_miXn3IGq)AmQ)3sisq{raD4(k*bHvpCe-TdWq^NRTEVM)i9xbgQ&ccnUVx* zEY%vS%gDcSg=!tuIK8$Th2_((_h^+7;R|G{n06&O2#6%LK`a}n?h_fL18btz<@lFG za}xS}u?#DBMB> zw^b($1Z)`9G?eP95EKi&$eOy@K%h;ryrR3la%;>|o*>CgB(s>dDcNOXg}CK9SPmD? zmr-s{0wRmxUnbDrYfRvnZ@d z6johZ2sMX{YkGSKWd}m|@V7`Degt-43=2M?+jR%8{(H$&MLLmS;-|JxnX2pnz;el1jsvqQz}pGSF<`mqEXRQ5sC4#BbwnB_4` zc5bFE-Gb#JV3tox9fp-vVEN{(tOCpRse`S+@)?%pz+zVJXSooTrNCUg`R6`hxwb{) zC@{O6MKY8tfZ5@!yy=p5Y|#+myRL=^{tc(6YgAnkg3I(Cd!r5l;|;l-MQ8B`;*SCE z{u)uP^C$lOPM z5d~UhKhRRmvv{LIa^|oavk1$QiEApSrP@~Jjbg`<*dW4TO?4qG%a%sTPUFz(QtW5( zM)lA+5)0TvH~aBaOAs|}?u2FO;yc-CZ1gNM1dAxJ?%m?YsGR`}-xk2*dxC}r5j$d* zE!#Vtbo69h>V4V`BL%_&$} z+oJAo@jQ^Tk`;%xw-4G>hhb&)B?##U+(6Fi7nno`C<|#PVA%$Y{}N-?(Gc$1%tr4Pc}}hm~yY#fTOe!@v9s-ik$dX~|ygArPhByaXn8 zpI^FUjNWMsTFKTP3X7m?UK)3m zp6rI^_zxRYrx6_QmhoWoDR`fp4R7gu6;gdO)!KexaoO2D88F9x#TM1(9Bn7g;|?|o z)~$n&Lh#hCP6_LOPD>a)NmhW})LADx2kq=X7}7wYRj-0?dXr&bHaRWCfSqvzFa=sn z-8^gSyn-RmH=BZ{AJZ~!8n5621GbUJV7Qvs%JNv&$%Q17s_X%s-41vAPfIR>;x0Wlqr5?09S>x#%Qkt>?(&XjFRY}*L6BeQ3 z<6XEBh^S7>AbwGm@XP{RkeEKj6@_o%oV?hDuUpUJ+r#JZO?!IUc;r0R?>mi)*ZpQ) z#((dn=A#i_&EQn|hd)N$#A*fjBFuiHcYvo?@y1 z5|fV=a^a~d!c-%ZbMNqkMKiSzM{Yq=7_c&1H!mXk60Uv32dV;vMg&-kQ)Q{+PFtwc zj|-uQ;b^gts??J*9VxxOro}W~Q9j4Em|zSRv)(WSO9$F$s=Ydu%Q+5DOid~lwk&we zY%W(Z@ofdwPHncEZzZgmqS|!gTj3wQq9rxQy+^eNYKr1mj&?tm@wkO*9@UtnRMG>c aR{jt9+;fr}hV%pg00001^@s67{VYS000c7NklQEG_j zup^)eW&WUIApqy$=APz8jE@awGp)!bsTjDbrJO`$x^ZR^dr;>)LW>{ zs70vpsD38v)19rI=GNk1b(0?Js9~rjsQsu*K;@SD40RB-3^gKU-MYC7G!Bw{fZsqp zih4iIi;Hr_xZ033Iu{sQxLS=}yBXgLMn40d++>aQ0#%8D1EbGZp7+ z5=mK?t31BkVYbGOxE9`i748x`YgCMwL$qMsChbSGSE1`p{nSmadR zcQ#R)(?!~dmtD0+D2!K zR9%!Xp1oOJzm(vbLvT^$IKp@+W2=-}qTzTgVtQ!#Y7Gxz}stUIm<1;oBQ^Sh2X{F4ibaOOx;5ZGSNK z0maF^@(UtV$=p6DXLgRURwF95C=|U8?osGhgOED*b z7woJ_PWXBD>V-NjQAm{~T%sjyJ{5tn2f{G%?J!KRSrrGvQ1(^`YLA5B!~eycY(e5_ z*%aa{at13SxC(=7JT7$IQF~R3sy`Nn%EMv!$-8ZEAryB*yB1k&stni)=)8-ODo41g zkJu~roIgAih94tb=YsL%iH5@^b~kU9M-=aqgXIrbtxMpFy5mekFm#edF9z7RQ6V}R zBIhbXs~pMzt0VWy1Fi$^fh+1xxLDoK09&5&MJl(q#THjPm(0=z2H2Yfm^a&E)V+a5 zbi>08u;bJsDRUKR9(INSc7XyuWv(JsD+BB*0hS)FO&l&7MdViuur@-<-EHw>kHRGY zqoT}3fDv2-m{NhBG8X}+rgOEZ;amh*DqN?jEfQdqxdj08`Sr=C-KmT)qU1 z+9Cl)a1mgXxhQiHVB}l`m;-RpmKy?0*|yl?FXvJkFxuu!fKlcmz$kN(a}i*saM3nr z0!;a~_%Xqy24IxA2rz<+08=B-Q|2PT)O4;EaxP^6qixOv7-cRh?*T?zZU`{nIM-at zTKYWr9rJ=tppQ9I#Z#mLgINVB!pO-^FOcvFw6NhV0gztuO?g ztoA*C-52Q-Z-P#xB4HAY3KQVd%dz1S4PA3vHp0aa=zAO?FCt zC_GaTyVBg2F!bBr3U@Zy2iJgIAt>1sf$JWA9kh{;L+P*HfUBX1Zy{4MgNbDfBV_ly z!y#+753arsZUt@366jIC0klaC@ckuk!qu=pAyf7&QmiBUT^L1&tOHzsK)4n|pmrVT zs2($4=?s~VejTFHbFdDOwG;_58LkIj1Fh@{glkO#F1>a==ymJS$z;gdedT1zPx4Kj ztjS`y_C}%af-RtpehdQDt3a<=W5C4$)9W@QAse;WUry$WYmr51ml9lkeunUrE`-3e zmq1SgSOPNEE-Mf+AGJ$g0M;3@w!$Ej;hMh=v=I+Lpz^n%Pg^MgwyqOkNyu2c^of)C z1~ALor3}}+RiF*K4+4{(1%1j3pif1>sv0r^mTZ?5Jd-It!tfPfiG_p$AY*Vfak%FG z4z#;wLtw&E&?}w+eKG^=#jF7HQzr8rV0mY<1YAJ_uGz~$E13p?F^fPSzXSn$8UcI$ z8er9{5w5iv0qf8%70zV71T1IBB1N}R5Kp%NO0=5wJalZt8;xYp;b{1K) zHY>2wW-`Sl{=NpR%iu3(u6l&)rc%%cSA#aV7WCowfbFR4wcc{LQZv~o1u_`}EJA3>ki`?9CKYTA!rhO)if*zRdd}Kn zEPfYbhoVE~!FI_2YbC5qAj1kq;xP6%J8+?2PAs?`V3}nyFVD#sV3+uP`pi}{$l9U^ zSz}_M9f7RgnnRhaoIJgT8us!1aB&4!*vYF07Hp&}L zCRlop0oK4DL@ISz{2_BPlezc;xj2|I z23RlDNpi9LgTG_#(w%cMaS)%N`e>~1&a3<{Xy}>?WbF>OOLuO+j&hc^YohQ$4F&ze z+hwnro1puQjnKm;vFG~o>`kCeUIlkA-2tI?WBKCFLMBY=J{hpSsQ=PDtU$=duS_hq zHpymHt^uuV1q@uc4bFb{MdG*|VoW@15Osrqt2@8ll0qO=j*uOXn{M0UJX#SUztui9FN4)K3{9!y8PC-AHHvpVTU;x|-7P+taAtyglk#rjlH2 z5Gq8ik}BPaGiM{#Woyg;*&N9R2{J0V+WGB69cEtH7F?U~Kbi6ksi*`CFXsi931q7Y zGO82?whBhN%w1iDetv%~wM*Y;E^)@Vl?VDj-f*RX>{;o_=$fU!&KAXbuadYZ46Zbg z&6jMF=49$uL^73y;;N5jaHYv)BTyfh&`qVLYn?`o6BCA_z-0niZz=qPG!vonK3MW_ zo$V96zM!+kJRs{P-5-rQVse0VBH*n6A58)4uc&gfHMa{gIhV2fGf{st>E8sKyP-$8zp~wJX^A*@DI&-;8>gANXZj zU)R+Y)PB?=)a|Kj>8NXEu^S_h^7R`~Q&7*Kn!xyvzVv&^>?^iu;S~R2e-2fJx-oUb cX)(b1KSk$MOV07*qoM6N<$f&6$jw%VRuvdN2+38CZWny1cRtlsl+0_KtW)EU14Ei(F!UtWuj4IK+3{sK@>rh zs1Z;=(DD&U6+tlyL?UnHVN^&g6QhFi2#HS+*qz;(>63G(`|jRtW|nz$Pv7qTovP!^ zP_jES{mr@O-02w%!^a?^1ZP!_KmQiz0L~jZ=W@Qt`8wzOoclQsAS<5YdH;a(4bGLE zk8s}1If(PSIgVi!XE!5kA?~z*sobvNyohr;=Q_@h2@$6Flyej3J)D-6YfheRGl`HEcPk|~huT_2-U?PfL=4BPV)f1o!%rQ!NMt_MYw-5bUSwQ9Z&zC>u zOrl~UJglJNa%f50Ok}?WB{on`Ci`p^Y!xBA?m@rcJXLxtrE0FhRF3d*ir>yzO|BD$ z3V}HpFcCh6bTzY}Nt_(W%QYd3NG)jJ4<`F<1Od) zfQblTdC&h2lCz`>y?>|9o2CdvC8qZeIZt%jN;B7Hdn2l*k4M4MFEtq`q_#5?}c$b$pf_3y{Y!cRDafZBEj-*OD|gz#PBDeu3QoueOesLzB+O zxjf2wvf6Wwz>@AiOo2mO4=TkAV+g~%_n&R;)l#!cBxjuoD$aS-`IIJv7cdX%2{WT7 zOm%5rs(wqyPE^k5SIpUZ!&Lq4<~%{*>_Hu$2|~Xa;iX*tz8~G6O3uFOS?+)tWtdi| zV2b#;zRN!m@H&jd=!$7YY6_}|=!IU@=SjvGDFtL;aCtw06U;-v^0%k0FOyESt z1Wv$={b_H&8FiRV?MrzoHWd>%v6KTRU;-v^Miiz+@q`(BoT!+<37CKhoKb)|8!+RG z6BQFU^@fRW;s8!mOf2QViKQGk0TVER6EG1`#;Nm39Do^PoT!+<37AD!%oJe86(=et zZ~|sLzU>V-qYiU6V8$0GmU7_K8|Fd0B?+9Un1BhKAz#V~Fk^`mJtlCX#{^8^M8!me z8Yg;8-~>!e<-iG;h*0B1kBKm}hItVGY6WnjVpgnTTAC$rqQ^v)4KvOtpY|sIj@WYg zyw##ZZ5AC2IKNC;^hwg9BPk0wLStlmBr;E|$5GoAo$&Ui_;S9WY62n3)i49|T%C#i017z3J=$RF|KyZWnci*@lW4 z=AKhNN6+m`Q!V3Ye68|8y@%=am>YD0nG99M)NWc20%)gwO!96j7muR}Fr&54SxKP2 zP30S~lt=a*qDlbu3+Av57=9v&vr<6g0&`!8E2fq>I|EJGKs}t|{h7+KT@)LfIV-3K zK)r_fr2?}FFyn*MYoLC>oV-J~eavL2ho4a4^r{E-8m2hi>~hA?_vIG4a*KT;2eyl1 zh_hUvUJpNCFwBvRq5BI*srSle>c6%n`#VNsyC|MGa{(P&08p=C9+WUw9Hl<1o9T4M zdD=_C0F7#o8A_bRR?sFNmU0R6tW`ElnF8p53IdHo#S9(JoZCz}fHwJ6F<&?qrpVqE zte|m%89JQD+XwaPU#%#lVs-@-OL);|MdfINd6!XwP2h(eyafTUsoRkA%&@fe?9m@jw-v(yTTiV2(*fthQH9}SqmsRPVnwwbV$1E(_lkmo&S zF-truCU914_$jpqjr(>Ha4HkM4YMT>m~NosUu&UZ>zirfHo%N6PPs9^_o$WqPA0#5 z%tG>qFCL+b*0s?sZ;Sht0nE7Kl>OVXy=gjWxxK;OJ3yGd7-pZf7JYNcZo2*1SF`u6 zHJyRRxGw9mDlOiXqVMsNe#WX`fC`vrtjSQ%KmLcl(lC>ZOQzG^%iql2w-f_K@r?OE zwCICifM#L-HJyc7Gm>Ern?+Sk3&|Khmu4(~3qa$(m6Ub^U0E5RHq49za|XklN#?kP zl;EstdW?(_4D>kwjWy2f!LM)y?F94kyU3`W!6+AyId-89v}sXJpuic^NLL7GJItl~ zsiuB98AI-(#Mnm|=A-R6&2fwJ0JVSY#Q>&3$zFh|@;#%0qeF=j5Ajq@4i0tIIW z&}sk$&fGwoJpe&u-JeGLi^r?dO`m=y(QO{@h zQqAC7$rvz&5+mo3IqE?h=a~6m>%r5Quapvzq;{y~p zJpyXOBgD9VrW7@#p6l7O?o3feml(DtSL>D^R) zZUY%T2b0-vBAFN7VB;M88!~HuOXi4KcI6aRQ&h|XQ0A?m%j2=l1f0cGP}h(oVfJ`N zz#PpmFC*ieab)zJK<4?^k=g%OjPnkANzbAbmGZHoVRk*mTfm75s_cWVa`l*f$B@xu z5E*?&@seIo#*Y~1rBm!7sF9~~u6Wrj5oICUOuz}CS)jdNIznfzCA(stJ(7$c^e5wN z?lt>eYgbA!kvAR7zYSD&*r1$b|(@;9dcZ^67R0 zXAXJKa|5Sdmj!g578Nwt6d$sXuc&MWezA0Whd`94$h{{?1IwXP4)Tx4obDK%xoFZ_Z zjjHJ_P@R_e5blG@yEjnaJb`l;s%Lb2&=8$&Ct-fV`E^4CUs)=jTk!I}2d&n!f@)bm z@ z_4Dc86+3l2*p|~;o-Sb~oXb_RuLmoifDU^&Te$*FevycC0*nE3Xws8gsWp|Rj2>SM zns)qcYj?^2sd8?N!_w~4v+f-HCF|a$TNZDoNl$I1Uq87euoNgKb6&r26TNrfkUa@o zfdiFA@p{K&mH3b8i!lcoz)V{n8Q@g(vR4ns4r6w;K z>1~ecQR0-<^J|Ndg5fvVUM9g;lbu-){#ghGw(fg>L zh)T5Ljb%lWE;V9L!;Cqk>AV1(rULYF07ZBJbGb9qbSoLAd;in9{)95YqX$J43-dY7YU*k~vrM25 zxh5_IqO0LYZW%oxQ5HOzmk4x{atE*vipUk}sh88$b2tn?!ujEHn`tQLe&vo}nMb&{ zio`xzZ&GG6&ZyN3jnaQy#iVqXE9VT(3tWY$n-)uWDQ|tc{`?fq2F`oQ{;d3aWPg4Hp-(iE{ry>MIPWL> iW8Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/flutter_install_plugin/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000..89c2725b --- /dev/null +++ b/packages/flutter_install_plugin/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/packages/flutter_install_plugin/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/flutter_install_plugin/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..f2e259c7 --- /dev/null +++ b/packages/flutter_install_plugin/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/flutter_install_plugin/example/ios/Runner/Base.lproj/Main.storyboard b/packages/flutter_install_plugin/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000..f3c28516 --- /dev/null +++ b/packages/flutter_install_plugin/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/flutter_install_plugin/example/ios/Runner/Info.plist b/packages/flutter_install_plugin/example/ios/Runner/Info.plist new file mode 100644 index 00000000..9b71deb0 --- /dev/null +++ b/packages/flutter_install_plugin/example/ios/Runner/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Flutter Install Plugin + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + flutter_install_plugin_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 + + + diff --git a/packages/flutter_install_plugin/example/ios/Runner/Runner-Bridging-Header.h b/packages/flutter_install_plugin/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000..308a2a56 --- /dev/null +++ b/packages/flutter_install_plugin/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/flutter_install_plugin/example/lib/main.dart b/packages/flutter_install_plugin/example/lib/main.dart new file mode 100644 index 00000000..f7c53226 --- /dev/null +++ b/packages/flutter_install_plugin/example/lib/main.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:flutter_install_plugin/flutter_install_plugin.dart'; +import 'package:permission_handler/permission_handler.dart'; + +void main() { + runApp(const MyApp()); +} +class MyApp extends StatefulWidget { + const MyApp({Key key}) : super(key: key); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + + String _apkFilePath = ''; + @override + void initState() { + super.initState(); + } + + void onClickInstallApk() async { + // if (_apkFilePath.isEmpty) { + // print('make sure the apk file is set'); + // return; + // } + final permissions = + await Permission.storage.request(); + if (permissions == PermissionStatus.granted) { + FlutterInstallPlugin.installApk('/sdcard/Download/app-debug1.apk', 'com.example.flutter_install_plugin_example') + .then((result) { + print('install apk $result'); + }).catchError((error) { + print('install apk error: $error'); + }); + } else { + print('Permission request fail!'); + } + } + + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: Center( + child: Column( + children: [ + TextButton(onPressed: (){ + FlutterInstallPlugin.gotoAppStore('https://itunes.apple.com/cn/app/%E5%86%8D%E6%83%A0%E5%90%88%E4%BC%99%E4%BA%BA/id1375433239?l=zh&ls=1&mt=8'); + }, child: Text('gotoAppStore')), + SizedBox(height: 20,), + TextField( + + decoration: InputDecoration( + hintText: + 'apk file path to install. Like /storage/emulated/0/demo/update.apk'), + onChanged: (path) => _apkFilePath = path, + ), + SizedBox(height: 20,), + TextButton(onPressed: (){ + onClickInstallApk(); + }, child: Text('download apk')), + + ], + ), + ), + ), + ); + } +} diff --git a/packages/flutter_install_plugin/example/pubspec.yaml b/packages/flutter_install_plugin/example/pubspec.yaml new file mode 100644 index 00000000..0bb88fe1 --- /dev/null +++ b/packages/flutter_install_plugin/example/pubspec.yaml @@ -0,0 +1,85 @@ +name: flutter_install_plugin_example +description: Demonstrates how to use the flutter_install_plugin plugin. + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter 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.7.0 <3.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + flutter_install_plugin: + # When depending on this package from a real application you should use: + # flutter_install_plugin: ^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.2 + permission_handler: ^9.1.0 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^1.0.0 + +# 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/packages/flutter_install_plugin/example/test/widget_test.dart b/packages/flutter_install_plugin/example/test/widget_test.dart new file mode 100644 index 00000000..42df2174 --- /dev/null +++ b/packages/flutter_install_plugin/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_install_plugin_example/main.dart'; + +void main() { + testWidgets('Verify Platform version', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that platform version is retrieved. + expect( + find.byWidgetPredicate( + (Widget widget) => widget is Text && + widget.data.startsWith('Running on:'), + ), + findsOneWidget, + ); + }); +} diff --git a/packages/flutter_install_plugin/ios/.gitignore b/packages/flutter_install_plugin/ios/.gitignore new file mode 100644 index 00000000..0c885071 --- /dev/null +++ b/packages/flutter_install_plugin/ios/.gitignore @@ -0,0 +1,38 @@ +.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/ephemeral/ +/Flutter/flutter_export_environment.sh \ No newline at end of file diff --git a/packages/flutter_install_plugin/ios/Assets/.gitkeep b/packages/flutter_install_plugin/ios/Assets/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/packages/flutter_install_plugin/ios/Classes/FlutterInstallPlugin.h b/packages/flutter_install_plugin/ios/Classes/FlutterInstallPlugin.h new file mode 100644 index 00000000..0718634d --- /dev/null +++ b/packages/flutter_install_plugin/ios/Classes/FlutterInstallPlugin.h @@ -0,0 +1,4 @@ +#import + +@interface FlutterInstallPlugin : NSObject +@end diff --git a/packages/flutter_install_plugin/ios/Classes/FlutterInstallPlugin.m b/packages/flutter_install_plugin/ios/Classes/FlutterInstallPlugin.m new file mode 100644 index 00000000..1d945bb1 --- /dev/null +++ b/packages/flutter_install_plugin/ios/Classes/FlutterInstallPlugin.m @@ -0,0 +1,15 @@ +#import "FlutterInstallPlugin.h" +#if __has_include() +#import +#else +// Support project import fallback if the generated compatibility header +// is not copied when this plugin is created as a library. +// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 +#import "flutter_install_plugin-Swift.h" +#endif + +@implementation FlutterInstallPlugin ++ (void)registerWithRegistrar:(NSObject*)registrar { + [SwiftFlutterInstallPlugin registerWithRegistrar:registrar]; +} +@end diff --git a/packages/flutter_install_plugin/ios/Classes/SwiftFlutterInstallPlugin.swift b/packages/flutter_install_plugin/ios/Classes/SwiftFlutterInstallPlugin.swift new file mode 100644 index 00000000..cbf29410 --- /dev/null +++ b/packages/flutter_install_plugin/ios/Classes/SwiftFlutterInstallPlugin.swift @@ -0,0 +1,35 @@ +import Flutter +import UIKit + +public class SwiftFlutterInstallPlugin: NSObject, FlutterPlugin { + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "flutter_install_plugin", binaryMessenger: registrar.messenger()) + let instance = SwiftFlutterInstallPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "gotoAppStore": + print(call.arguments ?? "null") + guard let urlString = (call.arguments as? Dictionary)?["urlString"] as? String else { + result(FlutterError(code: "参数url不能为空", message: nil, details: nil)) + return + } + gotoAppStore(urlString: urlString) + default: + result(FlutterMethodNotImplemented) + } + } + //跳转到应用的AppStore页页面 + func gotoAppStore(urlString: String) { + if let url = URL(string: urlString) { + //根据iOS系统版本,分别处理 + if #available(iOS 10, *) { + UIApplication.shared.open(url, options: [:],completionHandler: {(success) in }) + } else { + UIApplication.shared.openURL(url) + } + } + } +} diff --git a/packages/flutter_install_plugin/ios/flutter_install_plugin.podspec b/packages/flutter_install_plugin/ios/flutter_install_plugin.podspec new file mode 100644 index 00000000..011055e0 --- /dev/null +++ b/packages/flutter_install_plugin/ios/flutter_install_plugin.podspec @@ -0,0 +1,23 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint flutter_install_plugin.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'flutter_install_plugin' + s.version = '0.0.1' + s.summary = 'A new Flutter project.' + s.description = <<-DESC +A new Flutter 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.dependency 'Flutter' + s.platform = :ios, '9.0' + + # Flutter.framework does not contain a i386 slice. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } + s.swift_version = '5.0' +end diff --git a/packages/flutter_install_plugin/lib/flutter_install_plugin.dart b/packages/flutter_install_plugin/lib/flutter_install_plugin.dart new file mode 100644 index 00000000..06dd6f04 --- /dev/null +++ b/packages/flutter_install_plugin/lib/flutter_install_plugin.dart @@ -0,0 +1,26 @@ + +import 'dart:async'; + +import 'package:flutter/services.dart'; + +class FlutterInstallPlugin { + + + + static const MethodChannel _channel = MethodChannel('flutter_install_plugin'); + + /// for Android : install apk by its file absolute path; + /// if the target platform is higher than android 24: + /// a [appId] is required + /// (the caller's applicationId which is defined in build.gradle) + static Future installApk(String filePath, String appId) async { + Map params = {'filePath': filePath, 'appId': appId}; + return await _channel.invokeMethod('installApk', params); + } + + /// for iOS: go to app store by the url + static Future gotoAppStore(String urlString) async { + Map params = {'urlString': urlString}; + return await _channel.invokeMethod('gotoAppStore', params); + } +} diff --git a/packages/flutter_install_plugin/pubspec.yaml b/packages/flutter_install_plugin/pubspec.yaml new file mode 100644 index 00000000..20db726a --- /dev/null +++ b/packages/flutter_install_plugin/pubspec.yaml @@ -0,0 +1,64 @@ +name: flutter_install_plugin +description: A new Flutter project. +version: 0.0.1 +homepage: + +environment: + sdk: ">=2.7.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^1.0.0 + +# 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: com.example.flutter_install_plugin + pluginClass: FlutterInstallPlugin + ios: + pluginClass: FlutterInstallPlugin + + # 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/packages/flutter_install_plugin/test/flutter_install_plugin_test.dart b/packages/flutter_install_plugin/test/flutter_install_plugin_test.dart new file mode 100644 index 00000000..9b72ec2a --- /dev/null +++ b/packages/flutter_install_plugin/test/flutter_install_plugin_test.dart @@ -0,0 +1,23 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_install_plugin/flutter_install_plugin.dart'; + +void main() { + const MethodChannel channel = MethodChannel('flutter_install_plugin'); + + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + return '42'; + }); + }); + + tearDown(() { + channel.setMockMethodCallHandler(null); + }); + + test('gotoAppStore', () async { + expect(await FlutterInstallPlugin.gotoAppStore, '42'); + }); +} From ab99e5e95e11b862ebd3b06bcb4936f51bc68c0a Mon Sep 17 00:00:00 2001 From: zzw <602129484@qq.com> Date: Wed, 23 Feb 2022 14:59:38 +0800 Subject: [PATCH 04/14] feat: add drag_list_null_safety plugin --- packages/drag_list/CHANGELOG.md | 31 ++ packages/drag_list/LICENSE | 21 + packages/drag_list/README.md | 75 ++++ packages/drag_list/example/main.dart | 66 +++ packages/drag_list/lib/drag_list.dart | 3 + packages/drag_list/lib/src/axis_dimen.dart | 9 + .../drag_list/lib/src/drag_item_status.dart | 22 + packages/drag_list/lib/src/drag_list.dart | 162 +++++++ .../drag_list/lib/src/drag_list_item.dart | 116 +++++ .../drag_list/lib/src/drag_list_state.dart | 398 ++++++++++++++++++ packages/drag_list/lib/src/drag_overlay.dart | 115 +++++ packages/drag_list/pubspec.yaml | 18 + 12 files changed, 1036 insertions(+) create mode 100644 packages/drag_list/CHANGELOG.md create mode 100644 packages/drag_list/LICENSE create mode 100644 packages/drag_list/README.md create mode 100644 packages/drag_list/example/main.dart create mode 100755 packages/drag_list/lib/drag_list.dart create mode 100644 packages/drag_list/lib/src/axis_dimen.dart create mode 100644 packages/drag_list/lib/src/drag_item_status.dart create mode 100755 packages/drag_list/lib/src/drag_list.dart create mode 100755 packages/drag_list/lib/src/drag_list_item.dart create mode 100755 packages/drag_list/lib/src/drag_list_state.dart create mode 100644 packages/drag_list/lib/src/drag_overlay.dart create mode 100644 packages/drag_list/pubspec.yaml diff --git a/packages/drag_list/CHANGELOG.md b/packages/drag_list/CHANGELOG.md new file mode 100644 index 00000000..c33d3373 --- /dev/null +++ b/packages/drag_list/CHANGELOG.md @@ -0,0 +1,31 @@ +## [1.0.8] - 27/04/2020 + +* Added `DragItem` that provides index values in builders + +## [1.0.7] - 13/02/2020 + +* Added feedback widget builders + +## [1.0.6] - 06/01/2020 + +* Added auto scroll when dragging an item feature + +## [1.0.4] - 01/10/2019 + +* Added scrollPhysics property + +## [1.0.3] - 25/06/2019 + +* Added padding property + +## [1.0.2] - 22/06/2019 + +* Added controller property + +## [1.0.1] - 25/05/2019 + +* Added shrinkWrap property + +## [1.0.0] - 20/03/2019 + +* Implemented core functionality diff --git a/packages/drag_list/LICENSE b/packages/drag_list/LICENSE new file mode 100644 index 00000000..94329fc6 --- /dev/null +++ b/packages/drag_list/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Tomasz Wyrowinski + +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/packages/drag_list/README.md b/packages/drag_list/README.md new file mode 100644 index 00000000..b531cedd --- /dev/null +++ b/packages/drag_list/README.md @@ -0,0 +1,75 @@ +# DragList + +[![pub package](https://img.shields.io/pub/v/drag_list.svg)](https://pub.dartlang.org/packages/drag_list) + +Flutter list widget that allows to drag and drop items and define custom drag handle widget. + +![DragList demo](https://giant.gfycat.com/BraveElegantDarklingbeetle.gif) + +## Getting Started + +Add `DragList` component to your widget tree: + +```Dart +child: DragList( + items: ['Tuna', 'Meat', 'Cheese', 'Potato', 'Eggs', 'Bread'], + itemExtent: 72.0, + builder: (context, item, handle) { + return Container( + height: 72.0, + child: Row(children: [ + Spacer(), + Text(item), + Spacer(), + handle, + ]), + ); + }, +), +``` + +Optionally, provide custom handle builder: + +```Dart +child: DragList( + // ... + handleBuilder: (context) { + return Padding( + padding: EdgeInsets.all(16.0), + child: Container( + color: Colors.green, + child: Text('Handle'), + ), + ); + }, +), +``` + +Add other optional parameter if needed: + +```Dart +child: DragList( + // ... + animDuration: Duration(milliseconds: 500), + dragDelay: Duration(seconds: 1), + handleAlignment: -0.3, + scrollDirection: Axis.horizontal, + onItemReorder: (from, to) { + // handle item reorder on your own + }, +), +``` + +Use `handleless` constructor if you want list item to be dragged no matter where it's tapped on: + +```Dart +DragList.handleless( + // ... + builder: (context, item) { + return Container( + height: 72.0, + child: Center(child: Text(item)), + ); + }, +), +``` diff --git a/packages/drag_list/example/main.dart b/packages/drag_list/example/main.dart new file mode 100644 index 00000000..916c6160 --- /dev/null +++ b/packages/drag_list/example/main.dart @@ -0,0 +1,66 @@ +import 'package:drag_list/drag_list.dart'; +import 'package:flutter/material.dart'; + +void main() => runApp(CountriesPage()); + +class CountriesPage extends StatelessWidget { + final _itemHeight = 72.0; + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'DragList example', + home: Scaffold( + appBar: AppBar(title: Text('Largest countries')), + body: Padding( + padding: EdgeInsets.all(16.0), + child: DragList( + items: _countries, + itemExtent: _itemHeight, + handleBuilder: (_) => AnimatedIcon( + icon: AnimatedIcons.menu_arrow, + progress: AlwaysStoppedAnimation(0.0), + ), + feedbackHandleBuilder: (_, transition) => AnimatedIcon( + icon: AnimatedIcons.menu_arrow, + progress: transition, + ), + itemBuilder: (_, item, handle) => Container( + height: _itemHeight, + child: Padding( + padding: EdgeInsets.all(8.0), + child: Row(children: [ + Expanded(child: Center(child: Text(item.value))), + handle, + ]), + ), + ), + ), + ), + ), + ); + } + + final _countries = [ + 'Russia', + 'China', + 'United States', + 'Canada', + 'Brazil', + 'Australia', + 'India', + 'Argentina', + 'Kazakhstan', + 'Algeria', + 'DR Congo', + 'Saudi', + 'Mexico', + 'Indonesia', + 'Sudan', + 'Libya', + 'Iran', + 'Mongolia', + 'Peru', + 'Niger', + ]; +} diff --git a/packages/drag_list/lib/drag_list.dart b/packages/drag_list/lib/drag_list.dart new file mode 100755 index 00000000..6eec128a --- /dev/null +++ b/packages/drag_list/lib/drag_list.dart @@ -0,0 +1,3 @@ +library drag_list; + +export 'package:drag_list/src/drag_list.dart'; diff --git a/packages/drag_list/lib/src/axis_dimen.dart b/packages/drag_list/lib/src/axis_dimen.dart new file mode 100644 index 00000000..dd727b00 --- /dev/null +++ b/packages/drag_list/lib/src/axis_dimen.dart @@ -0,0 +1,9 @@ +import 'package:flutter/material.dart'; + +mixin AxisDimen { + Axis get axis; + double axisOffset(Offset offset) => + axis == Axis.vertical ? offset.dy : offset.dx; + double axisSize(Size size) => + axis == Axis.vertical ? size.height : size.width; +} diff --git a/packages/drag_list/lib/src/drag_item_status.dart b/packages/drag_list/lib/src/drag_item_status.dart new file mode 100644 index 00000000..a5d4825e --- /dev/null +++ b/packages/drag_list/lib/src/drag_item_status.dart @@ -0,0 +1,22 @@ +class DragItemStatus { + const DragItemStatus._(this.name); + + final String name; + + static const BEFORE = DragItemStatus._('BEFORE'); + static const AFTER = DragItemStatus._('AFTER'); + static const HOVER = DragItemStatus._('HOVER'); + static const SETTLED = DragItemStatus._('SETTLED'); + + factory DragItemStatus(int currentIndex, int? hoverIndex) { + if (hoverIndex == null) return DragItemStatus.SETTLED; + if (currentIndex == hoverIndex) return DragItemStatus.HOVER; + if (currentIndex < hoverIndex) return DragItemStatus.BEFORE; + if (currentIndex > hoverIndex) return DragItemStatus.AFTER; + throw Exception('Cannot determine DragItemStatus. ' + + 'Indices were: $currentIndex (current), $hoverIndex (hover).'); + } + + @override + String toString() => '$runtimeType.$name'; +} diff --git a/packages/drag_list/lib/src/drag_list.dart b/packages/drag_list/lib/src/drag_list.dart new file mode 100755 index 00000000..8b5452dc --- /dev/null +++ b/packages/drag_list/lib/src/drag_list.dart @@ -0,0 +1,162 @@ +import 'package:flutter/material.dart'; + +import 'axis_dimen.dart'; +import 'drag_list_state.dart'; + +typedef Widget DragItemBuilder( + BuildContext context, DragItem item, Widget handle); +typedef Widget DragHandleBuilder(BuildContext context); + +typedef Widget FeedbackDragItemBuilder(BuildContext context, + DragItem item, Widget handle, Animation transition); +typedef Widget FeedbackDragHandleBuilder( + BuildContext context, Animation transition); + +typedef Widget BareDragItemBuilder(BuildContext context, DragItem item); +typedef Widget BareFeedbackDragItemBuilder( + BuildContext context, DragItem item, Animation transition); + +typedef void ItemReorderCallback(int from, int to); + +class DragItem { + final T value; + final int itemIndex; + final int dispIndex; + + DragItem(this.value, this.itemIndex, this.dispIndex); + + @override + String toString() => + "DragItem<$T>(value: $value, itemIndex: $itemIndex, dispIndex: $dispIndex)"; +} + +class DragList extends StatefulWidget with AxisDimen { + /// List of items displayed in the list. + final List items; + + /// Extent of each item widget in the list. Corresponds to + /// width/height in case of horizontal/vertical axis orientation. + final double itemExtent; + + /// Relative position within item widget that corresponds to the center of + /// handle, where -1.0 stands for beginning and 1.0 for end of the item. + final double handleAlignment; + + /// Duration of raise and drop animation of dragged item. + final Duration animDuration; + + /// Duration between list item touch and drag start. + final Duration dragDelay; + + /// Builder function that creates handle widget for each element of the list. + final DragHandleBuilder handleBuilder; + + /// Builder function that creates widget for each element of the list. + final DragItemBuilder itemBuilder; + + /// Builder function that creates handle widget of currently dragged item. + /// If null, [handleBuilder] function will be used instead. + final FeedbackDragHandleBuilder feedbackHandleBuilder; + + /// Builder function that creates widget of currently dragged item. + /// If null, [builder] function will be used instead. + final FeedbackDragItemBuilder feedbackItemBuilder; + + /// Callback function that invokes if dragged item changed + /// its index and drag action is ended. By default this + /// will swap start and end position in [items] list. + final ItemReorderCallback? onItemReorder; + + /// Axis orientation of the list widget. + final Axis scrollDirection; + + /// Whether the extent of the scroll view in the scrollDirection + /// should be determined by the contents being viewed. + final bool shrinkWrap; + + /// An object that can be used to control the position to which this scroll + /// view is scrolled. + final ScrollController? controller; + + /// The amount of space by which to inset the children. + final EdgeInsetsGeometry? padding; + + /// How the scroll view should respond to user input. + final ScrollPhysics? physics; + + DragList({ + required this.items, + required this.itemExtent, + required this.itemBuilder, + Key? key, + Duration? animDuration, + Duration? dragDelay, + double? handleAlignment, + Axis? scrollDirection, + bool? shrinkWrap, + FeedbackDragItemBuilder? feedbackItemBuilder, + FeedbackDragHandleBuilder? feedbackHandleBuilder, + DragHandleBuilder? handleBuilder, + this.onItemReorder, + this.controller, + this.padding, + this.physics, + }) : this.animDuration = animDuration ?? Duration(milliseconds: 300), + this.dragDelay = dragDelay ?? Duration.zero, + this.handleAlignment = handleAlignment ?? 0.0, + this.scrollDirection = scrollDirection ?? Axis.vertical, + this.shrinkWrap = shrinkWrap ?? false, + this.handleBuilder = handleBuilder ??= + ((_) => Center(child: Icon(Icons.drag_handle, size: 24.0))), + this.feedbackItemBuilder = feedbackItemBuilder ??= + ((context, item, handle, _) => itemBuilder(context, item, handle)), + this.feedbackHandleBuilder = + feedbackHandleBuilder ??= ((context, _) => handleBuilder ==null ? Container(): handleBuilder(context)), + super(key: key) { + assert(this.handleAlignment >= -1.0 && this.handleAlignment <= 1.0, + 'Handle alignment has to be in bounds (-1, 1) inclusive. Passed value was: $handleAlignment.'); + } + + DragList.handleless({ + required List items, + required double itemExtent, + required BareDragItemBuilder itemBuilder, + Key? key, + BareFeedbackDragItemBuilder? feedbackItemBuilder, + Duration? animDuration, + Duration? dragDelay, + double? handleAlignment, + Axis? scrollDirection, + ScrollPhysics? physics, + bool? shrinkWrap, + ItemReorderCallback? onItemReorder, + }) : this( + items: items, + itemExtent: itemExtent, + key: key, + scrollDirection: scrollDirection, + physics: physics, + shrinkWrap: shrinkWrap, + animDuration: animDuration, + dragDelay: dragDelay ?? Duration(milliseconds: 300), + onItemReorder: onItemReorder, + handleBuilder: (_) => Container(), + itemBuilder: (context, item, handle) => Stack(children: [ + itemBuilder(context, item), + Positioned.fill(child: handle), + ]), + feedbackItemBuilder: (context, item, _, transition) { + if(feedbackItemBuilder != null){ + return feedbackItemBuilder(context, item, transition); + } + return Container(); + } + + ); + + @override + DragListState createState() => DragListState(); + + @override + Axis get axis => scrollDirection; +} diff --git a/packages/drag_list/lib/src/drag_list_item.dart b/packages/drag_list/lib/src/drag_list_item.dart new file mode 100755 index 00000000..4e22130c --- /dev/null +++ b/packages/drag_list/lib/src/drag_list_item.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; + +import 'drag_item_status.dart'; + +typedef Widget DragListItemBuilder(BuildContext context, Widget handle); +typedef void OffsetChanged(Offset value); + +class DragListItem extends StatefulWidget { + DragListItem({ + required Key key, + required this.builder, + required this.handle, + required this.onDragStop, + required this.onDragUpdate, + required this.onDragTouch, + required this.extent, + required this.status, + required this.animDuration, + required this.scrollDirection, + }) : super(key: key); + + final DragListItemBuilder builder; + final Widget handle; + final OffsetChanged onDragStop; + final OffsetChanged onDragUpdate; + final OffsetChanged onDragTouch; + final double extent; + final DragItemStatus status; + final Duration animDuration; + final Axis scrollDirection; + + @override + DragListItemState createState() => DragListItemState(); +} + +class DragListItemState extends State + with SingleTickerProviderStateMixin { + late AnimationController _animator; + Animation? _transAnim; + DragItemStatus? _status; + DragItemStatus? _prevStatus; + + @override + void initState() { + super.initState(); + _status = DragItemStatus.SETTLED; + _animator = AnimationController( + vsync: this, + value: 1.0, + duration: widget.animDuration, + ); + _transAnim = _animator.drive(Tween(begin: 0.0, end: 0.0)); + } + + @override + void dispose() { + _animator.dispose(); + super.dispose(); + } + + @override + void didUpdateWidget(DragListItem old) { + super.didUpdateWidget(old); + if (widget.status != old.status) { + _prevStatus = _status; + _status = widget.status; + if (_didChangeIndex) { + _updateTransAnim(); + } + } + } + + bool get _didChangeIndex => + (_prevStatus == DragItemStatus.AFTER && + _status == DragItemStatus.BEFORE) || + (_prevStatus == DragItemStatus.BEFORE && _status == DragItemStatus.AFTER); + + void _updateTransAnim() { + final trans = widget.extent * (_status == DragItemStatus.BEFORE ? 1 : -1); + _transAnim = Tween(begin: trans, end: 0.0) + .chain(CurveTween(curve: Curves.easeInOut)) + .animate(_animator); + _animator.forward(from: 1 - _animator.value); + } + + @override + Widget build(BuildContext context) { + return Opacity( + opacity: _status == DragItemStatus.HOVER ? 0.0 : 1.0, + child: AbsorbPointer( + absorbing: _status == DragItemStatus.HOVER, + child: AnimatedBuilder( + animation: _transAnim!, + child: widget.builder(context, _wrapHandle()), + builder: (_, child) => Transform.translate( + offset: widget.scrollDirection == Axis.vertical + ? Offset(0.0, _transAnim!.value) + : Offset(_transAnim!.value, 0.0), + child: child, + ), + ), + ), + ); + } + + Widget _wrapHandle() { + return Listener( + behavior: HitTestBehavior.translucent, + onPointerCancel: (it) => widget.onDragStop(it.position), + onPointerUp: (it) => widget.onDragStop(it.position), + onPointerDown: (it) => widget.onDragTouch(it.position), + onPointerMove: (it) => widget.onDragUpdate(it.delta), + child: widget.handle, + ); + } +} diff --git a/packages/drag_list/lib/src/drag_list_state.dart b/packages/drag_list/lib/src/drag_list_state.dart new file mode 100755 index 00000000..f5dcaf0b --- /dev/null +++ b/packages/drag_list/lib/src/drag_list_state.dart @@ -0,0 +1,398 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:async/async.dart'; +import 'package:flutter/material.dart'; + +import 'drag_item_status.dart'; +import 'drag_list.dart'; +import 'drag_list_item.dart'; +import 'drag_overlay.dart'; + +class DragListState extends State> + with SingleTickerProviderStateMixin { + int? _dragIndex; + int? _hoverIndex; + bool _isDropping = false; + bool _isDragging = false; + double _overdragDelta = 0.0; + double _totalDragDelta = 0.0; + double _boundedDragDelta = 0.0; + double? _touchStartOffset; + double? _dragStartOffset; + double _lastFrameAnimDelta = 0.0; + double? _lastTouchOffset; + double _touchScrollOffset = 0.0; + Offset? _touchStartPoint; + Offset? _dragStartPoint; + CancelableOperation? _startDragJob; + StreamSubscription? _overdragSub; + OverlayEntry? _dragOverlay; + ScrollController? _scrollController; + late ScrollController _innerController; + Map _itemKeys = {}; + + AnimationController? _animator; + Animation? _baseAnim; + Animation? _deltaAnim; + Animation? _elevAnim; + Animation? _transAnim; + + RenderBox get _listBox => context.findRenderObject() as RenderBox; + bool get _isDragSettled => _dragIndex == null; + bool get _dragsForwards => _boundedDragDelta > 0; + double get _scrollOffset => _scrollController!.offset; + double get _listSize => widget.axisSize(_listBox.size); + double get _handleCenterOffset => + widget.itemExtent * (1 + widget.handleAlignment) / 2; + + @override + void didUpdateWidget(DragList oldWidget) { + super.didUpdateWidget(oldWidget); + _updateScrollController(); + } + + @override + void initState() { + super.initState(); + _innerController = ScrollController(); + _updateScrollController(); + _clearState(); + _itemKeys = {}; + _animator = AnimationController(vsync: this, duration: widget.animDuration) + ..addListener(_onAnimUpdate) + ..addStatusListener(_onAnimStatus); + _baseAnim = _animator!.drive(CurveTween(curve: Curves.easeInOut)); + _elevAnim = _baseAnim!.drive(Tween(begin: 0.0, end: 2.0)); + _dragOverlay = OverlayEntry(builder: _buildOverlay); + } + + void _updateScrollController() { + _scrollController = widget.controller ?? _innerController; + } + + void _onAnimUpdate() { + final toAdd = _deltaAnim!.value - _lastFrameAnimDelta; + _lastFrameAnimDelta = _deltaAnim!.value; + _onDeltaChanged(toAdd); + } + + void _onAnimStatus(AnimationStatus status) { + if (!_animator!.isAnimating) { + _lastFrameAnimDelta = 0.0; + if (_animator!.isDismissed) { + _onDragSettled(); + } + } + } + + void _onDragSettled() { + if (_dragIndex != _hoverIndex) { + (widget.onItemReorder ?? _defaultOnItemReorder) + .call(_dragIndex!, _hoverIndex!); + _swapItemKeys(_dragIndex!, _hoverIndex!); + } + _dragOverlay!.remove(); + setState(_clearState); + // Jump to current offset to make sure _drag in ScrollableState has been disposed. + // Happened every time when list view was touched after an item had been dragged. + _scrollController!.jumpTo(_scrollOffset); + } + + void _swapItemKeys(int from, int to) { + final sign = from < to ? 1 : -1; + GlobalKey? temp = _itemKeys[from]; + List.generate((to - from).abs(), (it) => from + it * sign) + .forEach((it) { + GlobalKey? t = _itemKeys[it + sign]; + if(t != null){ + _itemKeys[it] = t; + } + + }); + if(temp != null){ + _itemKeys[to] = temp; + } + } + + void _defaultOnItemReorder(int from, int to) => + widget.items.insert(to, widget.items.removeAt(from)); + + void _clearState() { + _overdragDelta = 0.0; + _lastFrameAnimDelta = 0.0; + _totalDragDelta = 0.0; + _boundedDragDelta = 0.0; + _touchScrollOffset = 0.0; + _isDropping = false; + _isDragging = false; + _lastTouchOffset = null; + _touchStartOffset = null; + _dragStartOffset = null; + _dragIndex = null; + _hoverIndex = null; + _startDragJob = null; + _dragStartPoint = null; + _touchStartPoint = null; + } + + Widget _buildOverlay(BuildContext context) { + return DragOverlay( + scrollDirection: widget.scrollDirection, + itemStart: _touchStartOffset! - _dragStartOffset! + _boundedDragDelta, + listBox: _listBox, + itemExtent: widget.itemExtent, + translation: _transAnim!.value, + elevation: _elevAnim!.value, + child: widget.feedbackItemBuilder( + context, + DragItem(widget.items[_dragIndex!], _dragIndex!, _hoverIndex!), + widget.feedbackHandleBuilder(context, _animator!), + _animator!, + ), + ); + } + + @override + void dispose() { + _innerController.dispose(); + _animator!.dispose(); + _clearDragJob(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ListView.builder( + physics: _isDragSettled ? widget.physics : NeverScrollableScrollPhysics(), + padding: widget.padding, + scrollDirection: widget.scrollDirection, + shrinkWrap: widget.shrinkWrap, + itemExtent: widget.itemExtent, + controller: _scrollController, + itemCount: widget.items.length, + itemBuilder: (context, index) { + final itemIndex = _calcItemIndex(index); + return _buildDragItem(context, itemIndex, index); + }, + ); + } + + int _calcItemIndex(int index) { + if (_dragIndex == _hoverIndex) { + return index; + } + if (index == _hoverIndex) { + return _dragIndex!; + } + if (index > _hoverIndex! && index <= _dragIndex!) { + return index - 1; + } + if (index < _hoverIndex! && index >= _dragIndex!) { + return index + 1; + } + return index; + } + + Widget _buildDragItem(BuildContext context, int itemIndex, int dispIndex) { + return DragListItem( + key: _itemKeys.putIfAbsent(itemIndex, () => GlobalKey()), + handle: widget.handleBuilder(context), + builder: (context, handle) => widget.itemBuilder(context, + DragItem(widget.items[itemIndex], itemIndex, dispIndex), handle), + onDragTouch: (position) => _onItemDragTouch(itemIndex, position), + onDragStop: (position) => _onItemDragStop(itemIndex, position), + onDragUpdate: (delta) => _onItemDragUpdate(itemIndex, delta), + extent: widget.itemExtent, + animDuration: widget.animDuration, + scrollDirection: widget.scrollDirection, + status: DragItemStatus(dispIndex, _hoverIndex), + ); + } + + void _onItemDragTouch(int index, Offset position) { + if (_isDragSettled) { + _lastTouchOffset = widget.axisOffset(_listBox.globalToLocal(position)); + _touchStartPoint = position; + _dragStartPoint = position; + _touchScrollOffset = _scrollOffset; + _scheduleDragStart(index); + } + } + + void _scheduleDragStart(int index) { + _clearDragJob(); + _startDragJob = CancelableOperation.fromFuture(Future.delayed( + widget.dragDelay, + () => _onItemDragStart(index), + )); + } + + void _onItemDragStart(int index) { + _isDragging = true; + _clearDragJob(); + _registerStartPoint(_touchStartPoint!); + Overlay.of(context)?.insert(_dragOverlay!); + setState(() { + _dragIndex = index; + _hoverIndex = index; + }); + _runRaiseAnim(); + } + + void _registerStartPoint(Offset position) { + final localPos = _listBox.globalToLocal(position); + _touchStartOffset = + widget.axisOffset(localPos) + _touchScrollOffset - _scrollOffset; + _dragStartOffset = (_touchStartOffset! + _scrollOffset) % widget.itemExtent; + } + + void _runRaiseAnim() { + _transAnim = _baseAnim!.drive(Tween(begin: _calcTranslation(), end: 0.0)); + _deltaAnim = _baseAnim!.drive(Tween(begin: 0.0, end: _calcRaiseDelta())); + _animator!.forward(); + } + + double _calcRaiseDelta() { + final touchToStartDelta = + widget.axisOffset(_dragStartPoint! - _touchStartPoint!) + + _scrollOffset - + _touchScrollOffset; + return _dragStartOffset! - _handleCenterOffset + touchToStartDelta; + } + + void _onItemDragStop(int index, Offset position) { + _isDragging = false; + _clearDragJob(); + _stopOverdrag(); + if (!_isDragSettled && !_isDropping) { + _totalDragDelta = _calcBoundedDelta(_totalDragDelta); + final localPos = _listBox.globalToLocal(position); + _runDropAnim(localPos); + } + } + + void _runDropAnim(Offset stopOffset) { + _isDropping = true; + final delta = _calcDropDelta(stopOffset); + _lastFrameAnimDelta += delta * (1 - _baseAnim!.value); + _deltaAnim = _baseAnim!.drive(Tween(begin: delta, end: 0.0)); + final trans = _calcTranslation(); + _transAnim = _baseAnim!.drive(Tween( + begin: trans, + end: trans * (1 - 1 / _baseAnim!.value), + )); + _animator!.reverse(); + } + + double _calcDropDelta(Offset stopOffset) { + final rawPos = widget.axisOffset(stopOffset); + final halfItemStart = widget.itemExtent * widget.handleAlignment / 2; + final stopPos = rawPos.clamp(halfItemStart, _listSize + halfItemStart); + final hoverStartPos = _hoverIndex! * widget.itemExtent - _scrollOffset; + return -(stopPos - hoverStartPos - _handleCenterOffset); + } + + double _calcTranslation() { + final rawClip = _dragsForwards + ? 1 - ((_scrollOffset + _listSize) / widget.itemExtent - _hoverIndex!) + : _scrollOffset / widget.itemExtent - _hoverIndex!; + final clip = max(rawClip - 0.5, 0.0) * (_dragsForwards ? 1 : -1); + return clip * widget.itemExtent; + } + + void _onItemDragUpdate(int index, Offset delta) { + _lastTouchOffset = _lastTouchOffset! + widget.axisOffset(delta); + if (_startDragJob != null) { + _updateStartPoint(delta); + } + if (!_isDragSettled && !_isDropping) { + _onDeltaChanged(widget.axisOffset(delta)); + } + } + + void _updateStartPoint(Offset delta) { + _dragStartPoint =_dragStartPoint! + delta; + final dragSinceTouch = + widget.axisOffset(_dragStartPoint! - _touchStartPoint!).abs(); + if (dragSinceTouch > widget.itemExtent / 2) { + _clearDragJob(); + } + } + + void _clearDragJob() { + if (_startDragJob != null) { + _startDragJob!.cancel(); + _startDragJob = null; + } + } + + void _onDeltaChanged(double delta) { + _updateDelta(delta); + _updateOverdragScroll(); + if (_overdragSub == null) { + _updateHoverIndex(); + } + } + + void _updateDelta(double delta) { + _totalDragDelta += delta; + _boundedDragDelta = _calcBoundedDelta(_totalDragDelta); + Overlay.of(context)?.setState(() {}); + } + + double _calcBoundedDelta(double delta) { + final minDelta = + -_touchStartOffset! + _dragStartOffset! - widget.itemExtent / 2; + final maxDelta = minDelta + _listSize; + return delta.clamp(minDelta, maxDelta); + } + + void _updateOverdragScroll() { + final isDraggedBeyond = _dragsForwards + ? _lastTouchOffset! > _listSize - widget.itemExtent / 2 + : _lastTouchOffset! < widget.itemExtent / 2; + if (_overdragSub == null && _isDragging && isDraggedBeyond) { + _overdragSub = Stream.periodic(Duration(milliseconds: 16)) + .listen((_) => _onOverdragUpdate()); + } else if (_overdragSub != null && !isDraggedBeyond) { + _stopOverdrag(); + } + } + + void _onOverdragUpdate() { + final canScrollMore = _dragsForwards + ? _scrollOffset < widget.items.length * widget.itemExtent - _listSize + : _scrollOffset > 0; + if (canScrollMore) { + _updateOverdragOffset(); + _updateHoverIndex(); + } else { + _stopOverdrag(); + } + } + + void _updateOverdragOffset() { + final overdragScale = _dragsForwards + ? 1 - (_listSize - _lastTouchOffset!) / (widget.itemExtent / 2) + : -1 + _lastTouchOffset! / (widget.itemExtent / 2); + final offsetDelta = 6.0 * overdragScale.clamp(-1.0, 1.0); + _scrollController?.jumpTo(_scrollOffset + offsetDelta); + _overdragDelta += offsetDelta; + } + + void _updateHoverIndex() { + final halfExtent = widget.itemExtent / 2 * (_dragsForwards ? 1 : -1); + final rawIndex = _dragIndex! + + (_boundedDragDelta + halfExtent + _overdragDelta) ~/ widget.itemExtent; + final index = rawIndex.clamp(0, widget.items.length - 1); + if (_hoverIndex != index) { + setState(() => _hoverIndex = index); + } + } + + void _stopOverdrag() { + _overdragSub?.cancel(); + _overdragSub = null; + } +} diff --git a/packages/drag_list/lib/src/drag_overlay.dart b/packages/drag_list/lib/src/drag_overlay.dart new file mode 100644 index 00000000..116c41cb --- /dev/null +++ b/packages/drag_list/lib/src/drag_overlay.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; + +class DragOverlay extends StatelessWidget { + DragOverlay({ + required this.child, + required this.elevation, + required this.translation, + required this.itemStart, + required this.itemExtent, + required RenderBox listBox, + required Axis scrollDirection, + }) : this.axisBuilder = AxisOverlayBuilder(scrollDirection), + this.listPos = listBox.localToGlobal(Offset.zero), + this.listSize = listBox.size; + + final Widget child; + final Offset listPos; + final Size listSize; + final double elevation; + final double translation; + final double itemStart; + final double itemExtent; + final AxisOverlayBuilder axisBuilder; + + @override + Widget build(BuildContext context) { + return axisBuilder.builGlobalPos( + overlay: this, + child: ClipRect( + child: Stack(children: [ + axisBuilder.buildLocalPos( + overlay: this, + child: Transform.translate( + offset: axisBuilder.buildTransOffset(translation), + child: Material( + elevation: elevation, + child: child, + ), + )) + ]), + ), + ); + } +} + +abstract class AxisOverlayBuilder { + AxisOverlayBuilder._(); + + Positioned builGlobalPos({ DragOverlay? overlay,required Widget child}); + Positioned buildLocalPos({ DragOverlay? overlay,required Widget child}); + Offset buildTransOffset(double translation); + + factory AxisOverlayBuilder(Axis scrollDirection) => + scrollDirection == Axis.vertical + ? _VerticalBuilder() + : _HorizontalBuilder(); +} + +class _VerticalBuilder extends AxisOverlayBuilder { + _VerticalBuilder() : super._(); + + @override + Positioned builGlobalPos({DragOverlay? overlay,required Widget child}) { + return Positioned( + top: overlay?.listPos.dy, + height: overlay?.listSize.height, + left: 0.0, + right: 0.0, + child: child, + ); + } + + @override + Positioned buildLocalPos({DragOverlay? overlay,required Widget child}) { + return Positioned( + top: overlay?.itemStart, + height: overlay?.itemExtent, + left: overlay?.listPos.dx, + width: overlay?.listSize.width, + child: child, + ); + } + + @override + Offset buildTransOffset(double translation) => Offset(0.0, translation); +} + +class _HorizontalBuilder extends AxisOverlayBuilder { + _HorizontalBuilder() : super._(); + + @override + Positioned builGlobalPos({DragOverlay? overlay,required Widget child}) { + return Positioned( + top: 0.0, + bottom: 0.0, + left: overlay?.listPos.dx, + width: overlay?.listSize.width, + child: child, + ); + } + + @override + Positioned buildLocalPos({DragOverlay? overlay,required Widget child}) { + return Positioned( + left: overlay?.itemStart, + width: overlay?.itemExtent, + top: overlay?.listPos.dy, + height: overlay?.listSize.height, + child: child, + ); + } + + @override + Offset buildTransOffset(double translation) => Offset(translation, 0.0); +} diff --git a/packages/drag_list/pubspec.yaml b/packages/drag_list/pubspec.yaml new file mode 100644 index 00000000..06215aac --- /dev/null +++ b/packages/drag_list/pubspec.yaml @@ -0,0 +1,18 @@ +name: drag_list +description: Flutter list widget that allows to drag and drop items and define custom drag handle widget. +version: 1.0.8 +homepage: https://github.com/tomwyr/drag-list/ + +environment: + sdk: ">=2.12.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + async: ^2.8.2 + +dev_dependencies: + flutter_test: + sdk: flutter + +flutter: From ac7990295b25b35ae5875e3943d2adac2fcd1df1 Mon Sep 17 00:00:00 2001 From: zzw <602129484@qq.com> Date: Wed, 23 Feb 2022 17:17:22 +0800 Subject: [PATCH 05/14] =?UTF-8?q?feat:=20local=5Fauth=20=20=20flutter=5Fin?= =?UTF-8?q?stall=5Fplugin=20=E7=A9=BA=E5=AE=89=E5=85=A8=E5=8D=87=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/lib/main.dart | 2 +- .../example/pubspec.yaml | 2 +- .../example/test/widget_test.dart | 2 +- .../lib/flutter_install_plugin.dart | 4 +- packages/flutter_install_plugin/pubspec.yaml | 2 +- packages/local_auth/lib/auth_strings.dart | 40 +++++++++---------- packages/local_auth/lib/local_auth.dart | 15 ++++--- packages/local_auth/pubspec.yaml | 7 +--- 8 files changed, 37 insertions(+), 37 deletions(-) diff --git a/packages/flutter_install_plugin/example/lib/main.dart b/packages/flutter_install_plugin/example/lib/main.dart index f7c53226..c8ab9e71 100644 --- a/packages/flutter_install_plugin/example/lib/main.dart +++ b/packages/flutter_install_plugin/example/lib/main.dart @@ -9,7 +9,7 @@ void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { - const MyApp({Key key}) : super(key: key); + const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); diff --git a/packages/flutter_install_plugin/example/pubspec.yaml b/packages/flutter_install_plugin/example/pubspec.yaml index 0bb88fe1..3b537ce2 100644 --- a/packages/flutter_install_plugin/example/pubspec.yaml +++ b/packages/flutter_install_plugin/example/pubspec.yaml @@ -6,7 +6,7 @@ description: Demonstrates how to use the flutter_install_plugin plugin. publish_to: 'none' # Remove this line if you wish to publish to pub.dev environment: - sdk: ">=2.7.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions diff --git a/packages/flutter_install_plugin/example/test/widget_test.dart b/packages/flutter_install_plugin/example/test/widget_test.dart index 42df2174..c1afb6ea 100644 --- a/packages/flutter_install_plugin/example/test/widget_test.dart +++ b/packages/flutter_install_plugin/example/test/widget_test.dart @@ -19,7 +19,7 @@ void main() { expect( find.byWidgetPredicate( (Widget widget) => widget is Text && - widget.data.startsWith('Running on:'), + widget.data!.startsWith('Running on:'), ), findsOneWidget, ); diff --git a/packages/flutter_install_plugin/lib/flutter_install_plugin.dart b/packages/flutter_install_plugin/lib/flutter_install_plugin.dart index 06dd6f04..bed3506e 100644 --- a/packages/flutter_install_plugin/lib/flutter_install_plugin.dart +++ b/packages/flutter_install_plugin/lib/flutter_install_plugin.dart @@ -13,13 +13,13 @@ class FlutterInstallPlugin { /// if the target platform is higher than android 24: /// a [appId] is required /// (the caller's applicationId which is defined in build.gradle) - static Future installApk(String filePath, String appId) async { + static Future installApk(String filePath, String appId) async { Map params = {'filePath': filePath, 'appId': appId}; return await _channel.invokeMethod('installApk', params); } /// for iOS: go to app store by the url - static Future gotoAppStore(String urlString) async { + static Future gotoAppStore(String urlString) async { Map params = {'urlString': urlString}; return await _channel.invokeMethod('gotoAppStore', params); } diff --git a/packages/flutter_install_plugin/pubspec.yaml b/packages/flutter_install_plugin/pubspec.yaml index 20db726a..6404785c 100644 --- a/packages/flutter_install_plugin/pubspec.yaml +++ b/packages/flutter_install_plugin/pubspec.yaml @@ -4,7 +4,7 @@ version: 0.0.1 homepage: environment: - sdk: ">=2.7.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: diff --git a/packages/local_auth/lib/auth_strings.dart b/packages/local_auth/lib/auth_strings.dart index 81d48d60..12b6d879 100644 --- a/packages/local_auth/lib/auth_strings.dart +++ b/packages/local_auth/lib/auth_strings.dart @@ -20,25 +20,25 @@ class AndroidAuthMessages { }); /// 指纹识别提示 - final String tips; + final String? tips; /// 指纹识别失败 - final String notRecognized; + final String? notRecognized; /// 尝试次数过多,请稍后重试。 - final String failures; + final String? failures; /// touch 设置权限提示 - final String touchSetting; + final String? touchSetting; /// 指纹识别成功 - final String success; + final String? success; /// 取消按钮 - final String negativeBtn; + final String? negativeBtn; /// 使用密码支付 - final String positiveBtn; + final String? positiveBtn; Map get args { return { @@ -74,43 +74,43 @@ class IOSAuthMessages { }); /// 生物验证次数过多,被锁 - final String deviceLockout; + final String? deviceLockout; /// face 开启权限提示 - final String faceLimit; + final String? faceLimit; /// face 设置权限提示 - final String faceSetting; + final String? faceSetting; /// face 超出尝试次数 - final String faceFailures; + final String? faceFailures; /// face 超出尝试次数 - final String faceTips; + final String? faceTips; /// touch 开启权限提示 - final String touchLimit; + final String? touchLimit; /// touch 设置权限提示 - final String touchSetting; + final String? touchSetting; /// touch 超出尝试次数 - final String touchFailures; + final String? touchFailures; /// touch 超出尝试次数 - final String touchTips; + final String? touchTips; /// 取消按钮 - final String negativeBtn; + final String? negativeBtn; /// 密码解锁 - final String positiveBtn; + final String? positiveBtn; /// 使用密码支付 - final String payPassword; + final String? payPassword; /// 开启权限 - final String goSetting; + final String? goSetting; Map get args { return { diff --git a/packages/local_auth/lib/local_auth.dart b/packages/local_auth/lib/local_auth.dart index 6dc54c69..d3c93b5c 100644 --- a/packages/local_auth/lib/local_auth.dart +++ b/packages/local_auth/lib/local_auth.dart @@ -38,14 +38,14 @@ class LocalAuthentication { /// callbackValue [onPositiveCallback] -1000 -> 取消 -1001->失败 -1002->多次失败 -2000 ->设备不支持 -3000 -> 未设置指纹 -4000 -> 密码支付 -5000 -> 去设置开启 // ignore: missing_return - Future authenticateWithBiometrics({ + Future authenticateWithBiometrics({ AndroidAuthMessages androidAuthStrings = const AndroidAuthMessages(), IOSAuthMessages iOSAuthStrings = const IOSAuthMessages(), /// 是否需要密码支付 bool sensitiveTransaction = false, bool dark = false, - ValueChanged listening, + ValueChanged? listening, }) async { final Map args = { 'sensitiveTransaction': sensitiveTransaction ? 1 : 0, @@ -113,9 +113,9 @@ class LocalAuthentication { /// Returns true if device is capable of checking biometrics /// /// Returns a [Future] bool true or false: - Future get canCheckBiometrics async => + Future get canCheckBiometrics async => (await _channel.invokeListMethod('getAvailableBiometrics')) - .isNotEmpty; + ?.isNotEmpty; /// Returns a list of enrolled biometrics /// @@ -124,9 +124,12 @@ class LocalAuthentication { /// - BiometricType.fingerprint /// - BiometricType.iris (not yet implemented) Future> getAvailableBiometrics() async { - final List result = + final List? result = (await _channel.invokeListMethod('getAvailableBiometrics')); final List biometrics = []; + + if(result==null) return biometrics; + result.forEach((String value) { switch (value) { case 'face': @@ -150,7 +153,7 @@ class LocalAuthentication { return response == 0 ? false : true; } - Future stopAuthentication() { + Future stopAuthentication() { if (_platform.isAndroid) { return _channel.invokeMethod('stopAuthentication'); } diff --git a/packages/local_auth/pubspec.yaml b/packages/local_auth/pubspec.yaml index a28def03..50acf8a9 100644 --- a/packages/local_auth/pubspec.yaml +++ b/packages/local_auth/pubspec.yaml @@ -5,8 +5,7 @@ homepage: https://github.com/flutter/plugins/tree/master/packages/local_auth version: 0.6.3+1 environment: - sdk: '>=2.10.0 <3.0.0' - flutter: ">=1.20.4 <2.10.0" + sdk: '>=2.12.0 <3.0.0' flutter: plugin: @@ -21,10 +20,8 @@ dependencies: flutter: sdk: flutter meta: ^1.7.0 - #meta: ^1.0.5 - intl: "0.17.0" + intl: ^0.17.0 platform: ">=2.0.0 <4.0.0" - #flutter_plugin_android_lifecycle: ^1.0.2 flutter_plugin_android_lifecycle: ^2.0.5 dev_dependencies: integration_test: From be1d75aedb3b637e163eb74149e376215bbedf12 Mon Sep 17 00:00:00 2001 From: zzw <602129484@qq.com> Date: Wed, 23 Feb 2022 18:39:32 +0800 Subject: [PATCH 06/14] feat: flutter_webview_plugin --- packages/flutter_webview_plugin/CHANGELOG.md | 167 ++++++ packages/flutter_webview_plugin/LICENSE | 27 + packages/flutter_webview_plugin/README.md | 299 ++++++++++ .../analysis_options.yaml | 121 ++++ .../android/build.gradle | 71 +++ .../android/gradle.properties | 2 + .../android/settings.gradle | 1 + .../android/src/main/AndroidManifest.xml | 17 + .../flutter_webview_plugin/BrowserClient.java | 119 ++++ .../FlutterWebviewPlugin.java | 369 ++++++++++++ .../JavaScriptChannel.java | 60 ++ .../ObservableWebView.java | 49 ++ .../WebviewManager.java | 557 ++++++++++++++++++ .../android/src/main/res/xml/filepaths.xml | 20 + .../FlutterWebviewPluginTest.java | 41 ++ .../org.mockito.plugins.MockMaker | 1 + .../flutter_webview_plugin/android_test.sh | 3 + .../flutter_webview_plugin/example/.gitignore | 10 + .../flutter_webview_plugin/example/README.md | 8 + .../example/android/.gitignore | 12 + .../example/android/app/build.gradle | 50 ++ .../android/app/src/main/AndroidManifest.xml | 32 + .../MainActivity.java | 14 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../example/android/build.gradle | 38 ++ .../example/android/gradle.properties | 4 + .../gradle/wrapper/gradle-wrapper.properties | 6 + .../example/android/settings.gradle | 15 + .../example/ios/.gitignore | 41 ++ .../ios/Flutter/AppFrameworkInfo.plist | 30 + .../example/ios/Flutter/Debug.xcconfig | 2 + .../example/ios/Flutter/Release.xcconfig | 2 + .../ios/Runner.xcodeproj/project.pbxproj | 482 +++++++++++++++ .../contents.xcworkspacedata | 10 + .../xcshareddata/xcschemes/Runner.xcscheme | 87 +++ .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/Runner.xcscmblueprint | 30 + .../example/ios/Runner/AppDelegate.h | 6 + .../example/ios/Runner/AppDelegate.m | 37 ++ .../AppIcon.appiconset/Contents.json | 116 ++++ .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 564 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 1588 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 1025 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 1716 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 1920 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 1895 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 3831 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 1888 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 3294 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 3612 bytes .../Runner/Base.lproj/LaunchScreen.storyboard | 27 + .../ios/Runner/Base.lproj/Main.storyboard | 26 + .../example/ios/Runner/Info.plist | 50 ++ .../example/ios/Runner/main.m | 10 + .../example/lib/main.dart | 312 ++++++++++ .../example/pubspec.yaml | 12 + .../flutter_webview_plugin/ios/.gitignore | 31 + .../ios/Assets/.gitkeep | 0 .../ios/Classes/FlutterWebviewPlugin.h | 9 + .../ios/Classes/FlutterWebviewPlugin.m | 534 +++++++++++++++++ .../Classes/WebviewJavaScriptChannelHandler.h | 17 + .../Classes/WebviewJavaScriptChannelHandler.m | 36 ++ .../ios/flutter_webview_plugin.podspec | 21 + .../lib/flutter_webview_plugin.dart | 6 + .../flutter_webview_plugin/lib/src/base.dart | 380 ++++++++++++ .../lib/src/javascript_channel.dart | 36 ++ .../lib/src/javascript_message.dart | 11 + .../lib/src/webview_scaffold.dart | 259 ++++++++ packages/flutter_webview_plugin/pubspec.yaml | 28 + .../scripts/before_build_apks.sh | 26 + .../test/flutter_webview_plugin_test.dart | 51 ++ .../flutter_webview_plugin/travis.yml.bak | 96 +++ 81 files changed, 4952 insertions(+) create mode 100644 packages/flutter_webview_plugin/CHANGELOG.md create mode 100644 packages/flutter_webview_plugin/LICENSE create mode 100644 packages/flutter_webview_plugin/README.md create mode 100644 packages/flutter_webview_plugin/analysis_options.yaml create mode 100644 packages/flutter_webview_plugin/android/build.gradle create mode 100644 packages/flutter_webview_plugin/android/gradle.properties create mode 100644 packages/flutter_webview_plugin/android/settings.gradle create mode 100644 packages/flutter_webview_plugin/android/src/main/AndroidManifest.xml create mode 100644 packages/flutter_webview_plugin/android/src/main/java/com/flutter_webview_plugin/BrowserClient.java create mode 100644 packages/flutter_webview_plugin/android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java create mode 100755 packages/flutter_webview_plugin/android/src/main/java/com/flutter_webview_plugin/JavaScriptChannel.java create mode 100644 packages/flutter_webview_plugin/android/src/main/java/com/flutter_webview_plugin/ObservableWebView.java create mode 100644 packages/flutter_webview_plugin/android/src/main/java/com/flutter_webview_plugin/WebviewManager.java create mode 100644 packages/flutter_webview_plugin/android/src/main/res/xml/filepaths.xml create mode 100644 packages/flutter_webview_plugin/android/src/test/java/com/flutter_webview_plugin/FlutterWebviewPluginTest.java create mode 100644 packages/flutter_webview_plugin/android/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker create mode 100755 packages/flutter_webview_plugin/android_test.sh create mode 100644 packages/flutter_webview_plugin/example/.gitignore create mode 100644 packages/flutter_webview_plugin/example/README.md create mode 100644 packages/flutter_webview_plugin/example/android/.gitignore create mode 100644 packages/flutter_webview_plugin/example/android/app/build.gradle create mode 100644 packages/flutter_webview_plugin/example/android/app/src/main/AndroidManifest.xml create mode 100644 packages/flutter_webview_plugin/example/android/app/src/main/java/com/yourcompany/flutter_webview_plugin_example/MainActivity.java create mode 100644 packages/flutter_webview_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 packages/flutter_webview_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 packages/flutter_webview_plugin/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 packages/flutter_webview_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 packages/flutter_webview_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 packages/flutter_webview_plugin/example/android/build.gradle create mode 100644 packages/flutter_webview_plugin/example/android/gradle.properties create mode 100644 packages/flutter_webview_plugin/example/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 packages/flutter_webview_plugin/example/android/settings.gradle create mode 100644 packages/flutter_webview_plugin/example/ios/.gitignore create mode 100644 packages/flutter_webview_plugin/example/ios/Flutter/AppFrameworkInfo.plist create mode 100644 packages/flutter_webview_plugin/example/ios/Flutter/Debug.xcconfig create mode 100644 packages/flutter_webview_plugin/example/ios/Flutter/Release.xcconfig create mode 100644 packages/flutter_webview_plugin/example/ios/Runner.xcodeproj/project.pbxproj create mode 100644 packages/flutter_webview_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 packages/flutter_webview_plugin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 packages/flutter_webview_plugin/example/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 packages/flutter_webview_plugin/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/flutter_webview_plugin/example/ios/Runner.xcworkspace/xcshareddata/Runner.xcscmblueprint create mode 100644 packages/flutter_webview_plugin/example/ios/Runner/AppDelegate.h create mode 100644 packages/flutter_webview_plugin/example/ios/Runner/AppDelegate.m create mode 100644 packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 packages/flutter_webview_plugin/example/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 packages/flutter_webview_plugin/example/ios/Runner/Base.lproj/Main.storyboard create mode 100644 packages/flutter_webview_plugin/example/ios/Runner/Info.plist create mode 100644 packages/flutter_webview_plugin/example/ios/Runner/main.m create mode 100644 packages/flutter_webview_plugin/example/lib/main.dart create mode 100644 packages/flutter_webview_plugin/example/pubspec.yaml create mode 100644 packages/flutter_webview_plugin/ios/.gitignore create mode 100644 packages/flutter_webview_plugin/ios/Assets/.gitkeep create mode 100644 packages/flutter_webview_plugin/ios/Classes/FlutterWebviewPlugin.h create mode 100644 packages/flutter_webview_plugin/ios/Classes/FlutterWebviewPlugin.m create mode 100755 packages/flutter_webview_plugin/ios/Classes/WebviewJavaScriptChannelHandler.h create mode 100755 packages/flutter_webview_plugin/ios/Classes/WebviewJavaScriptChannelHandler.m create mode 100644 packages/flutter_webview_plugin/ios/flutter_webview_plugin.podspec create mode 100644 packages/flutter_webview_plugin/lib/flutter_webview_plugin.dart create mode 100644 packages/flutter_webview_plugin/lib/src/base.dart create mode 100644 packages/flutter_webview_plugin/lib/src/javascript_channel.dart create mode 100644 packages/flutter_webview_plugin/lib/src/javascript_message.dart create mode 100644 packages/flutter_webview_plugin/lib/src/webview_scaffold.dart create mode 100644 packages/flutter_webview_plugin/pubspec.yaml create mode 100755 packages/flutter_webview_plugin/scripts/before_build_apks.sh create mode 100644 packages/flutter_webview_plugin/test/flutter_webview_plugin_test.dart create mode 100644 packages/flutter_webview_plugin/travis.yml.bak diff --git a/packages/flutter_webview_plugin/CHANGELOG.md b/packages/flutter_webview_plugin/CHANGELOG.md new file mode 100644 index 00000000..17cb0606 --- /dev/null +++ b/packages/flutter_webview_plugin/CHANGELOG.md @@ -0,0 +1,167 @@ +# 0.3.10+1 +- fixed android build + +# 0.3.10 +- add mediaPlaybackRequiresUserGesture parameter +- Add ignore ssl error parameter + +# 0.3.9+1 + +- Fixed error methods on iOS + +# 0.3.9 + +- Fixed error methods on iOS +- fixed build +- fixed ios clean cookies +- 4 Make plugin work in headless mode when extending FlutterApplication +- added canGoBack and canGoForward methods + +# 0.3.8 + +- Fix iOS local URL support (fixes #114) +- bugfix: Added google() repository to allprojects to satisfy androidx build rules +- fixed min sdk for android + +# 0.3.7 + +- Added reloading url with headers +- Added support for reloading url with headers + +# 0.3.6 + +- Allow web contents debugging in Chrome +- Android: allow geolocation and file chooser simultaneously +- Add min sdk requirement and descriptions +- fix bug android webview httperror exception +- Exposes displayZoomControls, withOverviewMode and useWideViewPort settings for Android WebView + +# 0.3.5 + +- Ability to choose from camera or gallery when using +- Support for webview’s estimated loading progress #255 +- Fix back button handler to be compatible with the WillPopScope widget + +# 0.3.4 + +- WebView always hidden on iOS + +# 0.3.3 + +- BREAKING CHANGE - AndroidX support + +# 0.3.2 + +- enable Javascript in iOS, support abort loading specific URLs +- add resizeToAvoidBottomInset to WebviewScaffold; #301 + +# 0.3.1 + +- Add support for geolocation Android +- fix No269: Can't load target="_blank" links on iOS +- fix: reloadUrl will not return Future +- Fix height of keyboard +- Fix Hide/Show WebView +- hotfix widget back to initialChild after webview is tapped on Android + +# 0.3.0 + +- Fixes rect capture issue. Ensures WebView remains in the correct place on screen even when keyboard appears. +- Fixed iOS crash issue with Flutter `>= 0.10.2`. +- Added new `clearCookies` feature. +- Added support for `hidden` and `initialChild` feature to show page loading view. +- Added supportMultipleWindows: enables Multiple Window Support on Android. +- Added appCacheEnabled: enables Application Caches API on Android. +- Added allowFileURLs: allows `file://` local file URLs. +- iOS Now supports: `reload`, `goBack`, and `goForward`. +- iOS Bug fix `didFailNavigation` #77 +- Updated Android `compileSdkVersion` to `27` matching offical Flutter plugins. +- Fixed Android `reloadUrl` so settings are not cleared. +- Enabled compatible `Mixed Content Mode` on Android. + +# 0.2.1 + +- Added webview scrolling listener +- Added stopLoading() method + +# 0.2.0 + +- update sdk +- prevent negative webview height in scaffold +- handle type error in getCookies +- Support file upload via WebView on Android +- fix WebviewScaffold crash on iOS +- Scrollbar functionality to Web view +- Add support of HTTP errors +- Add headers when loading url + +# 0.1.6 + +- fix onStateChanged +- Taking safe areas into account for bottom bars +- iOS + + withLocalUrl option for iOS > 9.0 +- Android + + add reload, goBack and foForward function + +# 0.1.5 + +- iOS use WKWebView instead of UIWebView + +# 0.1.4 + +- support localstorage for ANDROID + +# 0.1.3 + +- support zoom in webview + +# 0.1.2 + +- support bottomNavigationBar and persistentFooterButtons on webview scaffold + +# 0.1.1 +- support back button navigation for Android + + if cannot go back, it will trigger onDestroy +- support preview dart2 + +# 0.1.0+1 + +- fix Android close webview + +# 0.1.0 + +- iOS && Android: + - get cookies + - eval javascript + - user agent setting + - state change event + - embed in rectangle or fullscreen if null + - hidden webview + +- Android + - adding Activity in manifest is not needed anymore + +- Add `WebviewScaffold` + +# 0.0.9 + +- Android: remove the need to use FlutterActivity as base activity + +# 0.0.5 + +- fix "onDestroy" event for iOS [#4](https://github.com/dart-flitter/flutter_webview_plugin/issues/4) +- fix fullscreen mode for iOS [#5](https://github.com/dart-flitter/flutter_webview_plugin/issues/5) + +# 0.0.4 + +- IOS implementation +- Update to last version of Flutter + +# 0.0.3 + +- Documentation + +# 0.0.2 + +- Initial version for Android diff --git a/packages/flutter_webview_plugin/LICENSE b/packages/flutter_webview_plugin/LICENSE new file mode 100644 index 00000000..2fe58f3d --- /dev/null +++ b/packages/flutter_webview_plugin/LICENSE @@ -0,0 +1,27 @@ +// Copyright 2017 Hadrien Lejard. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Hadrien Lejard nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/flutter_webview_plugin/README.md b/packages/flutter_webview_plugin/README.md new file mode 100644 index 00000000..3d03835b --- /dev/null +++ b/packages/flutter_webview_plugin/README.md @@ -0,0 +1,299 @@ +[![Flutter Community: flutter_webview_plugin](https://fluttercommunity.dev/_github/header/flutter_webview_plugin)](https://github.com/fluttercommunity/community) + +# NOTICE +> We are working closely with the Flutter Team to integrate all the Community Plugin features in the [Official WebView Plugin](https://pub.dev/packages/webview_flutter). We will try our best to resolve PRs and Bugfixes, but our priority right now is to merge our two code-bases. Once the merge is complete we will deprecate the Community Plugin in favor of the Official one. +> +> Thank you for all your support, hopefully you'll also show it for Official Plugin too. +> +> Keep Fluttering! + +# Flutter WebView Plugin + +[![pub package](https://img.shields.io/pub/v/flutter_webview_plugin.svg)](https://pub.dartlang.org/packages/flutter_webview_plugin) + +Plugin that allows Flutter to communicate with a native WebView. + +**_Warning:_** +The webview is not integrated in the widget tree, it is a native view on top of the flutter view. +You won't be able see snackbars, dialogs, or other flutter widgets that would overlap with the region of the screen taken up by the webview. + +The getSafeAcceptedType() function is available only for minimum SDK of 21. +eval() function only supports SDK of 19 or greater for evaluating Javascript. + +## Getting Started + +For help getting started with Flutter, view our online [documentation](http://flutter.io/). + +#### iOS + +In order for plugin to work correctly, you need to add new key to `ios/Runner/Info.plist` + +```xml +NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSAllowsArbitraryLoadsInWebContent + + +``` + +`NSAllowsArbitraryLoadsInWebContent` is for iOS 10+ and `NSAllowsArbitraryLoads` for iOS 9. + + +### How it works + +#### Launch WebView Fullscreen with Flutter navigation + +```dart +new MaterialApp( + routes: { + "/": (_) => new WebviewScaffold( + url: "https://www.google.com", + appBar: new AppBar( + title: new Text("Widget webview"), + ), + ), + }, + ); +``` + +Optional parameters `hidden` and `initialChild` are available so that you can show something else while waiting for the page to load. +If you set `hidden` to true it will show a default CircularProgressIndicator. If you additionally specify a Widget for initialChild +you can have it display whatever you like till page-load. + +e.g. The following will show a read screen with the text 'waiting.....'. +```dart +return new MaterialApp( + title: 'Flutter WebView Demo', + theme: new ThemeData( + primarySwatch: Colors.blue, + ), + routes: { + '/': (_) => const MyHomePage(title: 'Flutter WebView Demo'), + '/widget': (_) => new WebviewScaffold( + url: selectedUrl, + appBar: new AppBar( + title: const Text('Widget webview'), + ), + withZoom: true, + withLocalStorage: true, + hidden: true, + initialChild: Container( + color: Colors.redAccent, + child: const Center( + child: Text('Waiting.....'), + ), + ), + ), + }, +); +``` + +`FlutterWebviewPlugin` provide a singleton instance linked to one unique webview, +so you can take control of the webview from anywhere in the app + +listen for events + +```dart +final flutterWebviewPlugin = new FlutterWebviewPlugin(); + +flutterWebviewPlugin.onUrlChanged.listen((String url) { + +}); +``` + +#### Listen for scroll event in webview + +```dart +final flutterWebviewPlugin = new FlutterWebviewPlugin(); +flutterWebviewPlugin.onScrollYChanged.listen((double offsetY) { // latest offset value in vertical scroll + // compare vertical scroll changes here with old value +}); + +flutterWebviewPlugin.onScrollXChanged.listen((double offsetX) { // latest offset value in horizontal scroll + // compare horizontal scroll changes here with old value +}); + +```` + +Note: Do note there is a slight difference is scroll distance between ios and android. Android scroll value difference tends to be larger than ios devices. + + +#### Hidden WebView + +```dart +final flutterWebviewPlugin = new FlutterWebviewPlugin(); + +flutterWebviewPlugin.launch(url, hidden: true); +``` + +#### Close launched WebView + +```dart +flutterWebviewPlugin.close(); +``` + +#### Webview inside custom Rectangle + +```dart +final flutterWebviewPlugin = new FlutterWebviewPlugin(); + +flutterWebviewPlugin.launch(url, + fullScreen: false, + rect: new Rect.fromLTWH( + 0.0, + 0.0, + MediaQuery.of(context).size.width, + 300.0, + ), +); +``` + +#### Injecting custom code into the webview +Use `flutterWebviewPlugin.evalJavaScript(String code)`. This function must be run after the page has finished loading (i.e. listen to `onStateChanged` for events where state is `finishLoad`). + +If you have a large amount of JavaScript to embed, use an asset file. Add the asset file to `pubspec.yaml`, then call the function like: + +```dart +Future loadJS(String name) async { + var givenJS = rootBundle.loadString('assets/$name.js'); + return givenJS.then((String js) { + flutterWebViewPlugin.onStateChanged.listen((viewState) async { + if (viewState.type == WebViewState.finishLoad) { + flutterWebViewPlugin.evalJavascript(js); + } + }); + }); +} +``` + +### Accessing local files in the file system +Set the `withLocalUrl` option to true in the launch function or in the Webview scaffold to enable support for local URLs. + +Note that, on iOS, the `localUrlScope` option also needs to be set to a path to a directory. All files inside this folder (or subfolder) will be allowed access. If ommited, only the local file being opened will have access allowed, resulting in no subresources being loaded. This option is ignored on Android. + +### Ignoring SSL Errors + +Set the `ignoreSSLErrors` option to true to display content from servers with certificates usually not trusted by the Webview like self-signed certificates. + +**_Warning:_** Don't use this in production. + +Note that on iOS, you need to add new key to `ios/Runner/Info.plist` + +```xml +NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSAllowsArbitraryLoadsInWebContent + + +``` + +`NSAllowsArbitraryLoadsInWebContent` is for iOS 10+ and `NSAllowsArbitraryLoads` for iOS 9. +Otherwise you'll still not be able to display content from pages with untrusted certificates. + +You can test your ignorance if ssl certificates is working e.g. through https://self-signed.badssl.com/ + + + + +### Webview Events + +- `Stream` onDestroy +- `Stream` onUrlChanged +- `Stream` onStateChanged +- `Stream` onScrollXChanged +- `Stream` onScrollYChanged +- `Stream` onError + +**_Don't forget to dispose webview_** +`flutterWebviewPlugin.dispose()` + +### Webview Functions + +```dart +Future launch(String url, { + Map headers: null, + Set javascriptChannels: null, + bool withJavascript: true, + bool clearCache: false, + bool clearCookies: false, + bool hidden: false, + bool enableAppScheme: true, + Rect rect: null, + String userAgent: null, + bool withZoom: false, + bool displayZoomControls: false, + bool withLocalStorage: true, + bool withLocalUrl: true, + String localUrlScope: null, + bool withOverviewMode: false, + bool scrollBar: true, + bool supportMultipleWindows: false, + bool appCacheEnabled: false, + bool allowFileURLs: false, + bool useWideViewPort: false, + String invalidUrlRegex: null, + bool geolocationEnabled: false, + bool debuggingEnabled: false, + bool ignoreSSLErrors: false, +}); +``` + +```dart +Future evalJavascript(String code); +``` + +```dart +Future> getCookies(); +``` + +```dart +Future cleanCookies(); +``` + +```dart +Future resize(Rect rect); +``` + +```dart +Future show(); +``` + +```dart +Future hide(); +``` + +```dart +Future reloadUrl(String url); +``` + +```dart +Future close(); +``` + +```dart +Future reload(); +``` + +```dart +Future goBack(); +``` + +```dart +Future goForward(); +``` + +```dart +Future stopLoading(); +``` + +```dart +Future canGoBack(); +``` + +```dart +Future canGoForward(); +``` diff --git a/packages/flutter_webview_plugin/analysis_options.yaml b/packages/flutter_webview_plugin/analysis_options.yaml new file mode 100644 index 00000000..18e07b8f --- /dev/null +++ b/packages/flutter_webview_plugin/analysis_options.yaml @@ -0,0 +1,121 @@ +analyzer: + +linter: + rules: + # these rules are documented on and in the same order as + # the Dart Lint rules page to make maintenance easier + # https://github.com/dart-lang/linter/blob/master/example/all.yaml + - always_declare_return_types + - always_put_control_body_on_new_line + # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 + - always_require_non_null_named_parameters + # - always_specify_types + - annotate_overrides + # - avoid_annotating_with_dynamic # conflicts with always_specify_types + - avoid_as + # - avoid_bool_literals_in_conditional_expressions # not yet tested + # - avoid_catches_without_on_clauses # we do this commonly + # - avoid_catching_errors # we do this commonly + - avoid_classes_with_only_static_members + # - avoid_double_and_int_checks # only useful when targeting JS runtime + - avoid_empty_else + - avoid_field_initializers_in_const_classes + # - avoid_function_literals_in_foreach_calls + - avoid_init_to_null + # - avoid_js_rounded_ints # only useful when targeting JS runtime + - avoid_null_checks_in_equality_operators + # - avoid_positional_boolean_parameters # not yet tested + # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) + - avoid_relative_lib_imports + - avoid_renaming_method_parameters + - avoid_return_types_on_setters + # - avoid_returning_null # we do this commonly + # - avoid_returning_this # https://github.com/dart-lang/linter/issues/842 + # - avoid_setters_without_getters # not yet tested + # - avoid_single_cascade_in_expression_statements # not yet tested + - avoid_slow_async_io + # - avoid_types_as_parameter_names # https://github.com/dart-lang/linter/pull/954/files + # - avoid_types_on_closure_parameters # conflicts with always_specify_types + # - avoid_unused_constructor_parameters # https://github.com/dart-lang/linter/pull/847 + - await_only_futures + - camel_case_types + - cancel_subscriptions + # - cascade_invocations # not yet tested + # - close_sinks # https://github.com/flutter/flutter/issues/5789 + # - comment_references # blocked on https://github.com/dart-lang/dartdoc/issues/1153 + # - constant_identifier_names # https://github.com/dart-lang/linter/issues/204 + - control_flow_in_finally + - directives_ordering + - empty_catches + - empty_constructor_bodies + - empty_statements + - hash_and_equals + - implementation_imports + # - invariant_booleans # https://github.com/flutter/flutter/issues/5790 + - iterable_contains_unrelated_type + # - join_return_with_assignment # not yet tested + - library_names + - library_prefixes + - list_remove_unrelated_type + # - literal_only_boolean_expressions # https://github.com/flutter/flutter/issues/5791 + - no_adjacent_strings_in_list + - no_duplicate_case_values + - non_constant_identifier_names + # - omit_local_variable_types # opposite of always_specify_types + # - one_member_abstracts # too many false positives + # - only_throw_errors # https://github.com/flutter/flutter/issues/5792 + - overridden_fields + - package_api_docs + - package_names + - package_prefixed_library_names + # - parameter_assignments # we do this commonly + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + - prefer_bool_in_asserts + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + # - prefer_constructors_over_static_methods # not yet tested + - prefer_contains + - prefer_equal_for_default_values + # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods + - prefer_final_fields + - prefer_final_locals + # - prefer_foreach + # - prefer_function_declarations_over_variables # not yet tested + - prefer_initializing_formals + # - prefer_interpolation_to_compose_strings # not yet tested + # - prefer_iterable_whereType # https://github.com/dart-lang/sdk/issues/32463 + - prefer_is_empty + - prefer_is_not_empty + - prefer_single_quotes + - prefer_typing_uninitialized_variables + - recursive_getters + - slash_for_doc_comments + - sort_constructors_first + - sort_unnamed_constructors_first + - super_goes_last + - test_types_in_equals + - throw_in_finally + # - type_annotate_public_apis # subset of always_specify_types + - type_init_formals + # - unawaited_futures # https://github.com/flutter/flutter/issues/5793 + - unnecessary_brace_in_string_interps + - unnecessary_getters_setters + # - unnecessary_lambdas # https://github.com/dart-lang/linter/issues/498 + - unnecessary_null_aware_assignments + - unnecessary_null_in_if_null_operators + - unnecessary_overrides + - unnecessary_parenthesis + # - unnecessary_statements # not yet tested + - unnecessary_this + - unrelated_type_equality_checks + - use_rethrow_when_possible + # - use_setters_to_change_properties # not yet tested + # - use_string_buffers # https://github.com/dart-lang/linter/pull/664 + # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review + - valid_regexps + # - void_checks # not yet tested \ No newline at end of file diff --git a/packages/flutter_webview_plugin/android/build.gradle b/packages/flutter_webview_plugin/android/build.gradle new file mode 100644 index 00000000..aa2719b2 --- /dev/null +++ b/packages/flutter_webview_plugin/android/build.gradle @@ -0,0 +1,71 @@ +group 'com.flutter_webview_plugin' +version '1.0-SNAPSHOT' + +def ANDROIDX_WARNING = "flutterPluginsAndroidXWarning"; +gradle.buildFinished { buildResult -> + if (buildResult.failure && !rootProject.ext.has(ANDROIDX_WARNING)) { + println ' *********************************************************' + println 'WARNING: This version of flutter_webview_plugin will break your Android build if it or its dependencies aren\'t compatible with AndroidX.' + println ' See https://goo.gl/CP92wY for more information on the problem and how to fix it.' + println ' This warning prints for all Android build failures. The real root cause of the error may be unrelated.' + println ' *********************************************************' + rootProject.ext.set(ANDROIDX_WARNING, true); + } +} + +buildscript { + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.3' + } +} + +allprojects { + repositories { + jcenter() + google() + } +} + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 28 + + defaultConfig { + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + // NOTE(jeffmikels): When targetSdkVersion or minSdkVersion is not set or < 4, gradle adds + // additional scary permissions such as WRITE_EXTERNAL_STORAGE and READ_PHONE_STATE. + minSdkVersion 16 + } + lintOptions { + disable 'InvalidPackage' + } + + testOptions { + unitTests { + includeAndroidResources = true + } + } +} +dependencies { + implementation 'androidx.appcompat:appcompat:1.1.0' + + testImplementation 'junit:junit:4.12' + testImplementation 'androidx.test:core:1.2.0' + +// When running unit tests for project, gradle needs to have flutter.jar in path +// since there's no FLUTTER_HOME variable, we need to pass flutterPath from console with command: +// ./gradlew test -DflutterPath=/Users/rafal.wachol/Utils/flutter +// +// while develop you can set path to this jar explicitly so IDE won't complain + if(System.getProperty('flutterPath')) { + testImplementation files(System.getProperty('flutterPath') + '/bin/cache/artifacts/engine/android-x64/flutter.jar') + } + + testImplementation 'org.mockito:mockito-inline:2.28.2' +} diff --git a/packages/flutter_webview_plugin/android/gradle.properties b/packages/flutter_webview_plugin/android/gradle.properties new file mode 100644 index 00000000..7be3d8b4 --- /dev/null +++ b/packages/flutter_webview_plugin/android/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableR8=true diff --git a/packages/flutter_webview_plugin/android/settings.gradle b/packages/flutter_webview_plugin/android/settings.gradle new file mode 100644 index 00000000..976b01e8 --- /dev/null +++ b/packages/flutter_webview_plugin/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'flutter_webview_plugin' diff --git a/packages/flutter_webview_plugin/android/src/main/AndroidManifest.xml b/packages/flutter_webview_plugin/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..20ea4b76 --- /dev/null +++ b/packages/flutter_webview_plugin/android/src/main/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/packages/flutter_webview_plugin/android/src/main/java/com/flutter_webview_plugin/BrowserClient.java b/packages/flutter_webview_plugin/android/src/main/java/com/flutter_webview_plugin/BrowserClient.java new file mode 100644 index 00000000..d3e136e6 --- /dev/null +++ b/packages/flutter_webview_plugin/android/src/main/java/com/flutter_webview_plugin/BrowserClient.java @@ -0,0 +1,119 @@ +package com.flutter_webview_plugin; + +import android.annotation.TargetApi; +import android.graphics.Bitmap; +import android.os.Build; +import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Created by lejard_h on 20/12/2017. + */ + +public class BrowserClient extends WebViewClient { + private Pattern invalidUrlPattern = null; + + public BrowserClient() { + this(null); + } + + public BrowserClient(String invalidUrlRegex) { + super(); + if (invalidUrlRegex != null) { + invalidUrlPattern = Pattern.compile(invalidUrlRegex); + } + } + + public void updateInvalidUrlRegex(String invalidUrlRegex) { + if (invalidUrlRegex != null) { + invalidUrlPattern = Pattern.compile(invalidUrlRegex); + } else { + invalidUrlPattern = null; + } + } + + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + super.onPageStarted(view, url, favicon); + Map data = new HashMap<>(); + data.put("url", url); + data.put("type", "startLoad"); + FlutterWebviewPlugin.channel.invokeMethod("onState", data); + } + + @Override + public void onPageFinished(WebView view, String url) { + super.onPageFinished(view, url); + Map data = new HashMap<>(); + data.put("url", url); + + FlutterWebviewPlugin.channel.invokeMethod("onUrlChanged", data); + + data.put("type", "finishLoad"); + FlutterWebviewPlugin.channel.invokeMethod("onState", data); + + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override + public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { + // returning true causes the current WebView to abort loading the URL, + // while returning false causes the WebView to continue loading the URL as usual. + String url = request.getUrl().toString(); + boolean isInvalid = checkInvalidUrl(url); + Map data = new HashMap<>(); + data.put("url", url); + data.put("type", isInvalid ? "abortLoad" : "shouldStart"); + + FlutterWebviewPlugin.channel.invokeMethod("onState", data); + return isInvalid; + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + // returning true causes the current WebView to abort loading the URL, + // while returning false causes the WebView to continue loading the URL as usual. + boolean isInvalid = checkInvalidUrl(url); + Map data = new HashMap<>(); + data.put("url", url); + data.put("type", isInvalid ? "abortLoad" : "shouldStart"); + + FlutterWebviewPlugin.channel.invokeMethod("onState", data); + return isInvalid; + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override + public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) { + super.onReceivedHttpError(view, request, errorResponse); + Map data = new HashMap<>(); + data.put("url", request.getUrl().toString()); + data.put("code", Integer.toString(errorResponse.getStatusCode())); + FlutterWebviewPlugin.channel.invokeMethod("onHttpError", data); + } + + @Override + public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { + super.onReceivedError(view, errorCode, description, failingUrl); + Map data = new HashMap<>(); + data.put("url", failingUrl); + data.put("code", Integer.toString(errorCode)); + FlutterWebviewPlugin.channel.invokeMethod("onHttpError", data); + } + + private boolean checkInvalidUrl(String url) { + if (invalidUrlPattern == null) { + return false; + } else { + Matcher matcher = invalidUrlPattern.matcher(url); + return matcher.lookingAt(); + } + } +} \ No newline at end of file diff --git a/packages/flutter_webview_plugin/android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java b/packages/flutter_webview_plugin/android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java new file mode 100644 index 00000000..52531a4d --- /dev/null +++ b/packages/flutter_webview_plugin/android/src/main/java/com/flutter_webview_plugin/FlutterWebviewPlugin.java @@ -0,0 +1,369 @@ +package com.flutter_webview_plugin; + + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.graphics.Point; +import android.view.Display; +import android.webkit.WebStorage; +import android.widget.FrameLayout; +import android.webkit.CookieManager; +import android.webkit.ValueCallback; +import android.os.Build; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +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.PluginRegistry; + +/** + * FlutterWebviewPlugin + */ +public class FlutterWebviewPlugin implements FlutterPlugin, ActivityAware, MethodCallHandler, PluginRegistry.ActivityResultListener { + private Activity activity; + private WebviewManager webViewManager; + private Context context; + static MethodChannel channel; + private static final String CHANNEL_NAME = "flutter_webview_plugin"; + private static final String JS_CHANNEL_NAMES_FIELD = "javascriptChannelNames"; + + public static void registerWith(PluginRegistry.Registrar registrar) { + if (registrar.activity() != null) { + channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME); + final FlutterWebviewPlugin instance = new FlutterWebviewPlugin(registrar.activity(), registrar.activeContext()); + registrar.addActivityResultListener(instance); + channel.setMethodCallHandler(instance); + } + } + + public FlutterWebviewPlugin() {} + + FlutterWebviewPlugin(Activity activity, Context context) { + this.activity = activity; + this.context = context; + } + + @Override + public void onMethodCall(MethodCall call, MethodChannel.Result result) { + switch (call.method) { + case "launch": + openUrl(call, result); + break; + case "close": + close(call, result); + break; + case "eval": + eval(call, result); + break; + case "resize": + resize(call, result); + break; + case "reload": + reload(call, result); + break; + case "back": + back(call, result); + break; + case "forward": + forward(call, result); + break; + case "hide": + hide(call, result); + break; + case "show": + show(call, result); + break; + case "reloadUrl": + reloadUrl(call, result); + break; + case "stopLoading": + stopLoading(call, result); + break; + case "cleanCookies": + cleanCookies(call, result); + break; + case "canGoBack": + canGoBack(result); + break; + case "canGoForward": + canGoForward(result); + break; + case "cleanCache": + cleanCache(result); + break; + default: + result.notImplemented(); + break; + } + } + + private void cleanCache(MethodChannel.Result result) { + webViewManager.cleanCache(); + WebStorage.getInstance().deleteAllData(); + result.success(null); + } + + void openUrl(MethodCall call, MethodChannel.Result result) { + boolean hidden = call.argument("hidden"); + String url = call.argument("url"); + String userAgent = call.argument("userAgent"); + boolean withJavascript = call.argument("withJavascript"); + boolean clearCache = call.argument("clearCache"); + boolean clearCookies = call.argument("clearCookies"); + boolean mediaPlaybackRequiresUserGesture = call.argument("mediaPlaybackRequiresUserGesture"); + boolean withZoom = call.argument("withZoom"); + boolean displayZoomControls = call.argument("displayZoomControls"); + boolean withLocalStorage = call.argument("withLocalStorage"); + boolean withOverviewMode = call.argument("withOverviewMode"); + boolean supportMultipleWindows = call.argument("supportMultipleWindows"); + boolean appCacheEnabled = call.argument("appCacheEnabled"); + Map headers = call.argument("headers"); + boolean scrollBar = call.argument("scrollBar"); + boolean allowFileURLs = call.argument("allowFileURLs"); + boolean useWideViewPort = call.argument("useWideViewPort"); + String invalidUrlRegex = call.argument("invalidUrlRegex"); + boolean geolocationEnabled = call.argument("geolocationEnabled"); + boolean debuggingEnabled = call.argument("debuggingEnabled"); + boolean ignoreSSLErrors = call.argument("ignoreSSLErrors"); + + if (webViewManager == null || webViewManager.closed == true) { + Map arguments = (Map) call.arguments; + List channelNames = new ArrayList(); + if (arguments.containsKey(JS_CHANNEL_NAMES_FIELD)) { + channelNames = (List) arguments.get(JS_CHANNEL_NAMES_FIELD); + } + webViewManager = new WebviewManager(activity, context, channelNames); + } + + FrameLayout.LayoutParams params = buildLayoutParams(call); + + activity.addContentView(webViewManager.webView, params); + + webViewManager.openUrl(withJavascript, + clearCache, + hidden, + clearCookies, + mediaPlaybackRequiresUserGesture, + userAgent, + url, + headers, + withZoom, + displayZoomControls, + withLocalStorage, + withOverviewMode, + scrollBar, + supportMultipleWindows, + appCacheEnabled, + allowFileURLs, + useWideViewPort, + invalidUrlRegex, + geolocationEnabled, + debuggingEnabled, + ignoreSSLErrors + ); + result.success(null); + } + + private FrameLayout.LayoutParams buildLayoutParams(MethodCall call) { + Map rc = call.argument("rect"); + FrameLayout.LayoutParams params; + if (rc != null) { + params = new FrameLayout.LayoutParams( + dp2px(activity, rc.get("width").intValue()), dp2px(activity, rc.get("height").intValue())); + params.setMargins(dp2px(activity, rc.get("left").intValue()), dp2px(activity, rc.get("top").intValue()), + 0, 0); + } else { + Display display = activity.getWindowManager().getDefaultDisplay(); + Point size = new Point(); + display.getSize(size); + int width = size.x; + int height = size.y; + params = new FrameLayout.LayoutParams(width, height); + } + + return params; + } + + private void stopLoading(MethodCall call, MethodChannel.Result result) { + if (webViewManager != null) { + webViewManager.stopLoading(call, result); + } + result.success(null); + } + + void close(MethodCall call, MethodChannel.Result result) { + if (webViewManager != null) { + webViewManager.close(call, result); + webViewManager = null; + } + } + + /** + * Checks if can navigate back + * + * @param result + */ + private void canGoBack(MethodChannel.Result result) { + if (webViewManager != null) { + result.success(webViewManager.canGoBack()); + } else { + result.error("Webview is null", null, null); + } + } + + /** + * Navigates back on the Webview. + */ + private void back(MethodCall call, MethodChannel.Result result) { + if (webViewManager != null) { + webViewManager.back(call, result); + } + result.success(null); + } + + /** + * Checks if can navigate forward + * @param result + */ + private void canGoForward(MethodChannel.Result result) { + if (webViewManager != null) { + result.success(webViewManager.canGoForward()); + } else { + result.error("Webview is null", null, null); + } + } + + /** + * Navigates forward on the Webview. + */ + private void forward(MethodCall call, MethodChannel.Result result) { + if (webViewManager != null) { + webViewManager.forward(call, result); + } + result.success(null); + } + + /** + * Reloads the Webview. + */ + private void reload(MethodCall call, MethodChannel.Result result) { + if (webViewManager != null) { + webViewManager.reload(call, result); + } + result.success(null); + } + + private void reloadUrl(MethodCall call, MethodChannel.Result result) { + if (webViewManager != null) { + String url = call.argument("url"); + Map headers = call.argument("headers"); + if (headers != null) { + webViewManager.reloadUrl(url, headers); + } else { + webViewManager.reloadUrl(url); + } + + } + result.success(null); + } + + private void eval(MethodCall call, final MethodChannel.Result result) { + if (webViewManager != null) { + webViewManager.eval(call, result); + } + } + + private void resize(MethodCall call, final MethodChannel.Result result) { + if (webViewManager != null) { + FrameLayout.LayoutParams params = buildLayoutParams(call); + webViewManager.resize(params); + } + result.success(null); + } + + private void hide(MethodCall call, final MethodChannel.Result result) { + if (webViewManager != null) { + webViewManager.hide(call, result); + } + result.success(null); + } + + private void show(MethodCall call, final MethodChannel.Result result) { + if (webViewManager != null) { + webViewManager.show(call, result); + } + result.success(null); + } + + private void cleanCookies(MethodCall call, final MethodChannel.Result result) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + CookieManager.getInstance().removeAllCookies(new ValueCallback() { + @Override + public void onReceiveValue(Boolean aBoolean) { + + } + }); + } else { + CookieManager.getInstance().removeAllCookie(); + } + result.success(null); + } + + private int dp2px(Context context, float dp) { + final float scale = context.getResources().getDisplayMetrics().density; + return (int) (dp * scale + 0.5f); + } + + @Override + public boolean onActivityResult(int i, int i1, Intent intent) { + if (webViewManager != null && webViewManager.resultHandler != null) { + return webViewManager.resultHandler.handleResult(i, i1, intent); + } + return false; + } + + @Override + public void onAttachedToEngine(FlutterPluginBinding binding) { + channel = new MethodChannel(binding.getBinaryMessenger(), CHANNEL_NAME); + context = binding.getApplicationContext(); + + channel.setMethodCallHandler(this); + + //final FlutterWebviewPlugin instance = new FlutterWebviewPlugin(registrar.activity(), registrar.activeContext()); + //registrar.addActivityResultListener(instance); + + } + + @Override + public void onDetachedFromEngine(FlutterPluginBinding binding) { + } + + @Override + public void onAttachedToActivity(ActivityPluginBinding binding) { + activity = binding.getActivity(); + binding.addActivityResultListener(this); + } + + @Override + public void onDetachedFromActivityForConfigChanges() { + + } + + @Override + public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) { + + } + + @Override + public void onDetachedFromActivity() { + + } +} diff --git a/packages/flutter_webview_plugin/android/src/main/java/com/flutter_webview_plugin/JavaScriptChannel.java b/packages/flutter_webview_plugin/android/src/main/java/com/flutter_webview_plugin/JavaScriptChannel.java new file mode 100755 index 00000000..86c82a64 --- /dev/null +++ b/packages/flutter_webview_plugin/android/src/main/java/com/flutter_webview_plugin/JavaScriptChannel.java @@ -0,0 +1,60 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package com.flutter_webview_plugin; + +import android.os.Handler; +import android.os.Looper; +import android.webkit.JavascriptInterface; + +import java.util.HashMap; + +import io.flutter.plugin.common.MethodChannel; + +/** + * Added as a JavaScript interface to the WebView for any JavaScript channel that the Dart code sets + * up. + * + *

Exposes a single method named `postMessage` to JavaScript, which sends a message over a method + * channel to the Dart code. + */ +class JavaScriptChannel { + private final MethodChannel methodChannel; + private final String javaScriptChannelName; + private final Handler platformThreadHandler; + + /** + * @param methodChannel the Flutter WebView method channel to which JS messages are sent + * @param javaScriptChannelName the name of the JavaScript channel, this is sent over the method + * channel with each message to let the Dart code know which JavaScript channel the message + * was sent through + */ + JavaScriptChannel( + MethodChannel methodChannel, String javaScriptChannelName, Handler platformThreadHandler) { + this.methodChannel = methodChannel; + this.javaScriptChannelName = javaScriptChannelName; + this.platformThreadHandler = platformThreadHandler; + } + + // Suppressing unused warning as this is invoked from JavaScript. + @SuppressWarnings("unused") + @JavascriptInterface + public void postMessage(final String message) { + Runnable postMessageRunnable = + new Runnable() { + @Override + public void run() { + HashMap arguments = new HashMap<>(); + arguments.put("channel", javaScriptChannelName); + arguments.put("message", message); + methodChannel.invokeMethod("javascriptChannelMessage", arguments); + } + }; + if (platformThreadHandler.getLooper() == Looper.myLooper()) { + postMessageRunnable.run(); + } else { + platformThreadHandler.post(postMessageRunnable); + } + } +} diff --git a/packages/flutter_webview_plugin/android/src/main/java/com/flutter_webview_plugin/ObservableWebView.java b/packages/flutter_webview_plugin/android/src/main/java/com/flutter_webview_plugin/ObservableWebView.java new file mode 100644 index 00000000..2c02f61f --- /dev/null +++ b/packages/flutter_webview_plugin/android/src/main/java/com/flutter_webview_plugin/ObservableWebView.java @@ -0,0 +1,49 @@ +package com.flutter_webview_plugin; + +import android.content.Context; +import android.util.AttributeSet; +import android.webkit.WebView; + +public class ObservableWebView extends WebView { + private OnScrollChangedCallback mOnScrollChangedCallback; + + public ObservableWebView(final Context context) + { + super(context); + } + + public ObservableWebView(final Context context, final AttributeSet attrs) + { + super(context, attrs); + } + + public ObservableWebView(final Context context, final AttributeSet attrs, final int defStyle) + { + super(context, attrs, defStyle); + } + + @Override + protected void onScrollChanged(final int l, final int t, final int oldl, final int oldt) + { + super.onScrollChanged(l, t, oldl, oldt); + if(mOnScrollChangedCallback != null) mOnScrollChangedCallback.onScroll(l, t, oldl, oldt); + } + + public OnScrollChangedCallback getOnScrollChangedCallback() + { + return mOnScrollChangedCallback; + } + + public void setOnScrollChangedCallback(final OnScrollChangedCallback onScrollChangedCallback) + { + mOnScrollChangedCallback = onScrollChangedCallback; + } + + /** + * Impliment in the activity/fragment/view that you want to listen to the webview + */ + public static interface OnScrollChangedCallback + { + public void onScroll(int l, int t, int oldl, int oldt); + } +} \ No newline at end of file diff --git a/packages/flutter_webview_plugin/android/src/main/java/com/flutter_webview_plugin/WebviewManager.java b/packages/flutter_webview_plugin/android/src/main/java/com/flutter_webview_plugin/WebviewManager.java new file mode 100644 index 00000000..e19b2a08 --- /dev/null +++ b/packages/flutter_webview_plugin/android/src/main/java/com/flutter_webview_plugin/WebviewManager.java @@ -0,0 +1,557 @@ +package com.flutter_webview_plugin; + +import android.content.Intent; +import android.net.Uri; +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Context; +import android.net.http.SslError; +import android.os.Build; +import android.os.Handler; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.CookieManager; +import android.webkit.GeolocationPermissions; +import android.webkit.SslErrorHandler; +import android.webkit.ValueCallback; +import android.webkit.WebChromeClient; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.widget.FrameLayout; +import android.provider.MediaStore; + +import androidx.core.content.FileProvider; + +import android.database.Cursor; +import android.provider.OpenableColumns; + +import java.util.List; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.io.File; +import java.util.Date; +import java.io.IOException; +import java.text.SimpleDateFormat; + +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; + +import static android.app.Activity.RESULT_OK; + +/** + * Created by lejard_h on 20/12/2017. + */ + +class WebviewManager { + + private ValueCallback mUploadMessage; + private ValueCallback mUploadMessageArray; + private final static int FILECHOOSER_RESULTCODE = 1; + private Uri fileUri; + private Uri videoUri; + + private long getFileSize(Uri fileUri) { + Cursor returnCursor = context.getContentResolver().query(fileUri, null, null, null, null); + returnCursor.moveToFirst(); + int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE); + return returnCursor.getLong(sizeIndex); + } + + @TargetApi(7) + class ResultHandler { + public boolean handleResult(int requestCode, int resultCode, Intent intent) { + boolean handled = false; + if (Build.VERSION.SDK_INT >= 21) { + if (requestCode == FILECHOOSER_RESULTCODE) { + Uri[] results = null; + if (resultCode == Activity.RESULT_OK) { + if (fileUri != null && getFileSize(fileUri) > 0) { + results = new Uri[]{fileUri}; + } else if (videoUri != null && getFileSize(videoUri) > 0) { + results = new Uri[]{videoUri}; + } else if (intent != null) { + results = getSelectedFiles(intent); + } + } + if (mUploadMessageArray != null) { + mUploadMessageArray.onReceiveValue(results); + mUploadMessageArray = null; + } + handled = true; + } + } else { + if (requestCode == FILECHOOSER_RESULTCODE) { + Uri result = null; + if (resultCode == RESULT_OK && intent != null) { + result = intent.getData(); + } + if (mUploadMessage != null) { + mUploadMessage.onReceiveValue(result); + mUploadMessage = null; + } + handled = true; + } + } + return handled; + } + } + + private Uri[] getSelectedFiles(Intent data) { + // we have one files selected + if (data.getData() != null) { + String dataString = data.getDataString(); + if (dataString != null) { + return new Uri[]{Uri.parse(dataString)}; + } + } + // we have multiple files selected + if (data.getClipData() != null) { + final int numSelectedFiles = data.getClipData().getItemCount(); + Uri[] result = new Uri[numSelectedFiles]; + for (int i = 0; i < numSelectedFiles; i++) { + result[i] = data.getClipData().getItemAt(i).getUri(); + } + return result; + } + return null; + } + + private final Handler platformThreadHandler; + boolean closed = false; + WebView webView; + Activity activity; + BrowserClient webViewClient; + ResultHandler resultHandler; + Context context; + private boolean ignoreSSLErrors = false; + + WebviewManager(final Activity activity, final Context context, final List channelNames) { + this.webView = new ObservableWebView(activity); + this.activity = activity; + this.context = context; + this.resultHandler = new ResultHandler(); + this.platformThreadHandler = new Handler(context.getMainLooper()); + webViewClient = new BrowserClient() { + @Override + public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { + if (ignoreSSLErrors){ + handler.proceed(); + }else { + super.onReceivedSslError(view, handler, error); + } + } + }; + webView.setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + switch (keyCode) { + case KeyEvent.KEYCODE_BACK: + if (webView.canGoBack()) { + webView.goBack(); + } else { + FlutterWebviewPlugin.channel.invokeMethod("onBack", null); + } + return true; + } + } + + return false; + } + }); + + ((ObservableWebView) webView).setOnScrollChangedCallback(new ObservableWebView.OnScrollChangedCallback() { + public void onScroll(int x, int y, int oldx, int oldy) { + Map yDirection = new HashMap<>(); + yDirection.put("yDirection", (double) y); + FlutterWebviewPlugin.channel.invokeMethod("onScrollYChanged", yDirection); + Map xDirection = new HashMap<>(); + xDirection.put("xDirection", (double) x); + FlutterWebviewPlugin.channel.invokeMethod("onScrollXChanged", xDirection); + } + }); + + webView.setWebViewClient(webViewClient); + webView.setWebChromeClient(new WebChromeClient() { + //The undocumented magic method override + //Eclipse will swear at you if you try to put @Override here + // For Android 3.0+ + public void openFileChooser(ValueCallback uploadMsg) { + + mUploadMessage = uploadMsg; + Intent i = new Intent(Intent.ACTION_GET_CONTENT); + i.addCategory(Intent.CATEGORY_OPENABLE); + i.setType("image/*"); + activity.startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE); + + } + + // For Android 3.0+ + public void openFileChooser(ValueCallback uploadMsg, String acceptType) { + mUploadMessage = uploadMsg; + Intent i = new Intent(Intent.ACTION_GET_CONTENT); + i.addCategory(Intent.CATEGORY_OPENABLE); + i.setType("*/*"); + activity.startActivityForResult( + Intent.createChooser(i, "File Browser"), + FILECHOOSER_RESULTCODE); + } + + //For Android 4.1 + public void openFileChooser(ValueCallback uploadMsg, String acceptType, String capture) { + mUploadMessage = uploadMsg; + Intent i = new Intent(Intent.ACTION_GET_CONTENT); + i.addCategory(Intent.CATEGORY_OPENABLE); + i.setType("image/*"); + activity.startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE); + + } + + //For Android 5.0+ + public boolean onShowFileChooser( + WebView webView, ValueCallback filePathCallback, + FileChooserParams fileChooserParams) { + if (mUploadMessageArray != null) { + mUploadMessageArray.onReceiveValue(null); + } + mUploadMessageArray = filePathCallback; + + final String[] acceptTypes = getSafeAcceptedTypes(fileChooserParams); + List intentList = new ArrayList(); + fileUri = null; + videoUri = null; + if (acceptsImages(acceptTypes)) { + Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + fileUri = getOutputFilename(MediaStore.ACTION_IMAGE_CAPTURE); + takePhotoIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); + intentList.add(takePhotoIntent); + } + if (acceptsVideo(acceptTypes)) { + Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); + videoUri = getOutputFilename(MediaStore.ACTION_VIDEO_CAPTURE); + takeVideoIntent.putExtra(MediaStore.EXTRA_OUTPUT, videoUri); + intentList.add(takeVideoIntent); + } + Intent contentSelectionIntent; + if (Build.VERSION.SDK_INT >= 21) { + final boolean allowMultiple = fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE; + contentSelectionIntent = fileChooserParams.createIntent(); + contentSelectionIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultiple); + } else { + contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT); + contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE); + contentSelectionIntent.setType("*/*"); + } + Intent[] intentArray = intentList.toArray(new Intent[intentList.size()]); + + Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER); + chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent); + chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray); + activity.startActivityForResult(chooserIntent, FILECHOOSER_RESULTCODE); + return true; + } + + + @Override + public void onProgressChanged(WebView view, int progress) { + Map args = new HashMap<>(); + args.put("progress", progress / 100.0); + FlutterWebviewPlugin.channel.invokeMethod("onProgressChanged", args); + } + + public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) { + callback.invoke(origin, true, false); + } + }); + registerJavaScriptChannelNames(channelNames); + } + + private Uri getOutputFilename(String intentType) { + String prefix = ""; + String suffix = ""; + + if (intentType == MediaStore.ACTION_IMAGE_CAPTURE) { + prefix = "image-"; + suffix = ".jpg"; + } else if (intentType == MediaStore.ACTION_VIDEO_CAPTURE) { + prefix = "video-"; + suffix = ".mp4"; + } + + String packageName = context.getPackageName(); + File capturedFile = null; + try { + capturedFile = createCapturedFile(prefix, suffix); + } catch (IOException e) { + e.printStackTrace(); + } + return FileProvider.getUriForFile(context, packageName + ".fileprovider", capturedFile); + } + + private File createCapturedFile(String prefix, String suffix) throws IOException { + String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); + String imageFileName = prefix + "_" + timeStamp; + File storageDir = context.getExternalFilesDir(null); + return File.createTempFile(imageFileName, suffix, storageDir); + } + + private Boolean acceptsImages(String[] types) { + return isArrayEmpty(types) || arrayContainsString(types, "image"); + } + + private Boolean acceptsVideo(String[] types) { + return isArrayEmpty(types) || arrayContainsString(types, "video"); + } + + private Boolean arrayContainsString(String[] array, String pattern) { + for (String content : array) { + if (content.contains(pattern)) { + return true; + } + } + return false; + } + + private Boolean isArrayEmpty(String[] arr) { + // when our array returned from getAcceptTypes() has no values set from the + // webview + // i.e. , without any "accept" attr + // will be an array with one empty string element, afaik + return arr.length == 0 || (arr.length == 1 && arr[0].length() == 0); + } + + private String[] getSafeAcceptedTypes(WebChromeClient.FileChooserParams params) { + + // the getAcceptTypes() is available only in api 21+ + // for lower level, we ignore it + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + return params.getAcceptTypes(); + } + + final String[] EMPTY = {}; + return EMPTY; + } + + private void clearCookies() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + CookieManager.getInstance().removeAllCookies(new ValueCallback() { + @Override + public void onReceiveValue(Boolean aBoolean) { + + } + }); + } else { + CookieManager.getInstance().removeAllCookie(); + } + } + + private void clearCache() { + webView.clearCache(true); + webView.clearFormData(); + } + + private void registerJavaScriptChannelNames(List channelNames) { + for (String channelName : channelNames) { + webView.addJavascriptInterface( + new JavaScriptChannel(FlutterWebviewPlugin.channel, channelName, platformThreadHandler), channelName); + } + } + + void openUrl( + boolean withJavascript, + boolean clearCache, + boolean hidden, + boolean clearCookies, + boolean mediaPlaybackRequiresUserGesture, + String userAgent, + String url, + Map headers, + boolean withZoom, + boolean displayZoomControls, + boolean withLocalStorage, + boolean withOverviewMode, + boolean scrollBar, + boolean supportMultipleWindows, + boolean appCacheEnabled, + boolean allowFileURLs, + boolean useWideViewPort, + String invalidUrlRegex, + boolean geolocationEnabled, + boolean debuggingEnabled, + boolean ignoreSSLErrors + ) { + webView.getSettings().setJavaScriptEnabled(withJavascript); + webView.getSettings().setBuiltInZoomControls(withZoom); + webView.getSettings().setSupportZoom(withZoom); + webView.getSettings().setDisplayZoomControls(displayZoomControls); + webView.getSettings().setDomStorageEnabled(withLocalStorage); + webView.getSettings().setLoadWithOverviewMode(withOverviewMode); + webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(supportMultipleWindows); + + webView.getSettings().setSupportMultipleWindows(supportMultipleWindows); + + webView.getSettings().setAppCacheEnabled(appCacheEnabled); + + webView.getSettings().setAllowFileAccessFromFileURLs(allowFileURLs); + webView.getSettings().setAllowUniversalAccessFromFileURLs(allowFileURLs); + + webView.getSettings().setUseWideViewPort(useWideViewPort); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + webView.getSettings().setMediaPlaybackRequiresUserGesture(mediaPlaybackRequiresUserGesture); + } + + // Handle debugging + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + webView.setWebContentsDebuggingEnabled(debuggingEnabled); + } + //ignore SSL errors + this.ignoreSSLErrors = ignoreSSLErrors; + + webViewClient.updateInvalidUrlRegex(invalidUrlRegex); + + if (geolocationEnabled) { + webView.getSettings().setGeolocationEnabled(true); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE); + } + + if (clearCache) { + clearCache(); + } + + if (hidden) { + webView.setVisibility(View.GONE); + } + + if (clearCookies) { + clearCookies(); + } + + if (userAgent != null) { + webView.getSettings().setUserAgentString(userAgent); + } + + if (!scrollBar) { + webView.setVerticalScrollBarEnabled(false); + } + + if (headers != null) { + webView.loadUrl(url, headers); + } else { + webView.loadUrl(url); + } + } + + void reloadUrl(String url) { + webView.loadUrl(url); + } + + void reloadUrl(String url, Map headers) { + webView.loadUrl(url, headers); + } + + void close(MethodCall call, MethodChannel.Result result) { + if (webView != null) { + ViewGroup vg = (ViewGroup) (webView.getParent()); + vg.removeView(webView); + } + webView = null; + if (result != null) { + result.success(null); + } + + closed = true; + FlutterWebviewPlugin.channel.invokeMethod("onDestroy", null); + } + + void close() { + close(null, null); + } + + @TargetApi(Build.VERSION_CODES.KITKAT) + void eval(MethodCall call, final MethodChannel.Result result) { + String code = call.argument("code"); + + webView.evaluateJavascript(code, new ValueCallback() { + @Override + public void onReceiveValue(String value) { + result.success(value); + } + }); + } + + /** + * Reloads the Webview. + */ + void reload(MethodCall call, MethodChannel.Result result) { + if (webView != null) { + webView.reload(); + } + } + + /** + * Navigates back on the Webview. + */ + void back(MethodCall call, MethodChannel.Result result) { + if (webView != null && webView.canGoBack()) { + webView.goBack(); + } + } + + /** + * Navigates forward on the Webview. + */ + void forward(MethodCall call, MethodChannel.Result result) { + if (webView != null && webView.canGoForward()) { + webView.goForward(); + } + } + + void resize(FrameLayout.LayoutParams params) { + webView.setLayoutParams(params); + } + + /** + * Checks if going back on the Webview is possible. + */ + boolean canGoBack() { + return webView.canGoBack(); + } + + /** + * Checks if going forward on the Webview is possible. + */ + boolean canGoForward() { + return webView.canGoForward(); + } + + /** + * Clears cache + */ + void cleanCache(){ + webView.clearCache(true); + } + + void hide(MethodCall call, MethodChannel.Result result) { + if (webView != null) { + webView.setVisibility(View.GONE); + } + } + + void show(MethodCall call, MethodChannel.Result result) { + if (webView != null) { + webView.setVisibility(View.VISIBLE); + } + } + + void stopLoading(MethodCall call, MethodChannel.Result result) { + if (webView != null) { + webView.stopLoading(); + } + } +} diff --git a/packages/flutter_webview_plugin/android/src/main/res/xml/filepaths.xml b/packages/flutter_webview_plugin/android/src/main/res/xml/filepaths.xml new file mode 100644 index 00000000..43f25539 --- /dev/null +++ b/packages/flutter_webview_plugin/android/src/main/res/xml/filepaths.xml @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/packages/flutter_webview_plugin/android/src/test/java/com/flutter_webview_plugin/FlutterWebviewPluginTest.java b/packages/flutter_webview_plugin/android/src/test/java/com/flutter_webview_plugin/FlutterWebviewPluginTest.java new file mode 100644 index 00000000..8feb0584 --- /dev/null +++ b/packages/flutter_webview_plugin/android/src/test/java/com/flutter_webview_plugin/FlutterWebviewPluginTest.java @@ -0,0 +1,41 @@ +package com.flutter_webview_plugin; + +import android.app.Activity; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; + +import io.flutter.plugin.common.ErrorLogResult; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; + +import static org.mockito.Mockito.verify; + +public class FlutterWebviewPluginTest { + + @Mock + Activity mockActivity; + + MethodCall mockMethodCall; + MethodChannel.Result mockResult; + + @Spy + FlutterWebviewPlugin flutterWebviewPlugin = new FlutterWebviewPlugin(mockActivity, mockActivity); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void shouldInvokeClose() { + mockMethodCall = new MethodCall("close", null); + mockResult = new ErrorLogResult(""); + flutterWebviewPlugin.onMethodCall(mockMethodCall, mockResult); + verify(flutterWebviewPlugin).close(mockMethodCall, mockResult); + + } +} \ No newline at end of file diff --git a/packages/flutter_webview_plugin/android/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/packages/flutter_webview_plugin/android/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 00000000..ca6ee9ce --- /dev/null +++ b/packages/flutter_webview_plugin/android/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline \ No newline at end of file diff --git a/packages/flutter_webview_plugin/android_test.sh b/packages/flutter_webview_plugin/android_test.sh new file mode 100755 index 00000000..89c75823 --- /dev/null +++ b/packages/flutter_webview_plugin/android_test.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd example/android +./gradlew test -DflutterPath=$FLUTTER_HOME diff --git a/packages/flutter_webview_plugin/example/.gitignore b/packages/flutter_webview_plugin/example/.gitignore new file mode 100644 index 00000000..eb15c3d2 --- /dev/null +++ b/packages/flutter_webview_plugin/example/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +.atom/ +.idea +.packages +.pub/ +build/ +ios/.generated/ +packages +pubspec.lock +.flutter-plugins diff --git a/packages/flutter_webview_plugin/example/README.md b/packages/flutter_webview_plugin/example/README.md new file mode 100644 index 00000000..05659a04 --- /dev/null +++ b/packages/flutter_webview_plugin/example/README.md @@ -0,0 +1,8 @@ +# flutter_webview_plugin_example + +Demonstrates how to use the flutter_webview_plugin plugin. + +## Getting Started + +For help getting started with Flutter, view our online +[documentation](http://flutter.io/). diff --git a/packages/flutter_webview_plugin/example/android/.gitignore b/packages/flutter_webview_plugin/example/android/.gitignore new file mode 100644 index 00000000..b985fd38 --- /dev/null +++ b/packages/flutter_webview_plugin/example/android/.gitignore @@ -0,0 +1,12 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +PluginRegistry.java + +.project +.settings diff --git a/packages/flutter_webview_plugin/example/android/app/build.gradle b/packages/flutter_webview_plugin/example/android/app/build.gradle new file mode 100644 index 00000000..8e1b54c1 --- /dev/null +++ b/packages/flutter_webview_plugin/example/android/app/build.gradle @@ -0,0 +1,50 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withInputStream { stream -> + localProperties.load(stream) + } +} + +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.") +} + +apply plugin: 'com.android.application' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 28 + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.yourcompany.flutter_webview_plugin_example" + minSdkVersion 16 + } + + 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.annotation:annotation:1.1.0' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test:rules:1.2.0' +} diff --git a/packages/flutter_webview_plugin/example/android/app/src/main/AndroidManifest.xml b/packages/flutter_webview_plugin/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..981cfe8c --- /dev/null +++ b/packages/flutter_webview_plugin/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + diff --git a/packages/flutter_webview_plugin/example/android/app/src/main/java/com/yourcompany/flutter_webview_plugin_example/MainActivity.java b/packages/flutter_webview_plugin/example/android/app/src/main/java/com/yourcompany/flutter_webview_plugin_example/MainActivity.java new file mode 100644 index 00000000..5d63d22f --- /dev/null +++ b/packages/flutter_webview_plugin/example/android/app/src/main/java/com/yourcompany/flutter_webview_plugin_example/MainActivity.java @@ -0,0 +1,14 @@ +package com.yourcompany.flutter_webview_plugin_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); + } +} \ No newline at end of file diff --git a/packages/flutter_webview_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/flutter_webview_plugin/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/packages/flutter_webview_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/flutter_webview_plugin/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/packages/flutter_webview_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/flutter_webview_plugin/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/packages/flutter_webview_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/flutter_webview_plugin/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/packages/flutter_webview_plugin/example/android/build.gradle b/packages/flutter_webview_plugin/example/android/build.gradle new file mode 100644 index 00000000..b4e2316a --- /dev/null +++ b/packages/flutter_webview_plugin/example/android/build.gradle @@ -0,0 +1,38 @@ +buildscript { + repositories { + google() + jcenter() + maven { + url 'https://maven.google.com/' + } + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.3' + } +} + +allprojects { + repositories { + google() + jcenter() + maven { + url 'https://maven.google.com/' + } + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} + +wrapper { + gradleVersion = '4.4' + distributionUrl = distributionUrl.replace("bin", "all") +} diff --git a/packages/flutter_webview_plugin/example/android/gradle.properties b/packages/flutter_webview_plugin/example/android/gradle.properties new file mode 100644 index 00000000..a6738207 --- /dev/null +++ b/packages/flutter_webview_plugin/example/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true +android.enableR8=true diff --git a/packages/flutter_webview_plugin/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/flutter_webview_plugin/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..296b146b --- /dev/null +++ b/packages/flutter_webview_plugin/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/packages/flutter_webview_plugin/example/android/settings.gradle b/packages/flutter_webview_plugin/example/android/settings.gradle new file mode 100644 index 00000000..115da6cb --- /dev/null +++ b/packages/flutter_webview_plugin/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.withInputStream { stream -> plugins.load(stream) } +} + +plugins.each { name, path -> + def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() + include ":$name" + project(":$name").projectDir = pluginDirectory +} diff --git a/packages/flutter_webview_plugin/example/ios/.gitignore b/packages/flutter_webview_plugin/example/ios/.gitignore new file mode 100644 index 00000000..5ec8cc02 --- /dev/null +++ b/packages/flutter_webview_plugin/example/ios/.gitignore @@ -0,0 +1,41 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +PluginRegistry.h +PluginRegistry.m + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/app.flx +/Flutter/app.zip +/Flutter/App.framework +/Flutter/Flutter.framework +/Flutter/Generated.xcconfig +/ServiceDefinitions.json + +Pods/ diff --git a/packages/flutter_webview_plugin/example/ios/Flutter/AppFrameworkInfo.plist b/packages/flutter_webview_plugin/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000..6c2de808 --- /dev/null +++ b/packages/flutter_webview_plugin/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + UIRequiredDeviceCapabilities + + arm64 + + MinimumOSVersion + 8.0 + + diff --git a/packages/flutter_webview_plugin/example/ios/Flutter/Debug.xcconfig b/packages/flutter_webview_plugin/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000..9803018c --- /dev/null +++ b/packages/flutter_webview_plugin/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "Generated.xcconfig" +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" diff --git a/packages/flutter_webview_plugin/example/ios/Flutter/Release.xcconfig b/packages/flutter_webview_plugin/example/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000..a4a8c604 --- /dev/null +++ b/packages/flutter_webview_plugin/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include "Generated.xcconfig" +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" diff --git a/packages/flutter_webview_plugin/example/ios/Runner.xcodeproj/project.pbxproj b/packages/flutter_webview_plugin/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..185abd6b --- /dev/null +++ b/packages/flutter_webview_plugin/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,482 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 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, ); }; }; + 945777F11EF64758001C8557 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 945777F01EF64758001C8557 /* GeneratedPluginRegistrant.m */; }; + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; + 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; + 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; }; + 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 */; }; + C1AB67DD965258C508BF745D /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A3CDD044DB4E60255722586 /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 24E8D05517AF08B6C698879C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 2E218F99AE7B8D5BDA44909D /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/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 = ""; }; + 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 = ""; }; + 945777EF1EF64758001C8557 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 945777F01EF64758001C8557 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.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 = ""; }; + 9A3CDD044DB4E60255722586 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; 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 */, + C1AB67DD965258C508BF745D /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 840012C8B5EDBCF56B0E4AC1 /* Pods */ = { + isa = PBXGroup; + children = ( + 24E8D05517AF08B6C698879C /* Pods-Runner.debug.xcconfig */, + 2E218F99AE7B8D5BDA44909D /* Pods-Runner.release.xcconfig */, + ); + name = Pods; + 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 */, + 840012C8B5EDBCF56B0E4AC1 /* Pods */, + CF3B75C9A7D2FA2A4C99F110 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 945777EF1EF64758001C8557 /* GeneratedPluginRegistrant.h */, + 945777F01EF64758001C8557 /* GeneratedPluginRegistrant.m */, + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, + ); + path = Runner; + sourceTree = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 97C146F21CF9000F007C117D /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + CF3B75C9A7D2FA2A4C99F110 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 9A3CDD044DB4E60255722586 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 95BB15E9E1769C0D146AA592 /* [CP] Embed Pods 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 = 0830; + ORGANIZATIONNAME = "The Chromium Authors"; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + English, + 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 */, + 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 9740EEB41CF90195004384FC /* Debug.xcconfig 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"; + }; + 95BB15E9E1769C0D146AA592 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", + "${PODS_ROOT}/../../../../../flutter/bin/cache/artifacts/engine/ios/Flutter.framework", + "${BUILT_PRODUCTS_DIR}/flutter_webview_plugin/flutter_webview_plugin.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_webview_plugin.framework", + ); + 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"; + }; + AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + 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 */, + 945777F11EF64758001C8557 /* 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 */ + 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_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_OBJC_ROOT_CLASS = YES_ERROR; + 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_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = 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_OBJC_ROOT_CLASS = YES_ERROR; + 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; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ARCHS = arm64; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + 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.yourcompany.flutterWebviewPluginExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ARCHS = arm64; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + 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.yourcompany.flutterWebviewPluginExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/flutter_webview_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/flutter_webview_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..21a3cc14 --- /dev/null +++ b/packages/flutter_webview_plugin/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/flutter_webview_plugin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/flutter_webview_plugin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..adb11631 --- /dev/null +++ b/packages/flutter_webview_plugin/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/flutter_webview_plugin/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/flutter_webview_plugin/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..21a3cc14 --- /dev/null +++ b/packages/flutter_webview_plugin/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/flutter_webview_plugin/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/flutter_webview_plugin/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/flutter_webview_plugin/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/flutter_webview_plugin/example/ios/Runner.xcworkspace/xcshareddata/Runner.xcscmblueprint b/packages/flutter_webview_plugin/example/ios/Runner.xcworkspace/xcshareddata/Runner.xcscmblueprint new file mode 100644 index 00000000..b0af1238 --- /dev/null +++ b/packages/flutter_webview_plugin/example/ios/Runner.xcworkspace/xcshareddata/Runner.xcscmblueprint @@ -0,0 +1,30 @@ +{ + "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "089954AE9FE84C565F8189CE7B1E08C8949FC40C", + "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { + + }, + "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { + "B9BC852104FA5F3F91D8B12EB80D37232CB3B40D" : 9223372036854775807, + "089954AE9FE84C565F8189CE7B1E08C8949FC40C" : 9223372036854775807 + }, + "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "9839F02E-853D-4357-B537-73CD5272EC87", + "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { + "B9BC852104FA5F3F91D8B12EB80D37232CB3B40D" : "..\/..\/flutter", + "089954AE9FE84C565F8189CE7B1E08C8949FC40C" : "flutter_webview_plugin\/" + }, + "DVTSourceControlWorkspaceBlueprintNameKey" : "Runner", + "DVTSourceControlWorkspaceBlueprintVersion" : 204, + "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "example\/ios\/Runner.xcworkspace", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ + { + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/dart-flitter\/flutter_webview_plugin.git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "089954AE9FE84C565F8189CE7B1E08C8949FC40C" + }, + { + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/flutter\/flutter.git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "B9BC852104FA5F3F91D8B12EB80D37232CB3B40D" + } + ] +} \ No newline at end of file diff --git a/packages/flutter_webview_plugin/example/ios/Runner/AppDelegate.h b/packages/flutter_webview_plugin/example/ios/Runner/AppDelegate.h new file mode 100644 index 00000000..cf210d21 --- /dev/null +++ b/packages/flutter_webview_plugin/example/ios/Runner/AppDelegate.h @@ -0,0 +1,6 @@ +#import +#import + +@interface AppDelegate : FlutterAppDelegate + +@end diff --git a/packages/flutter_webview_plugin/example/ios/Runner/AppDelegate.m b/packages/flutter_webview_plugin/example/ios/Runner/AppDelegate.m new file mode 100644 index 00000000..187f6926 --- /dev/null +++ b/packages/flutter_webview_plugin/example/ios/Runner/AppDelegate.m @@ -0,0 +1,37 @@ +#include "AppDelegate.h" +#include "GeneratedPluginRegistrant.h" + +@implementation AppDelegate { + GeneratedPluginRegistrant *plugins; +} + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [GeneratedPluginRegistrant registerWithRegistry:self]; + // Override point for customization after application launch. + return [super application:application didFinishLaunchingWithOptions:launchOptions]; + return YES; +} + +- (void)applicationWillResignActive:(UIApplication *)application { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. +} + +- (void)applicationDidEnterBackground:(UIApplication *)application { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. +} + +- (void)applicationWillEnterForeground:(UIApplication *)application { + // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. +} + +- (void)applicationDidBecomeActive:(UIApplication *)application { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. +} + +- (void)applicationWillTerminate:(UIApplication *)application { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. +} + +@end diff --git a/packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d22f10b2 --- /dev/null +++ b/packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "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" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..28c6bf03016f6c994b70f38d1b7346e5831b531f GIT binary patch literal 564 zcmV-40?Yl0P)Px$?ny*JR5%f>l)FnDQ543{x%ZCiu33$Wg!pQFfT_}?5Q|_VSlIbLC`dpoMXL}9 zHfd9&47Mo(7D231gb+kjFxZHS4-m~7WurTH&doVX2KI5sU4v(sJ1@T9eCIKPjsqSr z)C01LsCxk=72-vXmX}CQD#BD;Cthymh&~=f$Q8nn0J<}ZrusBy4PvRNE}+1ceuj8u z0mW5k8fmgeLnTbWHGwfKA3@PdZxhn|PypR&^p?weGftrtCbjF#+zk_5BJh7;0`#Wr zgDpM_;Ax{jO##IrT`Oz;MvfwGfV$zD#c2xckpcXC6oou4ML~ezCc2EtnsQTB4tWNg z?4bkf;hG7IMfhgNI(FV5Gs4|*GyMTIY0$B=_*mso9Ityq$m^S>15>-?0(zQ<8Qy<_TjHE33(?_M8oaM zyc;NxzRVK@DL6RJnX%U^xW0Gpg(lXp(!uK1v0YgHjs^ZXSQ|m#lV7ip7{`C_J2TxPmfw%h$|%acrYHt)Re^PB%O&&=~a zhS(%I#+V>J-vjIib^<+s%ludY7y^C(P8nmqn9fp!i+?vr`bziDE=bx`%2W#Xyrj|i z!XQ4v1%L`m{7KT7q+LZNB^h8Ha2e=`Wp65^0;J00)_^G=au=8Yo;1b`CV&@#=jIBo zjN^JNVfYSs)+kDdGe7`1&8!?MQYKS?DuHZf3iogk_%#9E|5S zWeHrmAo>P;ejX7mwq#*}W25m^ZI+{(Z8fI?4jM_fffY0nok=+88^|*_DwcW>mR#e+ zX$F_KMdb6sRz!~7KkyN0G(3XQ+;z3X%PZ4gh;n-%62U<*VUKNv(D&Q->Na@Xb&u5Q3`3DGf+a8O5x7c#7+R+EAYl@R5us)CIw z7sT@_y~Ao@uL#&^LIh&QceqiT^+lb0YbFZt_SHOtWA%mgPEKVNvVgCsXy{5+zl*X8 zCJe)Q@y>wH^>l4;h1l^Y*9%-23TSmE>q5nI@?mt%n;Sj4Qq`Z+ib)a*a^cJc%E9^J zB;4s+K@rARbcBLT5P=@r;IVnBMKvT*)ew*R;&8vu%?Z&S>s?8?)3*YawM0P4!q$Kv zMmKh3lgE~&w&v%wVzH3Oe=jeNT=n@Y6J6TdHWTjXfX~-=1A1Bw`EW8rn}MqeI34nh zexFeA?&C3B2(E?0{drE@DA2pu(A#ElY&6el60Rn|Qpn-FkfQ8M93AfWIr)drgDFEU zghdWK)^71EWCP(@(=c4kfH1Y(4iugD4fve6;nSUpLT%!)MUHs1!zJYy4y||C+SwQ! z)KM&$7_tyM`sljP2fz6&Z;jxRn{Wup8IOUx8D4uh&(=O zx-7$a;U><*5L^!%xRlw)vAbh;sdlR||& ze}8_8%)c2Fwy=F&H|LM+p{pZB5DKTx>Y?F1N%BlZkXf!}JeGuMZk~LPi7{cidvUGB zAJ4LVeNV%XO>LTrklB#^-;8nb;}6l;1oW&WS=Mz*Az!4cqqQzbOSFq`$Q%PfD7srM zpKgP-D_0XPTRX*hAqeq0TDkJ;5HB1%$3Np)99#16c{ zJImlNL(npL!W|Gr_kxl1GVmF5&^$^YherS7+~q$p zt}{a=*RiD2Ikv6o=IM1kgc7zqpaZ;OB)P!1zz*i3{U()Dq#jG)egvK}@uFLa`oyWZ zf~=MV)|yJn`M^$N%ul5);JuQvaU1r2wt(}J_Qgyy`qWQI`hEeRX0uC@c1(dQ2}=U$ tNIIaX+dr)NRWXcxoR{>fqI{SF_dm1Ylv~=3YHI)h002ovPDHLkV1g(pWS;;4 literal 0 HcmV?d00001 diff --git a/packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f091b6b0bca859a3f474b03065bef75ba58a9e4c GIT binary patch literal 1588 zcmV-42Fv-0P)C1SqPt}wig>|5Crh^=oyX$BK<}M8eLU3e2hGT;=G|!_SP)7zNI6fqUMB=)y zRAZ>eDe#*r`yDAVgB_R*LB*MAc)8(b{g{9McCXW!lq7r(btRoB9!8B-#AI6JMb~YFBEvdsV)`mEQO^&#eRKx@b&x- z5lZm*!WfD8oCLzfHGz#u7sT0^VLMI1MqGxF^v+`4YYnVYgk*=kU?HsSz{v({E3lb9 z>+xILjBN)t6`=g~IBOelGQ(O990@BfXf(DRI5I$qN$0Gkz-FSc$3a+2fX$AedL4u{ z4V+5Ong(9LiGcIKW?_352sR;LtDPmPJXI{YtT=O8=76o9;*n%_m|xo!i>7$IrZ-{l z-x3`7M}qzHsPV@$v#>H-TpjDh2UE$9g6sysUREDy_R(a)>=eHw-WAyfIN z*qb!_hW>G)Tu8nSw9yn#3wFMiLcfc4pY0ek1}8(NqkBR@t4{~oC>ryc-h_ByH(Cg5 z>ao-}771+xE3um9lWAY1FeQFxowa1(!J(;Jg*wrg!=6FdRX+t_<%z&d&?|Bn){>zm zZQj(aA_HeBY&OC^jj*)N`8fa^ePOU72VpInJoI1?`ty#lvlNzs(&MZX+R%2xS~5Kh zX*|AU4QE#~SgPzOXe9>tRj>hjU@c1k5Y_mW*Jp3fI;)1&g3j|zDgC+}2Q_v%YfDax z!?umcN^n}KYQ|a$Lr+51Nf9dkkYFSjZZjkma$0KOj+;aQ&721~t7QUKx61J3(P4P1 zstI~7-wOACnWP4=8oGOwz%vNDqD8w&Q`qcNGGrbbf&0s9L0De{4{mRS?o0MU+nR_! zrvshUau0G^DeMhM_v{5BuLjb#Hh@r23lDAk8oF(C+P0rsBpv85EP>4CVMx#04MOfG z;P%vktHcXwTj~+IE(~px)3*MY77e}p#|c>TD?sMatC0Tu4iKKJ0(X8jxQY*gYtxsC z(zYC$g|@+I+kY;dg_dE>scBf&bP1Nc@Hz<3R)V`=AGkc;8CXqdi=B4l2k|g;2%#m& z*jfX^%b!A8#bI!j9-0Fi0bOXl(-c^AB9|nQaE`*)Hw+o&jS9@7&Gov#HbD~#d{twV zXd^Tr^mWLfFh$@Dr$e;PBEz4(-2q1FF0}c;~B5sA}+Q>TOoP+t>wf)V9Iy=5ruQa;z)y zI9C9*oUga6=hxw6QasLPnee@3^Rr*M{CdaL5=R41nLs(AHk_=Y+A9$2&H(B7!_pURs&8aNw7?`&Z&xY_Ye z)~D5Bog^td-^QbUtkTirdyK^mTHAOuptDflut!#^lnKqU md>ggs(5nOWAqO?umG&QVYK#ibz}*4>0000U6E9hRK9^#O7(mu>ETqrXGsduA8$)?`v2seloOCza43C{NQ$$gAOH**MCn0Q?+L7dl7qnbRdqZ8LSVp1ItDxhxD?t@5_yHg6A8yI zC*%Wgg22K|8E#!~cTNYR~@Y9KepMPrrB8cABapAFa=`H+UGhkXUZV1GnwR1*lPyZ;*K(i~2gp|@bzp8}og7e*#% zEnr|^CWdVV!-4*Y_7rFvlww2Ze+>j*!Z!pQ?2l->4q#nqRu9`ELo6RMS5=br47g_X zRw}P9a7RRYQ%2Vsd0Me{_(EggTnuN6j=-?uFS6j^u69elMypu?t>op*wBx<=Wx8?( ztpe^(fwM6jJX7M-l*k3kEpWOl_Vk3@(_w4oc}4YF4|Rt=2V^XU?#Yz`8(e?aZ@#li0n*=g^qOcVpd-Wbok=@b#Yw zqn8u9a)z>l(1kEaPYZ6hwubN6i<8QHgsu0oE) ziJ(p;Wxm>sf!K+cw>R-(^Y2_bahB+&KI9y^);#0qt}t-$C|Bo71lHi{_+lg#f%RFy z0um=e3$K3i6K{U_4K!EX?F&rExl^W|G8Z8;`5z-k}OGNZ0#WVb$WCpQu-_YsiqKP?BB# vzVHS-CTUF4Ozn5G+mq_~Qqto~ahA+K`|lyv3(-e}00000NkvXXu0mjfd`9t{ literal 0 HcmV?d00001 diff --git a/packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d0ef06e7edb86cdfe0d15b4b0d98334a86163658 GIT binary patch literal 1716 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 literal 0 HcmV?d00001 diff --git a/packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c8f9ed8f5cee1c98386d13b17e89f719e83555b2 GIT binary patch literal 1895 zcmV-t2blPYP)FQtfgmafE#=YDCq`qUBt#QpG%*H6QHY765~R=q zZ6iudfM}q!Pz#~9JgOi8QJ|DSu?1-*(kSi1K4#~5?#|rh?sS)(-JQqX*}ciXJ56_H zdw=^s_srbAdqxlvGyrgGet#6T7_|j;95sL%MtM;q86vOxKM$f#puR)Bjv9Zvz9-di zXOTSsZkM83)E9PYBXC<$6(|>lNLVBb&&6y{NByFCp%6+^ALR@NCTse_wqvNmSWI-m z!$%KlHFH2omF!>#%1l3LTZg(s7eof$7*xB)ZQ0h?ejh?Ta9fDv59+u#MokW+1t8Zb zgHv%K(u9G^Lv`lh#f3<6!JVTL3(dCpxHbnbA;kKqQyd1~^Xe0VIaYBSWm6nsr;dFj z4;G-RyL?cYgsN1{L4ZFFNa;8)Rv0fM0C(~Tkit94 zz#~A)59?QjD&pAPSEQ)p8gP|DS{ng)j=2ux)_EzzJ773GmQ_Cic%3JJhC0t2cx>|v zJcVusIB!%F90{+}8hG3QU4KNeKmK%T>mN57NnCZ^56=0?&3@!j>a>B43pi{!u z7JyDj7`6d)qVp^R=%j>UIY6f+3`+qzIc!Y_=+uN^3BYV|o+$vGo-j-Wm<10%A=(Yk^beI{t%ld@yhKjq0iNjqN4XMGgQtbKubPM$JWBz}YA65k%dm*awtC^+f;a-x4+ddbH^7iDWGg&N0n#MW{kA|=8iMUiFYvMoDY@sPC#t$55gn6ykUTPAr`a@!(;np824>2xJthS z*ZdmT`g5-`BuJs`0LVhz+D9NNa3<=6m;cQLaF?tCv8)zcRSh66*Z|vXhG@$I%U~2l z?`Q zykI#*+rQ=z6Jm=Bui-SfpDYLA=|vzGE(dYm=OC8XM&MDo7ux4UF1~0J1+i%aCUpRe zt3L_uNyQ*cE(38Uy03H%I*)*Bh=Lb^Xj3?I^Hnbeq72(EOK^Y93CNp*uAA{5Lc=ky zx=~RKa4{iTm{_>_vSCm?$Ej=i6@=m%@VvAITnigVg{&@!7CDgs908761meDK5azA} z4?=NOH|PdvabgJ&fW2{Mo$Q0CcD8Qc84%{JPYt5EiG{MdLIAeX%T=D7NIP4%Hw}p9 zg)==!2Lbp#j{u_}hMiao9=!VSyx0gHbeCS`;q&vzeq|fs`y&^X-lso(Ls@-706qmA z7u*T5PMo_w3{se1t2`zWeO^hOvTsohG_;>J0wVqVe+n)AbQCx)yh9;w+J6?NF5Lmo zecS@ieAKL8%bVd@+-KT{yI|S}O>pYckUFs;ry9Ow$CD@ztz5K-*D$^{i(_1llhSh^ zEkL$}tsQt5>QA^;QgjgIfBDmcOgi5YDyu?t6vSnbp=1+@6D& z5MJ}B8q;bRlVoxasyhcUF1+)o`&3r0colr}QJ3hcSdLu;9;td>kf@Tcn<@9sIx&=m z;AD;SCh95=&p;$r{Xz3iWCO^MX83AGJ(yH&eTXgv|0=34#-&WAmw{)U7OU9!Wz^!7 zZ%jZFi@JR;>Mhi7S>V7wQ176|FdW2m?&`qa(ScO^CFPR80HucLHOTy%5s*HR0^8)i h0WYBP*#0Ks^FNSabJA*5${_#%002ovPDHLkV1oKhTl@e3 literal 0 HcmV?d00001 diff --git a/packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 diff --git a/packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 diff --git a/packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..75b2d164a5a98e212cca15ea7bf2ab5de5108680 GIT binary patch literal 3831 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 literal 0 HcmV?d00001 diff --git a/packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/flutter_webview_plugin/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..c4df70d39da7941ef3f6dcb7f06a192d8dcb308d GIT binary patch literal 1888 zcmV-m2cP(fP)x~L`~4d)Rspd&<9kFh{hn*KP1LP0~$;u(LfAu zp%fx&qLBcRHx$G|3q(bv@+b;o0*D|jwD-Q9uQR(l*ST}s+uPgQ-MeFwZ#GS?b332? z&Tk$&_miXn3IGq)AmQ)3sisq{raD4(k*bHvpCe-TdWq^NRTEVM)i9xbgQ&ccnUVx* zEY%vS%gDcSg=!tuIK8$Th2_((_h^+7;R|G{n06&O2#6%LK`a}n?h_fL18btz<@lFG za}xS}u?#DBMB> zw^b($1Z)`9G?eP95EKi&$eOy@K%h;ryrR3la%;>|o*>CgB(s>dDcNOXg}CK9SPmD? zmr-s{0wRmxUnbDrYfRvnZ@d z6johZ2sMX{YkGSKWd}m|@V7`Degt-43=2M?+jR%8{(H$&MLLmS;-|JxnX2pnz;el1jsvqQz}pGSF<`mqEXRQ5sC4#BbwnB_4` zc5bFE-Gb#JV3tox9fp-vVEN{(tOCpRse`S+@)?%pz+zVJXSooTrNCUg`R6`hxwb{) zC@{O6MKY8tfZ5@!yy=p5Y|#+myRL=^{tc(6YgAnkg3I(Cd!r5l;|;l-MQ8B`;*SCE z{u)uP^C$lOPM z5d~UhKhRRmvv{LIa^|oavk1$QiEApSrP@~Jjbg`<*dW4TO?4qG%a%sTPUFz(QtW5( zM)lA+5)0TvH~aBaOAs|}?u2FO;yc-CZ1gNM1dAxJ?%m?YsGR`}-xk2*dxC}r5j$d* zE!#Vtbo69h>V4V`BL%_&$} z+oJAo@jQ^Tk`;%xw-4G>hhb&)B?##U+(6Fi7nno`C<|#PVA%$Y{}N-?(Gc$1%tr4Pc}}hm~yY#fTOe!@v9s-ik$dX~|ygArPhByaXn8 zpI^FUjNWMsTFKTP3X7m?UK)3m zp6rI^_zxRYrx6_QmhoWoDR`fp4R7gu6;gdO)!KexaoO2D88F9x#TM1(9Bn7g;|?|o z)~$n&Lh#hCP6_LOPD>a)NmhW})LADx2kq=X7}7wYRj-0?dXr&bHaRWCfSqvzFa=sn z-8^gSyn-RmH=BZ{AJZ~!8n5621GbUJV7Qvs%JNv&$%Q17s_X%s-41vAPfIR>;x0Wlqr5?09S>x#%Qkt>?(&XjFRY}*L6BeQ3 z<6XEBh^S7>AbwGm@XP{RkeEKj6@_o%oV?hDuUpUJ+r#JZO?!IUc;r0R?>mi)*ZpQ) z#((dn=A#i_&EQn|hd)N$#A*fjBFuiHcYvo?@y1 z5|fV=a^a~d!c-%ZbMNqkMKiSzM{Yq=7_c&1H!mXk60Uv32dV;vMg&-kQ)Q{+PFtwc zj|-uQ;b^gts??J*9VxxOro}W~Q9j4Em|zSRv)(WSO9$F$s=Ydu%Q+5DOid~lwk&we zY%W(Z@ofdwPHncEZzZgmqS|!gTj3wQq9rxQy+^eNYKr1mj&?tm@wkO*9@UtnRMG>c aR{jt9+;fr}hV%pg00001^@s67{VYS000c7NklQEG_j zup^)eW&WUIApqy$=APz8jE@awGp)!bsTjDbrJO`$x^ZR^dr;>)LW>{ zs70vpsD38v)19rI=GNk1b(0?Js9~rjsQsu*K;@SD40RB-3^gKU-MYC7G!Bw{fZsqp zih4iIi;Hr_xZ033Iu{sQxLS=}yBXgLMn40d++>aQ0#%8D1EbGZp7+ z5=mK?t31BkVYbGOxE9`i748x`YgCMwL$qMsChbSGSE1`p{nSmadR zcQ#R)(?!~dmtD0+D2!K zR9%!Xp1oOJzm(vbLvT^$IKp@+W2=-}qTzTgVtQ!#Y7Gxz}stUIm<1;oBQ^Sh2X{F4ibaOOx;5ZGSNK z0maF^@(UtV$=p6DXLgRURwF95C=|U8?osGhgOED*b z7woJ_PWXBD>V-NjQAm{~T%sjyJ{5tn2f{G%?J!KRSrrGvQ1(^`YLA5B!~eycY(e5_ z*%aa{at13SxC(=7JT7$IQF~R3sy`Nn%EMv!$-8ZEAryB*yB1k&stni)=)8-ODo41g zkJu~roIgAih94tb=YsL%iH5@^b~kU9M-=aqgXIrbtxMpFy5mekFm#edF9z7RQ6V}R zBIhbXs~pMzt0VWy1Fi$^fh+1xxLDoK09&5&MJl(q#THjPm(0=z2H2Yfm^a&E)V+a5 zbi>08u;bJsDRUKR9(INSc7XyuWv(JsD+BB*0hS)FO&l&7MdViuur@-<-EHw>kHRGY zqoT}3fDv2-m{NhBG8X}+rgOEZ;amh*DqN?jEfQdqxdj08`Sr=C-KmT)qU1 z+9Cl)a1mgXxhQiHVB}l`m;-RpmKy?0*|yl?FXvJkFxuu!fKlcmz$kN(a}i*saM3nr z0!;a~_%Xqy24IxA2rz<+08=B-Q|2PT)O4;EaxP^6qixOv7-cRh?*T?zZU`{nIM-at zTKYWr9rJ=tppQ9I#Z#mLgINVB!pO-^FOcvFw6NhV0gztuO?g ztoA*C-52Q-Z-P#xB4HAY3KQVd%dz1S4PA3vHp0aa=zAO?FCt zC_GaTyVBg2F!bBr3U@Zy2iJgIAt>1sf$JWA9kh{;L+P*HfUBX1Zy{4MgNbDfBV_ly z!y#+753arsZUt@366jIC0klaC@ckuk!qu=pAyf7&QmiBUT^L1&tOHzsK)4n|pmrVT zs2($4=?s~VejTFHbFdDOwG;_58LkIj1Fh@{glkO#F1>a==ymJS$z;gdedT1zPx4Kj ztjS`y_C}%af-RtpehdQDt3a<=W5C4$)9W@QAse;WUry$WYmr51ml9lkeunUrE`-3e zmq1SgSOPNEE-Mf+AGJ$g0M;3@w!$Ej;hMh=v=I+Lpz^n%Pg^MgwyqOkNyu2c^of)C z1~ALor3}}+RiF*K4+4{(1%1j3pif1>sv0r^mTZ?5Jd-It!tfPfiG_p$AY*Vfak%FG z4z#;wLtw&E&?}w+eKG^=#jF7HQzr8rV0mY<1YAJ_uGz~$E13p?F^fPSzXSn$8UcI$ z8er9{5w5iv0qf8%70zV71T1IBB1N}R5Kp%NO0=5wJalZt8;xYp;b{1K) zHY>2wW-`Sl{=NpR%iu3(u6l&)rc%%cSA#aV7WCowfbFR4wcc{LQZv~o1u_`}EJA3>ki`?9CKYTA!rhO)if*zRdd}Kn zEPfYbhoVE~!FI_2YbC5qAj1kq;xP6%J8+?2PAs?`V3}nyFVD#sV3+uP`pi}{$l9U^ zSz}_M9f7RgnnRhaoIJgT8us!1aB&4!*vYF07Hp&}L zCRlop0oK4DL@ISz{2_BPlezc;xj2|I z23RlDNpi9LgTG_#(w%cMaS)%N`e>~1&a3<{Xy}>?WbF>OOLuO+j&hc^YohQ$4F&ze z+hwnro1puQjnKm;vFG~o>`kCeUIlkA-2tI?WBKCFLMBY=J{hpSsQ=PDtU$=duS_hq zHpymHt^uuV1q@uc4bFb{MdG*|VoW@15Osrqt2@8ll0qO=j*uOXn{M0UJX#SUztui9FN4)K3{9!y8PC-AHHvpVTU;x|-7P+taAtyglk#rjlH2 z5Gq8ik}BPaGiM{#Woyg;*&N9R2{J0V+WGB69cEtH7F?U~Kbi6ksi*`CFXsi931q7Y zGO82?whBhN%w1iDetv%~wM*Y;E^)@Vl?VDj-f*RX>{;o_=$fU!&KAXbuadYZ46Zbg z&6jMF=49$uL^73y;;N5jaHYv)BTyfh&`qVLYn?`o6BCA_z-0niZz=qPG!vonK3MW_ zo$V96zM!+kJRs{P-5-rQVse0VBH*n6A58)4uc&gfHMa{gIhV2fGf{st>E8sKyP-$8zp~wJX^A*@DI&-;8>gANXZj zU)R+Y)PB?=)a|Kj>8NXEu^S_h^7R`~Q&7*Kn!xyvzVv&^>?^iu;S~R2e-2fJx-oUb cX)(b1KSk$MOV07*qoM6N<$f&6$jw%VRuvdN2+38CZWny1cRtlsl+0_KtW)EU14Ei(F!UtWuj4IK+3{sK@>rh zs1Z;=(DD&U6+tlyL?UnHVN^&g6QhFi2#HS+*qz;(>63G(`|jRtW|nz$Pv7qTovP!^ zP_jES{mr@O-02w%!^a?^1ZP!_KmQiz0L~jZ=W@Qt`8wzOoclQsAS<5YdH;a(4bGLE zk8s}1If(PSIgVi!XE!5kA?~z*sobvNyohr;=Q_@h2@$6Flyej3J)D-6YfheRGl`HEcPk|~huT_2-U?PfL=4BPV)f1o!%rQ!NMt_MYw-5bUSwQ9Z&zC>u zOrl~UJglJNa%f50Ok}?WB{on`Ci`p^Y!xBA?m@rcJXLxtrE0FhRF3d*ir>yzO|BD$ z3V}HpFcCh6bTzY}Nt_(W%QYd3NG)jJ4<`F<1Od) zfQblTdC&h2lCz`>y?>|9o2CdvC8qZeIZt%jN;B7Hdn2l*k4M4MFEtq`q_#5?}c$b$pf_3y{Y!cRDafZBEj-*OD|gz#PBDeu3QoueOesLzB+O zxjf2wvf6Wwz>@AiOo2mO4=TkAV+g~%_n&R;)l#!cBxjuoD$aS-`IIJv7cdX%2{WT7 zOm%5rs(wqyPE^k5SIpUZ!&Lq4<~%{*>_Hu$2|~Xa;iX*tz8~G6O3uFOS?+)tWtdi| zV2b#;zRN!m@H&jd=!$7YY6_}|=!IU@=SjvGDFtL;aCtw06U;-v^0%k0FOyESt z1Wv$={b_H&8FiRV?MrzoHWd>%v6KTRU;-v^Miiz+@q`(BoT!+<37CKhoKb)|8!+RG z6BQFU^@fRW;s8!mOf2QViKQGk0TVER6EG1`#;Nm39Do^PoT!+<37AD!%oJe86(=et zZ~|sLzU>V-qYiU6V8$0GmU7_K8|Fd0B?+9Un1BhKAz#V~Fk^`mJtlCX#{^8^M8!me z8Yg;8-~>!e<-iG;h*0B1kBKm}hItVGY6WnjVpgnTTAC$rqQ^v)4KvOtpY|sIj@WYg zyw##ZZ5AC2IKNC;^hwg9BPk0wLStlmBr;E|$5GoAo$&Ui_;S9WY62n3)i49|T%C#i017z3J=$RF|KyZWnci*@lW4 z=AKhNN6+m`Q!V3Ye68|8y@%=am>YD0nG99M)NWc20%)gwO!96j7muR}Fr&54SxKP2 zP30S~lt=a*qDlbu3+Av57=9v&vr<6g0&`!8E2fq>I|EJGKs}t|{h7+KT@)LfIV-3K zK)r_fr2?}FFyn*MYoLC>oV-J~eavL2ho4a4^r{E-8m2hi>~hA?_vIG4a*KT;2eyl1 zh_hUvUJpNCFwBvRq5BI*srSle>c6%n`#VNsyC|MGa{(P&08p=C9+WUw9Hl<1o9T4M zdD=_C0F7#o8A_bRR?sFNmU0R6tW`ElnF8p53IdHo#S9(JoZCz}fHwJ6F<&?qrpVqE zte|m%89JQD+XwaPU#%#lVs-@-OL);|MdfINd6!XwP2h(eyafTUsoRkA%&@fe?9m@jw-v(yTTiV2(*fthQH9}SqmsRPVnwwbV$1E(_lkmo&S zF-truCU914_$jpqjr(>Ha4HkM4YMT>m~NosUu&UZ>zirfHo%N6PPs9^_o$WqPA0#5 z%tG>qFCL+b*0s?sZ;Sht0nE7Kl>OVXy=gjWxxK;OJ3yGd7-pZf7JYNcZo2*1SF`u6 zHJyRRxGw9mDlOiXqVMsNe#WX`fC`vrtjSQ%KmLcl(lC>ZOQzG^%iql2w-f_K@r?OE zwCICifM#L-HJyc7Gm>Ern?+Sk3&|Khmu4(~3qa$(m6Ub^U0E5RHq49za|XklN#?kP zl;EstdW?(_4D>kwjWy2f!LM)y?F94kyU3`W!6+AyId-89v}sXJpuic^NLL7GJItl~ zsiuB98AI-(#Mnm|=A-R6&2fwJ0JVSY#Q>&3$zFh|@;#%0qeF=j5Ajq@4i0tIIW z&}sk$&fGwoJpe&u-JeGLi^r?dO`m=y(QO{@h zQqAC7$rvz&5+mo3IqE?h=a~6m>%r5Quapvzq;{y~p zJpyXOBgD9VrW7@#p6l7O?o3feml(DtSL>D^R) zZUY%T2b0-vBAFN7VB;M88!~HuOXi4KcI6aRQ&h|XQ0A?m%j2=l1f0cGP}h(oVfJ`N zz#PpmFC*ieab)zJK<4?^k=g%OjPnkANzbAbmGZHoVRk*mTfm75s_cWVa`l*f$B@xu z5E*?&@seIo#*Y~1rBm!7sF9~~u6Wrj5oICUOuz}CS)jdNIznfzCA(stJ(7$c^e5wN z?lt>eYgbA!kvAR7zYSD&*r1$b|(@;9dcZ^67R0 zXAXJKa|5Sdmj!g578Nwt6d$sXuc&MWezA0Whd`94$h{{?1IwXP4)Tx4obDK%xoFZ_Z zjjHJ_P@R_e5blG@yEjnaJb`l;s%Lb2&=8$&Ct-fV`E^4CUs)=jTk!I}2d&n!f@)bm z@ z_4Dc86+3l2*p|~;o-Sb~oXb_RuLmoifDU^&Te$*FevycC0*nE3Xws8gsWp|Rj2>SM zns)qcYj?^2sd8?N!_w~4v+f-HCF|a$TNZDoNl$I1Uq87euoNgKb6&r26TNrfkUa@o zfdiFA@p{K&mH3b8i!lcoz)V{n8Q@g(vR4ns4r6w;K z>1~ecQR0-<^J|Ndg5fvVUM9g;lbu-){#ghGw(fg>L zh)T5Ljb%lWE;V9L!;Cqk>AV1(rULYF07ZBJbGb9qbSoLAd;in9{)95YqX$J43-dY7YU*k~vrM25 zxh5_IqO0LYZW%oxQ5HOzmk4x{atE*vipUk}sh88$b2tn?!ujEHn`tQLe&vo}nMb&{ zio`xzZ&GG6&ZyN3jnaQy#iVqXE9VT(3tWY$n-)uWDQ|tc{`?fq2F`oQ{;d3aWPg4Hp-(iE{ry>MIPWL> iW8 + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/flutter_webview_plugin/example/ios/Runner/Base.lproj/Main.storyboard b/packages/flutter_webview_plugin/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000..f3c28516 --- /dev/null +++ b/packages/flutter_webview_plugin/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/flutter_webview_plugin/example/ios/Runner/Info.plist b/packages/flutter_webview_plugin/example/ios/Runner/Info.plist new file mode 100644 index 00000000..764d507b --- /dev/null +++ b/packages/flutter_webview_plugin/example/ios/Runner/Info.plist @@ -0,0 +1,50 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + flutter_webview_plugin_example + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + NSAppTransportSecurity + + NSAllowsArbitraryLoadsInWebContent + + + + diff --git a/packages/flutter_webview_plugin/example/ios/Runner/main.m b/packages/flutter_webview_plugin/example/ios/Runner/main.m new file mode 100644 index 00000000..1bdb8e28 --- /dev/null +++ b/packages/flutter_webview_plugin/example/ios/Runner/main.m @@ -0,0 +1,10 @@ +#import +#import +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, + NSStringFromClass([AppDelegate class])); + } +} diff --git a/packages/flutter_webview_plugin/example/lib/main.dart b/packages/flutter_webview_plugin/example/lib/main.dart new file mode 100644 index 00000000..3d283c3a --- /dev/null +++ b/packages/flutter_webview_plugin/example/lib/main.dart @@ -0,0 +1,312 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; + +const kAndroidUserAgent = + 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Mobile Safari/537.36'; + +String selectedUrl = 'https://flutter.io'; + +// ignore: prefer_collection_literals +final Set jsChannels = [ + JavascriptChannel( + name: 'Print', + onMessageReceived: (JavascriptMessage message) { + print(message.message); + }), +].toSet(); + +void main() { + WidgetsFlutterBinding.ensureInitialized(); + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + final flutterWebViewPlugin = FlutterWebviewPlugin(); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter WebView Demo', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + routes: { + '/': (_) => const MyHomePage(title: 'Flutter WebView Demo'), + '/widget': (_) { + return WebviewScaffold( + url: selectedUrl, + javascriptChannels: jsChannels, + mediaPlaybackRequiresUserGesture: false, + appBar: AppBar( + title: const Text('Widget WebView'), + ), + withZoom: true, + withLocalStorage: true, + hidden: true, + initialChild: Container( + color: Colors.redAccent, + child: const Center( + child: Text('Waiting.....'), + ), + ), + bottomNavigationBar: BottomAppBar( + child: Row( + children: [ + IconButton( + icon: const Icon(Icons.arrow_back_ios), + onPressed: () { + flutterWebViewPlugin.goBack(); + }, + ), + IconButton( + icon: const Icon(Icons.arrow_forward_ios), + onPressed: () { + flutterWebViewPlugin.goForward(); + }, + ), + IconButton( + icon: const Icon(Icons.autorenew), + onPressed: () { + flutterWebViewPlugin.reload(); + }, + ), + ], + ), + ), + ); + }, + }, + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({Key? key,required this.title}) : super(key: key); + + final String title; + + @override + _MyHomePageState createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + // Instance of WebView plugin + final flutterWebViewPlugin = FlutterWebviewPlugin(); + + // On destroy stream + StreamSubscription? _onDestroy; + + // On urlChanged stream + StreamSubscription? _onUrlChanged; + + // On urlChanged stream + StreamSubscription? _onStateChanged; + + StreamSubscription? _onHttpError; + + StreamSubscription? _onProgressChanged; + + StreamSubscription? _onScrollYChanged; + + StreamSubscription? _onScrollXChanged; + + final _urlCtrl = TextEditingController(text: selectedUrl); + + final _codeCtrl = TextEditingController(text: 'window.navigator.userAgent'); + + final _scaffoldKey = GlobalKey(); + + final _history = []; + + @override + void initState() { + super.initState(); + + flutterWebViewPlugin.close(); + + _urlCtrl.addListener(() { + selectedUrl = _urlCtrl.text; + }); + + // Add a listener to on destroy WebView, so you can make came actions. + _onDestroy = flutterWebViewPlugin.onDestroy.listen((_) { + if (mounted) { + // Actions like show a info toast. + _scaffoldKey.currentState?.showSnackBar( + const SnackBar(content: const Text('Webview Destroyed'))); + } + }); + + // Add a listener to on url changed + _onUrlChanged = flutterWebViewPlugin.onUrlChanged.listen((String url) { + if (mounted) { + setState(() { + _history.add('onUrlChanged: $url'); + }); + } + }); + + _onProgressChanged = + flutterWebViewPlugin.onProgressChanged.listen((double progress) { + if (mounted) { + setState(() { + _history.add('onProgressChanged: $progress'); + }); + } + }); + + _onScrollYChanged = + flutterWebViewPlugin.onScrollYChanged.listen((double y) { + if (mounted) { + setState(() { + _history.add('Scroll in Y Direction: $y'); + }); + } + }); + + _onScrollXChanged = + flutterWebViewPlugin.onScrollXChanged.listen((double x) { + if (mounted) { + setState(() { + _history.add('Scroll in X Direction: $x'); + }); + } + }); + + _onStateChanged = + flutterWebViewPlugin.onStateChanged.listen((WebViewStateChanged state) { + if (mounted) { + setState(() { + _history.add('onStateChanged: ${state.type} ${state.url}'); + }); + } + }); + + _onHttpError = + flutterWebViewPlugin.onHttpError.listen((WebViewHttpError error) { + if (mounted) { + setState(() { + _history.add('onHttpError: ${error.code} ${error.url}'); + }); + } + }); + } + + @override + void dispose() { + // Every listener should be canceled, the same should be done with this stream. + _onDestroy?.cancel(); + _onUrlChanged?.cancel(); + _onStateChanged?.cancel(); + _onHttpError?.cancel(); + _onProgressChanged?.cancel(); + _onScrollXChanged?.cancel(); + _onScrollYChanged?.cancel(); + + flutterWebViewPlugin.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + key: _scaffoldKey, + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all(24.0), + child: TextField(controller: _urlCtrl), + ), + RaisedButton( + onPressed: () { + flutterWebViewPlugin.launch( + selectedUrl, + rect: Rect.fromLTWH( + 0.0, 0.0, MediaQuery.of(context).size.width, 300.0), + userAgent: kAndroidUserAgent, + invalidUrlRegex: + r'^(https).+(twitter)', // prevent redirecting to twitter when user click on its icon in flutter website + ); + }, + child: const Text('Open Webview (rect)'), + ), + RaisedButton( + onPressed: () { + flutterWebViewPlugin.launch(selectedUrl, hidden: true); + }, + child: const Text('Open "hidden" Webview'), + ), + RaisedButton( + onPressed: () { + flutterWebViewPlugin.launch(selectedUrl); + }, + child: const Text('Open Fullscreen Webview'), + ), + RaisedButton( + onPressed: () { + Navigator.of(context).pushNamed('/widget'); + }, + child: const Text('Open widget webview'), + ), + Container( + padding: const EdgeInsets.all(24.0), + child: TextField(controller: _codeCtrl), + ), + RaisedButton( + onPressed: () { + final future = + flutterWebViewPlugin.evalJavascript(_codeCtrl.text); + future.then((String result) { + setState(() { + _history.add('eval: $result'); + }); + }); + }, + child: const Text('Eval some javascript'), + ), + RaisedButton( + onPressed: () { + final future = flutterWebViewPlugin.evalJavascript('alert("Hello World");'); + future.then((String result) { + setState(() { + _history.add('eval: $result'); + }); + }); + }, + child: const Text('Eval javascript alert()'), + ), + RaisedButton( + onPressed: () { + setState(() { + _history.clear(); + }); + flutterWebViewPlugin.close(); + }, + child: const Text('Close'), + ), + RaisedButton( + onPressed: () { + flutterWebViewPlugin.getCookies().then((m) { + setState(() { + _history.add('cookies: $m'); + }); + }); + }, + child: const Text('Cookies'), + ), + Text(_history.join('\n')) + ], + ), + ), + ); + } +} diff --git a/packages/flutter_webview_plugin/example/pubspec.yaml b/packages/flutter_webview_plugin/example/pubspec.yaml new file mode 100644 index 00000000..c7f63b2f --- /dev/null +++ b/packages/flutter_webview_plugin/example/pubspec.yaml @@ -0,0 +1,12 @@ +name: flutter_webview_plugin_example +description: Demonstrates how to use the flutter_webview_plugin plugin. +environment: + sdk: ">=2.12.0 <3.0.0" +dependencies: + flutter: + sdk: flutter + flutter_webview_plugin: + path: ../ + +flutter: + uses-material-design: true diff --git a/packages/flutter_webview_plugin/ios/.gitignore b/packages/flutter_webview_plugin/ios/.gitignore new file mode 100644 index 00000000..956c87f3 --- /dev/null +++ b/packages/flutter_webview_plugin/ios/.gitignore @@ -0,0 +1,31 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + diff --git a/packages/flutter_webview_plugin/ios/Assets/.gitkeep b/packages/flutter_webview_plugin/ios/Assets/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/packages/flutter_webview_plugin/ios/Classes/FlutterWebviewPlugin.h b/packages/flutter_webview_plugin/ios/Classes/FlutterWebviewPlugin.h new file mode 100644 index 00000000..e7df3100 --- /dev/null +++ b/packages/flutter_webview_plugin/ios/Classes/FlutterWebviewPlugin.h @@ -0,0 +1,9 @@ +#import +#import + +static FlutterMethodChannel *channel; + +@interface FlutterWebviewPlugin : NSObject +@property (nonatomic, retain) UIViewController *viewController; +@property (nonatomic, retain) WKWebView *webview; +@end diff --git a/packages/flutter_webview_plugin/ios/Classes/FlutterWebviewPlugin.m b/packages/flutter_webview_plugin/ios/Classes/FlutterWebviewPlugin.m new file mode 100644 index 00000000..fc266094 --- /dev/null +++ b/packages/flutter_webview_plugin/ios/Classes/FlutterWebviewPlugin.m @@ -0,0 +1,534 @@ +#import "FlutterWebviewPlugin.h" +#import "WebviewJavaScriptChannelHandler.h" + +static NSString *const CHANNEL_NAME = @"flutter_webview_plugin"; + +// UIWebViewDelegate +@interface FlutterWebviewPlugin() { + BOOL _enableAppScheme; + BOOL _enableZoom; + NSString* _invalidUrlRegex; + NSMutableSet* _javaScriptChannelNames; + NSNumber* _ignoreSSLErrors; +} +@end + +@implementation FlutterWebviewPlugin ++ (void)registerWithRegistrar:(NSObject*)registrar { + channel = [FlutterMethodChannel + methodChannelWithName:CHANNEL_NAME + binaryMessenger:[registrar messenger]]; + + UIViewController *viewController = [UIApplication sharedApplication].delegate.window.rootViewController; + FlutterWebviewPlugin* instance = [[FlutterWebviewPlugin alloc] initWithViewController:viewController]; + + [registrar addMethodCallDelegate:instance channel:channel]; +} + +- (instancetype)initWithViewController:(UIViewController *)viewController { + self = [super init]; + if (self) { + self.viewController = viewController; + } + return self; +} + +- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { + if ([@"launch" isEqualToString:call.method]) { + if (!self.webview) + [self initWebview:call withResult:result]; + else + [self navigate:call]; + result(nil); + } else if ([@"close" isEqualToString:call.method]) { + [self closeWebView]; + result(nil); + } else if ([@"eval" isEqualToString:call.method]) { + [self evalJavascript:call completionHandler:^(NSString * response) { + result(response); + }]; + } else if ([@"resize" isEqualToString:call.method]) { + [self resize:call]; + result(nil); + } else if ([@"reloadUrl" isEqualToString:call.method]) { + [self reloadUrl:call]; + result(nil); + } else if ([@"show" isEqualToString:call.method]) { + [self show]; + result(nil); + } else if ([@"hide" isEqualToString:call.method]) { + [self hide]; + result(nil); + } else if ([@"stopLoading" isEqualToString:call.method]) { + [self stopLoading]; + result(nil); + } else if ([@"cleanCookies" isEqualToString:call.method]) { + [self cleanCookies:result]; + } else if ([@"back" isEqualToString:call.method]) { + [self back]; + result(nil); + } else if ([@"forward" isEqualToString:call.method]) { + [self forward]; + result(nil); + } else if ([@"reload" isEqualToString:call.method]) { + [self reload]; + result(nil); + } else if ([@"canGoBack" isEqualToString:call.method]) { + [self onCanGoBack:call result:result]; + } else if ([@"canGoForward" isEqualToString:call.method]) { + [self onCanGoForward:call result:result]; + } else if ([@"cleanCache" isEqualToString:call.method]) { + [self cleanCache:result]; + } else { + result(FlutterMethodNotImplemented); + } +} + +- (void)initWebview:(FlutterMethodCall*)call withResult:(FlutterResult)result { + NSNumber *clearCache = call.arguments[@"clearCache"]; + NSNumber *clearCookies = call.arguments[@"clearCookies"]; + NSNumber *hidden = call.arguments[@"hidden"]; + NSDictionary *rect = call.arguments[@"rect"]; + _enableAppScheme = call.arguments[@"enableAppScheme"]; + NSString *userAgent = call.arguments[@"userAgent"]; + NSNumber *withZoom = call.arguments[@"withZoom"]; + NSNumber *scrollBar = call.arguments[@"scrollBar"]; + NSNumber *withJavascript = call.arguments[@"withJavascript"]; + _invalidUrlRegex = call.arguments[@"invalidUrlRegex"]; + _ignoreSSLErrors = call.arguments[@"ignoreSSLErrors"]; + _javaScriptChannelNames = [[NSMutableSet alloc] init]; + + WKUserContentController* userContentController = [[WKUserContentController alloc] init]; + if ([call.arguments[@"javascriptChannelNames"] isKindOfClass:[NSArray class]]) { + NSArray* javaScriptChannelNames = call.arguments[@"javascriptChannelNames"]; + [_javaScriptChannelNames addObjectsFromArray:javaScriptChannelNames]; + [self registerJavaScriptChannels:_javaScriptChannelNames controller:userContentController]; + } + + if (clearCache != (id)[NSNull null] && [clearCache boolValue]) { + [[NSURLCache sharedURLCache] removeAllCachedResponses]; + [self cleanCache:result]; + + } + + if (clearCookies != (id)[NSNull null] && [clearCookies boolValue]) { + NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; + for (NSHTTPCookie *cookie in [storage cookies]) + { + [storage deleteCookie:cookie]; + } + + [self cleanCookies:result]; + + } + + if (userAgent != (id)[NSNull null]) { + [[NSUserDefaults standardUserDefaults] registerDefaults:@{@"UserAgent": userAgent}]; + } + + CGRect rc; + if (rect != nil) { + rc = [self parseRect:rect]; + } else { + rc = self.viewController.view.bounds; + } + + WKWebViewConfiguration* configuration = [[WKWebViewConfiguration alloc] init]; + configuration.userContentController = userContentController; + self.webview = [[WKWebView alloc] initWithFrame:rc configuration:configuration]; + self.webview.UIDelegate = self; + self.webview.navigationDelegate = self; + self.webview.scrollView.delegate = self; + self.webview.hidden = [hidden boolValue]; + self.webview.scrollView.showsHorizontalScrollIndicator = [scrollBar boolValue]; + self.webview.scrollView.showsVerticalScrollIndicator = [scrollBar boolValue]; + + [self.webview addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:NULL]; + + WKPreferences* preferences = [[self.webview configuration] preferences]; + if ([withJavascript boolValue]) { + [preferences setJavaScriptEnabled:YES]; + } else { + [preferences setJavaScriptEnabled:NO]; + } + + _enableZoom = [withZoom boolValue]; + + UIViewController* presentedViewController = self.viewController.presentedViewController; + UIViewController* currentViewController = presentedViewController != nil ? presentedViewController : self.viewController; + [currentViewController.view addSubview:self.webview]; + + [self navigate:call]; +} + +- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { + if ([_ignoreSSLErrors boolValue]){ + SecTrustRef serverTrust = challenge.protectionSpace.serverTrust; + CFDataRef exceptions = SecTrustCopyExceptions(serverTrust); + SecTrustSetExceptions(serverTrust, exceptions); + CFRelease(exceptions); + completionHandler(NSURLSessionAuthChallengeUseCredential, + [NSURLCredential credentialForTrust:serverTrust]); + } + else { + completionHandler(NSURLSessionAuthChallengePerformDefaultHandling,nil); + } + +} + +- (CGRect)parseRect:(NSDictionary *)rect { + return CGRectMake([[rect valueForKey:@"left"] doubleValue], + [[rect valueForKey:@"top"] doubleValue], + [[rect valueForKey:@"width"] doubleValue], + [[rect valueForKey:@"height"] doubleValue]); +} + +- (void) scrollViewDidScroll:(UIScrollView *)scrollView { + id xDirection = @{@"xDirection": @(scrollView.contentOffset.x) }; + [channel invokeMethod:@"onScrollXChanged" arguments:xDirection]; + + id yDirection = @{@"yDirection": @(scrollView.contentOffset.y) }; + [channel invokeMethod:@"onScrollYChanged" arguments:yDirection]; +} + +- (void)navigate:(FlutterMethodCall*)call { + if (self.webview != nil) { + NSString *url = call.arguments[@"url"]; + NSNumber *withLocalUrl = call.arguments[@"withLocalUrl"]; + if ( [withLocalUrl boolValue]) { + NSURL *htmlUrl = [NSURL fileURLWithPath:url isDirectory:false]; + NSString *localUrlScope = call.arguments[@"localUrlScope"]; + if (@available(iOS 9.0, *)) { + if(localUrlScope == nil) { + [self.webview loadFileURL:htmlUrl allowingReadAccessToURL:htmlUrl]; + } + else { + NSURL *scopeUrl = [NSURL fileURLWithPath:localUrlScope]; + [self.webview loadFileURL:htmlUrl allowingReadAccessToURL:scopeUrl]; + } + } else { + @throw @"not available on version earlier than ios 9.0"; + } + } else { + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]]; + NSDictionary *headers = call.arguments[@"headers"]; + + if (headers != nil) { + [request setAllHTTPHeaderFields:headers]; + } + + [self.webview loadRequest:request]; + } + } +} + +- (void)evalJavascript:(FlutterMethodCall*)call + completionHandler:(void (^_Nullable)(NSString * response))completionHandler { + if (self.webview != nil) { + NSString *code = call.arguments[@"code"]; + [self.webview evaluateJavaScript:code + completionHandler:^(id _Nullable response, NSError * _Nullable error) { + completionHandler([NSString stringWithFormat:@"%@", response]); + }]; + } else { + completionHandler(nil); + } +} + +- (void)resize:(FlutterMethodCall*)call { + if (self.webview != nil) { + NSDictionary *rect = call.arguments[@"rect"]; + CGRect rc = [self parseRect:rect]; + self.webview.frame = rc; + } +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if ([keyPath isEqualToString:@"estimatedProgress"] && object == self.webview) { + [channel invokeMethod:@"onProgressChanged" arguments:@{@"progress": @(self.webview.estimatedProgress)}]; + } else { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } +} + +- (void)closeWebView { + if (self.webview != nil) { + [self.webview stopLoading]; + [self.webview removeFromSuperview]; + self.webview.navigationDelegate = nil; + [self.webview removeObserver:self forKeyPath:@"estimatedProgress"]; + self.webview = nil; + + // manually trigger onDestroy + [channel invokeMethod:@"onDestroy" arguments:nil]; + } +} + +- (void)reloadUrl:(FlutterMethodCall*)call { + if (self.webview != nil) { + NSString *url = call.arguments[@"url"]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]]; + NSDictionary *headers = call.arguments[@"headers"]; + + if (headers != nil) { + [request setAllHTTPHeaderFields:headers]; + } + + [self.webview loadRequest:request]; + } +} + +- (void)cleanCookies:(FlutterResult)result { + if(self.webview != nil) { + [[NSURLSession sharedSession] resetWithCompletionHandler:^{ + }]; + if (@available(iOS 9.0, *)) { + NSSet *websiteDataTypes = [NSSet setWithObject:WKWebsiteDataTypeCookies]; + WKWebsiteDataStore *dataStore = [WKWebsiteDataStore defaultDataStore]; + + void (^deleteAndNotify)(NSArray *) = + ^(NSArray *cookies) { + [dataStore removeDataOfTypes:websiteDataTypes + forDataRecords:cookies + completionHandler:^{ + result(nil); + }]; + }; + + [dataStore fetchDataRecordsOfTypes:websiteDataTypes completionHandler:deleteAndNotify]; + } else { + // support for iOS8 tracked in https://github.com/flutter/flutter/issues/27624. + NSLog(@"Clearing cookies is not supported for Flutter WebViews prior to iOS 9."); + } + } +} + +- (void)cleanCache:(FlutterResult)result { + if (self.webview != nil) { + if (@available(iOS 9.0, *)) { + NSSet* cacheDataTypes = [WKWebsiteDataStore allWebsiteDataTypes]; + WKWebsiteDataStore* dataStore = [WKWebsiteDataStore defaultDataStore]; + NSDate* dateFrom = [NSDate dateWithTimeIntervalSince1970:0]; + [dataStore removeDataOfTypes:cacheDataTypes + modifiedSince:dateFrom + completionHandler:^{ + result(nil); + }]; + } else { + // support for iOS8 tracked in https://github.com/flutter/flutter/issues/27624. + NSLog(@"Clearing cache is not supported for Flutter WebViews prior to iOS 9."); + } + } +} + +- (void)show { + if (self.webview != nil) { + self.webview.hidden = false; + } +} + +- (void)hide { + if (self.webview != nil) { + self.webview.hidden = true; + } +} +- (void)stopLoading { + if (self.webview != nil) { + [self.webview stopLoading]; + } +} +- (void)back { + if (self.webview != nil) { + [self.webview goBack]; + } +} + +- (void)onCanGoBack:(FlutterMethodCall*)call result:(FlutterResult)result { + BOOL canGoBack = [self.webview canGoBack]; + result([NSNumber numberWithBool:canGoBack]); +} + +- (void)onCanGoForward:(FlutterMethodCall*)call result:(FlutterResult)result { + BOOL canGoForward = [self.webview canGoForward]; + result([NSNumber numberWithBool:canGoForward]); +} + +- (void)forward { + if (self.webview != nil) { + [self.webview goForward]; + } +} +- (void)reload { + if (self.webview != nil) { + [self.webview reload]; + } +} + +- (bool)checkInvalidUrl:(NSURL*)url { + NSString* urlString = url != nil ? [url absoluteString] : nil; + if (![_invalidUrlRegex isEqual:[NSNull null]] && urlString != nil) { + NSError* error = NULL; + NSRegularExpression* regex = + [NSRegularExpression regularExpressionWithPattern:_invalidUrlRegex + options:NSRegularExpressionCaseInsensitive + error:&error]; + NSTextCheckingResult* match = [regex firstMatchInString:urlString + options:0 + range:NSMakeRange(0, [urlString length])]; + return match != nil; + } else { + return false; + } +} + +#pragma mark -- WkWebView Delegate +- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction + decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { + + BOOL isInvalid = [self checkInvalidUrl: navigationAction.request.URL]; + + id data = @{@"url": navigationAction.request.URL.absoluteString, + @"type": isInvalid ? @"abortLoad" : @"shouldStart", + @"navigationType": [NSNumber numberWithInteger:navigationAction.navigationType]}; + [channel invokeMethod:@"onState" arguments:data]; + + if (navigationAction.navigationType == WKNavigationTypeBackForward) { + [channel invokeMethod:@"onBackPressed" arguments:nil]; + } else if (!isInvalid) { + id data = @{@"url": navigationAction.request.URL.absoluteString}; + [channel invokeMethod:@"onUrlChanged" arguments:data]; + } + + if (_enableAppScheme || + ([webView.URL.scheme isEqualToString:@"http"] || + [webView.URL.scheme isEqualToString:@"https"] || + [webView.URL.scheme isEqualToString:@"about"] || + [webView.URL.scheme isEqualToString:@"file"])) { + if (isInvalid) { + decisionHandler(WKNavigationActionPolicyCancel); + } else { + decisionHandler(WKNavigationActionPolicyAllow); + } + } else { + decisionHandler(WKNavigationActionPolicyCancel); + } +} + +- (WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration + forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures { + + if (!navigationAction.targetFrame.isMainFrame) { + [webView loadRequest:navigationAction.request]; + } + + return nil; +} + +- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation { + [channel invokeMethod:@"onState" arguments:@{@"type": @"startLoad", @"url": webView.URL.absoluteString}]; +} + +- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error { + NSString* url = webView.URL == nil ? @"?" : webView.URL.absoluteString; + + [channel invokeMethod:@"onHttpError" arguments:@{@"code": [NSString stringWithFormat:@"%ld", error.code], @"url": url}]; +} + +- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { + [channel invokeMethod:@"onState" arguments:@{@"type": @"finishLoad", @"url": webView.URL.absoluteString}]; +} + +- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error { + [channel invokeMethod:@"onHttpError" arguments:@{@"code": [NSString stringWithFormat:@"%ld", error.code], @"error": error.localizedDescription}]; +} + +- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler { + if ([navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]]) { + NSHTTPURLResponse * response = (NSHTTPURLResponse *)navigationResponse.response; + + if (response.statusCode >= 400) { + [channel invokeMethod:@"onHttpError" arguments:@{@"code": [NSString stringWithFormat:@"%ld", response.statusCode], @"url": webView.URL.absoluteString}]; + } + } + decisionHandler(WKNavigationResponsePolicyAllow); +} + +- (void)registerJavaScriptChannels:(NSSet*)channelNames + controller:(WKUserContentController*)userContentController { + for (NSString* channelName in channelNames) { + FLTCommunityJavaScriptChannel* _channel = + [[FLTCommunityJavaScriptChannel alloc] initWithMethodChannel: channel + javaScriptChannelName:channelName]; + [userContentController addScriptMessageHandler:_channel name:channelName]; + NSString* wrapperSource = [NSString + stringWithFormat:@"window.%@ = webkit.messageHandlers.%@;", channelName, channelName]; + WKUserScript* wrapperScript = + [[WKUserScript alloc] initWithSource:wrapperSource + injectionTime:WKUserScriptInjectionTimeAtDocumentStart + forMainFrameOnly:NO]; + [userContentController addUserScript:wrapperScript]; + } +} + +#pragma mark -- UIScrollViewDelegate +- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { + if (scrollView.pinchGestureRecognizer.isEnabled != _enableZoom) { + scrollView.pinchGestureRecognizer.enabled = _enableZoom; + } +} + +#pragma mark -- WKUIDelegate +- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler +{ + UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil + message:message + preferredStyle:UIAlertControllerStyleAlert]; + + [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { + completionHandler(); + }]]; + + [self.viewController presentViewController:alert animated:YES completion:nil]; +} + +- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL result))completionHandler +{ + UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil + message:message + preferredStyle:UIAlertControllerStyleAlert]; + + [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { + completionHandler(NO); + }]]; + + [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + completionHandler(YES); + }]]; + + [self.viewController presentViewController:alert animated:YES completion:nil]; +} + +- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *result))completionHandler +{ + UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil + message:prompt + preferredStyle:UIAlertControllerStyleAlert]; + + [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.placeholder = prompt; + textField.secureTextEntry = NO; + textField.text = defaultText; + }]; + + [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { + completionHandler(nil); + }]]; + + [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + completionHandler([alert.textFields.firstObject text]); + }]]; + + [self.viewController presentViewController:alert animated:YES completion:nil]; +} + +@end diff --git a/packages/flutter_webview_plugin/ios/Classes/WebviewJavaScriptChannelHandler.h b/packages/flutter_webview_plugin/ios/Classes/WebviewJavaScriptChannelHandler.h new file mode 100755 index 00000000..1b13cddc --- /dev/null +++ b/packages/flutter_webview_plugin/ios/Classes/WebviewJavaScriptChannelHandler.h @@ -0,0 +1,17 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface FLTCommunityJavaScriptChannel : NSObject + +- (instancetype)initWithMethodChannel:(FlutterMethodChannel*)methodChannel + javaScriptChannelName:(NSString*)javaScriptChannelName; + +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/flutter_webview_plugin/ios/Classes/WebviewJavaScriptChannelHandler.m b/packages/flutter_webview_plugin/ios/Classes/WebviewJavaScriptChannelHandler.m new file mode 100755 index 00000000..573e1752 --- /dev/null +++ b/packages/flutter_webview_plugin/ios/Classes/WebviewJavaScriptChannelHandler.m @@ -0,0 +1,36 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "WebviewJavaScriptChannelHandler.h" + +@implementation FLTCommunityJavaScriptChannel { + FlutterMethodChannel* _methodChannel; + NSString* _javaScriptChannelName; +} + +- (instancetype)initWithMethodChannel:(FlutterMethodChannel*)methodChannel + javaScriptChannelName:(NSString*)javaScriptChannelName { + self = [super init]; + NSAssert(methodChannel != nil, @"methodChannel must not be null."); + NSAssert(javaScriptChannelName != nil, @"javaScriptChannelName must not be null."); + if (self) { + _methodChannel = methodChannel; + _javaScriptChannelName = javaScriptChannelName; + } + return self; +} + +- (void)userContentController:(WKUserContentController*)userContentController + didReceiveScriptMessage:(WKScriptMessage*)message { + NSAssert(_methodChannel != nil, @"Can't send a message to an unitialized JavaScript channel."); + NSAssert(_javaScriptChannelName != nil, + @"Can't send a message to an unitialized JavaScript channel."); + NSDictionary* arguments = @{ + @"channel" : _javaScriptChannelName, + @"message" : [NSString stringWithFormat:@"%@", message.body] + }; + [_methodChannel invokeMethod:@"javascriptChannelMessage" arguments:arguments]; +} + +@end diff --git a/packages/flutter_webview_plugin/ios/flutter_webview_plugin.podspec b/packages/flutter_webview_plugin/ios/flutter_webview_plugin.podspec new file mode 100644 index 00000000..5f69c9c7 --- /dev/null +++ b/packages/flutter_webview_plugin/ios/flutter_webview_plugin.podspec @@ -0,0 +1,21 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# +Pod::Spec.new do |s| + s.name = 'flutter_webview_plugin' + s.version = '0.0.1' + s.summary = 'A new flutter plugin project.' + s.description = <<-DESC +A new flutter plugin project. + DESC + s.homepage = 'https://github.com/dart-flitter/flutter_webview_plugin' + 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.ios.deployment_target = '8.0' +end + diff --git a/packages/flutter_webview_plugin/lib/flutter_webview_plugin.dart b/packages/flutter_webview_plugin/lib/flutter_webview_plugin.dart new file mode 100644 index 00000000..8df8e3b4 --- /dev/null +++ b/packages/flutter_webview_plugin/lib/flutter_webview_plugin.dart @@ -0,0 +1,6 @@ +library flutter_webview_plugin; + +export 'src/base.dart'; +export 'src/javascript_channel.dart'; +export 'src/javascript_message.dart'; +export 'src/webview_scaffold.dart'; diff --git a/packages/flutter_webview_plugin/lib/src/base.dart b/packages/flutter_webview_plugin/lib/src/base.dart new file mode 100644 index 00000000..6596807b --- /dev/null +++ b/packages/flutter_webview_plugin/lib/src/base.dart @@ -0,0 +1,380 @@ +import 'dart:async'; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_webview_plugin/src/javascript_channel.dart'; + +import 'javascript_message.dart'; + +const _kChannel = 'flutter_webview_plugin'; + +// TODO: more general state for iOS/android +enum WebViewState { shouldStart, startLoad, finishLoad, abortLoad } + +// TODO: use an id by webview to be able to manage multiple webview + +/// Singleton class that communicate with a Webview Instance +class FlutterWebviewPlugin { + factory FlutterWebviewPlugin() { + if(_instance == null) { + const MethodChannel methodChannel = const MethodChannel(_kChannel); + _instance = FlutterWebviewPlugin.private(methodChannel); + } + return _instance!; + } + + @visibleForTesting + FlutterWebviewPlugin.private(this._channel) { + _channel.setMethodCallHandler(_handleMessages); + } + + static FlutterWebviewPlugin? _instance; + + final MethodChannel _channel; + + final _onBack = StreamController.broadcast(); + final _onDestroy = StreamController.broadcast(); + final _onUrlChanged = StreamController.broadcast(); + final _onStateChanged = StreamController.broadcast(); + final _onScrollXChanged = StreamController.broadcast(); + final _onScrollYChanged = StreamController.broadcast(); + final _onProgressChanged = new StreamController.broadcast(); + final _onHttpError = StreamController.broadcast(); + final _onPostMessage = StreamController.broadcast(); + + final Map _javascriptChannels = + // ignoring warning as min SDK version doesn't support collection literals yet + // ignore: prefer_collection_literals + Map(); + + Future _handleMessages(MethodCall call) async { + switch (call.method) { + case 'onBack': + _onBack.add(null); + break; + case 'onDestroy': + _onDestroy.add(null); + break; + case 'onUrlChanged': + _onUrlChanged.add(call.arguments['url']); + break; + case 'onScrollXChanged': + _onScrollXChanged.add(call.arguments['xDirection']); + break; + case 'onScrollYChanged': + _onScrollYChanged.add(call.arguments['yDirection']); + break; + case 'onProgressChanged': + _onProgressChanged.add(call.arguments['progress']); + break; + case 'onState': + _onStateChanged.add( + WebViewStateChanged.fromMap( + Map.from(call.arguments), + ), + ); + break; + case 'onHttpError': + _onHttpError.add( + WebViewHttpError(call.arguments['code'], call.arguments['url'])); + break; + case 'javascriptChannelMessage': + _handleJavascriptChannelMessage( + call.arguments['channel'], call.arguments['message']); + break; + } + } + + /// Listening the OnDestroy LifeCycle Event for Android + Stream get onDestroy => _onDestroy.stream; + + /// Listening the back key press Event for Android + Stream get onBack => _onBack.stream; + + /// Listening url changed + Stream get onUrlChanged => _onUrlChanged.stream; + + /// Listening the onState Event for iOS WebView and Android + /// content is Map for type: {shouldStart(iOS)|startLoad|finishLoad} + /// more detail than other events + Stream get onStateChanged => _onStateChanged.stream; + + /// Listening web view loading progress estimation, value between 0.0 and 1.0 + Stream get onProgressChanged => _onProgressChanged.stream; + + /// Listening web view y position scroll change + Stream get onScrollYChanged => _onScrollYChanged.stream; + + /// Listening web view x position scroll change + Stream get onScrollXChanged => _onScrollXChanged.stream; + + Stream get onHttpError => _onHttpError.stream; + + /// Start the Webview with [url] + /// - [headers] specify additional HTTP headers + /// - [withJavascript] enable Javascript or not for the Webview + /// - [clearCache] clear the cache of the Webview + /// - [clearCookies] clear all cookies of the Webview + /// - [hidden] not show + /// - [rect]: show in rect, fullscreen if null + /// - [enableAppScheme]: false will enable all schemes, true only for httt/https/about + /// android: Not implemented yet + /// - [userAgent]: set the User-Agent of WebView + /// - [withZoom]: enable zoom on webview + /// - [withLocalStorage] enable localStorage API on Webview + /// Currently Android only. + /// It is always enabled in UIWebView of iOS and can not be disabled. + /// - [withLocalUrl]: allow url as a local path + /// Allow local files on iOs > 9.0 + /// - [localUrlScope]: allowed folder for local paths + /// iOS only. + /// If null and withLocalUrl is true, then it will use the url as the scope, + /// allowing only itself to be read. + /// - [scrollBar]: enable or disable scrollbar + /// - [supportMultipleWindows] enable multiple windows support in Android + /// - [invalidUrlRegex] is the regular expression of URLs that web view shouldn't load. + /// For example, when webview is redirected to a specific URL, you want to intercept + /// this process by stopping loading this URL and replacing webview by another screen. + /// Android only settings: + /// - [displayZoomControls]: display zoom controls on webview + /// - [withOverviewMode]: enable overview mode for Android webview ( setLoadWithOverviewMode ) + /// - [useWideViewPort]: use wide viewport for Android webview ( setUseWideViewPort ) + /// - [ignoreSSLErrors]: use to bypass Android/iOS SSL checks e.g. for self-signed certificates + Future launch( + String url, { + Map? headers, + Set? javascriptChannels, + bool? withJavascript, + bool? clearCache, + bool? clearCookies, + bool? mediaPlaybackRequiresUserGesture, + bool? hidden, + bool? enableAppScheme, + Rect? rect, + String? userAgent, + bool? withZoom, + bool? displayZoomControls, + bool? withLocalStorage, + bool? withLocalUrl, + String? localUrlScope, + bool? withOverviewMode, + bool? scrollBar, + bool? supportMultipleWindows, + bool? appCacheEnabled, + bool? allowFileURLs, + bool? useWideViewPort, + String? invalidUrlRegex, + bool? geolocationEnabled, + bool? debuggingEnabled, + bool? ignoreSSLErrors, + }) async { + final args = { + 'url': url, + 'withJavascript': withJavascript ?? true, + 'clearCache': clearCache ?? false, + 'hidden': hidden ?? false, + 'clearCookies': clearCookies ?? false, + 'mediaPlaybackRequiresUserGesture': mediaPlaybackRequiresUserGesture ?? true, + 'enableAppScheme': enableAppScheme ?? true, + 'userAgent': userAgent, + 'withZoom': withZoom ?? false, + 'displayZoomControls': displayZoomControls ?? false, + 'withLocalStorage': withLocalStorage ?? true, + 'withLocalUrl': withLocalUrl ?? false, + 'localUrlScope': localUrlScope, + 'scrollBar': scrollBar ?? true, + 'supportMultipleWindows': supportMultipleWindows ?? false, + 'appCacheEnabled': appCacheEnabled ?? false, + 'allowFileURLs': allowFileURLs ?? false, + 'useWideViewPort': useWideViewPort ?? false, + 'invalidUrlRegex': invalidUrlRegex, + 'geolocationEnabled': geolocationEnabled ?? false, + 'withOverviewMode': withOverviewMode ?? false, + 'debuggingEnabled': debuggingEnabled ?? false, + 'ignoreSSLErrors': ignoreSSLErrors ?? false, + }; + + if (headers != null) { + args['headers'] = headers; + } + + _assertJavascriptChannelNamesAreUnique(javascriptChannels); + + if (javascriptChannels != null) { + javascriptChannels.forEach((channel) { + _javascriptChannels[channel.name] = channel; + }); + } else { + if (_javascriptChannels.isNotEmpty) { + _javascriptChannels.clear(); + } + } + + args['javascriptChannelNames'] = + _extractJavascriptChannelNames(javascriptChannels).toList(); + + if (rect != null) { + args['rect'] = { + 'left': rect.left, + 'top': rect.top, + 'width': rect.width, + 'height': rect.height, + }; + } + await _channel.invokeMethod('launch', args); + } + + /// Execute Javascript inside webview + Future evalJavascript(String code) async { + final res = await _channel.invokeMethod('eval', {'code': code}); + return res; + } + + /// Close the Webview + /// Will trigger the [onDestroy] event + Future close() async { + _javascriptChannels.clear(); + await _channel.invokeMethod('close'); + } + + /// Reloads the WebView. + Future reload() async => await _channel.invokeMethod('reload'); + + /// Navigates back on the Webview. + Future goBack() async => await _channel.invokeMethod('back'); + + /// Checks if webview can navigate back + Future canGoBack() async => await _channel.invokeMethod('canGoBack'); + + /// Checks if webview can navigate back + Future canGoForward() async => await _channel.invokeMethod('canGoForward'); + + /// Navigates forward on the Webview. + Future goForward() async => await _channel.invokeMethod('forward'); + + // Hides the webview + Future hide() async => await _channel.invokeMethod('hide'); + + // Shows the webview + Future show() async => await _channel.invokeMethod('show'); + + // Clears browser cache + Future clearCache() async => await _channel.invokeMethod('cleanCache'); + + // Reload webview with a url + Future reloadUrl(String url, {Map? headers}) async { + final args = {'url': url}; + if (headers != null) { + args['headers'] = headers; + } + await _channel.invokeMethod('reloadUrl', args); + } + + // Clean cookies on WebView + Future cleanCookies() async { + // one liner to clear javascript cookies + await evalJavascript('document.cookie.split(";").forEach(function(c) { document.cookie = c.replace(/^ +/, "").replace(/=.*/, "=;expires=" + new Date().toUTCString() + ";path=/"); });'); + return await _channel.invokeMethod('cleanCookies'); + } + + // Stops current loading process + Future stopLoading() async => + await _channel.invokeMethod('stopLoading'); + + /// Close all Streams + void dispose() { + _onDestroy.close(); + _onUrlChanged.close(); + _onStateChanged.close(); + _onProgressChanged.close(); + _onScrollXChanged.close(); + _onScrollYChanged.close(); + _onHttpError.close(); + _onPostMessage.close(); + _instance = null; + } + + Future> getCookies() async { + final cookiesString = await evalJavascript('document.cookie'); + final cookies = {}; + + if (cookiesString?.isNotEmpty == true) { + cookiesString.split(';').forEach((String cookie) { + final split = cookie.split('='); + cookies[split[0]] = split[1]; + }); + } + + return cookies; + } + + /// resize webview + Future resize(Rect rect) async { + final args = {}; + args['rect'] = { + 'left': rect.left, + 'top': rect.top, + 'width': rect.width, + 'height': rect.height, + }; + await _channel.invokeMethod('resize', args); + } + + Set _extractJavascriptChannelNames(Set? channels) { + final Set channelNames = channels == null + // ignore: prefer_collection_literals + ? Set() + : channels.map((JavascriptChannel channel) => channel.name).toSet(); + return channelNames; + } + + void _handleJavascriptChannelMessage( + final String channelName, final String message) { + _javascriptChannels[channelName] + ?.onMessageReceived(JavascriptMessage(message)); + } + + void _assertJavascriptChannelNamesAreUnique( + final Set? channels) { + if (channels == null || channels.isEmpty) { + return; + } + + assert(_extractJavascriptChannelNames(channels).length == channels.length); + } +} + +class WebViewStateChanged { + WebViewStateChanged(this.type, this.url, this.navigationType); + + factory WebViewStateChanged.fromMap(Map map) { + WebViewState t = WebViewState.startLoad; + switch (map['type']) { + case 'shouldStart': + t = WebViewState.shouldStart; + break; + case 'startLoad': + t = WebViewState.startLoad; + break; + case 'finishLoad': + t = WebViewState.finishLoad; + break; + case 'abortLoad': + t = WebViewState.abortLoad; + break; + } + return WebViewStateChanged(t, map['url'], map['navigationType']); + } + + final WebViewState type; + final String url; + final int navigationType; +} + +class WebViewHttpError { + WebViewHttpError(this.code, this.url); + + final String url; + final String code; +} diff --git a/packages/flutter_webview_plugin/lib/src/javascript_channel.dart b/packages/flutter_webview_plugin/lib/src/javascript_channel.dart new file mode 100644 index 00000000..1724a649 --- /dev/null +++ b/packages/flutter_webview_plugin/lib/src/javascript_channel.dart @@ -0,0 +1,36 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; + +final RegExp _validChannelNames = RegExp('^[a-zA-Z_][a-zA-Z0-9]*\$'); + +/// A named channel for receiving messaged from JavaScript code running inside a web view. +class JavascriptChannel { + /// Constructs a Javascript channel. + /// + /// The parameters `name` and `onMessageReceived` must not be null. + JavascriptChannel({ + required this.name, + required this.onMessageReceived, + }) : assert(name != null), + assert(onMessageReceived != null), + assert(_validChannelNames.hasMatch(name)); + + /// The channel's name. + /// + /// Passing this channel object as part of a [WebView.javascriptChannels] adds a channel object to + /// the Javascript window object's property named `name`. + /// + /// The name must start with a letter or underscore(_), followed by any combination of those + /// characters plus digits. + /// + /// Note that any JavaScript existing `window` property with this name will be overriden. + /// + /// See also [WebView.javascriptChannels] for more details on the channel registration mechanism. + final String name; + + /// A callback that's invoked when a message is received through the channel. + final JavascriptMessageHandler onMessageReceived; +} + +/// Callback type for handling messages sent from Javascript running in a web view. +typedef void JavascriptMessageHandler(JavascriptMessage message); diff --git a/packages/flutter_webview_plugin/lib/src/javascript_message.dart b/packages/flutter_webview_plugin/lib/src/javascript_message.dart new file mode 100644 index 00000000..8ddc8ea0 --- /dev/null +++ b/packages/flutter_webview_plugin/lib/src/javascript_message.dart @@ -0,0 +1,11 @@ +/// A message that was sent by JavaScript code running in a [WebView]. + +class JavascriptMessage { + /// Constructs a JavaScript message object. + /// + /// The `message` parameter must not be null. + const JavascriptMessage(this.message) : assert(message != null); + + /// The contents of the message that was sent by the JavaScript code. + final String message; +} diff --git a/packages/flutter_webview_plugin/lib/src/webview_scaffold.dart b/packages/flutter_webview_plugin/lib/src/webview_scaffold.dart new file mode 100644 index 00000000..494cb7b3 --- /dev/null +++ b/packages/flutter_webview_plugin/lib/src/webview_scaffold.dart @@ -0,0 +1,259 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_webview_plugin/src/javascript_channel.dart'; + +import 'base.dart'; + +class WebviewScaffold extends StatefulWidget { + const WebviewScaffold({ + Key? key, + this.appBar, + required this.url, + this.headers, + this.javascriptChannels, + this.withJavascript, + this.clearCache, + this.clearCookies, + this.mediaPlaybackRequiresUserGesture = true, + this.enableAppScheme, + this.userAgent, + this.primary = true, + this.persistentFooterButtons, + this.bottomNavigationBar, + this.withZoom, + this.displayZoomControls, + this.withLocalStorage, + this.withLocalUrl, + this.localUrlScope, + this.withOverviewMode, + this.useWideViewPort, + this.scrollBar, + this.supportMultipleWindows, + this.appCacheEnabled, + this.hidden = false, + this.initialChild, + this.allowFileURLs, + this.resizeToAvoidBottomInset = false, + this.invalidUrlRegex, + this.geolocationEnabled, + this.debuggingEnabled = false, + this.ignoreSSLErrors = false, + }) : super(key: key); + + final PreferredSizeWidget? appBar; + final String url; + final Map? headers; + final Set? javascriptChannels; + final bool? withJavascript; + final bool? clearCache; + final bool? clearCookies; + final bool mediaPlaybackRequiresUserGesture; + final bool? enableAppScheme; + final String? userAgent; + final bool primary; + final List? persistentFooterButtons; + final Widget? bottomNavigationBar; + final bool? withZoom; + final bool? displayZoomControls; + final bool? withLocalStorage; + final bool? withLocalUrl; + final String? localUrlScope; + final bool? scrollBar; + final bool? supportMultipleWindows; + final bool? appCacheEnabled; + final bool hidden; + final Widget? initialChild; + final bool? allowFileURLs; + final bool resizeToAvoidBottomInset; + final String? invalidUrlRegex; + final bool? geolocationEnabled; + final bool? withOverviewMode; + final bool? useWideViewPort; + final bool debuggingEnabled; + final bool ignoreSSLErrors; + + @override + _WebviewScaffoldState createState() => _WebviewScaffoldState(); +} + +class _WebviewScaffoldState extends State { + final webviewReference = FlutterWebviewPlugin(); + Rect? _rect; + Timer? _resizeTimer; + StreamSubscription? _onStateChanged; + + var _onBack; + + @override + void initState() { + super.initState(); + webviewReference.close(); + + _onBack = webviewReference.onBack.listen((_) async { + if (!mounted) { + return; + } + + // The willPop/pop pair here is equivalent to Navigator.maybePop(), + // which is what's called from the flutter back button handler. + final pop = await _topMostRoute.willPop(); + if (pop == RoutePopDisposition.pop) { + // Close the webview if it's on the route at the top of the stack. + final isOnTopMostRoute = _topMostRoute == ModalRoute.of(context); + if (isOnTopMostRoute) { + webviewReference.close(); + } + Navigator.pop(context); + } + }); + + if (widget.hidden) { + _onStateChanged = + webviewReference.onStateChanged.listen((WebViewStateChanged state) { + if (state.type == WebViewState.finishLoad) { + webviewReference.show(); + } + }); + } + } + + /// Equivalent to [Navigator.of(context)._history.last]. + Route get _topMostRoute { + var topMost; + Navigator.popUntil(context, (route) { + topMost = route; + return true; + }); + return topMost; + } + + @override + void dispose() { + super.dispose(); + _onBack?.cancel(); + _resizeTimer!.cancel(); + webviewReference.close(); + if (widget.hidden) { + _onStateChanged!.cancel(); + } + webviewReference.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: widget.appBar, + resizeToAvoidBottomInset: widget.resizeToAvoidBottomInset, + persistentFooterButtons: widget.persistentFooterButtons, + bottomNavigationBar: widget.bottomNavigationBar, + body: _WebviewPlaceholder( + onRectChanged: (Rect value) { + if (_rect == null) { + _rect = value; + webviewReference.launch( + widget.url, + headers: widget.headers, + javascriptChannels: widget.javascriptChannels, + withJavascript: widget.withJavascript, + clearCache: widget.clearCache, + clearCookies: widget.clearCookies, + mediaPlaybackRequiresUserGesture: widget.mediaPlaybackRequiresUserGesture, + hidden: widget.hidden, + enableAppScheme: widget.enableAppScheme, + userAgent: widget.userAgent, + rect: _rect, + withZoom: widget.withZoom, + displayZoomControls: widget.displayZoomControls, + withLocalStorage: widget.withLocalStorage, + withLocalUrl: widget.withLocalUrl, + localUrlScope: widget.localUrlScope, + withOverviewMode: widget.withOverviewMode, + useWideViewPort: widget.useWideViewPort, + scrollBar: widget.scrollBar, + supportMultipleWindows: widget.supportMultipleWindows, + appCacheEnabled: widget.appCacheEnabled, + allowFileURLs: widget.allowFileURLs, + invalidUrlRegex: widget.invalidUrlRegex, + geolocationEnabled: widget.geolocationEnabled, + debuggingEnabled: widget.debuggingEnabled, + ignoreSSLErrors: widget.ignoreSSLErrors, + ); + } else { + if (_rect != value) { + _rect = value; + _resizeTimer?.cancel(); + _resizeTimer = Timer(const Duration(milliseconds: 250), () { + // avoid resizing to fast when build is called multiple time + webviewReference.resize(_rect!); + }); + } + } + }, + child: widget.initialChild ?? + const Center(child: const CircularProgressIndicator()), + ), + ); + } +} + +class _WebviewPlaceholder extends SingleChildRenderObjectWidget { + const _WebviewPlaceholder({ + Key? key, + required this.onRectChanged, + Widget? child, + }) : super(key: key, child: child); + + final ValueChanged onRectChanged; + + @override + RenderObject createRenderObject(BuildContext context) { + return _WebviewPlaceholderRender( + onRectChanged: onRectChanged, + ); + } + + @override + void updateRenderObject( + BuildContext context, _WebviewPlaceholderRender renderObject) { + renderObject..onRectChanged = onRectChanged; + } +} + +class _WebviewPlaceholderRender extends RenderProxyBox { + _WebviewPlaceholderRender({ + RenderBox? child, + ValueChanged? onRectChanged, + }) : _callback = onRectChanged, + super(child); + + ValueChanged? _callback; + Rect? _rect; + + Rect? get rect => _rect; + + set onRectChanged(ValueChanged callback) { + if (callback != _callback) { + _callback = callback; + notifyRect(); + } + } + + void notifyRect() { + if (_callback != null && _rect != null) { + _callback!(_rect!); + } + } + + @override + void paint(PaintingContext context, Offset offset) { + super.paint(context, offset); + final rect = offset & size; + if (_rect != rect) { + _rect = rect; + notifyRect(); + } + } +} diff --git a/packages/flutter_webview_plugin/pubspec.yaml b/packages/flutter_webview_plugin/pubspec.yaml new file mode 100644 index 00000000..1bc8b4ec --- /dev/null +++ b/packages/flutter_webview_plugin/pubspec.yaml @@ -0,0 +1,28 @@ +name: flutter_webview_plugin +description: Plugin that allow Flutter to communicate with a native Webview. +authors: +- Hadrien Lejard +- Toufik Zitouni +- Pedia +- Simon Lightfoot +- Rafal Wachol +homepage: https://github.com/dart-flitter/flutter_webview_plugin +version: 0.3.11 +maintainer: Rafal Wachol (@RafalWachol) + +environment: + sdk: ">=2.12.0 <3.0.0" + +flutter: + plugin: + androidPackage: com.flutter_webview_plugin + pluginClass: FlutterWebviewPlugin + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + mockito: ^5.1.0 + flutter_test: + sdk: flutter diff --git a/packages/flutter_webview_plugin/scripts/before_build_apks.sh b/packages/flutter_webview_plugin/scripts/before_build_apks.sh new file mode 100755 index 00000000..6dc242a0 --- /dev/null +++ b/packages/flutter_webview_plugin/scripts/before_build_apks.sh @@ -0,0 +1,26 @@ +#!/bin/bash +wget https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip +mkdir android-sdk +unzip -qq sdk-tools-linux-3859397.zip -d android-sdk +export ANDROID_HOME=`pwd`/android-sdk +export PATH=`pwd`/android-sdk/tools/bin:$PATH +mkdir -p /home/travis/.android # silence sdkmanager warning +echo 'count=0' > /home/travis/.android/repositories.cfg # silence sdkmanager warning + # suppressing output of sdkmanager to keep log under 4MB (travis limit) +echo y | sdkmanager "tools" >/dev/null +echo y | sdkmanager "platform-tools" >/dev/null +echo y | sdkmanager "build-tools;28.0.3" >/dev/null +echo y | sdkmanager "platforms;android-28" >/dev/null +echo y | sdkmanager "extras;android;m2repository" >/dev/null +echo y | sdkmanager "extras;google;m2repository" >/dev/null +echo y | sdkmanager "patcher;v4" >/dev/null +sdkmanager --list +wget https://services.gradle.org/distributions/gradle-4.10.2-all.zip +unzip -qq gradle-4.10.2-all.zip +export GRADLE_HOME=$PWD/gradle-4.10.2 +export PATH=$GRADLE_HOME/bin:$PATH +gradle -v +git clone --depth=1 https://github.com/flutter/flutter.git +export PATH=`pwd`/flutter/bin:`pwd`/flutter/bin/cache/dart-sdk/bin:$PATH +flutter doctor +pub global activate flutter_plugin_tools \ No newline at end of file diff --git a/packages/flutter_webview_plugin/test/flutter_webview_plugin_test.dart b/packages/flutter_webview_plugin/test/flutter_webview_plugin_test.dart new file mode 100644 index 00000000..98c8be4e --- /dev/null +++ b/packages/flutter_webview_plugin/test/flutter_webview_plugin_test.dart @@ -0,0 +1,51 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; +import 'package:mockito/mockito.dart'; + +void main() { + late MockMethodChannel methodChannel; + late FlutterWebviewPlugin webview; + + setUp(() { + methodChannel = MockMethodChannel(); + webview = new FlutterWebviewPlugin.private(methodChannel); + }); + + group('Method channel invoke', () { + test('Should invoke close', () async { + webview.close(); + verify(methodChannel.invokeMethod('close')).called(1); + }); + test('Should invoke reload', () async { + webview.reload(); + verify(methodChannel.invokeMethod('reload')).called(1); + }); + test('Should invoke goBack', () async { + webview.goBack(); + verify(methodChannel.invokeMethod('back')).called(1); + }); + test('Should invoke goForward', () async { + webview.goForward(); + verify(methodChannel.invokeMethod('forward')).called(1); + }); + test('Should invoke hide', () async { + webview.hide(); + verify(methodChannel.invokeMethod('hide')).called(1); + }); + test('Should invoke show', () async { + webview.show(); + verify(methodChannel.invokeMethod('show')).called(1); + }); + test('Should invoke canGoBack', () async { + webview.canGoBack(); + verify(methodChannel.invokeMethod('canGoBack')).called(1); + }); + test('Should invoke canGoForward', () async { + webview.canGoForward(); + verify(methodChannel.invokeMethod('canGoForward')).called(1); + }); + }); +} + +class MockMethodChannel extends Mock implements MethodChannel {} diff --git a/packages/flutter_webview_plugin/travis.yml.bak b/packages/flutter_webview_plugin/travis.yml.bak new file mode 100644 index 00000000..d71963b5 --- /dev/null +++ b/packages/flutter_webview_plugin/travis.yml.bak @@ -0,0 +1,96 @@ +matrix: + include: + # # Job 1) Run analyzer + # - os: linux + # env: + # - SHARD=Analyze + # sudo: false + # addons: + # apt: + # # Flutter depends on /usr/lib/x86_64-linux-gnu/libstdc++.so.6 version GLIBCXX_3.4.18 + # sources: + # - ubuntu-toolchain-r-test # if we don't specify this, the libstdc++6 we get is the wrong version + # packages: + # - libstdc++6 + # - fonts-droid + # before_script: + # - git clone https://github.com/flutter/flutter.git + # - export PATH=`pwd`/flutter/bin:`pwd`/flutter/bin/cache/dart-sdk/bin:$PATH + # - flutter doctor + # - pub global activate flutter_plugin_tools + # script: + # - ./script/incremental_build.sh analyze + # Job 2) Run dart unit tests + - os: linux + env: + - SHARD=Format+Test + jdk: oraclejdk8 + sudo: false + addons: + apt: + # Flutter depends on /usr/lib/x86_64-linux-gnu/libstdc++.so.6 version GLIBCXX_3.4.18 + sources: + - ubuntu-toolchain-r-test # if we don't specify this, the libstdc++6 we get is the wrong version + - llvm-toolchain-precise # for clang-format-5.0 + packages: + - libstdc++6 + - fonts-droid + - clang-format-5.0 + before_script: + - git clone --depth=1 https://github.com/flutter/flutter.git + - export PATH=`pwd`/flutter/bin:`pwd`/flutter/bin/cache/dart-sdk/bin:$PATH + - flutter doctor + script: + - flutter test + # Job 3.1) Build example APKs and run Java tests, shard 1/2 + - os: linux + env: + - SHARD="Build example apks 1/2" + - PLUGIN_SHARDING="--shardIndex 0 --shardCount 2" + jdk: oraclejdk8 + sudo: false + addons: + apt: + # Flutter depends on /usr/lib/x86_64-linux-gnu/libstdc++.so.6 version GLIBCXX_3.4.18 + sources: + - ubuntu-toolchain-r-test # if we don't specify this, the libstdc++6 we get is the wrong version + packages: + - lib32stdc++6 # https://github.com/flutter/flutter/issues/6207 + - libstdc++6 + - fonts-droid + before_script: + - ./scripts/before_build_apks.sh + - export ANDROID_HOME=`pwd`/android-sdk + - export PATH=`pwd`/flutter/bin:`pwd`/flutter/bin/cache/dart-sdk/bin:$PATH + - export FLUTTER_HOME=`pwd`/flutter + script: + - cd example/android + - ./gradlew test -DflutterPath=$FLUTTER_HOME + # # Job 3.2) Build example APKs and run Java tests, shard 2/2 + # - os: linux + # env: + # - SHARD="Build example apks 2/2" + # - PLUGIN_SHARDING="--shardIndex 1 --shardCount 2" + # jdk: oraclejdk8 + # sudo: false + # addons: + # apt: + # # Flutter depends on /usr/lib/x86_64-linux-gnu/libstdc++.so.6 version GLIBCXX_3.4.18 + # sources: + # - ubuntu-toolchain-r-test # if we don't specify this, the libstdc++6 we get is the wrong version + # packages: + # - lib32stdc++6 # https://github.com/flutter/flutter/issues/6207 + # - libstdc++6 + # - fonts-droid + # before_script: + # - ./script/before_build_apks.sh + # - export ANDROID_HOME=`pwd`/android-sdk + # - export PATH=`pwd`/flutter/bin:`pwd`/flutter/bin/cache/dart-sdk/bin:$PATH + # script: + # - ./script/incremental_build.sh build-examples --apk + # - ./script/incremental_build.sh java-test # must come after apk build + + +cache: + directories: + - $HOME/.pub-cache \ No newline at end of file From 56c64b181ab30c42468b5023ad352fea56d65061 Mon Sep 17 00:00:00 2001 From: zzw <602129484@qq.com> Date: Wed, 23 Feb 2022 19:23:29 +0800 Subject: [PATCH 07/14] =?UTF-8?q?fix:=E7=A9=BA=E5=AE=89=E5=85=A8=E8=AD=A6?= =?UTF-8?q?=E5=91=8A=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/flutter_webview_plugin/lib/src/base.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/flutter_webview_plugin/lib/src/base.dart b/packages/flutter_webview_plugin/lib/src/base.dart index 6596807b..abdf87f4 100644 --- a/packages/flutter_webview_plugin/lib/src/base.dart +++ b/packages/flutter_webview_plugin/lib/src/base.dart @@ -226,7 +226,7 @@ class FlutterWebviewPlugin { } /// Execute Javascript inside webview - Future evalJavascript(String code) async { + Future evalJavascript(String code) async { final res = await _channel.invokeMethod('eval', {'code': code}); return res; } @@ -245,10 +245,10 @@ class FlutterWebviewPlugin { Future goBack() async => await _channel.invokeMethod('back'); /// Checks if webview can navigate back - Future canGoBack() async => await _channel.invokeMethod('canGoBack'); + Future canGoBack() async => await _channel.invokeMethod('canGoBack'); /// Checks if webview can navigate back - Future canGoForward() async => await _channel.invokeMethod('canGoForward'); + Future canGoForward() async => await _channel.invokeMethod('canGoForward'); /// Navigates forward on the Webview. Future goForward() async => await _channel.invokeMethod('forward'); @@ -299,7 +299,7 @@ class FlutterWebviewPlugin { final cookiesString = await evalJavascript('document.cookie'); final cookies = {}; - if (cookiesString?.isNotEmpty == true) { + if (cookiesString != null && cookiesString.isNotEmpty == true) { cookiesString.split(';').forEach((String cookie) { final split = cookie.split('='); cookies[split[0]] = split[1]; From bfa730d846e242ee59e55fa884a10ab8fecbdd3d Mon Sep 17 00:00:00 2001 From: zzw <602129484@qq.com> Date: Mon, 18 Apr 2022 17:13:11 +0800 Subject: [PATCH 08/14] feat:add camera_picker --- .../.all-contributorsrc | 99 ++ .../ISSUE_TEMPLATE/bug-report--bug---.md | 45 + .../.github/ISSUE_TEMPLATE/config.yml | 1 + .../ISSUE_TEMPLATE/feature-request-------.md | 30 + .../.github/workflows/publish.yml | 20 + .../.github/workflows/publishable.yml | 27 + .../.github/workflows/runnable.yml | 68 + .../flutter_wechat_camera_picker/.gitignore | 76 + .../flutter_wechat_camera_picker/.metadata | 10 + .../flutter_wechat_camera_picker/CHANGELOG.md | 184 +++ packages/flutter_wechat_camera_picker/LICENSE | 201 +++ .../flutter_wechat_camera_picker/README-ZH.md | 129 ++ .../flutter_wechat_camera_picker/README.md | 141 ++ .../flutter_wechat_camera_picker/_config.yml | 1 + .../analysis_options.yaml | 229 +++ .../example/.gitignore | 43 + .../example/.metadata | 10 + .../example/README.md | 16 + .../example/android/.gitignore | 7 + .../example/android/app/build.gradle | 77 + .../android/app/src/main/AndroidManifest.xml | 53 + .../ExampleAppGlideModule.kt | 7 + .../wechat_camera_picker_demo/MainActivity.kt | 5 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values/styles.xml | 18 + .../example/android/build.gradle | 31 + .../example/android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 6 + .../example/android/key.jks | Bin 0 -> 4255 bytes .../example/android/settings.gradle | 15 + .../example/ios/.gitignore | 32 + .../ios/Flutter/AppFrameworkInfo.plist | 26 + .../example/ios/Flutter/Debug.xcconfig | 2 + .../example/ios/Flutter/Release.xcconfig | 2 + .../ios/Runner.xcodeproj/project.pbxproj | 583 +++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 91 ++ .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../example/ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 122 ++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 564 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 1588 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 1025 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 1716 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 1920 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 1895 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 3831 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 1888 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 3294 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 3612 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 + .../ios/Runner/Base.lproj/Main.storyboard | 26 + .../example/ios/Runner/Info.plist | 51 + .../ios/Runner/Runner-Bridging-Header.h | 1 + .../example/lib/main.dart | 91 ++ .../example/pubspec.yaml | 18 + .../lib/src/constants/config.dart | 140 ++ .../lib/src/constants/constants.dart | 13 + .../lib/src/constants/styles.dart | 11 + .../camera_picker_text_delegate.dart | 178 +++ .../lib/src/internals/enums.dart | 8 + .../lib/src/internals/extensions.dart | 28 + .../lib/src/internals/methods.dart | 26 + .../lib/src/internals/type_defs.dart | 60 + .../builder/tween_animation_builder_2.dart | 41 + .../lib/src/widgets/camera_picker.dart | 1397 +++++++++++++++++ .../src/widgets/camera_picker_page_route.dart | 77 + .../lib/src/widgets/camera_picker_viewer.dart | 495 ++++++ .../src/widgets/circular_progress_bar.dart | 120 ++ .../src/widgets/exposure_point_widget.dart | 110 ++ .../lib/wechat_camera_picker.dart | 9 + .../flutter_wechat_camera_picker/pubspec.yaml | 18 + 91 files changed, 5467 insertions(+) create mode 100644 packages/flutter_wechat_camera_picker/.all-contributorsrc create mode 100644 packages/flutter_wechat_camera_picker/.github/ISSUE_TEMPLATE/bug-report--bug---.md create mode 100644 packages/flutter_wechat_camera_picker/.github/ISSUE_TEMPLATE/config.yml create mode 100644 packages/flutter_wechat_camera_picker/.github/ISSUE_TEMPLATE/feature-request-------.md create mode 100644 packages/flutter_wechat_camera_picker/.github/workflows/publish.yml create mode 100644 packages/flutter_wechat_camera_picker/.github/workflows/publishable.yml create mode 100644 packages/flutter_wechat_camera_picker/.github/workflows/runnable.yml create mode 100644 packages/flutter_wechat_camera_picker/.gitignore create mode 100644 packages/flutter_wechat_camera_picker/.metadata create mode 100644 packages/flutter_wechat_camera_picker/CHANGELOG.md create mode 100644 packages/flutter_wechat_camera_picker/LICENSE create mode 100644 packages/flutter_wechat_camera_picker/README-ZH.md create mode 100644 packages/flutter_wechat_camera_picker/README.md create mode 100644 packages/flutter_wechat_camera_picker/_config.yml create mode 100644 packages/flutter_wechat_camera_picker/analysis_options.yaml create mode 100644 packages/flutter_wechat_camera_picker/example/.gitignore create mode 100644 packages/flutter_wechat_camera_picker/example/.metadata create mode 100644 packages/flutter_wechat_camera_picker/example/README.md create mode 100644 packages/flutter_wechat_camera_picker/example/android/.gitignore create mode 100644 packages/flutter_wechat_camera_picker/example/android/app/build.gradle create mode 100644 packages/flutter_wechat_camera_picker/example/android/app/src/main/AndroidManifest.xml create mode 100644 packages/flutter_wechat_camera_picker/example/android/app/src/main/kotlin/com/fluttercandies/wechat_camera_picker_demo/ExampleAppGlideModule.kt create mode 100644 packages/flutter_wechat_camera_picker/example/android/app/src/main/kotlin/com/fluttercandies/wechat_camera_picker_demo/MainActivity.kt create mode 100644 packages/flutter_wechat_camera_picker/example/android/app/src/main/res/drawable/launch_background.xml create mode 100644 packages/flutter_wechat_camera_picker/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 packages/flutter_wechat_camera_picker/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 packages/flutter_wechat_camera_picker/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 packages/flutter_wechat_camera_picker/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 packages/flutter_wechat_camera_picker/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 packages/flutter_wechat_camera_picker/example/android/app/src/main/res/values/styles.xml create mode 100644 packages/flutter_wechat_camera_picker/example/android/build.gradle create mode 100644 packages/flutter_wechat_camera_picker/example/android/gradle.properties create mode 100644 packages/flutter_wechat_camera_picker/example/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 packages/flutter_wechat_camera_picker/example/android/key.jks create mode 100644 packages/flutter_wechat_camera_picker/example/android/settings.gradle create mode 100644 packages/flutter_wechat_camera_picker/example/ios/.gitignore create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Flutter/AppFrameworkInfo.plist create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Flutter/Debug.xcconfig create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Flutter/Release.xcconfig create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner.xcodeproj/project.pbxproj create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/AppDelegate.swift create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/Base.lproj/Main.storyboard create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/Info.plist create mode 100644 packages/flutter_wechat_camera_picker/example/ios/Runner/Runner-Bridging-Header.h create mode 100644 packages/flutter_wechat_camera_picker/example/lib/main.dart create mode 100644 packages/flutter_wechat_camera_picker/example/pubspec.yaml create mode 100644 packages/flutter_wechat_camera_picker/lib/src/constants/config.dart create mode 100644 packages/flutter_wechat_camera_picker/lib/src/constants/constants.dart create mode 100644 packages/flutter_wechat_camera_picker/lib/src/constants/styles.dart create mode 100644 packages/flutter_wechat_camera_picker/lib/src/delegates/camera_picker_text_delegate.dart create mode 100644 packages/flutter_wechat_camera_picker/lib/src/internals/enums.dart create mode 100644 packages/flutter_wechat_camera_picker/lib/src/internals/extensions.dart create mode 100644 packages/flutter_wechat_camera_picker/lib/src/internals/methods.dart create mode 100644 packages/flutter_wechat_camera_picker/lib/src/internals/type_defs.dart create mode 100644 packages/flutter_wechat_camera_picker/lib/src/widgets/builder/tween_animation_builder_2.dart create mode 100644 packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker.dart create mode 100644 packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker_page_route.dart create mode 100644 packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker_viewer.dart create mode 100644 packages/flutter_wechat_camera_picker/lib/src/widgets/circular_progress_bar.dart create mode 100644 packages/flutter_wechat_camera_picker/lib/src/widgets/exposure_point_widget.dart create mode 100644 packages/flutter_wechat_camera_picker/lib/wechat_camera_picker.dart create mode 100644 packages/flutter_wechat_camera_picker/pubspec.yaml diff --git a/packages/flutter_wechat_camera_picker/.all-contributorsrc b/packages/flutter_wechat_camera_picker/.all-contributorsrc new file mode 100644 index 00000000..dfe849f3 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/.all-contributorsrc @@ -0,0 +1,99 @@ +{ + "files": [ + "README.md" + ], + "imageSize": 50, + "badgeTemplate": "[![All Contributors](https://img.shields.io/badge/all_contributors-<%= contributors.length %>-orange.svg?style=flat-square&logo=github)](#contributors)", + "commit": false, + "contributors": [ + { + "login": "AlexV525", + "name": "Alex Li", + "avatar_url": "https://avatars1.githubusercontent.com/u/15884415?v=4", + "profile": "https://blog.alexv525.com", + "contributions": [ + "code", + "design", + "doc", + "example", + "ideas", + "maintenance", + "question", + "review" + ] + }, + { + "login": "CaiJingLong", + "name": "Caijinglong", + "avatar_url": "https://avatars0.githubusercontent.com/u/14145407?v=4", + "profile": "https://www.kikt.top", + "contributions": [ + "example", + "ideas" + ] + }, + { + "login": "LaelLuo", + "name": "Lael", + "avatar_url": "https://avatars3.githubusercontent.com/u/26056971?v=4", + "profile": "https://github.com/LaelLuo", + "contributions": [ + "doc" + ] + }, + { + "login": "mjl0602", + "name": "mjl0602", + "avatar_url": "https://avatars1.githubusercontent.com/u/32868496?v=4", + "profile": "https://github.com/mjl0602", + "contributions": [ + "code", + "ideas" + ] + }, + { + "login": "siyukok", + "name": "AliasWang", + "avatar_url": "https://avatars0.githubusercontent.com/u/21030561?v=4", + "profile": "https://github.com/siyukok", + "contributions": [ + "code", + "ideas" + ] + }, + { + "login": "leftcoding", + "name": "leftcoding", + "avatar_url": "https://avatars.githubusercontent.com/u/7122926?v=4", + "profile": "https://github.com/leftcoding", + "contributions": [ + "bug" + ] + }, + { + "login": "TheVinhLuong", + "name": "Luong The Vinh", + "avatar_url": "https://avatars.githubusercontent.com/u/20371879?v=4", + "profile": "https://github.com/TheVinhLuong", + "contributions": [ + "code" + ] + }, + { + "login": "luomo-pro", + "name": "luomo-pro", + "avatar_url": "https://avatars.githubusercontent.com/u/41097395?v=4", + "profile": "https://github.com/luomo-pro", + "contributions": [ + "a11y", + "bug" + ] + } + ], + "contributorsPerLine": 7, + "projectName": "flutter_wechat_camera_picker", + "projectOwner": "fluttercandies", + "repoType": "github", + "repoHost": "https://github.com", + "skipCi": true +} diff --git a/packages/flutter_wechat_camera_picker/.github/ISSUE_TEMPLATE/bug-report--bug---.md b/packages/flutter_wechat_camera_picker/.github/ISSUE_TEMPLATE/bug-report--bug---.md new file mode 100644 index 00000000..4976f9a7 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/.github/ISSUE_TEMPLATE/bug-report--bug---.md @@ -0,0 +1,45 @@ +--- +name: Bug report (BUG模板) +about: Create a bug report helping us fix it. (创建一个 BUG 报告以帮助我们进行修复) +title: "[BUG] Error with something" +labels: await investigate, bug + +--- + +**Describe the bug** + + +**How to reproduce** + + +Steps to reproduce the behavior: + + +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. Error occurred. + +**Expected behavior** + + +**Screenshots (If contains)** + + +**Version information** + +- Device: [e.g. iPhone X] +- OS: [e.g. iOS 14.7.1] +- Package Version: [e.g. 2.4.1] +- Flutter Version: [e.g. v2.5.0] + +**Additional context** + diff --git a/packages/flutter_wechat_camera_picker/.github/ISSUE_TEMPLATE/config.yml b/packages/flutter_wechat_camera_picker/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..3ba13e0c --- /dev/null +++ b/packages/flutter_wechat_camera_picker/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/packages/flutter_wechat_camera_picker/.github/ISSUE_TEMPLATE/feature-request-------.md b/packages/flutter_wechat_camera_picker/.github/ISSUE_TEMPLATE/feature-request-------.md new file mode 100644 index 00000000..1af355e5 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/.github/ISSUE_TEMPLATE/feature-request-------.md @@ -0,0 +1,30 @@ +--- +name: Feature request (功能请求) +about: Request a new feature that the package didn't include. (请求一个依赖并未包含的功能) +title: "[Feature] Request a feature with something" +labels: feature, await investigate + +--- + +**Version information** + - Device: *e.g. iPhone X* + - OS: *e.g. iOS 14.7.1* + - Package Version: *e.g. v2.4.1* + - Flutter Version: *e.g. v2.5.0* + +**Is your feature request related to a problem?** + + +**Describe the solution you'd like** + + +**Describe alternatives you've considered** + + +**Additional context** + diff --git a/packages/flutter_wechat_camera_picker/.github/workflows/publish.yml b/packages/flutter_wechat_camera_picker/.github/workflows/publish.yml new file mode 100644 index 00000000..379474e4 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/.github/workflows/publish.yml @@ -0,0 +1,20 @@ +name: Publish + +on: + release: + types: [published] + workflow_dispatch: + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v1 + - name: Publish + uses: sakebook/actions-flutter-pub-publisher@v1.3.1 + with: + credential: ${{ secrets.CREDENTIAL_JSON }} + flutter_package: true + skip_test: true + dry_run: false diff --git a/packages/flutter_wechat_camera_picker/.github/workflows/publishable.yml b/packages/flutter_wechat_camera_picker/.github/workflows/publishable.yml new file mode 100644 index 00000000..f04eea90 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/.github/workflows/publishable.yml @@ -0,0 +1,27 @@ +name: Publishable + +on: + push: + branches: + - master + pull_request: + +jobs: + dry_run: + name: Dry run publish on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-latest] + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-java@v1 + with: + java-version: '11.x' + - uses: subosito/flutter-action@v1 + with: + channel: 'stable' + - run: dart --version + - run: flutter --version + - run: flutter pub get + - run: flutter pub publish --dry-run diff --git a/packages/flutter_wechat_camera_picker/.github/workflows/runnable.yml b/packages/flutter_wechat_camera_picker/.github/workflows/runnable.yml new file mode 100644 index 00000000..5dacff49 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/.github/workflows/runnable.yml @@ -0,0 +1,68 @@ +name: Runnable (stable) + +on: + push: + branches: + - master + pull_request: + +jobs: + analyze: + name: Analyze on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-java@v1 + with: + java-version: '11.x' + - uses: subosito/flutter-action@v1 + with: + channel: 'stable' + - run: dart --version + - run: flutter --version + - run: flutter pub get + - run: flutter analyze lib example/lib + + test_iOS: + needs: analyze + name: Test iOS on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-latest] + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-java@v1 + with: + java-version: '11.x' + - uses: subosito/flutter-action@v1 + with: + channel: 'stable' + - run: dart --version + - run: flutter --version + - run: flutter pub get + - run: cd example; flutter build ios --no-codesign + + test_android: + needs: analyze + name: Test Android on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-java@v1 + with: + java-version: '11.x' + - uses: subosito/flutter-action@v1 + with: + channel: 'stable' + - run: dart --version + - run: flutter --version + - run: flutter pub get + - run: sudo echo "y" | sudo $ANDROID_HOME/tools/bin/sdkmanager "ndk;20.0.5594570" + - run: cd example; flutter build apk --debug diff --git a/packages/flutter_wechat_camera_picker/.gitignore b/packages/flutter_wechat_camera_picker/.gitignore new file mode 100644 index 00000000..44b5f1eb --- /dev/null +++ b/packages/flutter_wechat_camera_picker/.gitignore @@ -0,0 +1,76 @@ +# 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 +*.lock \ No newline at end of file diff --git a/packages/flutter_wechat_camera_picker/.metadata b/packages/flutter_wechat_camera_picker/.metadata new file mode 100644 index 00000000..eeda8507 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/.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: stable + +project_type: package diff --git a/packages/flutter_wechat_camera_picker/CHANGELOG.md b/packages/flutter_wechat_camera_picker/CHANGELOG.md new file mode 100644 index 00000000..3e410d6f --- /dev/null +++ b/packages/flutter_wechat_camera_picker/CHANGELOG.md @@ -0,0 +1,184 @@ +# Changelog + +## 3.0.1 + +### Fixes + +- Remove redundant dispose with the controller. + +## 3.0.0 + +### New features + +- Add full semantics support. (#72) +- Add `lockCaptureOrientation`, allowing users to determine lock to the specific orientation during captures. (#68) +- Export `CameraPickerPageRoute`. +- Abstract `CamearPickerConfig`, which moved all arguments from `pickFromCamera` to `pickerConfig`. + +### Improvements + +- Improve camera initializes by adding a lock. +- Tweak asynchronous methods call during initializations. +- Make camera controllers available as soon as possible. + +### Fixes + +- Fix scaling issues with turns and orientations. +- Fix lint issues on Flutter 2.10. + +## 2.6.5 + +- Remove duplicate future requests when saving an entity. + +## 2.6.4 + +- Drop initialize when the controller has been already initialized. (#70) + +## 2.6.3 + +- Fix set exposure point crashes when switching between cameras. (#66) + +## 2.6.2 + +- Bind circular progress color with the theme. + +## 2.6.1 + +- Allow saving entities when the permission is limited on iOS. + +## 2.6.0 + +- Add `preferredLensDirection`, allowing users to choose which lens direction is preferred when first using the camera. +- Add `enableTapRecording`, allowing users to determine whether to allow the record can start with a single tap. +- Add `shouldAutoPreviewVideo`, allowing users to determine whether the video should be played instantly in the preview. + +## 2.5.2 + +- Request the permission state again when saving. +- Provide better experiences when video records need to be prepared. + +## 2.5.1 + +- Fix invalid widgets binding observer caller. + +## 2.5.0 + +- Add `onError` to handle errors during the picking process. +- `SaveEntityCallback` -> `EntitySaveCallback`. +- Improve folder structure of the plugin. + +## 2.4.2 + +- Flip the preview if the user is using a front camera. + +## 2.4.1 + +- Handle save exceptions more gracefully. +- Dispose the controller when previewing for better performance. + +## 2.4.0 + +- Bump `camera` to `0.9.x` . +- Remove `shouldLockPortrait` in picking. + +## 2.3.1 + +- Expose `enablePullToZoomInRecord` for the `pickFromCamera` method. +- Trigger shooting preparation only when start recording on iOS. + +## 2.3.0 + +- Expose `useRootNavigator` while picking. +- Initialize a new controller if failed to stop recording. (#39) +- Throw or rethrow exceptions that used to be caught. (#41) +- Update the back icon with preview. +- Enhance video capture on iOS with preparation. + +## 2.2.0 + +- Add `EntitySaveCallback` for the custom save method. + +## 2.1.2 + +- Improve the UI of the exposure point widget when manually focus. + +## 2.1.1 + +- Use basename when saving entities. + +## 2.1.0 + +- Add `shouldLockPortrait` to fit orientation for the device. +- Fix exposure offset issue when resetting the exposure point after adjusting the exposure offset manually. + +## 2.0.0 + +### New Features + +- Add `enableSetExposure`, allowing users to update the exposure from the point tapped on the screen. +- Add `enableExposureControlOnPoint`, allowing users to control the exposure offset with an offset slide from the exposure point. +- Add `enablePinchToZoom`, allowing users to zoom by pinching the screen. +- Add `enablePullToZoomInRecord`, allowing users to zoom by pulling up when recording video. +- Add `foregroundBuilder`, allowing users to build customized widgets beyond the camera preview. +- Add `shouldDeletePreviewFile`, allowing users to choose whether the captured file should be deleted. +- Sync `imageFormatGroup` from the `camera` plugin. + +### Breaking Changes + +- Migrate to non-nullable by default. +- `isAllowRecording` -> `enableRecording` +- `isOnlyAllowRecording` -> `onlyAllowRecording` + +### Fixes + +- All fixes from the `camera` plugin. + +## 1.3.1 + +- Constraint dependencies version. #22 + +## 1.3.0 + +- Add `enableAudio` parameter to control whether the package will require audio integration. #17 + +## 1.2.3 + +- Fix `maximumRecordingDuration` not passed in static method. #14 + +## 1.2.2 + +- Raise dependencies versions. + +## 1.2.1 + +- Add `cameraQuarterTurns`. + +## 1.2.0 + +- Expose resolution preset control. + +## 1.1.2 + +- Remove common exports. + +## 1.1.1 + +- Documents update. + +## 1.1.0+1 + +- Link confirm button's text with delegate. Fix #6. + +## 1.1.0 + +- Add `isOnlyAllowRecording` . Resolves #4. +- Make camera switching available. Resolves #5. + +## 1.0.0+1 + +- Fix potential non exist directory access. + +## 1.0.0 + +- Support taking pictures and videos. +- Support video recording duration limitation. diff --git a/packages/flutter_wechat_camera_picker/LICENSE b/packages/flutter_wechat_camera_picker/LICENSE new file mode 100644 index 00000000..e2509f39 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2020] [AlexV525] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/packages/flutter_wechat_camera_picker/README-ZH.md b/packages/flutter_wechat_camera_picker/README-ZH.md new file mode 100644 index 00000000..dd126ca1 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/README-ZH.md @@ -0,0 +1,129 @@ +# Flutter WeChat Camera Picker + +[![pub package](https://img.shields.io/pub/v/wechat_camera_picker?logo=dart&label=%E7%A8%B3%E5%AE%9A%E7%89%88&style=flat-square)](https://pub.flutter-io.cn/packages/wechat_camera_picker) +[![pub package](https://img.shields.io/pub/v/wechat_camera_picker?color=42a012&include_prereleases&label=%E5%BC%80%E5%8F%91%E7%89%88&logo=dart&style=flat-square)](https://pub.flutter-io.cn/packages/wechat_camera_picker) +[![GitHub stars](https://img.shields.io/github/stars/fluttercandies/flutter_wechat_camera_picker?logo=github&style=flat-square)](https://github.com/fluttercandies/flutter_wechat_camera_picker/stargazers) +[![GitHub forks](https://img.shields.io/github/forks/fluttercandies/flutter_wechat_camera_picker?logo=github&style=flat-square)](https://github.com/fluttercandies/flutter_wechat_camera_picker/network) +[![Build status](https://img.shields.io/github/workflow/status/fluttercandies/flutter_wechat_camera_picker/Build%20test?label=%E7%8A%B6%E6%80%81&logo=github&style=flat-square)](https://github.com/fluttercandies/flutter_wechat_camera_picker/actions?query=workflow%3A%22Build+test%22) +[![CodeFactor](https://img.shields.io/codefactor/grade/github/fluttercandies/flutter_wechat_camera_picker?logo=codefactor&label=%E4%BB%A3%E7%A0%81%E8%B4%A8%E9%87%8F&logoColor=%23ffffff&style=flat-square)](https://www.codefactor.io/repository/github/fluttercandies/flutter_wechat_camera_picker) +[![GitHub license](https://img.shields.io/github/license/fluttercandies/flutter_wechat_camera_picker?style=flat-square&label=%E5%8D%8F%E8%AE%AE)](https://github.com/fluttercandies/flutter_wechat_camera_picker/blob/master/LICENSE) +FlutterCandies + +Language: [English](README.md) | 中文简体 + +[**仿微信资源选择器**](https://pub.flutter-io.cn/packages/wechat_assets_picker) 的扩展。 +基于 `camera` 实现相机相关功能,`photo_manager` 实现资源相关内容。 + +## 目录 🗂 + +- [Flutter WeChat Camera Picker](#flutter-wechat-camera-picker) + - [目录 🗂](#目录-) + - [特性 ✨](#特性-) + - [截图 📸](#截图-) + - [准备工作 🍭](#准备工作-) + - [使用方法 📖](#使用方法-) + - [简单的使用方法](#简单的使用方法) + - [使用配置](#使用配置) + - [常见问题 💭](#常见问题-) + - [iOS 上的预览在旋转时行为诡异](#iOS-上的预览在旋转时行为诡异) + +## 特性 ✨ + +- [x] 🔐 支持健全的空安全 +- [x] 💚 99% 的微信风格 +- [x] 📷 支持拍照 + - [x] ☀️ 支持设置曝光参数 + - [x] 🔍️ 支持捏合缩放 +- [x] 🎥 支持录像 + - [x] ⏱ 支持限制录像时间 + - [x] 🔍 支持录像时缩放 +- [x] 🖾 支持自定义前景 widget 构建 + +## 截图 📸 + +| ![](https://tva1.sinaimg.cn/large/007S8ZIlgy1ggtt6yrdqej30u01t017w.jpg) | ![](https://tva1.sinaimg.cn/large/007S8ZIlgy1ggtt6yh3x4j30u01t0wuo.jpg) | +|-------------------------------------------------------------------------|-------------------------------------------------------------------------| +| ![](https://tva1.sinaimg.cn/large/007S8ZIlgy1ggtt6z1h7xj30u01t01kx.jpg) | ![](https://tva1.sinaimg.cn/large/007S8ZIlgy1ggtt6zarvhj30u01t0x5f.jpg) | + +## 准备工作 🍭 + +### 版本限制 + +Flutter SDK:`>=2.0.0` 。 + +### 配置 + +- [wechat_assets_picker#准备工作](https://github.com/fluttercandies/flutter_wechat_assets_picker/blob/master/README-ZH.md#preparing-for-use-) +- [camera#installation](https://pub.flutter-io.cn/packages/camera#installation) + +## 使用方法 📖 + +### 简单的使用方法 + +```dart +final AssetEntity? entity = await CameraPicker.pickFromCamera(context); +``` + +### 使用配置 + +你可以使用 `CameraPickerConfig` 来调整选择时的行为。 + +```dart +final AssetEntity? entity = await CameraPicker.pickFromCamera( + context, + pickerConfig: const CameraPickerConfig(), +); +``` + +`CameraPickerConfig` 的成员说明: + +### 简单的使用方法 + +```dart +final AssetEntity? entity = await CameraPicker.pickFromCamera(context); +``` + +你可以使用 `CameraPickerConfig` 来调整选择时的行为。 + +```dart +final AssetEntity? entity = await CameraPicker.pickFromCamera( + context, + pickerConfig: const CameraPickerConfig(), +); +``` + +`CameraPickerConfig` 的成员说明: + +| 参数名 | 类型 | 描述 | 默认值 | +|------------------------------|-----------------------------|----------------------------------------------------|----------------------------------------| +| enableRecording | `bool` | 选择器是否可以录像 | `false` | +| onlyEnableRecording | `bool` | 选择器是否仅可以录像。只在 `enableRecording` 为 `true` 时有效。 | `false` | +| enableTapRecording | `bool` | 选择器是否可以单击录像。只在 `onlyEnableRecording` 为 `true` 时生效。 | `false` | +| enableAudio | `bool` | 选择器是否需要录制音频。只在 `enableRecording` 为 `true` 时有效。 | `true` | +| enableSetExposure | `bool` | 用户是否可以在界面上通过点击设定曝光点 | `true` | +| enableExposureControlOnPoint | `bool` | 用户是否可以根据已经设置的曝光点调节曝光度 | `true` | +| enablePinchToZoom | `bool` | 用户是否可以在界面上双指缩放相机对焦 | `true` | +| enablePullToZoomInRecord | `bool` | 用户是否可以在录制视频时上拉缩放 | `true` | +| shouldDeletePreviewFile | `bool` | 返回页面时是否删除预览文件 | `false` | +| shouldAutoPreviewVideo | `bool` | 在预览时是否直接播放视频 | `false` | +| maximumRecordingDuration | `Duration` | 录制视频最长时长 | `const Duration(seconds: 15)` | +| theme | `ThemeData?` | 选择器的主题 | `CameraPicker.themeData(C.themeColor)` | +| textDelegate | `CameraPickerTextDelegate?` | 控制部件中的文字实现 | `DefaultCameraPickerTextDelegate` | +| resolutionPreset | `ResolutionPreset` | 相机的分辨率预设 | `ResolutionPreset.max` | +| cameraQuarterTurns | `int` | 摄像机视图顺时针旋转次数,每次 90 度 | `0` | +| imageFormatGroup | `ImageFormatGroup` | 输出图像的格式描述 | `ImageFormatGroup.unknown` | +| preferredLensDirection | `CameraLensDirection` | 首次使用相机时首选的镜头方向 | `CameraLensDirection.back` | +| lockCaptureOrientation | `DeviceOrientation?` | 摄像机在拍摄时锁定的旋转角度 | null | +| foregroundBuilder | `ForegroundBuilder?` | 覆盖在相机预览上方的前景构建 | null | +| previewTransformBuilder | `PreviewTransformBuilder?` | 对相机预览做变换的构建 | null | +| onEntitySaving | `EntitySaveCallback?` | 在查看器中保存图片时的回调 | null | +| onError | `CameraErrorHandler?` | 拍摄照片过程中的自定义错误处理 | null | + +## 常见问题 💭 + +### iOS 上的预览在旋转时行为诡异 + +目前,iOS 上的预览画面在旋转时并未正确地同步, +你可以在这个 issue 里了解更多相关的信息: +https://github.com/flutter/flutter/issues/89216 。 +除此之外的问题,你可以提交 issue 进行提问。 diff --git a/packages/flutter_wechat_camera_picker/README.md b/packages/flutter_wechat_camera_picker/README.md new file mode 100644 index 00000000..f6a7bdb3 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/README.md @@ -0,0 +1,141 @@ +# Flutter WeChat Camera Picker + +[![pub package](https://img.shields.io/pub/v/wechat_camera_picker?logo=dart&label=stable&style=flat-square)](https://pub.dev/packages/wechat_camera_picker) +[![pub package](https://img.shields.io/pub/v/wechat_camera_picker?color=42a012&include_prereleases&label=dev&logo=dart&style=flat-square)](https://pub.dev/packages/wechat_camera_picker) +[![GitHub stars](https://img.shields.io/github/stars/fluttercandies/flutter_wechat_camera_picker?logo=github&style=flat-square)](https://github.com/fluttercandies/flutter_wechat_camera_picker/stargazers) +[![GitHub forks](https://img.shields.io/github/forks/fluttercandies/flutter_wechat_camera_picker?logo=github&style=flat-square)](https://github.com/fluttercandies/flutter_wechat_camera_picker/network) +[![Build status](https://img.shields.io/github/workflow/status/fluttercandies/flutter_wechat_camera_picker/Build%20test?label=CI&logo=github&style=flat-square)](https://github.com/fluttercandies/flutter_wechat_camera_picker/actions?query=workflow%3A%22Build+test%22) +[![CodeFactor](https://img.shields.io/codefactor/grade/github/fluttercandies/flutter_wechat_camera_picker?logo=codefactor&logoColor=%23ffffff&style=flat-square)](https://www.codefactor.io/repository/github/fluttercandies/flutter_wechat_camera_picker) +[![GitHub license](https://img.shields.io/github/license/fluttercandies/flutter_wechat_camera_picker?style=flat-square)](https://github.com/fluttercandies/flutter_wechat_camera_picker/blob/master/LICENSE) +FlutterCandies + +Language: English | [中文简体](README-ZH.md) + +A **camera picker** which is an extension for +[wechat_assets_picker](https://pub.dev/packages/wechat_assets_picker). +Based on `camera` for camera functions and `photo_manager` for asset implementation. + +## Category 🗂 + +- [Flutter WeChat Camera Picker](#flutter-wechat-camera-picker) + - [Category 🗂](#category-) + - [Features ✨](#features-) + - [Screenshots 📸](#screenshots-) + - [Preparing for use 🍭](#preparing-for-use-) + - [Usage 📖](#usage-) + - [Simple usage](#simple-usage) + - [With configurations](#with-configurations) + - [Frequently asked question 💭](#frequently-asked-question-) + - [Why the orientation behavior is strange on iOS?](#why-the-orientation-behavior-is-strange-on-ios) + - [Contributors ✨](#contributors-) + +## Features ✨ + +- [x] 🔐 Non-nullable by default +- [x] 💚 99% similar to WeChat style +- [x] 📷 Picture taking support + - [x] ☀️ Exposure adjust support + - [x] 🔍️ Scale with pinch support +- [x] 🎥 Video recording support + - [x] ⏱ Duration limitation support + - [x] 🔍 Scale when recording support +- [x] 🖾 Foreground custom widget builder support + +## Screenshots 📸 + +| ![](https://tva1.sinaimg.cn/large/007S8ZIlgy1ggtt6yrdqej30u01t017w.jpg) | ![](https://tva1.sinaimg.cn/large/007S8ZIlgy1ggtt6yh3x4j30u01t0wuo.jpg) | +|-------------------------------------------------------------------------|-------------------------------------------------------------------------| +| ![](https://tva1.sinaimg.cn/large/007S8ZIlgy1ggtt6z1h7xj30u01t01kx.jpg) | ![](https://tva1.sinaimg.cn/large/007S8ZIlgy1ggtt6zarvhj30u01t0x5f.jpg) | + +## Preparing for use 🍭 + +### Version constraints + +Flutter SDK: `>=2.0.0` . + +### Setup + +- [wechat_assets_picker#preparing-for-use](https://github.com/fluttercandies/flutter_wechat_assets_picker#preparing-for-use-) +- [camera#installation](https://pub.dev/packages/camera#installation) + +## Usage 📖 + +### Simple usage + +```dart +final AssetEntity? entity = await CameraPicker.pickFromCamera(context); +``` + +### With configurations + +Use `CameraPickerConfig` for more picking behaviors. + +```dart +final AssetEntity? entity = await CameraPicker.pickFromCamera( + context, + pickerConfig: const CameraPickerConfig(), +); +``` + +Fields in `CameraPickerConfig`: + +| Name | Type | Description | Default Value | +|------------------------------|-----------------------------|-------------------------------------------------------------------------------------------------------|----------------------------------------| +| enableRecording | `bool` | Whether the picker can record video. | `false` | +| onlyEnableRecording | `bool` | Whether the picker can only record video. Only available when `enableRecording` is `true `. | `false` | +| enableTapRecording | `bool` | Whether allow the record can start with single tap. Only available when `enableRecording` is `true `. | `false` | +| enableAudio | `bool` | Whether Whether the picker should record audio. Only available with recording. | `true` | +| enableSetExposure | `bool` | Whether users can set the exposure point by tapping. | `true` | +| enableExposureControlOnPoint | `bool` | Whether users can adjust exposure according to the set point. | `true` | +| enablePinchToZoom | `bool` | Whether users can zoom the camera by pinch. | `true` | +| enablePullToZoomInRecord | `bool` | Whether users can zoom by pulling up when recording video. | `true` | +| shouldDeletePreviewFile | `bool` | Whether the preview file will be delete when pop. | `false` | +| shouldAutoPreviewVideo | `bool` | Whether the video should be played instantly in the preview. | `false` | +| maximumRecordingDuration | `Duration` | The maximum duration of the video recording process. | `const Duration(seconds: 15)` | +| theme | `ThemeData?` | Theme data for the picker. | `CameraPicker.themeData(C.themeColor)` | +| textDelegate | `CameraPickerTextDelegate?` | Text delegate that controls text in widgets. | `DefaultCameraPickerTextDelegate` | +| resolutionPreset | `ResolutionPreset` | Present resolution for the camera. | `ResolutionPreset.max` | +| cameraQuarterTurns | `int` | The number of clockwise quarter turns the camera view should be rotated. | `0` | +| imageFormatGroup | `ImageFormatGroup` | Describes the output of the raw image format. | `ImageFormatGroup.unknown` | +| preferredLensDirection | `CameraLensDirection` | Which lens direction is preferred when first using the camera. | `CameraLensDirection.back` | +| lockCaptureOrientation | `DeviceOrientation?` | Whether the camera should be locked to the specific orientation during captures. | null | +| foregroundBuilder | `ForegroundBuilder?` | The foreground widget builder which will cover the whole camera preview. | null | +| previewTransformBuilder | `PreviewTransformBuilder?` | The widget builder which will transform the camera preview. | null | +| onEntitySaving | `EntitySaveCallback?` | The callback type define for saving entity in the viewer. | null | +| onError | `CameraErrorHandler?` | The error handler when any error occurred during the picking process. | null | + +## Frequently asked question 💭 + +### Why the orientation behavior is strange on iOS? + +Currently, the preview is not correctly synced on the iOS. +You can find more details in this issue: +https://github.com/flutter/flutter/issues/89216 . +Other than that, please submit issues to describe your question. + +## Contributors ✨ + +Thank goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): + + + + + + + + + + + + + + + + +

Alex Li

💻 🎨 📖 💡 🤔 🚧 💬 👀

Caijinglong

💡 🤔

Lael

📖

mjl0602

💻 🤔

AliasWang

💻 🤔

leftcoding

🐛

Luong The Vinh

💻

luomo-pro

️️️️♿️ 🐛
+ + + + + +This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! diff --git a/packages/flutter_wechat_camera_picker/_config.yml b/packages/flutter_wechat_camera_picker/_config.yml new file mode 100644 index 00000000..c4192631 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-cayman \ No newline at end of file diff --git a/packages/flutter_wechat_camera_picker/analysis_options.yaml b/packages/flutter_wechat_camera_picker/analysis_options.yaml new file mode 100644 index 00000000..0cc1d70f --- /dev/null +++ b/packages/flutter_wechat_camera_picker/analysis_options.yaml @@ -0,0 +1,229 @@ +# Specify analysis options. +# +# Until there are meta linter rules, each desired lint must be explicitly enabled. +# See: https://github.com/dart-lang/linter/issues/288 +# +# For a list of lints, see: http://dart-lang.github.io/linter/lints/ +# See the configuration guide for more +# https://github.com/dart-lang/sdk/tree/master/pkg/analyzer#configuring-the-analyzer +# +# There are other similar analysis options files in the flutter repos, +# which should be kept in sync with this file: +# +# - analysis_options.yaml (this file) +# - packages/flutter/lib/analysis_options_user.yaml +# - https://github.com/flutter/plugins/blob/master/analysis_options.yaml +# - https://github.com/flutter/engine/blob/master/analysis_options.yaml +# +# This file contains the analysis options used by Flutter tools, such as IntelliJ, +# Android Studio, and the `flutter analyze` command. + +analyzer: + strong-mode: + implicit-casts: false + implicit-dynamic: false + errors: + # treat missing required parameters as a warning (not a hint) + missing_required_param: warning + # treat missing returns as a warning (not a hint) + missing_return: warning + # allow having TODOs in the code + todo: ignore + # allow self-reference to deprecated members (we do this because otherwise we have + # to annotate every member in every test, assert, etc, when we deprecate something) + deprecated_member_use_from_same_package: ignore + # Ignore analyzer hints for updating pubspecs when using Future or + # Stream and not importing dart:async + # Please see https://github.com/flutter/flutter/pull/24528 for details. + sdk_version_async_exported_from_core: ignore + exclude: + - "bin/cache/**" + # the following two are relative to the stocks example and the flutter package respectively + # see https://github.com/dart-lang/sdk/issues/28463 + - "lib/i18n/messages_*.dart" + - "lib/src/http/**" + - "test_fixes/**" + +linter: + rules: + # these rules are documented on and in the same order as + # the Dart Lint rules page to make maintenance easier + # https://github.com/dart-lang/linter/blob/master/example/all.yaml + - always_declare_return_types + - always_put_control_body_on_new_line + # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 + - always_require_non_null_named_parameters + - always_specify_types + # - always_use_package_imports # we do this commonly + - annotate_overrides + # - avoid_annotating_with_dynamic # conflicts with always_specify_types + # - avoid_as # required for implicit-casts: true + - avoid_bool_literals_in_conditional_expressions + # - avoid_catches_without_on_clauses # we do this commonly + # - avoid_catching_errors # we do this commonly + - avoid_classes_with_only_static_members + # - avoid_double_and_int_checks # only useful when targeting JS runtime + - avoid_empty_else + - avoid_equals_and_hash_code_on_mutable_classes + # - avoid_escaping_inner_quotes # not yet tested + - avoid_field_initializers_in_const_classes + - avoid_function_literals_in_foreach_calls + # - avoid_implementing_value_types # not yet tested + - avoid_init_to_null + # - avoid_js_rounded_ints # only useful when targeting JS runtime + - avoid_null_checks_in_equality_operators + # - avoid_positional_boolean_parameters # not yet tested + # - avoid_print # not yet tested + # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) + # - avoid_redundant_argument_values # not yet tested + - avoid_relative_lib_imports + - avoid_renaming_method_parameters + - avoid_return_types_on_setters + # - avoid_returning_null # there are plenty of valid reasons to return null + # - avoid_returning_null_for_future # not yet tested + - avoid_returning_null_for_void + # - avoid_returning_this # there are plenty of valid reasons to return this + # - avoid_setters_without_getters # not yet tested + - avoid_shadowing_type_parameters + - avoid_single_cascade_in_expression_statements + - avoid_slow_async_io + # - avoid_type_to_string # we do this commonly + - avoid_types_as_parameter_names + # - avoid_types_on_closure_parameters # conflicts with always_specify_types + # - avoid_unnecessary_containers # not yet tested + - avoid_unused_constructor_parameters + - avoid_void_async + # - avoid_web_libraries_in_flutter # not yet tested + - await_only_futures + - camel_case_extensions + - camel_case_types + - cancel_subscriptions + # - cascade_invocations # not yet tested + - cast_nullable_to_non_nullable + # - close_sinks # not reliable enough + # - comment_references # blocked on https://github.com/flutter/flutter/issues/20765 + # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 + - control_flow_in_finally + # - curly_braces_in_flow_control_structures # not required by flutter style + # - diagnostic_describe_all_properties # not yet tested + - directives_ordering + # - do_not_use_environment # we do this commonly + - empty_catches + - empty_constructor_bodies + - empty_statements + - exhaustive_cases + # - file_names # not yet tested + - flutter_style_todos + - hash_and_equals + - implementation_imports + # - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811 + - iterable_contains_unrelated_type + # - join_return_with_assignment # not required by flutter style + - leading_newlines_in_multiline_strings + - library_names + - library_prefixes + # - lines_longer_than_80_chars # not required by flutter style + - list_remove_unrelated_type + # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181 + # - missing_whitespace_between_adjacent_strings # not yet tested + - no_adjacent_strings_in_list + # - no_default_cases # too many false positives + - no_duplicate_case_values + - no_logic_in_create_state + # - no_runtimeType_toString # ok in tests; we enable this only in packages/ + - non_constant_identifier_names + - null_check_on_nullable_type_parameter + # - null_closures # not required by flutter style + # - omit_local_variable_types # opposite of always_specify_types + # - one_member_abstracts # too many false positives + # - only_throw_errors # https://github.com/flutter/flutter/issues/5792 + - overridden_fields + - package_api_docs + # - package_names # non conforming packages in sdk + - package_prefixed_library_names + # - parameter_assignments # we do this commonly + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + # - prefer_asserts_with_message # not required by flutter style + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + # - prefer_constructors_over_static_methods # far too many false positives + - prefer_contains + # - prefer_double_quotes # opposite of prefer_single_quotes + - prefer_equal_for_default_values + # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + - prefer_for_elements_to_map_fromIterable + - prefer_foreach + # - prefer_function_declarations_over_variables # not yet tested + - prefer_generic_function_type_aliases + - prefer_if_elements_to_conditional_expressions + - prefer_if_null_operators + - prefer_initializing_formals + - prefer_inlined_adds + # - prefer_int_literals # not yet tested + # - prefer_interpolation_to_compose_strings # not yet tested + - prefer_is_empty + - prefer_is_not_empty + - prefer_is_not_operator + - prefer_iterable_whereType + # - prefer_mixin # https://github.com/dart-lang/language/issues/32 + # - prefer_null_aware_operators # disable until NNBD, see https://github.com/flutter/flutter/pull/32711#issuecomment-492930932 + # - prefer_relative_imports # not yet tested + - prefer_single_quotes + - prefer_spread_collections + - prefer_typing_uninitialized_variables + - prefer_void_to_null + # - provide_deprecation_message # not yet tested + # - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml + - recursive_getters + # - sized_box_for_whitespace # not yet tested + - slash_for_doc_comments + # - sort_child_properties_last # not yet tested + - sort_constructors_first + # - sort_pub_dependencies # prevents separating pinned transitive dependencies + - sort_unnamed_constructors_first + - test_types_in_equals + - throw_in_finally + - tighten_type_of_initializing_formals + # - type_annotate_public_apis # subset of always_specify_types + - type_init_formals + # - unawaited_futures # too many false positives + # - unnecessary_await_in_return # not yet tested + - unnecessary_brace_in_string_interps + - unnecessary_const + # - unnecessary_final # conflicts with prefer_final_locals + - unnecessary_getters_setters + # - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498 + - unnecessary_new + - unnecessary_null_aware_assignments + # - unnecessary_null_checks # not yet tested + - unnecessary_null_in_if_null_operators + - unnecessary_nullable_for_final_variable_declarations + - unnecessary_overrides + - unnecessary_parenthesis + # - unnecessary_raw_strings # not yet tested + - unnecessary_statements + - unnecessary_string_escapes + - unnecessary_string_interpolations + - unnecessary_this + - unrelated_type_equality_checks + # - unsafe_html # not yet tested + - use_full_hex_values_for_flutter_colors + # - use_function_type_syntax_for_parameters # not yet tested + - use_is_even_rather_than_modulo + # - use_key_in_widget_constructors # not yet tested + - use_late_for_private_fields_and_variables + - use_raw_strings + - use_rethrow_when_possible + # - use_setters_to_change_properties # not yet tested + # - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182 + # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review + - valid_regexps + - void_checks \ No newline at end of file diff --git a/packages/flutter_wechat_camera_picker/example/.gitignore b/packages/flutter_wechat_camera_picker/example/.gitignore new file mode 100644 index 00000000..1ba9c339 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/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/packages/flutter_wechat_camera_picker/example/.metadata b/packages/flutter_wechat_camera_picker/example/.metadata new file mode 100644 index 00000000..ade6bc98 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/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: stable + +project_type: app diff --git a/packages/flutter_wechat_camera_picker/example/README.md b/packages/flutter_wechat_camera_picker/example/README.md new file mode 100644 index 00000000..8a501fdc --- /dev/null +++ b/packages/flutter_wechat_camera_picker/example/README.md @@ -0,0 +1,16 @@ +# wechat_camera_picker_demo + +A new Flutter project. + +## 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/packages/flutter_wechat_camera_picker/example/android/.gitignore b/packages/flutter_wechat_camera_picker/example/android/.gitignore new file mode 100644 index 00000000..bc2100d8 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/example/android/.gitignore @@ -0,0 +1,7 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java diff --git a/packages/flutter_wechat_camera_picker/example/android/app/build.gradle b/packages/flutter_wechat_camera_picker/example/android/app/build.gradle new file mode 100644 index 00000000..8d915f87 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/example/android/app/build.gradle @@ -0,0 +1,77 @@ +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 plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion flutter.compileSdkVersion + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + applicationId "com.fluttercandies.wechat_camera_picker_demo" + minSdkVersion 21 + targetSdkVersion flutter.compileSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + signingConfigs { + forAll { + storeFile file("${rootDir.absolutePath}/key.jks") + storePassword 'picker' + keyAlias 'picker' + keyPassword 'picker' + } + } + + buildTypes { + debug { + signingConfig signingConfigs.forAll + } + profile { + signingConfig signingConfigs.forAll + } + release { + signingConfig signingConfigs.forAll + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation 'com.github.bumptech.glide:glide:4.11.0' + kapt 'com.github.bumptech.glide:compiler:4.11.0' +} diff --git a/packages/flutter_wechat_camera_picker/example/android/app/src/main/AndroidManifest.xml b/packages/flutter_wechat_camera_picker/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..53cfe1f1 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/flutter_wechat_camera_picker/example/android/app/src/main/kotlin/com/fluttercandies/wechat_camera_picker_demo/ExampleAppGlideModule.kt b/packages/flutter_wechat_camera_picker/example/android/app/src/main/kotlin/com/fluttercandies/wechat_camera_picker_demo/ExampleAppGlideModule.kt new file mode 100644 index 00000000..59b579fc --- /dev/null +++ b/packages/flutter_wechat_camera_picker/example/android/app/src/main/kotlin/com/fluttercandies/wechat_camera_picker_demo/ExampleAppGlideModule.kt @@ -0,0 +1,7 @@ +package com.fluttercandies.wechat_camera_picker_demo + +import com.bumptech.glide.annotation.GlideModule +import com.bumptech.glide.module.AppGlideModule + +@GlideModule +class ExampleAppGlideModule : AppGlideModule() diff --git a/packages/flutter_wechat_camera_picker/example/android/app/src/main/kotlin/com/fluttercandies/wechat_camera_picker_demo/MainActivity.kt b/packages/flutter_wechat_camera_picker/example/android/app/src/main/kotlin/com/fluttercandies/wechat_camera_picker_demo/MainActivity.kt new file mode 100644 index 00000000..182c0525 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/example/android/app/src/main/kotlin/com/fluttercandies/wechat_camera_picker_demo/MainActivity.kt @@ -0,0 +1,5 @@ +package com.fluttercandies.wechat_camera_picker_demo + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/packages/flutter_wechat_camera_picker/example/android/app/src/main/res/drawable/launch_background.xml b/packages/flutter_wechat_camera_picker/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000..304732f8 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/flutter_wechat_camera_picker/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/flutter_wechat_camera_picker/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/packages/flutter_wechat_camera_picker/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/flutter_wechat_camera_picker/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/packages/flutter_wechat_camera_picker/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/flutter_wechat_camera_picker/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/packages/flutter_wechat_camera_picker/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/flutter_wechat_camera_picker/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/packages/flutter_wechat_camera_picker/example/android/app/src/main/res/values/styles.xml b/packages/flutter_wechat_camera_picker/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..1f83a33f --- /dev/null +++ b/packages/flutter_wechat_camera_picker/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/flutter_wechat_camera_picker/example/android/build.gradle b/packages/flutter_wechat_camera_picker/example/android/build.gradle new file mode 100644 index 00000000..538d08d5 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/example/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.6.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.0.3' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/packages/flutter_wechat_camera_picker/example/android/gradle.properties b/packages/flutter_wechat_camera_picker/example/android/gradle.properties new file mode 100644 index 00000000..94adc3a3 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/flutter_wechat_camera_picker/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/flutter_wechat_camera_picker/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..562c5e44 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/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-7.3.3-all.zip diff --git a/packages/flutter_wechat_camera_picker/example/android/key.jks b/packages/flutter_wechat_camera_picker/example/android/key.jks new file mode 100644 index 0000000000000000000000000000000000000000..5eb0b84ce730fcaaea7b3eda5f77dfc6f4f01d6c GIT binary patch literal 4255 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 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/packages/flutter_wechat_camera_picker/example/ios/.gitignore b/packages/flutter_wechat_camera_picker/example/ios/.gitignore new file mode 100644 index 00000000..e96ef602 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/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/packages/flutter_wechat_camera_picker/example/ios/Flutter/AppFrameworkInfo.plist b/packages/flutter_wechat_camera_picker/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000..f2872cf4 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/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 + 9.0 + + diff --git a/packages/flutter_wechat_camera_picker/example/ios/Flutter/Debug.xcconfig b/packages/flutter_wechat_camera_picker/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000..e8efba11 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/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/packages/flutter_wechat_camera_picker/example/ios/Flutter/Release.xcconfig b/packages/flutter_wechat_camera_picker/example/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000..399e9340 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/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/packages/flutter_wechat_camera_picker/example/ios/Runner.xcodeproj/project.pbxproj b/packages/flutter_wechat_camera_picker/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..acaf87a3 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,583 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXBuildFile section */ + 0A0E89E1223357E5C8DF60A9 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D89E84167F0EF469C20425C /* Pods_Runner.framework */; }; + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 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 = ""; }; + 180763EBD166204634D65430 /* 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 = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3D89E84167F0EF469C20425C /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 86785547D2CA1390AA68C185 /* 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 = ""; }; + 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; }; + 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 = ""; }; + FF4130927988AA89C13011AF /* 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 = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0A0E89E1223357E5C8DF60A9 /* Pods_Runner.framework in Frameworks */, + ); + 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 */, + AD82BCD6598FE589D9EE5885 /* Pods */, + C500A9C842C2026CDD7B2B42 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + ); + name = "Supporting Files"; + sourceTree = ""; + }; + AD82BCD6598FE589D9EE5885 /* Pods */ = { + isa = PBXGroup; + children = ( + 86785547D2CA1390AA68C185 /* Pods-Runner.debug.xcconfig */, + FF4130927988AA89C13011AF /* Pods-Runner.release.xcconfig */, + 180763EBD166204634D65430 /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + C500A9C842C2026CDD7B2B42 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 3D89E84167F0EF469C20425C /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + C187617FADA857B8B546364C /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 187C2E5DFF21C7CEB3EB430D /* [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 = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + 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 */ + 187C2E5DFF21C7CEB3EB430D /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 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"; + }; + C187617FADA857B8B546364C /* [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 = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift 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; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 64MAFNBS88; + 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.fluttercandies.wechatCameraPickerDemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + 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; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 64MAFNBS88; + 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.fluttercandies.wechatCameraPickerDemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 64MAFNBS88; + 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.fluttercandies.wechatCameraPickerDemo; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + 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/packages/flutter_wechat_camera_picker/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/flutter_wechat_camera_picker/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/flutter_wechat_camera_picker/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/flutter_wechat_camera_picker/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/flutter_wechat_camera_picker/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/flutter_wechat_camera_picker/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/flutter_wechat_camera_picker/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/flutter_wechat_camera_picker/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..3db53b6e --- /dev/null +++ b/packages/flutter_wechat_camera_picker/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/flutter_wechat_camera_picker/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/flutter_wechat_camera_picker/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..21a3cc14 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/flutter_wechat_camera_picker/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/flutter_wechat_camera_picker/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/flutter_wechat_camera_picker/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/flutter_wechat_camera_picker/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/flutter_wechat_camera_picker/example/ios/Runner/AppDelegate.swift b/packages/flutter_wechat_camera_picker/example/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000..70693e4a --- /dev/null +++ b/packages/flutter_wechat_camera_picker/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d36b1fab --- /dev/null +++ b/packages/flutter_wechat_camera_picker/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/packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 literal 0 HcmV?d00001 diff --git a/packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f091b6b0bca859a3f474b03065bef75ba58a9e4c GIT binary patch literal 1588 zcmV-42Fv-0P)C1SqPt}wig>|5Crh^=oyX$BK<}M8eLU3e2hGT;=G|!_SP)7zNI6fqUMB=)y zRAZ>eDe#*r`yDAVgB_R*LB*MAc)8(b{g{9McCXW!lq7r(btRoB9!8B-#AI6JMb~YFBEvdsV)`mEQO^&#eRKx@b&x- z5lZm*!WfD8oCLzfHGz#u7sT0^VLMI1MqGxF^v+`4YYnVYgk*=kU?HsSz{v({E3lb9 z>+xILjBN)t6`=g~IBOelGQ(O990@BfXf(DRI5I$qN$0Gkz-FSc$3a+2fX$AedL4u{ z4V+5Ong(9LiGcIKW?_352sR;LtDPmPJXI{YtT=O8=76o9;*n%_m|xo!i>7$IrZ-{l z-x3`7M}qzHsPV@$v#>H-TpjDh2UE$9g6sysUREDy_R(a)>=eHw-WAyfIN z*qb!_hW>G)Tu8nSw9yn#3wFMiLcfc4pY0ek1}8(NqkBR@t4{~oC>ryc-h_ByH(Cg5 z>ao-}771+xE3um9lWAY1FeQFxowa1(!J(;Jg*wrg!=6FdRX+t_<%z&d&?|Bn){>zm zZQj(aA_HeBY&OC^jj*)N`8fa^ePOU72VpInJoI1?`ty#lvlNzs(&MZX+R%2xS~5Kh zX*|AU4QE#~SgPzOXe9>tRj>hjU@c1k5Y_mW*Jp3fI;)1&g3j|zDgC+}2Q_v%YfDax z!?umcN^n}KYQ|a$Lr+51Nf9dkkYFSjZZjkma$0KOj+;aQ&721~t7QUKx61J3(P4P1 zstI~7-wOACnWP4=8oGOwz%vNDqD8w&Q`qcNGGrbbf&0s9L0De{4{mRS?o0MU+nR_! zrvshUau0G^DeMhM_v{5BuLjb#Hh@r23lDAk8oF(C+P0rsBpv85EP>4CVMx#04MOfG z;P%vktHcXwTj~+IE(~px)3*MY77e}p#|c>TD?sMatC0Tu4iKKJ0(X8jxQY*gYtxsC z(zYC$g|@+I+kY;dg_dE>scBf&bP1Nc@Hz<3R)V`=AGkc;8CXqdi=B4l2k|g;2%#m& z*jfX^%b!A8#bI!j9-0Fi0bOXl(-c^AB9|nQaE`*)Hw+o&jS9@7&Gov#HbD~#d{twV zXd^Tr^mWLfFh$@Dr$e;PBEz4(-2q1FF0}c;~B5sA}+Q>TOoP+t>wf)V9Iy=5ruQa;z)y zI9C9*oUga6=hxw6QasLPnee@3^Rr*M{CdaL5=R41nLs(AHk_=Y+A9$2&H(B7!_pURs&8aNw7?`&Z&xY_Ye z)~D5Bog^td-^QbUtkTirdyK^mTHAOuptDflut!#^lnKqU md>ggs(5nOWAqO?umG&QVYK#ibz}*4>0000U6E9hRK9^#O7(mu>ETqrXGsduA8$)?`v2seloOCza43C{NQ$$gAOH**MCn0Q?+L7dl7qnbRdqZ8LSVp1ItDxhxD?t@5_yHg6A8yI zC*%Wgg22K|8E#!~cTNYR~@Y9KepMPrrB8cABapAFa=`H+UGhkXUZV1GnwR1*lPyZ;*K(i~2gp|@bzp8}og7e*#% zEnr|^CWdVV!-4*Y_7rFvlww2Ze+>j*!Z!pQ?2l->4q#nqRu9`ELo6RMS5=br47g_X zRw}P9a7RRYQ%2Vsd0Me{_(EggTnuN6j=-?uFS6j^u69elMypu?t>op*wBx<=Wx8?( ztpe^(fwM6jJX7M-l*k3kEpWOl_Vk3@(_w4oc}4YF4|Rt=2V^XU?#Yz`8(e?aZ@#li0n*=g^qOcVpd-Wbok=@b#Yw zqn8u9a)z>l(1kEaPYZ6hwubN6i<8QHgsu0oE) ziJ(p;Wxm>sf!K+cw>R-(^Y2_bahB+&KI9y^);#0qt}t-$C|Bo71lHi{_+lg#f%RFy z0um=e3$K3i6K{U_4K!EX?F&rExl^W|G8Z8;`5z-k}OGNZ0#WVb$WCpQu-_YsiqKP?BB# vzVHS-CTUF4Ozn5G+mq_~Qqto~ahA+K`|lyv3(-e}00000NkvXXu0mjfd`9t{ literal 0 HcmV?d00001 diff --git a/packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d0ef06e7edb86cdfe0d15b4b0d98334a86163658 GIT binary patch literal 1716 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 literal 0 HcmV?d00001 diff --git a/packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c8f9ed8f5cee1c98386d13b17e89f719e83555b2 GIT binary patch literal 1895 zcmV-t2blPYP)FQtfgmafE#=YDCq`qUBt#QpG%*H6QHY765~R=q zZ6iudfM}q!Pz#~9JgOi8QJ|DSu?1-*(kSi1K4#~5?#|rh?sS)(-JQqX*}ciXJ56_H zdw=^s_srbAdqxlvGyrgGet#6T7_|j;95sL%MtM;q86vOxKM$f#puR)Bjv9Zvz9-di zXOTSsZkM83)E9PYBXC<$6(|>lNLVBb&&6y{NByFCp%6+^ALR@NCTse_wqvNmSWI-m z!$%KlHFH2omF!>#%1l3LTZg(s7eof$7*xB)ZQ0h?ejh?Ta9fDv59+u#MokW+1t8Zb zgHv%K(u9G^Lv`lh#f3<6!JVTL3(dCpxHbnbA;kKqQyd1~^Xe0VIaYBSWm6nsr;dFj z4;G-RyL?cYgsN1{L4ZFFNa;8)Rv0fM0C(~Tkit94 zz#~A)59?QjD&pAPSEQ)p8gP|DS{ng)j=2ux)_EzzJ773GmQ_Cic%3JJhC0t2cx>|v zJcVusIB!%F90{+}8hG3QU4KNeKmK%T>mN57NnCZ^56=0?&3@!j>a>B43pi{!u z7JyDj7`6d)qVp^R=%j>UIY6f+3`+qzIc!Y_=+uN^3BYV|o+$vGo-j-Wm<10%A=(Yk^beI{t%ld@yhKjq0iNjqN4XMGgQtbKubPM$JWBz}YA65k%dm*awtC^+f;a-x4+ddbH^7iDWGg&N0n#MW{kA|=8iMUiFYvMoDY@sPC#t$55gn6ykUTPAr`a@!(;np824>2xJthS z*ZdmT`g5-`BuJs`0LVhz+D9NNa3<=6m;cQLaF?tCv8)zcRSh66*Z|vXhG@$I%U~2l z?`Q zykI#*+rQ=z6Jm=Bui-SfpDYLA=|vzGE(dYm=OC8XM&MDo7ux4UF1~0J1+i%aCUpRe zt3L_uNyQ*cE(38Uy03H%I*)*Bh=Lb^Xj3?I^Hnbeq72(EOK^Y93CNp*uAA{5Lc=ky zx=~RKa4{iTm{_>_vSCm?$Ej=i6@=m%@VvAITnigVg{&@!7CDgs908761meDK5azA} z4?=NOH|PdvabgJ&fW2{Mo$Q0CcD8Qc84%{JPYt5EiG{MdLIAeX%T=D7NIP4%Hw}p9 zg)==!2Lbp#j{u_}hMiao9=!VSyx0gHbeCS`;q&vzeq|fs`y&^X-lso(Ls@-706qmA z7u*T5PMo_w3{se1t2`zWeO^hOvTsohG_;>J0wVqVe+n)AbQCx)yh9;w+J6?NF5Lmo zecS@ieAKL8%bVd@+-KT{yI|S}O>pYckUFs;ry9Ow$CD@ztz5K-*D$^{i(_1llhSh^ zEkL$}tsQt5>QA^;QgjgIfBDmcOgi5YDyu?t6vSnbp=1+@6D& z5MJ}B8q;bRlVoxasyhcUF1+)o`&3r0colr}QJ3hcSdLu;9;td>kf@Tcn<@9sIx&=m z;AD;SCh95=&p;$r{Xz3iWCO^MX83AGJ(yH&eTXgv|0=34#-&WAmw{)U7OU9!Wz^!7 zZ%jZFi@JR;>Mhi7S>V7wQ176|FdW2m?&`qa(ScO^CFPR80HucLHOTy%5s*HR0^8)i h0WYBP*#0Ks^FNSabJA*5${_#%002ovPDHLkV1oKhTl@e3 literal 0 HcmV?d00001 diff --git a/packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 diff --git a/packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 diff --git a/packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..75b2d164a5a98e212cca15ea7bf2ab5de5108680 GIT binary patch literal 3831 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 literal 0 HcmV?d00001 diff --git a/packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..c4df70d39da7941ef3f6dcb7f06a192d8dcb308d GIT binary patch literal 1888 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 iW8Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/flutter_wechat_camera_picker/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000..89c2725b --- /dev/null +++ b/packages/flutter_wechat_camera_picker/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/packages/flutter_wechat_camera_picker/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/flutter_wechat_camera_picker/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..f2e259c7 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/flutter_wechat_camera_picker/example/ios/Runner/Base.lproj/Main.storyboard b/packages/flutter_wechat_camera_picker/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000..f3c28516 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/flutter_wechat_camera_picker/example/ios/Runner/Info.plist b/packages/flutter_wechat_camera_picker/example/ios/Runner/Info.plist new file mode 100644 index 00000000..5f80c903 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/example/ios/Runner/Info.plist @@ -0,0 +1,51 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + wechat_camera_picker_demo + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + NSCameraUsageDescription + Take a photo for display + NSMicrophoneUsageDescription + Take a video for display + NSPhotoLibraryUsageDescription + Read and write your photos + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/packages/flutter_wechat_camera_picker/example/ios/Runner/Runner-Bridging-Header.h b/packages/flutter_wechat_camera_picker/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000..308a2a56 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/flutter_wechat_camera_picker/example/lib/main.dart b/packages/flutter_wechat_camera_picker/example/lib/main.dart new file mode 100644 index 00000000..aa6b2859 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/example/lib/main.dart @@ -0,0 +1,91 @@ +import 'dart:typed_data'; +import 'dart:ui' as ui; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:wechat_camera_picker/wechat_camera_picker.dart'; + +void main() { + runApp(MyApp()); + SystemChrome.setSystemUIOverlayStyle( + const SystemUiOverlayStyle(statusBarColor: Colors.transparent), + ); +} + +class MyApp extends StatelessWidget { + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'WeChat Camera Picker Demo', + theme: ThemeData( + brightness: MediaQueryData.fromWindow(ui.window).platformBrightness, + primarySwatch: Colors.blue, + visualDensity: VisualDensity.adaptivePlatformDensity, + ), + home: const MyHomePage(), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({Key? key}) : super(key: key); + + @override + _MyHomePageState createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + AssetEntity? entity; + Uint8List? data; + + Future pick(BuildContext context) async { + final Size size = MediaQuery.of(context).size; + final double scale = MediaQuery.of(context).devicePixelRatio; + try { + final AssetEntity? _entity = await CameraPicker.pickFromCamera( + context, + pickerConfig: const CameraPickerConfig(enableRecording: true), + ); + if (_entity != null && entity != _entity) { + entity = _entity; + if (mounted) { + setState(() {}); + } + data = await _entity.thumbnailDataWithSize( + ThumbnailSize( + (size.width * scale).toInt(), + (size.height * scale).toInt(), + ), + ); + if (mounted) { + setState(() {}); + } + } + } catch (e) { + rethrow; + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('WeChat Camera Picker Demo')), + body: Stack( + children: [ + if (entity != null && data != null) + Positioned.fill(child: Image.memory(data!, fit: BoxFit.contain)) + else if (entity != null && data == null) + const Center(child: CircularProgressIndicator()) + else + const Center(child: Text('Click the button to start picking.')), + ], + ), + floatingActionButton: FloatingActionButton( + onPressed: () => pick(context), + tooltip: 'Increment', + child: const Icon(Icons.camera_enhance), + ), + ); + } +} diff --git a/packages/flutter_wechat_camera_picker/example/pubspec.yaml b/packages/flutter_wechat_camera_picker/example/pubspec.yaml new file mode 100644 index 00000000..e0d86f1c --- /dev/null +++ b/packages/flutter_wechat_camera_picker/example/pubspec.yaml @@ -0,0 +1,18 @@ +name: wechat_camera_picker_demo +description: A new Flutter project. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev +version: 3.0.0+6 + +environment: + sdk: '>=2.14.0 <3.0.0' + flutter: '>=2.5.0' + +dependencies: + flutter: + sdk: flutter + + wechat_camera_picker: + path: ../ + +flutter: + uses-material-design: true diff --git a/packages/flutter_wechat_camera_picker/lib/src/constants/config.dart b/packages/flutter_wechat_camera_picker/lib/src/constants/config.dart new file mode 100644 index 00000000..185f6fdb --- /dev/null +++ b/packages/flutter_wechat_camera_picker/lib/src/constants/config.dart @@ -0,0 +1,140 @@ +/// +/// [Author] Alex (https://github.com/AlexV525) +/// [Date] 2022/2/14 12:05 +/// +import 'package:camera/camera.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import '../delegates/camera_picker_text_delegate.dart'; +import '../internals/type_defs.dart'; + +/// {@template wechat_camera_picker.CameraPickerConfig} +/// Configurations for the [CameraPicker]. +/// [CameraPicker] 的配置项 +/// {@endtemplate} +class CameraPickerConfig { + const CameraPickerConfig({ + this.enableRecording = false, + this.onlyEnableRecording = false, + this.enableTapRecording = false, + this.enableAudio = true, + this.enableSetExposure = true, + this.enableExposureControlOnPoint = true, + this.enablePinchToZoom = true, + this.enablePullToZoomInRecord = true, + this.shouldDeletePreviewFile = false, + this.shouldAutoPreviewVideo = false, + this.maximumRecordingDuration = const Duration(seconds: 15), + this.theme, + this.textDelegate, + this.cameraQuarterTurns = 0, + this.resolutionPreset = ResolutionPreset.max, + this.imageFormatGroup = ImageFormatGroup.unknown, + this.preferredLensDirection = CameraLensDirection.back, + this.lockCaptureOrientation, + this.foregroundBuilder, + this.previewTransformBuilder, + this.onEntitySaving, + this.onError, + }) : assert( + enableRecording == true || onlyEnableRecording != true, + 'Recording mode error.', + ); + + /// Whether the picker can record video. + /// 选择器是否可以录像 + final bool enableRecording; + + /// Whether the picker can record video. + /// 选择器是否可以录像 + final bool onlyEnableRecording; + + /// Whether allow the record can start with single tap. + /// 选择器是否可以单击录像 + /// + /// It only works when [onlyEnableRecording] is true. + /// 仅在 [onlyEnableRecording] 为 true 时生效。 + final bool enableTapRecording; + + /// Whether the picker should record audio. + /// 选择器录像时是否需要录制声音 + final bool enableAudio; + + /// Whether users can set the exposure point by tapping. + /// 用户是否可以在界面上通过点击设定曝光点 + final bool enableSetExposure; + + /// Whether users can adjust exposure according to the set point. + /// 用户是否可以根据已经设置的曝光点调节曝光度 + final bool enableExposureControlOnPoint; + + /// Whether users can zoom the camera by pinch. + /// 用户是否可以在界面上双指缩放相机对焦 + final bool enablePinchToZoom; + + /// Whether users can zoom by pulling up when recording video. + /// 用户是否可以在录制视频时上拉缩放 + final bool enablePullToZoomInRecord; + + /// {@template wechat_camera_picker.shouldDeletePreviewFile} + /// Whether the preview file will be delete when pop. + /// 返回页面时是否删除预览文件 + /// {@endtemplate} + final bool shouldDeletePreviewFile; + + /// {@template wechat_camera_picker.shouldAutoPreviewVideo} + /// Whether the video should be played instantly in the preview. + /// 在预览时是否直接播放视频 + /// {@endtemplate} + final bool shouldAutoPreviewVideo; + + /// The maximum duration of the video recording process. + /// 录制视频最长时长 + /// + /// Defaults to 15 seconds, allow `null` for unrestricted video recording. + /// 默认为 15 秒,可以使用 `null` 来设置无限制的视频录制 + final Duration? maximumRecordingDuration; + + /// Theme data for the picker. + /// 选择器的主题 + final ThemeData? theme; + + /// The number of clockwise quarter turns the camera view should be rotated. + /// 摄像机视图顺时针旋转次数,每次90度 + final int cameraQuarterTurns; + + /// Text delegate that controls text in widgets. + /// 控制部件中的文字实现 + final CameraPickerTextDelegate? textDelegate; + + /// Present resolution for the camera. + /// 相机的分辨率预设 + final ResolutionPreset resolutionPreset; + + /// The [ImageFormatGroup] describes the output of the raw image format. + /// 输出图像的格式描述 + final ImageFormatGroup imageFormatGroup; + + /// Which lens direction is preferred when first using the camera, + /// typically with the front or the back direction. + /// 首次使用相机时首选的镜头方向,通常是前置或后置。 + final CameraLensDirection preferredLensDirection; + + /// {@macro wechat_camera_picker.ForegroundBuilder} + final ForegroundBuilder? foregroundBuilder; + + /// {@macro wechat_camera_picker.PreviewTransformBuilder} + final PreviewTransformBuilder? previewTransformBuilder; + + /// Whether the camera should be locked to the specific orientation + /// during captures. + /// 摄像机在拍摄时锁定的旋转角度 + final DeviceOrientation? lockCaptureOrientation; + + /// {@macro wechat_camera_picker.EntitySaveCallback} + final EntitySaveCallback? onEntitySaving; + + /// {@macro wechat_camera_picker.CameraErrorHandler} + final CameraErrorHandler? onError; +} diff --git a/packages/flutter_wechat_camera_picker/lib/src/constants/constants.dart b/packages/flutter_wechat_camera_picker/lib/src/constants/constants.dart new file mode 100644 index 00000000..83b41e2f --- /dev/null +++ b/packages/flutter_wechat_camera_picker/lib/src/constants/constants.dart @@ -0,0 +1,13 @@ +/// +/// [Author] Alex (https://github.com/AlexV525) +/// [Date] 2020/7/15 02:06 +/// +import '../delegates/camera_picker_text_delegate.dart'; + +export 'package:photo_manager/photo_manager.dart'; + +class Constants { + const Constants._(); + + static CameraPickerTextDelegate textDelegate = CameraPickerTextDelegate(); +} diff --git a/packages/flutter_wechat_camera_picker/lib/src/constants/styles.dart b/packages/flutter_wechat_camera_picker/lib/src/constants/styles.dart new file mode 100644 index 00000000..03a3a021 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/lib/src/constants/styles.dart @@ -0,0 +1,11 @@ +/// +/// [Author] Alex (https://github.com/AlexV525) +/// [Date] 2021/9/30 17:00 +/// +import 'package:flutter/painting.dart'; + +class C { + static const Color themeColor = Color(0xff00bc56); // WeChat +} + +const BorderRadius maxBorderRadius = BorderRadius.all(Radius.circular(999999)); diff --git a/packages/flutter_wechat_camera_picker/lib/src/delegates/camera_picker_text_delegate.dart b/packages/flutter_wechat_camera_picker/lib/src/delegates/camera_picker_text_delegate.dart new file mode 100644 index 00000000..898f6e6b --- /dev/null +++ b/packages/flutter_wechat_camera_picker/lib/src/delegates/camera_picker_text_delegate.dart @@ -0,0 +1,178 @@ +/// +/// [Author] Alex (https://github.com/AlexV525) +/// [Date] 2020/7/16 11:07 +/// +import 'package:camera/camera.dart' show CameraLensDirection, FlashMode; + +/// Text delegate that controls text in widgets, implemented with Chinese. +/// 控制部件中的文字实现,中文。 +class CameraPickerTextDelegate { + /// Confirm string for the confirm button. + /// 确认按钮的字段 + String get confirm => '确认'; + String get cropper => '裁剪'; + + /// Tips string above the shooting button before shooting. + /// 拍摄前确认按钮上方的提示文字 + String get shootingTips => '轻触拍照'; + + /// Load failed string for item. + /// 资源加载失败时的字段 + String get loadFailed => '加载失败'; + + /// Semantics fields. + /// + /// Fields below are only for semantics usage. For customizable these fields, + /// head over to [EnglishCameraPickerTextDelegate] for better understanding. + String get sActionManuallyFocusHint => '手动聚焦'; + + String get sActionPreviewHint => '预览'; + + String get sActionRecordHint => '录像'; + + String get sActionShootHint => '拍照'; + + String get sActionShootingButtonTooltip => '拍照按钮'; + + String get sActionStopRecordingHint => '停止录像'; + + String sCameraLensDirectionLabel(CameraLensDirection value) { + switch (value) { + case CameraLensDirection.front: + return '前置'; + case CameraLensDirection.back: + return '后置'; + case CameraLensDirection.external: + return '外置'; + } + } + + String? sCameraPreviewLabel(CameraLensDirection? value) { + if (value == null) { + return null; + } + return '${sCameraLensDirectionLabel(value)}画面预览'; + } + + String sFlashModeLabel(FlashMode mode) { + final String _modeString; + switch (mode) { + case FlashMode.off: + _modeString = '关闭'; + break; + case FlashMode.auto: + _modeString = '自动'; + break; + case FlashMode.always: + _modeString = '拍照时闪光'; + break; + case FlashMode.torch: + _modeString = '始终闪光'; + break; + } + return '闪光模式: $_modeString'; + } + + String sSwitchCameraLensDirectionLabel(CameraLensDirection value) { + return '切换至${sCameraLensDirectionLabel(value)}摄像头'; + } +} + +/// Default text delegate including recording implements with Chinese. +/// 中文文字实现(包括摄像) +class CameraPickerTextDelegateWithRecording extends CameraPickerTextDelegate { + @override + String get shootingTips => '轻触拍照,长按摄像'; +} + +/// Default text delegate including only recording implements with Chinese. +/// 中文文字实现(仅摄像) +class CameraPickerTextDelegateWithOnlyRecording + extends CameraPickerTextDelegate { + @override + String get shootingTips => '长按摄像'; +} + +/// Default text delegate including tap recording implements with Chinese. +/// 中文文字实现(仅轻触摄像) +class CameraPickerTextDelegateWithTapRecording + extends CameraPickerTextDelegate { + @override + String get shootingTips => '轻触摄像'; +} + +/// Default text delegate implements with English. +/// 英文文字实现 +class EnglishCameraPickerTextDelegate extends CameraPickerTextDelegate { + @override + String get confirm => 'Confirm'; + + @override + String get cropper => '裁剪'; + + @override + String get shootingTips => 'Tap to take photo.'; + + @override + String get loadFailed => 'Load failed'; + + @override + String get sActionManuallyFocusHint => 'manually focus'; + + @override + String get sActionPreviewHint => 'preview'; + + @override + String get sActionRecordHint => 'record'; + + @override + String get sActionShootHint => 'take picture'; + + @override + String get sActionShootingButtonTooltip => 'shooting button'; + + @override + String get sActionStopRecordingHint => 'stop recording'; + + @override + String sCameraLensDirectionLabel(CameraLensDirection value) => value.name; + + @override + String? sCameraPreviewLabel(CameraLensDirection? value) { + if (value == null) { + return null; + } + return '${sCameraLensDirectionLabel(value)} camera preview'; + } + + @override + String sFlashModeLabel(FlashMode mode) => 'Flash mode: ${mode.name}'; + + @override + String sSwitchCameraLensDirectionLabel(CameraLensDirection value) => + 'Switch to the ${sCameraLensDirectionLabel(value)} camera'; +} + +/// Default text delegate including recording implements with English. +/// 英文文字实现(包括摄像) +class EnglishCameraPickerTextDelegateWithRecording + extends EnglishCameraPickerTextDelegate { + @override + String get shootingTips => 'Tap to take photo. Long press to record video.'; +} + +/// Default text delegate including only recording implements with English. +/// 英文文字实现(仅摄像) +class EnglishCameraPickerTextDelegateWithOnlyRecording + extends EnglishCameraPickerTextDelegate { + @override + String get shootingTips => 'Long press to record video.'; +} + +/// Default text delegate including tap recording implements with English. +/// 英文文字实现(仅轻触摄像) +class EnglishCameraPickerTextDelegateWithTapRecording + extends EnglishCameraPickerTextDelegate { + @override + String get shootingTips => 'Tap to record video.'; +} diff --git a/packages/flutter_wechat_camera_picker/lib/src/internals/enums.dart b/packages/flutter_wechat_camera_picker/lib/src/internals/enums.dart new file mode 100644 index 00000000..8dc54c27 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/lib/src/internals/enums.dart @@ -0,0 +1,8 @@ +/// +/// [Author] Alex (https://github.com/AlexV525) +/// [Date] 2021/9/30 17:06 +/// + +/// Two types for the viewer: image and video. +/// 两种预览类型:图片和视频 +enum CameraPickerViewType { image, video } diff --git a/packages/flutter_wechat_camera_picker/lib/src/internals/extensions.dart b/packages/flutter_wechat_camera_picker/lib/src/internals/extensions.dart new file mode 100644 index 00000000..609b4468 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/lib/src/internals/extensions.dart @@ -0,0 +1,28 @@ +/// +/// [Author] Alex (https://github.com/AlexV525) +/// [Date] 2021/9/30 17:02 +/// +import 'dart:async'; + +import 'package:flutter/widgets.dart'; + +extension SafeSetStateExtension on State { + FutureOr safeSetState(FutureOr Function() fn) async { + await fn(); + if (mounted && + !context.debugDoingBuild && + context.owner?.debugBuilding != true) { + // ignore: invalid_use_of_protected_member + setState(() {}); + } + } +} + +extension NullableObjectExtension on Object? { + StackTrace? get nullableStackTrace { + if (this is Error?) { + return (this as Error?)?.stackTrace; + } + return null; + } +} diff --git a/packages/flutter_wechat_camera_picker/lib/src/internals/methods.dart b/packages/flutter_wechat_camera_picker/lib/src/internals/methods.dart new file mode 100644 index 00000000..a04b9c26 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/lib/src/internals/methods.dart @@ -0,0 +1,26 @@ +/// +/// [Author] Alex (https://github.com/AlexV525) +/// [Date] 2021/9/30 17:01 +/// +import 'dart:developer'; + +import 'package:flutter/foundation.dart'; + +import 'extensions.dart'; +import 'type_defs.dart'; + +/// Log only in debug mode. +/// 只在调试模式打印 +void realDebugPrint(dynamic message) { + if (!kReleaseMode) { + log('$message', name: 'CameraPicker - LOG'); + } +} + +void handleErrorWithHandler(Object error, CameraErrorHandler? handler) { + if (handler != null) { + handler(error, error.nullableStackTrace); + return; + } + throw error; +} diff --git a/packages/flutter_wechat_camera_picker/lib/src/internals/type_defs.dart b/packages/flutter_wechat_camera_picker/lib/src/internals/type_defs.dart new file mode 100644 index 00000000..35be3b37 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/lib/src/internals/type_defs.dart @@ -0,0 +1,60 @@ +/// +/// [Author] Alex (https://github.com/AlexV525) +/// [Date] 2021/9/30 17:04 +/// +import 'dart:async' show FutureOr; +import 'dart:io' show File; + +import 'package:camera/camera.dart' show CameraController, CameraValue; +import 'package:flutter/widgets.dart' show BuildContext, Widget; + +import '../internals/enums.dart'; + +/// {@template wechat_camera_picker.EntitySaveCallback} +/// The callback type define for saving entity in the viewer. +/// 在查看器中保存图片时的回调 +/// +/// ### Notice about the implementation +/// * After the callback is implemented, the default saving method +/// won't called anymore. +/// * Don't call `Navigator.of(context).pop/maybePop` without popping `null` or +/// `AssetEntity`, otherwise there will be a type cast error occurred. +/// +/// ### 在实现时需要注意 +/// * 实现该方法后,原本的保存方法不会再被调用; +/// * 不要使用 `Navigator.of(context).pop/maybePop` 返回 `null` 或 `AssetEntity` +/// 以外类型的内容,否则会抛出类型转换异常。 +/// {@endtemplate} +typedef EntitySaveCallback = FutureOr Function({ + BuildContext context, + CameraPickerViewType viewType, + File file, +}); + +/// {@template wechat_camera_picker.CameraErrorHandler} +/// The error handler when any error occurred during the picking process. +/// 拍摄照片过程中的自定义错误处理 +/// {@endtemplate} +typedef CameraErrorHandler = void Function( + Object error, + StackTrace? stackTrace, +); + +/// {@template wechat_camera_picker.ForegroundBuilder} +/// Build the foreground/overlay widget with the given [cameraValue]. +/// 根据给定的 [cameraValue] 构建自定义的前景 widget +/// {@endtemplate} +typedef ForegroundBuilder = Widget Function( + BuildContext context, + CameraValue cameraValue, +); + +/// {@template wechat_camera_picker.PreviewTransformBuilder} +/// Build the transformed widget with the given [controller]. +/// 根据给定的 [controller] 构建自定义的变换 widget +/// {@endtemplate} +typedef PreviewTransformBuilder = Widget? Function( + BuildContext context, + CameraController controller, + Widget child, +); diff --git a/packages/flutter_wechat_camera_picker/lib/src/widgets/builder/tween_animation_builder_2.dart b/packages/flutter_wechat_camera_picker/lib/src/widgets/builder/tween_animation_builder_2.dart new file mode 100644 index 00000000..b1bff5e3 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/lib/src/widgets/builder/tween_animation_builder_2.dart @@ -0,0 +1,41 @@ +/// +/// [Author] Alex (https://github.com/AlexV525) +/// [Date] 2021-01-09 18:33 +/// +import 'package:flutter/material.dart'; + +class TweenAnimationBuilder2 extends StatelessWidget { + const TweenAnimationBuilder2({ + Key? key, + required this.firstTween, + required this.secondTween, + required this.builder, + this.firstTweenDuration = kThemeAnimationDuration, + this.secondTweenDuration = kThemeAnimationDuration, + this.firstTweenCurve = Curves.linear, + this.secondTweenCurve = Curves.linear, + }) : super(key: key); + + final Tween firstTween; + final Tween secondTween; + final Duration firstTweenDuration; + final Duration secondTweenDuration; + final Curve firstTweenCurve; + final Curve secondTweenCurve; + final Widget Function(BuildContext, A, B) builder; + + @override + Widget build(BuildContext context) { + return TweenAnimationBuilder( + tween: firstTween, + curve: firstTweenCurve, + duration: firstTweenDuration, + builder: (__, A first, _) => TweenAnimationBuilder( + tween: secondTween, + curve: secondTweenCurve, + duration: secondTweenDuration, + builder: (_, B second, __) => builder(context, first, second), + ), + ); + } +} diff --git a/packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker.dart b/packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker.dart new file mode 100644 index 00000000..46262a49 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker.dart @@ -0,0 +1,1397 @@ +/// +/// [Author] Alex (https://github.com/AlexV525) +/// [Date] 2020/7/13 11:08 +/// +// ignore_for_file: deprecated_member_use +import 'dart:async'; +import 'dart:io'; +import 'dart:math' as math; + +import 'package:camera/camera.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/semantics.dart'; +import 'package:flutter/services.dart'; + +import '../constants/config.dart'; +import '../constants/constants.dart'; +import '../constants/styles.dart'; +import '../delegates/camera_picker_text_delegate.dart'; +import '../internals/enums.dart'; +import '../internals/extensions.dart'; +import '../internals/methods.dart'; +import '../widgets/circular_progress_bar.dart'; + +import 'camera_picker_page_route.dart'; +import 'camera_picker_viewer.dart'; +import 'exposure_point_widget.dart'; + +const Color _lockedColor = Colors.amber; +const Duration _kDuration = Duration(milliseconds: 300); + +/// Create a camera picker integrate with [CameraDescription]. +/// 通过 [CameraDescription] 整合的拍照选择 +/// +/// The picker provides create an [AssetEntity] through the camera. +/// 该选择器可以通过拍照创建 [AssetEntity]。 +class CameraPicker extends StatefulWidget { + CameraPicker({ + Key? key, + this.pickerConfig = const CameraPickerConfig(), + }) : super(key: key) { + // Set text delegate accordingly. + if (pickerConfig.textDelegate != null) { + Constants.textDelegate = pickerConfig.textDelegate!; + } else if (pickerConfig.enableRecording && + pickerConfig.onlyEnableRecording && + pickerConfig.enableTapRecording) { + Constants.textDelegate = CameraPickerTextDelegateWithTapRecording(); + } else if (pickerConfig.enableRecording && + pickerConfig.onlyEnableRecording) { + Constants.textDelegate = CameraPickerTextDelegateWithOnlyRecording(); + } else if (pickerConfig.enableRecording) { + Constants.textDelegate = CameraPickerTextDelegateWithRecording(); + } else { + Constants.textDelegate = CameraPickerTextDelegate(); + } + } + + final CameraPickerConfig pickerConfig; + + /// Static method to create [AssetEntity] through camera. + /// 通过相机创建 [AssetEntity] 的静态方法 + static Future pickFromCamera( + BuildContext context, { + CameraPickerConfig pickerConfig = const CameraPickerConfig(), + bool useRootNavigator = true, + CameraPickerPageRouteBuilder? pageRouteBuilder, + }) { + final Widget picker = CameraPicker(pickerConfig: pickerConfig); + return Navigator.of( + context, + rootNavigator: useRootNavigator, + ).push( + pageRouteBuilder?.call(picker) ?? + CameraPickerPageRoute(builder: (_) => picker), + ); + } + + /// Build a dark theme according to the theme color. + /// 通过主题色构建一个默认的暗黑主题 + static ThemeData themeData(Color themeColor) { + return ThemeData.dark().copyWith( + primaryColor: Colors.grey[900], + primaryColorLight: Colors.grey[900], + primaryColorDark: Colors.grey[900], + canvasColor: Colors.grey[850], + scaffoldBackgroundColor: Colors.grey[900], + bottomAppBarColor: Colors.grey[900], + cardColor: Colors.grey[900], + highlightColor: Colors.transparent, + toggleableActiveColor: themeColor, + textSelectionTheme: TextSelectionThemeData( + cursorColor: themeColor, + selectionColor: themeColor.withAlpha(100), + selectionHandleColor: themeColor, + ), + indicatorColor: themeColor, + appBarTheme: const AppBarTheme( + systemOverlayStyle: SystemUiOverlayStyle( + statusBarBrightness: Brightness.dark, + statusBarIconBrightness: Brightness.light, + ), + elevation: 0, + ), + buttonTheme: ButtonThemeData(buttonColor: themeColor), + colorScheme: ColorScheme( + primary: Colors.grey[900]!, + primaryVariant: Colors.grey[900]!, + secondary: themeColor, + secondaryVariant: themeColor, + background: Colors.grey[900]!, + surface: Colors.grey[900]!, + brightness: Brightness.dark, + error: const Color(0xffcf6679), + onPrimary: Colors.black, + onSecondary: Colors.black, + onSurface: Colors.white, + onBackground: Colors.white, + onError: Colors.black, + ), + ); + } + + @override + CameraPickerState createState() => CameraPickerState(); +} + +class CameraPickerState extends State + with WidgetsBindingObserver { + /// The [Duration] for record detection. (200ms) + /// 检测是否开始录制的时长 (200毫秒) + final Duration recordDetectDuration = const Duration(milliseconds: 200); + + /// The last exposure point offset on the screen. + /// 最后一次手动聚焦的点坐标 + final ValueNotifier _lastExposurePoint = + ValueNotifier(null); + + /// The last pressed position on the shooting button before starts recording. + /// 在开始录像前,最后一次在拍照按钮按下的位置 + Offset? _lastShootingButtonPressedPosition; + + /// Current exposure mode. + /// 当前曝光模式 + final ValueNotifier _exposureMode = + ValueNotifier(ExposureMode.auto); + + final ValueNotifier _isExposureModeDisplays = + ValueNotifier(false); + + /// The controller for the current camera. + /// 当前相机实例的控制器 + CameraController get controller => _controller!; + CameraController? _controller; + bool _shouldLockInitialize = false; + + /// Available cameras. + /// 可用的相机实例 + late List cameras; + + /// Current exposure offset. + /// 当前曝光值 + final ValueNotifier _currentExposureOffset = ValueNotifier(0); + + /// The maximum available value for exposure. + /// 最大可用曝光值 + double _maxAvailableExposureOffset = 0; + + /// The minimum available value for exposure. + /// 最小可用曝光值 + double _minAvailableExposureOffset = 0; + + /// The maximum available value for zooming. + /// 最大可用缩放值 + double _maxAvailableZoom = 1; + + /// The minimum available value for zooming. + /// 最小可用缩放值 + double _minAvailableZoom = 1; + + /// Counting pointers (number of user fingers on screen). + /// 屏幕上的触摸点计数 + int _pointers = 0; + double _currentZoom = 1; + double _baseZoom = 1; + + /// The index of the current cameras. Defaults to `0`. + /// 当前相机的索引。默认为0 + int currentCameraIndex = 0; + + /// Whether the [shootingButton] should animate according to the gesture. + /// 拍照按钮是否需要执行动画 + /// + /// This happens when the [shootingButton] is being long pressed. + /// It will animate for video recording state. + /// 当长按拍照按钮时,会进入准备录制视频的状态,此时需要执行动画。 + bool isShootingButtonAnimate = false; + + /// The [Timer] for keep the [_lastExposurePoint] displays. + /// 用于控制上次手动聚焦点显示的计时器 + Timer? _exposurePointDisplayTimer; + + Timer? _exposureModeDisplayTimer; + + /// The [Timer] for record start detection. + /// 用于检测是否开始录制的计时器 + /// + /// When the [shootingButton] started animate, this [Timer] will start + /// at the same time. When the time is more than [recordDetectDuration], + /// which means we should start recoding, the timer finished. + /// 当拍摄按钮开始执行动画时,定时器会同时启动。时长超过检测时长时,定时器完成。 + Timer? _recordDetectTimer; + + /// The [Timer] for record countdown. + /// 用于录制视频倒计时的计时器 + /// + /// Stop record When the record time reached the [maximumRecordingDuration]. + /// However, if there's no limitation on record time, this will be useless. + /// 当录像时间达到了最大时长,将通过定时器停止录像。 + /// 但如果录像时间没有限制,定时器将不会起作用。 + Timer? _recordCountdownTimer; + + //////////////////////////////////////////////////////////////////////////// + ////////////////////////////// Global Getters ////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + CameraPickerConfig get config => widget.pickerConfig; + + bool get enableRecording => config.enableRecording; + + bool get onlyEnableRecording => enableRecording && config.onlyEnableRecording; + + bool get enableTapRecording => + onlyEnableRecording && config.enableTapRecording; + + /// No audio integration required when it's only for camera. + /// 在仅允许拍照时不需要启用音频 + bool get enableAudio => enableRecording && config.enableAudio; + + /// Whether the picker needs to prepare for video recording on iOS. + /// 是否需要为 iOS 的录制视频执行准备操作 + bool get shouldPrepareForVideoRecording => + enableRecording && enableAudio && Platform.isIOS; + + bool get enableSetExposure => config.enableSetExposure; + + bool get enableExposureControlOnPoint => config.enableExposureControlOnPoint; + + bool get enablePinchToZoom => config.enablePinchToZoom; + + bool get enablePullToZoomInRecord => + enableRecording && config.enablePullToZoomInRecord; + + bool get shouldDeletePreviewFile => config.shouldDeletePreviewFile; + + bool get shouldAutoPreviewVideo => config.shouldAutoPreviewVideo; + + Duration? get maximumRecordingDuration => config.maximumRecordingDuration; + + /// Whether the recording restricted to a specific duration. + /// 录像是否有限制的时长 + bool get isRecordingRestricted => maximumRecordingDuration != null; + + /// A getter to the current [CameraDescription]. + /// 获取当前相机实例 + CameraDescription get currentCamera => cameras.elementAt(currentCameraIndex); + + /// If there's no theme provided from the user, use [CameraPicker.themeData] . + /// 如果用户未提供主题, + late final ThemeData _theme = + config.theme ?? CameraPicker.themeData(C.themeColor); + + /// Get [ThemeData] of the [CameraPicker] through [Constants.pickerKey]. + /// 通过常量全局 Key 获取当前选择器的主题 + ThemeData get theme => _theme; + + CameraPickerTextDelegate get _textDelegate => Constants.textDelegate; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance?.addObserver(this); + + // TODO(Alex): Currently hide status bar will cause the viewport shaking on Android. + /// Hide system status bar automatically when the platform is not Android. + /// 在非 Android 设备上自动隐藏状态栏 + if (!Platform.isAndroid) { + SystemChrome.setEnabledSystemUIOverlays([]); + } + + initCameras(); + } + + @override + void dispose() { + if (!Platform.isAndroid) { + SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values); + } + WidgetsBinding.instance?.removeObserver(this); + _controller?.dispose(); + _currentExposureOffset.dispose(); + _lastExposurePoint.dispose(); + _exposureMode.dispose(); + _isExposureModeDisplays.dispose(); + _exposurePointDisplayTimer?.cancel(); + _exposureModeDisplayTimer?.cancel(); + _recordDetectTimer?.cancel(); + _recordCountdownTimer?.cancel(); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + final CameraController? c = _controller; + // App state changed before we got the chance to initialize. + if (c == null || !c.value.isInitialized) { + return; + } + if (state == AppLifecycleState.inactive) { + c.dispose(); + } else if (state == AppLifecycleState.resumed && !_shouldLockInitialize) { + // Drop initialize when the controller has been already initialized. + // This will typically resolve the lifecycle issue on iOS when permissions + // are requested for the first time. + initCameras(currentCamera); + } + } + + /// Adjust the proper scale type according to the [constraints]. + /// 根据 [constraints] 获取相机预览适用的缩放。 + double _effectiveCameraScale( + BoxConstraints constraints, + CameraController controller, + ) { + final int turns = config.cameraQuarterTurns; + final String orientation = controller.value.deviceOrientation.toString(); + // Fetch the biggest size from the constraints. + Size size = constraints.biggest; + // Flip the size when the preview needs to turn with an odd count of quarters. + if ((turns.isOdd && orientation.contains('portrait')) || + (turns.isEven && orientation.contains('landscape'))) { + size = size.flipped; + } + // Calculate scale depending on the size and camera ratios. + double scale = size.aspectRatio * controller.value.aspectRatio; + // Prevent scaling down. + if (scale < 1) { + scale = 1 / scale; + } + return scale; + } + + /// Initialize cameras instances. + /// 初始化相机实例 + void initCameras([CameraDescription? cameraDescription]) { + // Save the current controller to a local variable. + final CameraController? _c = _controller; + // Then unbind the controller from widgets, which requires a build frame. + safeSetState(() { + _shouldLockInitialize = true; + _maxAvailableZoom = 1; + _minAvailableZoom = 1; + _currentZoom = 1; + _baseZoom = 1; + _controller = null; + // Meanwhile, cancel the existed exposure point and mode display. + _exposureModeDisplayTimer?.cancel(); + _exposurePointDisplayTimer?.cancel(); + _lastExposurePoint.value = null; + if (_currentExposureOffset.value != 0) { + _currentExposureOffset.value = 0; + } + }); + // **IMPORTANT**: Push methods into a post frame callback, which ensures the + // controller has already unbind from widgets. + SchedulerBinding.instance?.addPostFrameCallback((_) async { + // Dispose at last to avoid disposed usage with assertions. + await _c?.dispose(); + + // When the [cameraDescription] is null, which means this is the first + // time initializing cameras, so available cameras should be fetched. + if (cameraDescription == null) { + cameras = await availableCameras(); + } + + // After cameras fetched, judge again with the list is empty or not to + // ensure there is at least an available camera for use. + if (cameraDescription == null && (cameras.isEmpty)) { + _shouldLockInitialize = false; + handleErrorWithHandler( + CameraException( + 'No CameraDescription found.', + 'No cameras are available in the controller.', + ), + config.onError, + ); + } + + final int preferredIndex = cameras.indexWhere( + (CameraDescription e) => + e.lensDirection == config.preferredLensDirection, + ); + final int index; + if (preferredIndex != -1 && _c == null) { + index = preferredIndex; + currentCameraIndex = preferredIndex; + } else { + index = currentCameraIndex; + } + // Initialize the controller with the given resolution preset. + _controller = CameraController( + cameraDescription ?? cameras[index], + config.resolutionPreset, + enableAudio: enableAudio, + imageFormatGroup: config.imageFormatGroup, + )..addListener(() { + if (controller.value.hasError) { + handleErrorWithHandler( + CameraException( + 'CameraController exception', + controller.value.errorDescription, + ), + config.onError, + ); + } + }); + + try { + await controller.initialize(); + safeSetState(() {}); + // Call recording preparation first. + if (shouldPrepareForVideoRecording) { + await controller.prepareForVideoRecording(); + } + // Then call other asynchronous methods. + await Future.wait(>[ + if (config.lockCaptureOrientation != null) + controller.lockCaptureOrientation(config.lockCaptureOrientation), + (() async => _maxAvailableExposureOffset = + await controller.getMaxExposureOffset())(), + (() async => _minAvailableExposureOffset = + await controller.getMinExposureOffset())(), + (() async => + _maxAvailableZoom = await controller.getMaxZoomLevel())(), + (() async => + _minAvailableZoom = await controller.getMinZoomLevel())(), + ]); + } catch (e) { + handleErrorWithHandler(e, config.onError); + } finally { + safeSetState(() { + _shouldLockInitialize = false; + }); + } + }); + } + + /// The method to switch cameras. + /// 切换相机的方法 + /// + /// Switch cameras in order. When the [currentCameraIndex] reached the length + /// of cameras, start from the beginning. + /// 按顺序切换相机。当达到相机数量时从头开始。 + void switchCameras() { + ++currentCameraIndex; + if (currentCameraIndex == cameras.length) { + currentCameraIndex = 0; + } + initCameras(currentCamera); + } + + /// Obtain the next camera description for semantics. + CameraDescription get _nextCameraDescription { + final int nextIndex = currentCameraIndex + 1; + if (nextIndex == cameras.length) { + return cameras[0]; + } + return cameras[nextIndex]; + } + + /// The method to switch between flash modes. + /// 切换闪光灯模式的方法 + Future switchFlashesMode() async { + switch (controller.value.flashMode) { + case FlashMode.off: + await controller.setFlashMode(FlashMode.auto); + break; + case FlashMode.auto: + await controller.setFlashMode(FlashMode.always); + break; + case FlashMode.always: + case FlashMode.torch: + await controller.setFlashMode(FlashMode.off); + break; + } + } + + Future zoom(double scale) async { + final double _zoom = (_baseZoom * scale) + .clamp(_minAvailableZoom, _maxAvailableZoom) + .toDouble(); + if (_zoom == _currentZoom) { + return; + } + _currentZoom = _zoom; + + await controller.setZoomLevel(_currentZoom); + } + + /// Handle when the scale gesture start. + /// 处理缩放开始的手势 + void _handleScaleStart(ScaleStartDetails details) { + _baseZoom = _currentZoom; + } + + /// Handle when the double tap scale details is updating. + /// 处理双指缩放更新 + Future _handleScaleUpdate(ScaleUpdateDetails details) async { + // When there are not exactly two fingers on screen don't scale + if (_pointers != 2) { + return; + } + + zoom(details.scale); + } + + void _restartPointDisplayTimer() { + _exposurePointDisplayTimer?.cancel(); + _exposurePointDisplayTimer = Timer(const Duration(seconds: 5), () { + _lastExposurePoint.value = null; + }); + } + + void _restartModeDisplayTimer() { + _exposureModeDisplayTimer?.cancel(); + _exposureModeDisplayTimer = Timer(const Duration(seconds: 2), () { + _isExposureModeDisplays.value = false; + }); + } + + /// Use the specific [mode] to update the exposure mode. + /// 设置曝光模式 + void switchExposureMode() { + if (_exposureMode.value == ExposureMode.auto) { + _exposureMode.value = ExposureMode.locked; + } else { + _exposureMode.value = ExposureMode.auto; + } + _exposurePointDisplayTimer?.cancel(); + if (_exposureMode.value == ExposureMode.auto) { + _exposurePointDisplayTimer = Timer(const Duration(seconds: 5), () { + _lastExposurePoint.value = null; + }); + } + controller.setExposureMode(_exposureMode.value); + _restartModeDisplayTimer(); + } + + /// Use the [details] point to set exposure and focus. + /// 通过点击点的 [details] 设置曝光和对焦。 + Future setExposureAndFocusPoint( + Offset position, + BoxConstraints constraints, + ) async { + _isExposureModeDisplays.value = false; + // Ignore point update when the new point is less than 8% and higher than + // 92% of the screen's height. + if (position.dy < constraints.maxHeight / 12 || + position.dy > constraints.maxHeight / 12 * 11) { + return; + } + realDebugPrint( + 'Setting new exposure point (x: ${position.dx}, y: ${position.dy})', + ); + _lastExposurePoint.value = position; + _restartPointDisplayTimer(); + _currentExposureOffset.value = 0; + if (_exposureMode.value == ExposureMode.locked) { + await controller.setExposureMode(ExposureMode.auto); + _exposureMode.value = ExposureMode.auto; + } + controller.setExposurePoint( + _lastExposurePoint.value!.scale( + 1 / constraints.maxWidth, + 1 / constraints.maxHeight, + ), + ); + if (controller.value.focusPointSupported == true) { + controller.setFocusPoint( + _lastExposurePoint.value!.scale( + 1 / constraints.maxWidth, + 1 / constraints.maxHeight, + ), + ); + } + } + + /// Update the exposure offset using the exposure controller. + /// 使用曝光控制器更新曝光值 + void updateExposureOffset(double value) { + if (value == _currentExposureOffset.value) { + return; + } + _currentExposureOffset.value = value; + controller.setExposureOffset(value); + if (!_isExposureModeDisplays.value) { + _isExposureModeDisplays.value = true; + } + _restartModeDisplayTimer(); + _restartPointDisplayTimer(); + } + + /// Update the scale value while the user is shooting. + /// 用户在录制时通过滑动更新缩放 + void onShootingButtonMove( + PointerMoveEvent event, + BoxConstraints constraints, + ) { + _lastShootingButtonPressedPosition ??= event.position; + if (controller.value.isRecordingVideo) { + // First calculate relative offset. + final Offset _offset = + event.position - _lastShootingButtonPressedPosition!; + // Then turn negative, + // multiply double with 10 * 1.5 - 1 = 14, + // plus 1 to ensure always scale. + final double _scale = _offset.dy / constraints.maxHeight * -14 + 1; + zoom(_scale); + } + } + + /// The method to take a picture. + /// 拍照方法 + /// + /// The picture will only taken when [isInitialized], and the camera is not + /// taking pictures. + /// 仅当初始化成功且相机未在拍照时拍照。 + Future takePicture() async { + if (!controller.value.isInitialized) { + handleErrorWithHandler( + StateError('Camera has not initialized.'), + config.onError, + ); + } + if (controller.value.isTakingPicture) { + return; + } + try { + final XFile _file = await controller.takePicture(); + // Delay disposing the controller to hold the preview. + Future.delayed(const Duration(milliseconds: 500), () { + _controller?.dispose(); + safeSetState(() { + _controller = null; + }); + }); + final AssetEntity? entity = await CameraPickerViewer.pushToViewer( + context, + pickerState: this, + pickerType: CameraPickerViewType.image, + previewXFile: _file, + theme: theme, + shouldDeletePreviewFile: shouldDeletePreviewFile, + shouldAutoPreviewVideo: shouldAutoPreviewVideo, + onEntitySaving: config.onEntitySaving, + ); + if (entity != null) { + Navigator.of(context).pop(entity); + return; + } + initCameras(currentCamera); + safeSetState(() {}); + } catch (e) { + realDebugPrint('Error when preview the captured file: $e'); + handleErrorWithHandler(e, config.onError); + } + } + + /// When the [shootingButton]'s `onLongPress` called, the [_recordDetectTimer] + /// will be initialized to achieve press time detection. If the duration + /// reached to same as [recordDetectDuration], and the timer still active, + /// start recording video. + /// 当 [shootingButton] 触发了长按,初始化一个定时器来实现时间检测。如果长按时间 + /// 达到了 [recordDetectDuration] 且定时器未被销毁,则开始录制视频。 + void recordDetection() { + _recordDetectTimer = Timer(recordDetectDuration, () { + startRecordingVideo(); + safeSetState(() {}); + }); + setState(() { + isShootingButtonAnimate = true; + }); + } + + /// This will be given to the [Listener] in the [shootingButton]. When it's + /// called, which means no more pressing on the button, cancel the timer and + /// reset the status. + /// 这个方法会赋值给 [shootingButton] 中的 [Listener]。当按钮释放了点击后,定时器 + /// 将被取消,并且状态会重置。 + void recordDetectionCancel(PointerUpEvent event) { + _recordDetectTimer?.cancel(); + if (isShootingButtonAnimate) { + safeSetState(() { + isShootingButtonAnimate = false; + }); + } + if (controller.value.isRecordingVideo) { + _lastShootingButtonPressedPosition = null; + safeSetState(() {}); + stopRecordingVideo(); + } + } + + /// Set record file path and start recording. + /// 设置拍摄文件路径并开始录制视频 + Future startRecordingVideo() async { + if (!controller.value.isRecordingVideo) { + controller.startVideoRecording().then((dynamic _) { + safeSetState(() {}); + if (isRecordingRestricted) { + _recordCountdownTimer = Timer(maximumRecordingDuration!, () { + stopRecordingVideo(); + }); + } + }).catchError((Object e) { + realDebugPrint('Error when start recording video: $e'); + if (controller.value.isRecordingVideo) { + controller.stopVideoRecording().catchError((Object e) { + realDebugPrint( + 'Error when stop recording video after an error start: $e', + ); + stopRecordingVideo(); + }); + } + handleErrorWithHandler(e, config.onError); + }); + } + } + + /// Stop the recording process. + /// 停止录制视频 + Future stopRecordingVideo() async { + void _handleError() { + _recordCountdownTimer?.cancel(); + isShootingButtonAnimate = false; + safeSetState(() {}); + } + + if (controller.value.isRecordingVideo) { + controller.stopVideoRecording().then((XFile file) async { + final AssetEntity? entity = await CameraPickerViewer.pushToViewer( + context, + pickerState: this, + pickerType: CameraPickerViewType.video, + previewXFile: file, + theme: theme, + shouldDeletePreviewFile: shouldDeletePreviewFile, + shouldAutoPreviewVideo: shouldAutoPreviewVideo, + ); + if (entity != null) { + Navigator.of(context).pop(entity); + } + }).catchError((Object e) { + realDebugPrint('Error when stop recording video: $e'); + realDebugPrint('Try to initialize a new CameraController...'); + initCameras(); + _handleError(); + handleErrorWithHandler(e, config.onError); + }).whenComplete(() { + isShootingButtonAnimate = false; + safeSetState(() {}); + }); + return; + } + _handleError(); + } + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + /////////////////////////// Just a line breaker //////////////////////////// + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + PointerUpEventListener? get onPointerUp { + if (enableRecording && !enableTapRecording) { + return recordDetectionCancel; + } + return null; + } + + PointerMoveEventListener? onPointerMove(BoxConstraints c) { + if (enablePullToZoomInRecord) { + return (PointerMoveEvent e) => onShootingButtonMove(e, c); + } + return null; + } + + GestureTapCallback? get onTap { + if (enableTapRecording) { + if (_controller?.value.isRecordingVideo == true) { + return stopRecordingVideo; + } + return () { + startRecordingVideo(); + setState(() { + isShootingButtonAnimate = true; + }); + }; + } + if (!onlyEnableRecording) { + return takePicture; + } + return null; + } + + String? get onTapHint { + if (enableTapRecording) { + if (_controller?.value.isRecordingVideo == true) { + return _textDelegate.sActionStopRecordingHint; + } + return _textDelegate.sActionRecordHint; + } + if (!onlyEnableRecording) { + return _textDelegate.sActionShootHint; + } + return null; + } + + GestureLongPressCallback? get onLongPress { + if (enableRecording && !enableTapRecording) { + return recordDetection; + } + return null; + } + + String? get onLongPressHint { + if (enableRecording && !enableTapRecording) { + return _textDelegate.sActionRecordHint; + } + return null; + } + + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + /////////////////////////// Just a line breaker //////////////////////////// + //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + + /// Settings action section widget. + /// 设置操作区 + /// + /// This displayed at the top of the screen. + /// 该区域显示在屏幕上方。 + Widget settingsAction(BuildContext context) { + return _initializeWrapper( + builder: (CameraValue v, __) { + if (v.isRecordingVideo) { + return const SizedBox.shrink(); + } + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Row( + children: [ + if (cameras.length > 1) switchCamerasButton, + const Spacer(), + switchFlashesButton(v), + ], + ), + ); + }, + ); + } + + /// The button to switch between cameras. + /// 切换相机的按钮 + Widget get switchCamerasButton { + return IconButton( + tooltip: _textDelegate.sSwitchCameraLensDirectionLabel( + _nextCameraDescription.lensDirection, + ), + onPressed: switchCameras, + icon: Icon( + Platform.isIOS + ? Icons.flip_camera_ios_outlined + : Icons.flip_camera_android_outlined, + size: 24, + ), + ); + } + + /// The button to switch flash modes. + /// 切换闪光灯模式的按钮 + Widget switchFlashesButton(CameraValue value) { + IconData icon; + switch (value.flashMode) { + case FlashMode.off: + icon = Icons.flash_off; + break; + case FlashMode.auto: + icon = Icons.flash_auto; + break; + case FlashMode.always: + case FlashMode.torch: + icon = Icons.flash_on; + break; + } + return IconButton( + onPressed: switchFlashesMode, + tooltip: _textDelegate.sFlashModeLabel(value.flashMode), + icon: Icon(icon, size: 24), + ); + } + + /// Text widget for shooting tips. + /// 拍摄的提示文字 + Widget tipsTextWidget(CameraController? controller) { + return AnimatedOpacity( + duration: recordDetectDuration, + opacity: controller?.value.isRecordingVideo == true ? 0 : 1, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 20.0), + child: Text( + _textDelegate.shootingTips, + style: const TextStyle(fontSize: 15.0), + ), + ), + ); + } + + /// Shooting action section widget. + /// 拍照操作区 + /// + /// This displayed at the top of the screen. + /// 该区域显示在屏幕下方。 + Widget shootingActions( + BuildContext context, + CameraController? controller, + BoxConstraints constraints, + ) { + return SizedBox( + height: 118, + child: Row( + children: [ + if (controller?.value.isRecordingVideo != true) + Expanded(child: backButton(context, constraints)) + else + const Spacer(), + Expanded( + child: Center( + child: MergeSemantics(child: shootingButton(constraints)), + ), + ), + const Spacer(), + ], + ), + ); + } + + /// The back button near to the [shootingButton]. + /// 靠近拍照键的返回键 + Widget backButton(BuildContext context, BoxConstraints constraints) { + return IconButton( + onPressed: Navigator.of(context).pop, + tooltip: MaterialLocalizations.of(context).backButtonTooltip, + icon: Container( + alignment: Alignment.center, + width: 27, + height: 27, + decoration: const BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + child: const Icon(Icons.keyboard_arrow_down, color: Colors.black), + ), + ); + } + + /// The shooting button. + /// 拍照按钮 + Widget shootingButton(BoxConstraints constraints) { + const Size outerSize = Size.square(115); + const Size innerSize = Size.square(82); + return Semantics( + label: _textDelegate.sActionShootingButtonTooltip, + onTap: onTap, + onTapHint: onTapHint, + onLongPress: onLongPress, + onLongPressHint: onLongPressHint, + child: Listener( + behavior: HitTestBehavior.opaque, + onPointerUp: onPointerUp, + onPointerMove: onPointerMove(constraints), + child: GestureDetector( + onTap: onTap, + onLongPress: onLongPress, + child: SizedBox.fromSize( + size: outerSize, + child: Stack( + children: [ + Center( + child: AnimatedContainer( + duration: kThemeChangeDuration, + width: isShootingButtonAnimate + ? outerSize.width + : innerSize.width, + height: isShootingButtonAnimate + ? outerSize.height + : innerSize.height, + padding: EdgeInsets.all(isShootingButtonAnimate ? 41 : 11), + decoration: BoxDecoration( + color: theme.canvasColor.withOpacity(0.85), + shape: BoxShape.circle, + ), + child: const DecoratedBox( + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + ), + ), + ), + _initializeWrapper( + isInitialized: () => + controller.value.isRecordingVideo && + isRecordingRestricted, + builder: (_, __) => CircularProgressBar( + duration: maximumRecordingDuration!, + outerRadius: outerSize.width, + ringsColor: theme.indicatorColor, + ringsWidth: 2.0, + ), + ), + ], + ), + ), + ), + ), + ); + } + + Widget _exposureSlider( + ExposureMode mode, + double size, + double height, + double gap, + ) { + final bool isLocked = mode == ExposureMode.locked; + final Color? color = isLocked ? _lockedColor : theme.iconTheme.color; + + Widget _line() { + return ValueListenableBuilder( + valueListenable: _isExposureModeDisplays, + builder: (_, bool value, Widget? child) => AnimatedOpacity( + duration: _kDuration, + opacity: value ? 1 : 0, + child: child, + ), + child: Center(child: Container(width: 1, color: color)), + ); + } + + return ValueListenableBuilder( + valueListenable: _currentExposureOffset, + builder: (_, double exposure, __) { + final double _effectiveTop = (size + gap) + + (_minAvailableExposureOffset.abs() - exposure) * + (height - size * 3) / + (_maxAvailableExposureOffset - _minAvailableExposureOffset); + final double _effectiveBottom = height - _effectiveTop - size; + return Stack( + clipBehavior: Clip.none, + children: [ + Positioned.fill( + top: _effectiveTop + gap, + child: _line(), + ), + Positioned.fill( + bottom: _effectiveBottom + gap, + child: _line(), + ), + Positioned( + top: (_minAvailableExposureOffset.abs() - exposure) * + (height - size * 3) / + (_maxAvailableExposureOffset - _minAvailableExposureOffset), + child: Transform.rotate( + angle: exposure, + child: Icon( + Icons.wb_sunny_outlined, + size: size, + color: color, + ), + ), + ), + Positioned.fill( + top: -10, + bottom: -10, + child: RotatedBox( + quarterTurns: 3, + child: Directionality( + textDirection: TextDirection.ltr, + child: Opacity( + opacity: 0, + child: Slider( + value: exposure, + min: _minAvailableExposureOffset, + max: _maxAvailableExposureOffset, + onChanged: updateExposureOffset, + ), + ), + ), + ), + ), + ], + ); + }, + ); + } + + /// The area widget for the last exposure point that user manually set. + /// 用户手动设置的曝光点的区域显示 + Widget _focusingAreaWidget(BoxConstraints constraints) { + Widget _buildControl(double size, double height) { + const double _verticalGap = 3; + return ValueListenableBuilder( + valueListenable: _exposureMode, + builder: (_, ExposureMode mode, __) { + final bool isLocked = mode == ExposureMode.locked; + return Column( + children: [ + ValueListenableBuilder( + valueListenable: _isExposureModeDisplays, + builder: (_, bool value, Widget? child) => AnimatedOpacity( + duration: _kDuration, + opacity: value ? 1 : 0, + child: child, + ), + child: GestureDetector( + onTap: switchExposureMode, + child: SizedBox.fromSize( + size: Size.square(size), + child: Icon( + isLocked ? Icons.lock_rounded : Icons.lock_open_rounded, + size: size, + color: isLocked ? _lockedColor : null, + ), + ), + ), + ), + const SizedBox(height: _verticalGap), + Expanded( + child: _exposureSlider(mode, size, height, _verticalGap), + ), + const SizedBox(height: _verticalGap), + SizedBox.fromSize(size: Size.square(size)), + ], + ); + }, + ); + } + + Widget _buildFromPoint(Offset point) { + const double _controllerWidth = 20; + final double _pointWidth = constraints.maxWidth / 5; + final double _exposureControlWidth = + enableExposureControlOnPoint ? _controllerWidth : 0; + final double _width = _pointWidth + _exposureControlWidth + 2; + + final bool _shouldReverseLayout = point.dx > constraints.maxWidth / 4 * 3; + + final double _effectiveLeft = math.min( + constraints.maxWidth - _width, + math.max(0, point.dx - _width / 2), + ); + final double _effectiveTop = math.min( + constraints.maxHeight - _pointWidth * 3, + math.max(0, point.dy - _pointWidth * 3 / 2), + ); + + return Positioned( + left: _effectiveLeft, + top: _effectiveTop, + width: _width, + height: _pointWidth * 3, + child: ExcludeSemantics( + child: Row( + textDirection: + _shouldReverseLayout ? TextDirection.rtl : TextDirection.ltr, + children: [ + ExposurePointWidget( + key: ValueKey(DateTime.now().millisecondsSinceEpoch), + size: _pointWidth, + color: theme.iconTheme.color!, + ), + if (enableExposureControlOnPoint) const SizedBox(width: 2), + if (enableExposureControlOnPoint) + SizedBox.fromSize( + size: Size(_exposureControlWidth, _pointWidth * 3), + child: _buildControl(_controllerWidth, _pointWidth * 3), + ), + ], + ), + ), + ); + } + + return ValueListenableBuilder( + valueListenable: _lastExposurePoint, + builder: (_, Offset? point, __) { + if (point == null) { + return const SizedBox.shrink(); + } + return _buildFromPoint(point); + }, + ); + } + + /// The [GestureDetector] widget for setting exposure point manually. + /// 用于手动设置曝光点的 [GestureDetector] + Widget _exposureDetectorWidget( + BuildContext context, + BoxConstraints constraints, + ) { + void _focus(TapUpDetails d) { + // Only call exposure point updates when the controller is initialized. + if (_controller?.value.isInitialized == true) { + Feedback.forTap(context); + setExposureAndFocusPoint(d.globalPosition, constraints); + } + } + + return Positioned.fill( + child: Semantics( + label: _textDelegate.sCameraPreviewLabel( + _controller?.description.lensDirection, + ), + image: true, + onTap: () { + // Focus on the center point when using semantics tap. + final Size size = MediaQuery.of(context).size; + final TapUpDetails details = TapUpDetails( + kind: PointerDeviceKind.touch, + globalPosition: Offset(size.width / 2, size.height / 2), + ); + _focus(details); + }, + onTapHint: _textDelegate.sActionManuallyFocusHint, + sortKey: const OrdinalSortKey(1), + hidden: _controller == null, + excludeSemantics: true, + child: GestureDetector( + onTapUp: _focus, + behavior: HitTestBehavior.translucent, + child: const SizedBox.expand(), + ), + ), + ); + } + + Widget _cameraPreview( + BuildContext context, { + required DeviceOrientation orientation, + required BoxConstraints constraints, + }) { + Widget _preview = Listener( + onPointerDown: (_) => _pointers++, + onPointerUp: (_) => _pointers--, + child: GestureDetector( + onScaleStart: enablePinchToZoom ? _handleScaleStart : null, + onScaleUpdate: enablePinchToZoom ? _handleScaleUpdate : null, + // Enabled cameras switching by default if we have multiple cameras. + onDoubleTap: cameras.length > 1 ? switchCameras : null, + child: CameraPreview(controller), + ), + ); + + // Make a transformed widget if it's defined. + final Widget? _transformedWidget = config.previewTransformBuilder?.call( + context, + controller, + _preview, + ); + _preview = _transformedWidget ?? _preview; + + _preview = RotatedBox( + quarterTurns: -config.cameraQuarterTurns, + child: Transform.scale( + scale: _effectiveCameraScale(constraints, controller), + child: Center(child: _preview), + ), + ); + return _preview; + } + + Widget _initializeWrapper({ + required Widget Function(CameraValue, Widget?) builder, + bool Function()? isInitialized, + Widget? child, + }) { + if (_controller != null) { + return ValueListenableBuilder( + valueListenable: controller, + builder: (_, CameraValue value, Widget? w) { + return isInitialized?.call() ?? value.isInitialized + ? builder(value, w) + : const SizedBox.shrink(); + }, + child: child, + ); + } + return const SizedBox.shrink(); + } + + Widget _cameraBuilder({ + required BuildContext context, + required CameraValue value, + required BoxConstraints constraints, + }) { + return RepaintBoundary( + child: Stack( + children: [ + Positioned.fill( + child: _cameraPreview( + context, + orientation: value.deviceOrientation, + constraints: constraints, + ), + ), + if (config.foregroundBuilder != null) + Positioned.fill(child: config.foregroundBuilder!(context, value)), + ], + ), + ); + } + + Widget _contentBuilder(BoxConstraints constraints) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.only(bottom: 20.0), + child: Column( + children: [ + Semantics( + sortKey: const OrdinalSortKey(0), + hidden: _controller == null, + child: settingsAction(context), + ), + const Spacer(), + ExcludeSemantics(child: tipsTextWidget(_controller)), + Semantics( + sortKey: const OrdinalSortKey(2), + hidden: _controller == null, + child: shootingActions(context, _controller, constraints), + ), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return AnnotatedRegion( + value: SystemUiOverlayStyle.light, + child: Theme( + data: theme, + child: Material( + color: Colors.black, + child: RotatedBox( + quarterTurns: config.cameraQuarterTurns, + child: LayoutBuilder( + builder: (BuildContext c, BoxConstraints constraints) => Stack( + fit: StackFit.expand, + alignment: Alignment.center, + children: [ + ExcludeSemantics( + child: _initializeWrapper( + builder: (CameraValue value, __) => _cameraBuilder( + context: c, + value: value, + constraints: constraints, + ), + ), + ), + if (enableSetExposure) + _exposureDetectorWidget(c, constraints), + _initializeWrapper( + builder: (_, __) => _focusingAreaWidget(constraints), + ), + _contentBuilder(constraints), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker_page_route.dart b/packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker_page_route.dart new file mode 100644 index 00000000..a9b56c1c --- /dev/null +++ b/packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker_page_route.dart @@ -0,0 +1,77 @@ +/// +/// [Author] Alex (https://github.com/AlexV525) +/// [Date] 2020/7/15 21:44 +/// +import 'package:flutter/material.dart'; + +/// Build [AssetPickerPageRoute] with the given generic type. +/// 构建匹配泛型的 [AssetPickerPageRoute] +typedef CameraPickerPageRouteBuilder = CameraPickerPageRoute Function( + Widget picker, +); + +/// Built a slide page transition for the picker. +/// 为选择器构造一个上下进出的页面过渡动画 +class CameraPickerPageRoute extends PageRoute { + CameraPickerPageRoute({ + required this.builder, + this.transitionCurve = Curves.easeIn, + this.transitionDuration = const Duration(milliseconds: 300), + this.barrierColor, + this.barrierDismissible = false, + this.barrierLabel, + this.maintainState = true, + this.opaque = true, + this.canTransitionFromPredicate, + }); + + final WidgetBuilder builder; + + final Curve transitionCurve; + @override + final Duration transitionDuration; + + @override + final Color? barrierColor; + @override + final bool barrierDismissible; + @override + final String? barrierLabel; + @override + final bool opaque; + @override + final bool maintainState; + + final bool Function(TransitionRoute)? canTransitionFromPredicate; + + @override + bool canTransitionFrom(TransitionRoute previousRoute) => + canTransitionFromPredicate?.call(previousRoute) ?? false; + + @override + Widget buildPage( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + ) { + return builder(context); + } + + @override + Widget buildTransitions( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child, + ) { + return SlideTransition( + position: Tween( + begin: const Offset(0, 1), + end: Offset.zero, + ).animate( + CurvedAnimation(curve: transitionCurve, parent: animation), + ), + child: child, + ); + } +} diff --git a/packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker_viewer.dart b/packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker_viewer.dart new file mode 100644 index 00000000..50b49604 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker_viewer.dart @@ -0,0 +1,495 @@ +/// +/// [Author] Alex (https://github.com/AlexV525) +/// [Date] 2020/7/16 22:02 +/// +import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:camera/camera.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/semantics.dart'; +import 'package:image_cropper/image_cropper.dart'; +import 'package:path/path.dart' as path; +import 'package:video_player/video_player.dart'; + +import '../constants/constants.dart'; +import '../internals/enums.dart'; +import '../internals/methods.dart'; +import '../internals/type_defs.dart'; + +import 'camera_picker.dart'; + +class CameraPickerViewer extends StatefulWidget { + const CameraPickerViewer({ + Key? key, + required this.pickerState, + required this.pickerType, + required this.previewXFile, + required this.theme, + this.shouldDeletePreviewFile = false, + this.shouldAutoPreviewVideo = false, + this.onEntitySaving, + this.onError, + }) : super(key: key); + + /// State of the picker. + /// 选择器的状态实例 + final CameraPickerState pickerState; + + /// The type of the viewer. (Image | Video) + /// 预览的类型(图片或视频) + final CameraPickerViewType pickerType; + + /// The [XFile] of the preview file. + /// 预览文件的 [XFile] 实例 + final XFile previewXFile; + + /// The [ThemeData] which the picker is using. + /// 选择器使用的主题 + final ThemeData theme; + + /// {@macro wechat_camera_picker.shouldDeletePreviewFile} + final bool shouldDeletePreviewFile; + + /// {@macro wechat_camera_picker.shouldAutoPreviewVideo} + final bool shouldAutoPreviewVideo; + + /// {@macro wechat_camera_picker.EntitySaveCallback} + final EntitySaveCallback? onEntitySaving; + + /// {@macro wechat_camera_picker.CameraErrorHandler} + final CameraErrorHandler? onError; + + /// Static method to push with the navigator. + /// 跳转至选择预览的静态方法 + static Future pushToViewer( + BuildContext context, { + required CameraPickerState pickerState, + required CameraPickerViewType pickerType, + required XFile previewXFile, + required ThemeData theme, + bool shouldDeletePreviewFile = false, + bool shouldAutoPreviewVideo = false, + EntitySaveCallback? onEntitySaving, + CameraErrorHandler? onError, + }) { + return Navigator.of(context).push( + PageRouteBuilder( + pageBuilder: (_, __, ___) => CameraPickerViewer( + pickerState: pickerState, + pickerType: pickerType, + previewXFile: previewXFile, + theme: theme, + shouldDeletePreviewFile: shouldDeletePreviewFile, + shouldAutoPreviewVideo: shouldAutoPreviewVideo, + onEntitySaving: onEntitySaving, + onError: onError, + ), + transitionsBuilder: ( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child, + ) { + return FadeTransition(opacity: animation, child: child); + }, + ), + ); + } + + @override + _CameraPickerViewerState createState() => _CameraPickerViewerState(); +} + +class _CameraPickerViewerState extends State { + /// Controller for the video player. + /// 视频播放的控制器 + late final VideoPlayerController videoController = + VideoPlayerController.file(previewFile); + + /// Whether the controller has initialized. + /// 控制器是否已初始化 + late bool hasLoaded = pickerType == CameraPickerViewType.image; + + /// Whether there's any error when initialize the video controller. + /// 初始化视频控制器时是否发生错误 + bool hasErrorWhenInitializing = false; + + /// Whether the player is playing. + /// 播放器是否在播放 + final ValueNotifier isPlaying = ValueNotifier(false); + + /// Whether the controller is playing. + /// 播放控制器是否在播放 + bool get isControllerPlaying => videoController.value.isPlaying; + + CameraPickerState get pickerState => widget.pickerState; + + CameraPickerViewType get pickerType => widget.pickerType; + + XFile get previewXFile => widget.previewXFile; + + /// Construct an [File] instance through [previewXFile]. + /// 通过 [previewXFile] 构建 [File] 实例。 + File? _previewFile; + File get previewFile { + if(_previewFile == null) return File(previewXFile.path); + return _previewFile!; + } + set previewFile(File file){ + _previewFile = file; + } + ThemeData get theme => widget.theme; + + bool get shouldDeletePreviewFile => widget.shouldDeletePreviewFile; + + bool get shouldAutoPreviewVideo => widget.shouldAutoPreviewVideo; + + @override + void initState() { + super.initState(); + if (pickerType == CameraPickerViewType.video) { + initializeVideoPlayerController(); + } + } + + @override + void dispose() { + /// Remove listener from the controller and dispose it when widget dispose. + /// 部件销毁时移除控制器的监听并销毁控制器。 + videoController.removeListener(videoPlayerListener); + videoController.pause(); + videoController.dispose(); + super.dispose(); + } + + /// Get media url from the asset, then initialize the controller and add with + /// a listener. + /// 从资源获取媒体url后初始化,并添加监听。 + Future initializeVideoPlayerController() async { + try { + await videoController.initialize(); + videoController.addListener(videoPlayerListener); + hasLoaded = true; + if (shouldAutoPreviewVideo) { + videoController.play(); + } + } catch (e) { + hasErrorWhenInitializing = true; + realDebugPrint('Error when initializing video controller: $e'); + handleErrorWithHandler(e, widget.onError); + } finally { + if (mounted) { + setState(() {}); + } + } + } + + /// Listener for the video player. + /// 播放器的监听方法 + void videoPlayerListener() { + if (isControllerPlaying != isPlaying.value) { + isPlaying.value = isControllerPlaying; + } + } + + /// Callback for the play button. + /// 播放按钮的回调 + /// + /// Normally it only switches play state for the player. If the video reaches + /// the end, then click the button will make the video replay. + /// 一般来说按钮只切换播放暂停。当视频播放结束时,点击按钮将从头开始播放。 + Future playButtonCallback() async { + if (isPlaying.value) { + videoController.pause(); + } else { + if (videoController.value.duration == videoController.value.position) { + videoController + ..seekTo(Duration.zero) + ..play(); + } else { + videoController.play(); + } + } + } + + /// When users confirm to use the taken file, create the [AssetEntity]. + /// While the entity might returned null, there's no side effects if popping `null` + /// because the parent picker will ignore it. + Future createAssetEntityAndPop(File file) async { + if (widget.onEntitySaving != null) { + await widget.onEntitySaving!( + context: context, + viewType: pickerType, + file: file, + ); + return; + } + AssetEntity? entity; + try { + final PermissionState _ps = await PhotoManager.requestPermissionExtend(); + if (_ps == PermissionState.authorized || _ps == PermissionState.limited) { + switch (pickerType) { + case CameraPickerViewType.image: + final Uint8List data = await file.readAsBytes(); + entity = await PhotoManager.editor.saveImage( + data, + title: path.basename(file.path), + ); + break; + case CameraPickerViewType.video: + entity = await PhotoManager.editor.saveVideo( + file, + title: path.basename(file.path), + ); + break; + } + if (shouldDeletePreviewFile && file.existsSync()) { + file.delete(); + } + return; + } + handleErrorWithHandler( + StateError( + 'Permission is not fully granted to save the captured file.', + ), + widget.onError, + ); + } catch (e) { + realDebugPrint('Saving entity failed: $e'); + handleErrorWithHandler(e, widget.onError); + } finally { + Navigator.of(context).pop(entity); + } + } + + /// The back button for the preview section. + /// 预览区的返回按钮 + Widget previewBackButton(BuildContext context) { + return Semantics( + sortKey: const OrdinalSortKey(0), + child: Padding( + padding: const EdgeInsets.all(10.0), + child: IconButton( + onPressed: () { + if (previewFile.existsSync()) { + previewFile.delete(); + } + Navigator.of(context).pop(); + }, + padding: EdgeInsets.zero, + constraints: BoxConstraints.tight(const Size.square(28)), + tooltip: MaterialLocalizations.of(context).backButtonTooltip, + iconSize: 18, + icon: Container( + padding: const EdgeInsets.all(5), + decoration: BoxDecoration( + color: Theme.of(context).iconTheme.color, + shape: BoxShape.circle, + ), + child: Icon( + Icons.keyboard_return_rounded, + color: Theme.of(context).canvasColor, + ), + ), + ), + ), + ); + } + + Widget previewWidget(BuildContext context) { + final Widget _builder; + if (pickerType == CameraPickerViewType.video) { + _builder = Stack( + children: [ + Center( + child: AspectRatio( + aspectRatio: videoController.value.aspectRatio, + child: VideoPlayer(videoController), + ), + ), + playControlButton, + ], + ); + } else { + _builder = Image.file(previewFile); + } + return MergeSemantics( + child: Semantics( + label: Constants.textDelegate.sActionPreviewHint, + image: true, + onTapHint: Constants.textDelegate.sActionPreviewHint, + sortKey: const OrdinalSortKey(1), + child: _builder, + ), + ); + } + + /// The confirm button for the preview section. + /// 预览区的确认按钮 + Widget get previewConfirmButton { + return MaterialButton( + minWidth: 20.0, + height: 32.0, + padding: const EdgeInsets.symmetric(horizontal: 20.0), + color: theme.colorScheme.secondary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(3.0), + ), + child: Text( + Constants.textDelegate.confirm, + style: TextStyle( + color: theme.textTheme.bodyText1?.color, + fontSize: 17.0, + fontWeight: FontWeight.normal, + ), + ), + onPressed: ()=>createAssetEntityAndPop(previewFile), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + ); + } + + /// The confirm button for the preview section. + /// 裁剪按钮 + Widget get cropperButton { + return MaterialButton( + minWidth: 20.0, + height: 32.0, + padding: const EdgeInsets.symmetric(horizontal: 20.0), + color: theme.colorScheme.secondary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(3.0), + ), + child: Text( + Constants.textDelegate.cropper, + style: TextStyle( + color: theme.textTheme.bodyText1?.color, + fontSize: 17.0, + fontWeight: FontWeight.normal, + ), + ), + onPressed: ()=>imageCropper(), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + ); + } + /// A play control button the video playing process. + /// 控制视频播放的按钮 + Widget get playControlButton { + return ValueListenableBuilder( + valueListenable: isPlaying, + builder: (_, bool value, Widget? child) => GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: value ? playButtonCallback : null, + child: Center( + child: AnimatedOpacity( + duration: kThemeAnimationDuration, + opacity: value ? 0.0 : 1.0, + child: GestureDetector( + onTap: playButtonCallback, + child: DecoratedBox( + decoration: const BoxDecoration( + boxShadow: [BoxShadow(color: Colors.black12)], + shape: BoxShape.circle, + ), + child: Icon( + value ? Icons.pause_circle_outline : Icons.play_circle_filled, + size: 70.0, + color: Colors.white, + ), + ), + ), + ), + ), + ), + ); + } + + /// Actions section for the viewer. Including 'back' and 'confirm' button. + /// 预览的操作区。包括"返回"和"确定"按钮。 + Widget viewerActions(BuildContext context) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 20.0, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Semantics( + sortKey: const OrdinalSortKey(0), + child: Row( + children: [ + previewBackButton(context), + const Spacer(), + ], + ), + ), + Semantics( + sortKey: const OrdinalSortKey(2), + child: Row( + children: [ + if (pickerType == CameraPickerViewType.image) cropperButton else SizedBox(), + const Spacer(), + previewConfirmButton, + ], + ), + ), + ], + ), + ), + ); + } + Future imageCropper() async{ + + File? croppedFile = await ImageCropper().cropImage( + sourcePath: previewFile.path , + aspectRatioPresets: [ + CropAspectRatioPreset.square, + CropAspectRatioPreset.ratio3x2, + CropAspectRatioPreset.original, + CropAspectRatioPreset.ratio4x3, + CropAspectRatioPreset.ratio16x9 + ], + androidUiSettings: AndroidUiSettings( + toolbarTitle: 'Cropper', + toolbarColor: Colors.deepOrange, + toolbarWidgetColor: Colors.white, + initAspectRatio: CropAspectRatioPreset.original, + lockAspectRatio: false), + iosUiSettings: IOSUiSettings( + minimumAspectRatio: 1.0, + ) + ); + + if(croppedFile != null){ + setState(() { + previewFile = croppedFile; + }); + } + } + @override + Widget build(BuildContext context) { + if (hasErrorWhenInitializing) { + return Center( + child: Text( + Constants.textDelegate.loadFailed, + style: const TextStyle(inherit: false), + ), + ); + } + if (!hasLoaded) { + return const SizedBox.shrink(); + } + return Material( + color: Colors.black, + child: Stack( + children: [ + Positioned.fill(child: previewWidget(context)), + viewerActions(context), + ], + ), + ); + } +} diff --git a/packages/flutter_wechat_camera_picker/lib/src/widgets/circular_progress_bar.dart b/packages/flutter_wechat_camera_picker/lib/src/widgets/circular_progress_bar.dart new file mode 100644 index 00000000..b4b52a74 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/lib/src/widgets/circular_progress_bar.dart @@ -0,0 +1,120 @@ +/// +/// [Author] Alex (https://github.com/AlexV525) +/// [Date] 2020/7/15 21:13 +/// +import 'dart:math' as math; + +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; + +import '../constants/styles.dart'; + +class CircularProgressBar extends StatefulWidget { + const CircularProgressBar({ + Key? key, + required this.outerRadius, + required this.ringsWidth, + this.ringsColor = C.themeColor, + this.progress = 0.0, + this.duration = const Duration(seconds: 15), + }) : super(key: key); + + final double outerRadius; + final double ringsWidth; + final Color ringsColor; + final double progress; + final Duration duration; + + @override + _CircleProgressState createState() => _CircleProgressState(); +} + +class _CircleProgressState extends State + with SingleTickerProviderStateMixin { + final GlobalKey paintKey = GlobalKey(); + + late final AnimationController progressController = AnimationController( + duration: widget.duration, + vsync: this, + )..value = widget.progress; + + @override + void initState() { + super.initState(); + SchedulerBinding.instance?.addPostFrameCallback((Duration _) { + progressController.forward(); + }); + } + + @override + void dispose() { + progressController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final Size size = Size.square(widget.outerRadius * 2); + return Center( + child: AnimatedBuilder( + animation: progressController, + builder: (_, __) => CustomPaint( + key: paintKey, + size: size, + painter: ProgressPainter( + progress: progressController.value, + ringsWidth: widget.ringsWidth, + ringsColor: widget.ringsColor, + ), + ), + ), + ); + } +} + +class ProgressPainter extends CustomPainter { + const ProgressPainter({ + required this.ringsWidth, + required this.ringsColor, + required this.progress, + }); + + final double ringsWidth; + final Color ringsColor; + final double progress; + + @override + void paint(Canvas canvas, Size size) { + final double center = size.width / 2; + final Offset offsetCenter = Offset(center, center); + final double drawRadius = size.width / 2 - ringsWidth; + final double angle = 360.0 * progress; + final double radians = angle.toRad; + + final double outerRadius = center; + final double innerRadius = center - ringsWidth * 2; + + final double progressWidth = outerRadius - innerRadius; + canvas.save(); + canvas.translate(0.0, size.width); + canvas.rotate(-90.0.toRad); + final Rect arcRect = Rect.fromCircle( + center: offsetCenter, + radius: drawRadius, + ); + final Paint progressPaint = Paint() + ..color = ringsColor + ..style = PaintingStyle.stroke + ..strokeWidth = progressWidth; + canvas + ..drawArc(arcRect, 0, radians, false, progressPaint) + ..restore(); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => true; +} + +extension _MathExtension on double { + double get toRad => this * (math.pi / 180.0); +} diff --git a/packages/flutter_wechat_camera_picker/lib/src/widgets/exposure_point_widget.dart b/packages/flutter_wechat_camera_picker/lib/src/widgets/exposure_point_widget.dart new file mode 100644 index 00000000..1932e2b1 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/lib/src/widgets/exposure_point_widget.dart @@ -0,0 +1,110 @@ +/// +/// [Author] Alex (https://github.com/AlexV525) +/// [Date] 2021-01-09 18:43 +/// +import 'package:flutter/material.dart'; + +import 'builder/tween_animation_builder_2.dart'; + +class ExposurePointWidget extends StatelessWidget { + const ExposurePointWidget({ + Key? key, + required this.size, + required this.color, + }) : super(key: key); + + final double size; + final Color color; + + @override + Widget build(BuildContext context) { + return TweenAnimationBuilder2( + firstTween: Tween(begin: 0, end: 1), + secondTween: Tween(begin: 1.5, end: 1), + secondTweenCurve: Curves.easeOutBack, + secondTweenDuration: const Duration(milliseconds: 400), + builder: (_, double opacity, double scale) => Opacity( + opacity: opacity, + child: Transform.scale( + scale: scale, + child: SizedBox.fromSize( + size: Size.square(size), + child: CustomPaint( + painter: ExposurePointPainter(size: size, color: color), + ), + ), + ), + ), + ); + } +} + +/// A [CustomPaint] that draws the exposure point with four arcs and one circle. +/// 包含了四条弧及一个圆的曝光点绘制 +class ExposurePointPainter extends CustomPainter { + const ExposurePointPainter({ + required this.size, + required this.color, + this.radius = 2, + this.strokeWidth = 2, + }) : assert(size > 0); + + final double size; + final double radius; + final double strokeWidth; + final Color color; + + Radius get _circularRadius => Radius.circular(radius); + + @override + void paint(Canvas canvas, Size size) { + final Size _dividedSize = size / 3; + final double _lineLength = _dividedSize.width - radius; + final Paint _paint = Paint() + ..style = PaintingStyle.stroke + ..color = color + ..strokeWidth = strokeWidth; + + final Path path = Path() + // 先移动到左上组弧的顺时针起始位 + ..moveTo(0, _dividedSize.height) + // 左上组弧 + ..relativeLineTo(0, -_lineLength) + ..relativeArcToPoint(Offset(radius, -radius), radius: _circularRadius) + ..relativeLineTo(_lineLength, 0) + // 移动至右上组弧起始位 + ..relativeMoveTo(_dividedSize.width, 0) + // 右上组弧 + ..relativeLineTo(_lineLength, 0) + ..relativeArcToPoint(Offset(radius, radius), radius: _circularRadius) + ..relativeLineTo(0, _lineLength) + // 移动至右下组弧起始位 + ..relativeMoveTo(0, _dividedSize.height) + // 右下组弧 + ..relativeLineTo(0, _lineLength) + ..relativeArcToPoint(Offset(-radius, radius), radius: _circularRadius) + ..relativeLineTo(-_lineLength, 0) + // 移动至左下组弧起始位 + ..relativeMoveTo(-_dividedSize.width, 0) + // 左下组弧 + ..relativeLineTo(-_lineLength, 0) + ..relativeArcToPoint(Offset(-radius, -radius), radius: _circularRadius) + ..relativeLineTo(0, -_lineLength) + // 移动至左上组弧起始位 + ..relativeMoveTo(0, -_dividedSize.height) + ..close(); + canvas + ..drawPath(path, _paint) + // 中心圆 + ..drawCircle( + Offset(size.width / 2, size.height / 2), + _dividedSize.width / 2, + _paint, + ); + } + + @override + bool shouldRepaint(ExposurePointPainter oldDelegate) { + return oldDelegate.size != size || oldDelegate.radius != radius; + } +} diff --git a/packages/flutter_wechat_camera_picker/lib/wechat_camera_picker.dart b/packages/flutter_wechat_camera_picker/lib/wechat_camera_picker.dart new file mode 100644 index 00000000..7a2a2917 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/lib/wechat_camera_picker.dart @@ -0,0 +1,9 @@ +library wechat_camera_picker; + +export 'package:camera/camera.dart'; +export 'package:photo_manager/photo_manager.dart'; + +export 'src/constants/config.dart'; +export 'src/delegates/camera_picker_text_delegate.dart'; +export 'src/widgets/camera_picker.dart'; +export 'src/widgets/camera_picker_page_route.dart'; diff --git a/packages/flutter_wechat_camera_picker/pubspec.yaml b/packages/flutter_wechat_camera_picker/pubspec.yaml new file mode 100644 index 00000000..b1f63792 --- /dev/null +++ b/packages/flutter_wechat_camera_picker/pubspec.yaml @@ -0,0 +1,18 @@ +name: wechat_camera_picker +description: A camera picker which is an extension to wechat_assets_picker, but can be run separately. +version: 3.0.1 +homepage: https://github.com/fluttercandies/flutter_wechat_camera_picker + +environment: + sdk: '>=2.14.0 <3.0.0' + flutter: '>=2.5.0' + +dependencies: + flutter: + sdk: flutter + image_cropper: ^1.5.1 + camera: ^0.9.4+6 + path: ^1.8.0 + path_provider: ^2.0.8 + photo_manager: ^2.0.1 + video_player: ^2.2.1 From 740b4c31cdeddee6dfc508824d0fbf3f99b821b9 Mon Sep 17 00:00:00 2001 From: zzw <602129484@qq.com> Date: Fri, 22 Apr 2022 13:43:04 +0800 Subject: [PATCH 09/14] =?UTF-8?q?feat:=E6=8B=8D=E7=85=A7=E6=8F=92=E4=BB=B6?= =?UTF-8?q?UI=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ios/Runner.xcodeproj/project.pbxproj | 12 +-- .../camera_picker_text_delegate.dart | 2 +- .../lib/src/widgets/camera_picker.dart | 17 ++-- .../lib/src/widgets/camera_picker_viewer.dart | 92 +++++++++---------- 4 files changed, 60 insertions(+), 63 deletions(-) diff --git a/packages/flutter_wechat_camera_picker/example/ios/Runner.xcodeproj/project.pbxproj b/packages/flutter_wechat_camera_picker/example/ios/Runner.xcodeproj/project.pbxproj index acaf87a3..7f9ccb9f 100644 --- a/packages/flutter_wechat_camera_picker/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/flutter_wechat_camera_picker/example/ios/Runner.xcodeproj/project.pbxproj @@ -363,7 +363,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 64MAFNBS88; + DEVELOPMENT_TEAM = 678MZVH5SX; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -378,7 +378,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.fluttercandies.wechatCameraPickerDemo; + PRODUCT_BUNDLE_IDENTIFIER = com.hashkey.me; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -500,7 +500,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 64MAFNBS88; + DEVELOPMENT_TEAM = 678MZVH5SX; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -515,7 +515,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.fluttercandies.wechatCameraPickerDemo; + PRODUCT_BUNDLE_IDENTIFIER = com.hashkey.me; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -531,7 +531,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 64MAFNBS88; + DEVELOPMENT_TEAM = 678MZVH5SX; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -546,7 +546,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = com.fluttercandies.wechatCameraPickerDemo; + PRODUCT_BUNDLE_IDENTIFIER = com.hashkey.me; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/packages/flutter_wechat_camera_picker/lib/src/delegates/camera_picker_text_delegate.dart b/packages/flutter_wechat_camera_picker/lib/src/delegates/camera_picker_text_delegate.dart index 898f6e6b..e6d9cb7d 100644 --- a/packages/flutter_wechat_camera_picker/lib/src/delegates/camera_picker_text_delegate.dart +++ b/packages/flutter_wechat_camera_picker/lib/src/delegates/camera_picker_text_delegate.dart @@ -108,7 +108,7 @@ class EnglishCameraPickerTextDelegate extends CameraPickerTextDelegate { String get confirm => 'Confirm'; @override - String get cropper => '裁剪'; + String get cropper => 'crop'; @override String get shootingTips => 'Tap to take photo.'; diff --git a/packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker.dart b/packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker.dart index 46262a49..42963e90 100644 --- a/packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker.dart +++ b/packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker.dart @@ -861,9 +861,9 @@ class CameraPickerState extends State padding: const EdgeInsets.symmetric(horizontal: 12.0), child: Row( children: [ - if (cameras.length > 1) switchCamerasButton, - const Spacer(), switchFlashesButton(v), + const Spacer(), + if (cameras.length > 1) switchCamerasButton, ], ), ); @@ -964,8 +964,8 @@ class CameraPickerState extends State tooltip: MaterialLocalizations.of(context).backButtonTooltip, icon: Container( alignment: Alignment.center, - width: 27, - height: 27, + width: 24, + height: 24, decoration: const BoxDecoration( color: Colors.white, shape: BoxShape.circle, @@ -978,8 +978,9 @@ class CameraPickerState extends State /// The shooting button. /// 拍照按钮 Widget shootingButton(BoxConstraints constraints) { - const Size outerSize = Size.square(115); - const Size innerSize = Size.square(82); + final scal = MediaQuery.of(context).size.width/375.0; + Size outerSize = Size.square(90.0*scal); + Size innerSize = Size.square(64.0*scal); return Semantics( label: _textDelegate.sActionShootingButtonTooltip, onTap: onTap, @@ -1006,9 +1007,9 @@ class CameraPickerState extends State height: isShootingButtonAnimate ? outerSize.height : innerSize.height, - padding: EdgeInsets.all(isShootingButtonAnimate ? 41 : 11), + padding: EdgeInsets.all(isShootingButtonAnimate ? 41*scal : 11*scal), decoration: BoxDecoration( - color: theme.canvasColor.withOpacity(0.85), + color: theme.canvasColor.withOpacity(0.30), shape: BoxShape.circle, ), child: const DecoratedBox( diff --git a/packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker_viewer.dart b/packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker_viewer.dart index 50b49604..09d2e9eb 100644 --- a/packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker_viewer.dart +++ b/packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker_viewer.dart @@ -284,12 +284,8 @@ class _CameraPickerViewerState extends State { iconSize: 18, icon: Container( padding: const EdgeInsets.all(5), - decoration: BoxDecoration( - color: Theme.of(context).iconTheme.color, - shape: BoxShape.circle, - ), child: Icon( - Icons.keyboard_return_rounded, + Icons.arrow_back_ios, color: Theme.of(context).canvasColor, ), ), @@ -329,19 +325,19 @@ class _CameraPickerViewerState extends State { /// The confirm button for the preview section. /// 预览区的确认按钮 Widget get previewConfirmButton { - return MaterialButton( + return MaterialButton( minWidth: 20.0, - height: 32.0, - padding: const EdgeInsets.symmetric(horizontal: 20.0), - color: theme.colorScheme.secondary, + height: 44.0, + padding: const EdgeInsets.symmetric(horizontal: 52.0), + color: const Color(0xfffa8232), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(3.0), + borderRadius: BorderRadius.circular(8.0), ), child: Text( Constants.textDelegate.confirm, style: TextStyle( color: theme.textTheme.bodyText1?.color, - fontSize: 17.0, + fontSize: 16.0, fontWeight: FontWeight.normal, ), ), @@ -355,17 +351,17 @@ class _CameraPickerViewerState extends State { Widget get cropperButton { return MaterialButton( minWidth: 20.0, - height: 32.0, - padding: const EdgeInsets.symmetric(horizontal: 20.0), - color: theme.colorScheme.secondary, + height: 44.0, + padding: const EdgeInsets.symmetric(horizontal: 52.0), + color: Colors.white, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(3.0), + borderRadius: BorderRadius.circular(8.0), ), child: Text( Constants.textDelegate.cropper, - style: TextStyle( - color: theme.textTheme.bodyText1?.color, - fontSize: 17.0, + style: const TextStyle( + color: Color(0xfffa8232), + fontSize: 16.0, fontWeight: FontWeight.normal, ), ), @@ -408,37 +404,37 @@ class _CameraPickerViewerState extends State { /// Actions section for the viewer. Including 'back' and 'confirm' button. /// 预览的操作区。包括"返回"和"确定"按钮。 Widget viewerActions(BuildContext context) { - return SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, - vertical: 20.0, - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + + return Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding(padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top -5),child: + Semantics( + sortKey: const OrdinalSortKey(0), + child: Row( children: [ - Semantics( - sortKey: const OrdinalSortKey(0), - child: Row( - children: [ - previewBackButton(context), - const Spacer(), - ], - ), - ), - Semantics( - sortKey: const OrdinalSortKey(2), - child: Row( - children: [ - if (pickerType == CameraPickerViewType.image) cropperButton else SizedBox(), - const Spacer(), - previewConfirmButton, - ], - ), - ), + previewBackButton(context), + const Spacer(), ], ), - ), + ),), + Padding(padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom -10), + child:Semantics( + sortKey: const OrdinalSortKey(2), + child: Container( + padding:const EdgeInsets.only(left: 15,right: 15), + child: Row( + children: [ + if (pickerType == CameraPickerViewType.image) cropperButton else const SizedBox(), + const Spacer(), + previewConfirmButton, + ], + ), + ), + ), + ), + + ], ); } Future imageCropper() async{ @@ -452,13 +448,13 @@ class _CameraPickerViewerState extends State { CropAspectRatioPreset.ratio4x3, CropAspectRatioPreset.ratio16x9 ], - androidUiSettings: AndroidUiSettings( + androidUiSettings: const AndroidUiSettings( toolbarTitle: 'Cropper', toolbarColor: Colors.deepOrange, toolbarWidgetColor: Colors.white, initAspectRatio: CropAspectRatioPreset.original, lockAspectRatio: false), - iosUiSettings: IOSUiSettings( + iosUiSettings:const IOSUiSettings( minimumAspectRatio: 1.0, ) ); From a012dc74a74245b54dc0905d41dee7d014e112c0 Mon Sep 17 00:00:00 2001 From: zzw <602129484@qq.com> Date: Thu, 28 Apr 2022 17:28:50 +0800 Subject: [PATCH 10/14] =?UTF-8?q?feat:=20=E6=8B=8D=E7=85=A7=20=E5=AE=89?= =?UTF-8?q?=E5=8D=93UI=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/src/widgets/camera_picker_viewer.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker_viewer.dart b/packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker_viewer.dart index 09d2e9eb..1b7e25bc 100644 --- a/packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker_viewer.dart +++ b/packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker_viewer.dart @@ -405,10 +405,15 @@ class _CameraPickerViewerState extends State { /// 预览的操作区。包括"返回"和"确定"按钮。 Widget viewerActions(BuildContext context) { + final top = MediaQuery.of(context).padding.top > 5 ? MediaQuery.of(context).padding.top - 5 : MediaQuery.of(context).padding.top; + + final bottom = MediaQuery.of(context).padding.bottom >10? MediaQuery.of(context).padding.bottom-10 :MediaQuery.of(context).padding.bottom; + return Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Padding(padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top -5),child: + Padding(padding: EdgeInsets.only(top: top ), + child: Semantics( sortKey: const OrdinalSortKey(0), child: Row( @@ -418,7 +423,7 @@ class _CameraPickerViewerState extends State { ], ), ),), - Padding(padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom -10), + Padding(padding: EdgeInsets.only(bottom: bottom), child:Semantics( sortKey: const OrdinalSortKey(2), child: Container( From eb07ef476759aaf4e362895de45588924fc7c6d4 Mon Sep 17 00:00:00 2001 From: zzw <602129484@qq.com> Date: Thu, 5 May 2022 12:59:07 +0800 Subject: [PATCH 11/14] =?UTF-8?q?feat:UI=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/src/widgets/camera_picker_viewer.dart | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker_viewer.dart b/packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker_viewer.dart index 1b7e25bc..6e22fd31 100644 --- a/packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker_viewer.dart +++ b/packages/flutter_wechat_camera_picker/lib/src/widgets/camera_picker_viewer.dart @@ -407,37 +407,27 @@ class _CameraPickerViewerState extends State { final top = MediaQuery.of(context).padding.top > 5 ? MediaQuery.of(context).padding.top - 5 : MediaQuery.of(context).padding.top; - final bottom = MediaQuery.of(context).padding.bottom >10? MediaQuery.of(context).padding.bottom-10 :MediaQuery.of(context).padding.bottom; return Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Padding(padding: EdgeInsets.only(top: top ), - child: - Semantics( - sortKey: const OrdinalSortKey(0), - child: Row( + child:Row( children: [ previewBackButton(context), const Spacer(), ], - ), - ),), - Padding(padding: EdgeInsets.only(bottom: bottom), - child:Semantics( - sortKey: const OrdinalSortKey(2), - child: Container( - padding:const EdgeInsets.only(left: 15,right: 15), - child: Row( - children: [ - if (pickerType == CameraPickerViewType.image) cropperButton else const SizedBox(), - const Spacer(), - previewConfirmButton, - ], - ), + ),), + Container( + padding:const EdgeInsets.only(left: 15,right: 15,top: 15,bottom: 15), + child: Row( + children: [ + if (pickerType == CameraPickerViewType.image) cropperButton else const SizedBox(), + const Spacer(), + previewConfirmButton, + ], ), ), - ), ], ); From 1daf9a2a45460e8d731719b734a1a313ee34e549 Mon Sep 17 00:00:00 2001 From: fujianlian Date: Mon, 20 Jun 2022 14:29:03 +0800 Subject: [PATCH 12/14] =?UTF-8?q?=E6=8C=87=E5=AE=9Acamera=20package?= =?UTF-8?q?=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/flutter_wechat_camera_picker/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter_wechat_camera_picker/pubspec.yaml b/packages/flutter_wechat_camera_picker/pubspec.yaml index b1f63792..990ae36e 100644 --- a/packages/flutter_wechat_camera_picker/pubspec.yaml +++ b/packages/flutter_wechat_camera_picker/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: flutter: sdk: flutter image_cropper: ^1.5.1 - camera: ^0.9.4+6 + camera: 0.9.4+6 path: ^1.8.0 path_provider: ^2.0.8 photo_manager: ^2.0.1 From 8c312fbaeb656c66ff6207aee56bdd19e382de8d Mon Sep 17 00:00:00 2001 From: fujianlian Date: Tue, 20 Dec 2022 17:19:40 +0800 Subject: [PATCH 13/14] Update icon_finger.xml --- .../local_auth/android/src/main/res/drawable/icon_finger.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/local_auth/android/src/main/res/drawable/icon_finger.xml b/packages/local_auth/android/src/main/res/drawable/icon_finger.xml index 1538fdfe..b543150e 100644 --- a/packages/local_auth/android/src/main/res/drawable/icon_finger.xml +++ b/packages/local_auth/android/src/main/res/drawable/icon_finger.xml @@ -32,7 +32,7 @@ From 9bb3e65d9cf81a416b6b7f80bff670b196acfb2a Mon Sep 17 00:00:00 2001 From: fujianlian Date: Fri, 26 May 2023 14:05:27 +0800 Subject: [PATCH 14/14] Update pubspec.yaml --- packages/local_auth/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/local_auth/pubspec.yaml b/packages/local_auth/pubspec.yaml index 50acf8a9..faab9238 100644 --- a/packages/local_auth/pubspec.yaml +++ b/packages/local_auth/pubspec.yaml @@ -20,7 +20,7 @@ dependencies: flutter: sdk: flutter meta: ^1.7.0 - intl: ^0.17.0 + intl: ^0.18.0 platform: ">=2.0.0 <4.0.0" flutter_plugin_android_lifecycle: ^2.0.5 dev_dependencies: