├── .all-contributorsrc ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug-report--bug---.md │ ├── config.yml │ └── feature-request-------.md └── workflows │ ├── publish.yml │ ├── publishable.yml │ └── runnable.yml ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── CODEOWNERS ├── LICENSE ├── README-ZH.md ├── README.md ├── _config.yml ├── analysis_options.yaml ├── example ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── fluttercandies │ │ │ │ └── wechatCameraPickerExample │ │ │ │ ├── ExampleAppGlideModule.kt │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── key.jks │ └── settings.gradle ├── assets │ └── flutter_candies_logo.png ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── ItunesArtwork@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── l10n.yaml ├── lib │ ├── extensions │ │ ├── color_extension.dart │ │ └── l10n_extensions.dart │ ├── l10n │ │ ├── app_en.arb │ │ ├── app_zh.arb │ │ └── gen │ │ │ ├── app_localizations.dart │ │ │ ├── app_localizations_en.dart │ │ │ ├── app_localizations_untranslated.json │ │ │ └── app_localizations_zh.dart │ ├── main.dart │ ├── models │ │ └── picker_method.dart │ ├── pages │ │ ├── home_page.dart │ │ └── splash_page.dart │ └── widgets │ │ ├── asset_widget_builder.dart │ │ ├── method_list_view.dart │ │ ├── preview_asset_widget.dart │ │ └── selected_assets_list_view.dart └── pubspec.yaml ├── guides └── migration_guide.md ├── lib ├── src │ ├── constants │ │ ├── config.dart │ │ ├── enums.dart │ │ └── type_defs.dart │ ├── delegates │ │ └── camera_picker_text_delegate.dart │ ├── internals │ │ ├── methods.dart │ │ └── singleton.dart │ ├── states │ │ ├── camera_picker_state.dart │ │ └── camera_picker_viewer_state.dart │ └── widgets │ │ ├── camera_focus_point.dart │ │ ├── camera_picker.dart │ │ ├── camera_picker_page_route.dart │ │ ├── camera_picker_viewer.dart │ │ └── camera_progress_button.dart └── wechat_camera_picker.dart ├── pubspec.yaml └── screenshots ├── README_1.jpg ├── README_2.jpg ├── README_3.jpg ├── README_4.jpg └── README_5.jpg /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 50, 6 | "badgeTemplate": "[![All Contributors](https://img.shields.io/badge/all_contributors-<%= contributors.length %>-orange.svg?style=flat-square&logo=github)](#contributors)", 7 | "commit": false, 8 | "contributors": [ 9 | { 10 | "login": "AlexV525", 11 | "name": "Alex Li", 12 | "avatar_url": "https://avatars1.githubusercontent.com/u/15884415?v=4", 13 | "profile": "https://blog.alexv525.com", 14 | "contributions": [ 15 | "code", 16 | "design", 17 | "doc", 18 | "example", 19 | "ideas", 20 | "maintenance", 21 | "question", 22 | "review" 23 | ] 24 | }, 25 | { 26 | "login": "CaiJingLong", 27 | "name": "Caijinglong", 28 | "avatar_url": "https://avatars0.githubusercontent.com/u/14145407?v=4", 29 | "profile": "https://www.kikt.top", 30 | "contributions": [ 31 | "example", 32 | "ideas" 33 | ] 34 | }, 35 | { 36 | "login": "LaelLuo", 37 | "name": "Lael", 38 | "avatar_url": "https://avatars3.githubusercontent.com/u/26056971?v=4", 39 | "profile": "https://github.com/LaelLuo", 40 | "contributions": [ 41 | "doc" 42 | ] 43 | }, 44 | { 45 | "login": "mjl0602", 46 | "name": "mjl0602", 47 | "avatar_url": "https://avatars1.githubusercontent.com/u/32868496?v=4", 48 | "profile": "https://github.com/mjl0602", 49 | "contributions": [ 50 | "code", 51 | "ideas" 52 | ] 53 | }, 54 | { 55 | "login": "siyukok", 56 | "name": "AliasWang", 57 | "avatar_url": "https://avatars0.githubusercontent.com/u/21030561?v=4", 58 | "profile": "https://github.com/siyukok", 59 | "contributions": [ 60 | "code", 61 | "ideas" 62 | ] 63 | }, 64 | { 65 | "login": "leftcoding", 66 | "name": "leftcoding", 67 | "avatar_url": "https://avatars.githubusercontent.com/u/7122926?v=4", 68 | "profile": "https://github.com/leftcoding", 69 | "contributions": [ 70 | "bug" 71 | ] 72 | }, 73 | { 74 | "login": "TheVinhLuong", 75 | "name": "Luong The Vinh", 76 | "avatar_url": "https://avatars.githubusercontent.com/u/20371879?v=4", 77 | "profile": "https://github.com/TheVinhLuong", 78 | "contributions": [ 79 | "code" 80 | ] 81 | }, 82 | { 83 | "login": "luomo-pro", 84 | "name": "luomo-pro", 85 | "avatar_url": "https://avatars.githubusercontent.com/u/41097395?v=4", 86 | "profile": "https://github.com/luomo-pro", 87 | "contributions": [ 88 | "a11y", 89 | "bug" 90 | ] 91 | }, 92 | { 93 | "login": "ZhuBoao", 94 | "name": "LeonardoZhu", 95 | "avatar_url": "https://avatars.githubusercontent.com/u/17305573?v=4", 96 | "profile": "https://github.com/ZhuBoao", 97 | "contributions": [ 98 | "code" 99 | ] 100 | }, 101 | { 102 | "login": "nploi", 103 | "name": "Nguyen Phuc Loi", 104 | "avatar_url": "https://avatars.githubusercontent.com/u/34020090?v=4", 105 | "profile": "https://www.linkedin.com/in/loinp", 106 | "contributions": [ 107 | "translation" 108 | ] 109 | }, 110 | { 111 | "login": "AmosHuKe", 112 | "name": "Amos", 113 | "avatar_url": "https://avatars.githubusercontent.com/u/32262985?v=4", 114 | "profile": "https://amoshk.top", 115 | "contributions": [ 116 | "bug" 117 | ] 118 | }, 119 | { 120 | "login": "yujune", 121 | "name": "Tee Yu June", 122 | "avatar_url": "https://avatars.githubusercontent.com/u/56582497?v=4", 123 | "profile": "https://github.com/yujune", 124 | "contributions": [ 125 | "code" 126 | ] 127 | } 128 | ], 129 | "contributorsPerLine": 7, 130 | "projectName": "flutter_wechat_camera_picker", 131 | "projectOwner": "fluttercandies", 132 | "repoType": "github", 133 | "repoHost": "https://github.com", 134 | "skipCi": true, 135 | "commitConvention": "angular", 136 | "commitType": "docs" 137 | } 138 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: AlexV525 2 | custom: ['https://www.alexv525.com/wechat.png', 'https://www.alexv525.com/alipay.jpg'] 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report--bug---.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report (BUG模板) 3 | about: Create a bug report helping us fix it. (创建一个 BUG 报告以帮助我们进行修复) 4 | title: "[BUG] Error with something" 5 | labels: await investigate, bug 6 | 7 | --- 8 | 9 | **Describe the bug** 10 | 12 | 13 | **How to reproduce** 14 | 19 | 20 | Steps to reproduce the behavior: 21 | 22 | 23 | 1. Go to '...' 24 | 2. Click on '....' 25 | 3. Scroll down to '....' 26 | 4. Error occurred. 27 | 28 | **Expected behavior** 29 | 31 | 32 | **Screenshots (If contains)** 33 | 35 | 36 | **Version information** 37 | 38 | - Device: [e.g. iPhone X] 39 | - OS: [e.g. iOS 14.7.1] 40 | - Package Version: [e.g. 2.4.1] 41 | - Flutter Version: [e.g. v2.5.0] 42 | 43 | **Additional context** 44 | 46 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request-------.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request (功能请求) 3 | about: Request a new feature that the package didn't include. (请求一个依赖并未包含的功能) 4 | title: "[Feature] Request a feature with something" 5 | labels: feature, await investigate 6 | 7 | --- 8 | 9 | **Version information** 10 | - Device: *e.g. iPhone X* 11 | - OS: *e.g. iOS 14.7.1* 12 | - Package Version: *e.g. v2.4.1* 13 | - Flutter Version: *e.g. v2.5.0* 14 | 15 | **Is your feature request related to a problem?** 16 | 18 | 19 | **Describe the solution you'd like** 20 | 22 | 23 | **Describe alternatives you've considered** 24 | 27 | 28 | **Additional context** 29 | 31 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: [published] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: k-paxian/dart-package-publisher@master 14 | with: 15 | credentialJson: ${{ secrets.CREDENTIAL_JSON }} 16 | flutter: true 17 | skipTests: true 18 | -------------------------------------------------------------------------------- /.github/workflows/publishable.yml: -------------------------------------------------------------------------------- 1 | name: Publishable 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | paths: 11 | - "**.md" 12 | - "**.yaml" 13 | - "**.yml" 14 | 15 | jobs: 16 | publish-dry-run: 17 | name: Publish dry-run with packages 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v3 21 | - uses: k-paxian/dart-package-publisher@master 22 | with: 23 | credentialJson: 'MockCredentialJson' 24 | flutter: true 25 | dryRunOnly: true 26 | skipTests: true 27 | -------------------------------------------------------------------------------- /.github/workflows/runnable.yml: -------------------------------------------------------------------------------- 1 | name: Runnable (stable) 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | workflow_dispatch: 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze on ${{ matrix.os }} 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | os: [ubuntu-latest] 19 | steps: 20 | - uses: actions/checkout@v3 21 | - uses: actions/setup-java@v3 22 | with: 23 | distribution: 'zulu' 24 | java-version: '17' 25 | - uses: subosito/flutter-action@v2 26 | with: 27 | channel: 'stable' 28 | - run: dart --version 29 | - run: flutter --version 30 | - run: flutter pub get 31 | - run: flutter analyze lib example/lib 32 | 33 | test_iOS: 34 | needs: analyze 35 | name: Test iOS on ${{ matrix.os }} 36 | runs-on: ${{ matrix.os }} 37 | strategy: 38 | matrix: 39 | os: [macos-latest] 40 | steps: 41 | - uses: actions/checkout@v3 42 | - uses: actions/setup-java@v3 43 | with: 44 | distribution: 'zulu' 45 | java-version: '17' 46 | - uses: subosito/flutter-action@v2 47 | with: 48 | architecture: x64 49 | channel: 'stable' 50 | - run: dart --version 51 | - run: flutter --version 52 | - run: flutter pub get 53 | - run: cd example; flutter build ios --no-codesign 54 | 55 | test_android: 56 | needs: analyze 57 | name: Test Android on ${{ matrix.os }} 58 | runs-on: ${{ matrix.os }} 59 | strategy: 60 | matrix: 61 | os: [ubuntu-latest] 62 | steps: 63 | - uses: actions/checkout@v3 64 | - uses: actions/setup-java@v3 65 | with: 66 | distribution: 'zulu' 67 | java-version: '17' 68 | - uses: subosito/flutter-action@v2 69 | with: 70 | channel: 'stable' 71 | - run: dart --version 72 | - run: flutter --version 73 | - run: flutter pub get 74 | - run: cd example; flutter build apk --debug 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | build/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Flutter.podspec 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/app.flx 64 | **/ios/Flutter/app.zip 65 | **/ios/Flutter/flutter_assets/ 66 | **/ios/Flutter/flutter_export_environment.sh 67 | **/ios/ServiceDefinitions.json 68 | **/ios/Runner/GeneratedPluginRegistrant.* 69 | 70 | # Exceptions to above rules. 71 | !**/ios/**/default.mode1v3 72 | !**/ios/**/default.mode2v3 73 | !**/ios/**/default.pbxuser 74 | !**/ios/**/default.perspectivev3 75 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 76 | *.lock -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 8af6b2f038c1172e61d418869363a28dffec3cb4 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # Changelog 6 | 7 | See the [Migration Guide](guides/migration_guide.md) for breaking changes between versions. 8 | 9 | ## 4.4.0 10 | 11 | ### Breaking changes 12 | 13 | - `CameraPickerState.build*` methods' signature are refactored to be more consistent. 14 | 15 | ### Fixes 16 | 17 | - Fix theme usages. 18 | - Fix lints. 19 | 20 | ## 4.3.7 21 | 22 | ### Fixes 23 | 24 | - Fix semantics with the capture button. 25 | - Avoid potential null operations when saving the entity. 26 | 27 | ## 4.3.6 28 | 29 | ### Fixes 30 | 31 | - Predicate the flash mode correctly when retrying the initialization. 32 | 33 | ## 4.3.5 34 | 35 | ### Improvements 36 | 37 | - Allows `sensor_plus` v6. 38 | 39 | ## 4.3.4 40 | 41 | ### Fixes 42 | 43 | - Fix preview file delete predication. 44 | 45 | ## 4.3.3 46 | 47 | ### New features 48 | 49 | - Adds `CameraPickerConfig.onPickConfirmed` to gets called when an asset entity is confirmed to be picked. 50 | - Introduces `CameraPickerConfig.permissionRequestOption`. 51 | 52 | ### Improvements 53 | 54 | - Evicts the captured image cache once returned from the viewer. 55 | - Allows `camera_android: ^0.10.9+6`. 56 | 57 | ## 4.3.2 58 | 59 | ### Fixes 60 | 61 | - Fix button displays when tap to record. 62 | - Prevent camera description exceptions when initializing the camera in the lifecycle callback. 63 | 64 | ### Improvements 65 | 66 | - Use more precise overlay styles. 67 | - Switching between different lens with a single camera by default. 68 | - Always delete the preview file when popping from the preview. 69 | 70 | ## 4.3.1 71 | 72 | ### Improvements 73 | 74 | - Downgrades the default resolution preset from `max` to `ultraHigh`. 75 | - Improves pinch zooming experiences. 76 | - Do not wait for focus mode and exposure mode to reset. 77 | - Updates the capture actions section size to compatible with more cases. 78 | 79 | ## 4.3.0+1 80 | 81 | ### Fixes 82 | 83 | - Fixes the breaking `ColorScheme.background` implementation on older Flutter SDKs. 84 | 85 | ## 4.3.0 86 | 87 | ### Improvements 88 | 89 | - Adapt the latest interface of WeChat. 90 | 91 | ### Fixes 92 | 93 | - Constraints `camera_android` version to resolves https://github.com/flutter/flutter/issues/150549. 94 | 95 | ## 4.2.2 96 | 97 | ### Fixes 98 | 99 | - Allows `wrapControllerMethod` to return nullable result. 100 | - Allows newer versions of `sensors_plus`. 101 | 102 | ### Improvements 103 | 104 | - Provide the back button when no controller has been initialized. 105 | - Improves paddings of the heading actions in the viewer. 106 | 107 | ## 4.2.1 108 | 109 | ### Fixes 110 | 111 | - Fix ignore locks when exception throws. 112 | 113 | ## 4.2.0 114 | 115 | ### Breaking changes 116 | 117 | - Migrate to Flutter 3.16, and drop supports for previous Flutter versions. 118 | - Bump `photo_manager` to v3.x. 119 | - Export `photo_manager_image_provider`. 120 | 121 | ### Improvements 122 | 123 | - Roll `sensors_plus`. 124 | - Catch exceptions when obtain/subscribe to the accelerometer stream. 125 | - Use `wechat_picker_library`. 126 | 127 | ### Fixes 128 | 129 | - Fix `onEntitySaving` not returned after called. (#223) 130 | - Predicate access denied to avoid deadlocks. 131 | 132 | ## 4.1.0 133 | 134 | ### New features 135 | 136 | - Automatically determine the capture orientation and lock accordingly. 137 | 138 | ### Fixes 139 | 140 | - Handle exceptions after all flows. 141 | - Fix various problems with the capture button. 142 | 143 | ## 4.0.3 144 | 145 | ### Fixes 146 | 147 | - Prevent duplicate shooting actions. 148 | 149 | ### Improvements 150 | 151 | - Provide overall invalid wrapping for controller methods. 152 | - Throw exceptions with more accurate stack traces. 153 | 154 | ## 4.0.2 155 | 156 | ### Fixes 157 | 158 | - Handles exceptions if locking methods are failed. 159 | 160 | ## 4.0.1 161 | 162 | ### Fixes 163 | 164 | - Fix uncaught exceptions for controller methods. 165 | 166 | ## 4.0.0 167 | 168 | To know more about breaking changes, see [Migration Guide][]. 169 | 170 | ### New features 171 | 172 | - Migrate to Flutter 3.3, and drop supports for previous Flutter versions. 173 | - Sync all UI details from WeChat 8.3.x. (#181) 174 | 175 | ### Improvements 176 | 177 | - Adapt layouts according to the device orientation. 178 | - Improve the performance when taking photos. 179 | - Improve the experience when using the exposure slider. 180 | - Prefer `FlashMode.off` for better performance. 181 | - Allow `cameras` to be set repeatedly. 182 | 183 | ### Fixes 184 | 185 | - Fix accessibility on the switch cameras button. 186 | 187 | ## 3.8.0 188 | 189 | ### New features 190 | 191 | - Add Vietnamese language text delegate. (#166). 192 | - Add `CameraPickerConfig.minimumRecordingDuration`. (#168) 193 | 194 | ### Improvements 195 | 196 | - Hide the loading widget in the preview until an actual saving process has been invoked. 197 | - Remove the implied system UI overlay manipulations. 198 | - Raise the lowest SDK constraint to 2.8.0. 199 | 200 | ## 3.7.0 201 | 202 | ### New features 203 | 204 | - Add `preferredFlashMode`, allowing users to choose which flash mode is preferred when first using the camera. (#158) 205 | 206 | ### Improvements 207 | 208 | - Allow flash modes failed to switch and can move on to next when switching. (#156) 209 | 210 | ### Fixes 211 | 212 | - Fix lifecycle integrations with the camera preview. (#157) 213 | 214 | ## 3.6.5 215 | 216 | ### Fixes 217 | 218 | - Correct sizes when using `cameraQuarterTurns`. (#149) 219 | 220 | ## 3.6.4 221 | 222 | ### Improvements 223 | 224 | - Improve stop-capturing experiences. (#146) 225 | - Precache captured images for better experiences. (#145) 226 | 227 | ## 3.6.3 228 | 229 | ### Improvements 230 | 231 | - Add the loading indicator when saving. (#140) 232 | 233 | ## 3.6.2 234 | 235 | ### Improvements 236 | 237 | - Bump `photo_manager` to explicitly remove the requirements of `requiredLegacyExternalStorage`. 238 | 239 | ## 3.6.1 240 | 241 | ### New features 242 | 243 | - Add torch flashlight support. (#137) 244 | 245 | ## 3.6.0 246 | 247 | ### New features 248 | 249 | - Upgrade `camera` to `0.10.x`. (#133) 250 | - Upgrade `photo_maanger` for Android 13. (#133) 251 | 252 | ## 3.5.0+1 253 | 254 | ### Fixes 255 | 256 | - Fix the too early `widget` access in `CameraPickerState`. (#124) 257 | 258 | ## 3.5.0 259 | 260 | ### New features 261 | 262 | - Support customize UI by override `State`s. (#113) 263 | 264 | ### Improvements 265 | 266 | - Expose multiple internal widgets. (#113) 267 | - Re-export `CameraPicker`'s constructor. (#116) 268 | - Avoid duplicate entity saving. (#117) 269 | - Prevent switching cameras when taking picture or recording video. (#120) 270 | 271 | ## 3.4.0 272 | 273 | ### New features 274 | 275 | - Add `enableScaledPreview`. (#108) 276 | 277 | ### Improvements 278 | 279 | - Catch more errors with handler. (#110) 280 | - Improve tapping exposure updates. (#109) 281 | - Prevent unnecessary zoom updates. (#107) 282 | 283 | ## 3.3.0 284 | 285 | ### Breaking Changes 286 | 287 | - Allow the foreground builder to be used all the time (#97) . 288 | The signature of the `ForegroundBuilder` has changed 289 | but can be easily migrated. 290 | 291 | ### Improvements 292 | 293 | - Allow text delegates to be obtained by `Locale`. (#99) 294 | 295 | ## 3.2.0+1 296 | 297 | ### New features 298 | 299 | - Support Flutter 3. 300 | 301 | ## 3.1.0 302 | 303 | ### New features 304 | 305 | - Add `onXFileCaptured`. (#87) 306 | 307 | ## 3.0.4 308 | 309 | ### Fixes 310 | 311 | - Unify the method to push to the viewer. (#86) 312 | 313 | ## 3.0.3 314 | 315 | ### Fixes 316 | 317 | - Correct arguments of `EntitySaveCallback`. (#85) 318 | 319 | ## 3.0.2 320 | 321 | ### Improvements 322 | 323 | - Export enums and typedefs. 324 | 325 | ## 3.0.1 326 | 327 | ### Fixes 328 | 329 | - Remove redundant dispose with the controller. 330 | 331 | ## 3.0.0 332 | 333 | ### New features 334 | 335 | - Add full semantics support. (#72) 336 | - Add `lockCaptureOrientation`, allowing users to determine lock to the specific orientation during captures. (#68) 337 | - Export `CameraPickerPageRoute`. 338 | - Abstract `CamearPickerConfig`, which moved all arguments from `pickFromCamera` to `pickerConfig`. 339 | 340 | ### Improvements 341 | 342 | - Improve camera initializes by adding a lock. 343 | - Tweak asynchronous methods call during initializations. 344 | - Make camera controllers available as soon as possible. 345 | 346 | ### Fixes 347 | 348 | - Fix scaling issues with turns and orientations. 349 | - Fix lint issues on Flutter 2.10. 350 | 351 | ## 2.6.5 352 | 353 | - Remove duplicate future requests when saving an entity. 354 | 355 | ## 2.6.4 356 | 357 | - Drop initialize when the controller has been already initialized. (#70) 358 | 359 | ## 2.6.3 360 | 361 | - Fix set exposure point crashes when switching between cameras. (#66) 362 | 363 | ## 2.6.2 364 | 365 | - Bind circular progress color with the theme. 366 | 367 | ## 2.6.1 368 | 369 | - Allow saving entities when the permission is limited on iOS. 370 | 371 | ## 2.6.0 372 | 373 | - Add `preferredLensDirection`, allowing users to choose which lens direction is preferred when first using the camera. 374 | - Add `enableTapRecording`, allowing users to determine whether to allow the record can start with a single tap. 375 | - Add `shouldAutoPreviewVideo`, allowing users to determine whether the video should be played instantly in the preview. 376 | 377 | ## 2.5.2 378 | 379 | - Request the permission state again when saving. 380 | - Provide better experiences when video records need to be prepared. 381 | 382 | ## 2.5.1 383 | 384 | - Fix invalid widgets binding observer caller. 385 | 386 | ## 2.5.0 387 | 388 | - Add `onError` to handle errors during the picking process. 389 | - `SaveEntityCallback` -> `EntitySaveCallback`. 390 | - Improve folder structure of the plugin. 391 | 392 | ## 2.4.2 393 | 394 | - Flip the preview if the user is using a front camera. 395 | 396 | ## 2.4.1 397 | 398 | - Handle save exceptions more gracefully. 399 | - Dispose the controller when previewing for better performance. 400 | 401 | ## 2.4.0 402 | 403 | - Bump `camera` to `0.9.x` . 404 | - Remove `shouldLockPortrait` in picking. 405 | 406 | ## 2.3.1 407 | 408 | - Expose `enablePullToZoomInRecord` for the `pickFromCamera` method. 409 | - Trigger shooting preparation only when start recording on iOS. 410 | 411 | ## 2.3.0 412 | 413 | - Expose `useRootNavigator` while picking. 414 | - Initialize a new controller if failed to stop recording. (#39) 415 | - Throw or rethrow exceptions that used to be caught. (#41) 416 | - Update the back icon with preview. 417 | - Enhance video capture on iOS with preparation. 418 | 419 | ## 2.2.0 420 | 421 | - Add `EntitySaveCallback` for the custom save method. 422 | 423 | ## 2.1.2 424 | 425 | - Improve the UI of the exposure point widget when manually focus. 426 | 427 | ## 2.1.1 428 | 429 | - Use basename when saving entities. 430 | 431 | ## 2.1.0 432 | 433 | - Add `shouldLockPortrait` to fit orientation for the device. 434 | - Fix exposure offset issue when resetting the exposure point after adjusting the exposure offset manually. 435 | 436 | ## 2.0.0 437 | 438 | ### New Features 439 | 440 | - Add `enableSetExposure`, allowing users to update the exposure from the point tapped on the screen. 441 | - Add `enableExposureControlOnPoint`, allowing users to control the exposure offset with an offset slide from the exposure point. 442 | - Add `enablePinchToZoom`, allowing users to zoom by pinching the screen. 443 | - Add `enablePullToZoomInRecord`, allowing users to zoom by pulling up when recording video. 444 | - Add `foregroundBuilder`, allowing users to build customized widgets beyond the camera preview. 445 | - Add `shouldDeletePreviewFile`, allowing users to choose whether the captured file should be deleted. 446 | - Sync `imageFormatGroup` from the `camera` plugin. 447 | 448 | ### Breaking Changes 449 | 450 | - Migrate to non-nullable by default. 451 | - `isAllowRecording` -> `enableRecording` 452 | - `isOnlyAllowRecording` -> `onlyAllowRecording` 453 | 454 | ### Fixes 455 | 456 | - All fixes from the `camera` plugin. 457 | 458 | ## 1.3.1 459 | 460 | - Constraint dependencies version. #22 461 | 462 | ## 1.3.0 463 | 464 | - Add `enableAudio` parameter to control whether the package will require audio integration. #17 465 | 466 | ## 1.2.3 467 | 468 | - Fix `maximumRecordingDuration` not passed in static method. #14 469 | 470 | ## 1.2.2 471 | 472 | - Raise dependencies versions. 473 | 474 | ## 1.2.1 475 | 476 | - Add `cameraQuarterTurns`. 477 | 478 | ## 1.2.0 479 | 480 | - Expose resolution preset control. 481 | 482 | ## 1.1.2 483 | 484 | - Remove common exports. 485 | 486 | ## 1.1.1 487 | 488 | - Documents update. 489 | 490 | ## 1.1.0+1 491 | 492 | - Link confirm button's text with delegate. Fix #6. 493 | 494 | ## 1.1.0 495 | 496 | - Add `isOnlyAllowRecording` . Resolves #4. 497 | - Make camera switching available. Resolves #5. 498 | 499 | ## 1.0.0+1 500 | 501 | - Fix potential non exist directory access. 502 | 503 | ## 1.0.0 504 | 505 | - Support taking pictures and videos. 506 | - Support video recording duration limitation. 507 | 508 | [Migration Guide]: guides/migration_guide.md 509 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @AlexV525 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [2019] [FlutterCandies] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README-ZH.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # Flutter WeChat Camera Picker 6 | 7 | [![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) 8 | [![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) 9 | [![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) 10 | 11 | [![Build status](https://img.shields.io/github/actions/workflow/status/fluttercandies/flutter_wechat_camera_picker/runnable.yml?branch=main&label=CI&logo=github&style=flat-square)](https://github.com/fluttercandies/flutter_wechat_camera_picker/actions/workflows/runnable.yml) 12 | [![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) 13 | [![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) 14 | [![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) 15 | 16 | 17 | 18 | Language: [English](README.md) | 中文 19 | 20 | 基于 **微信 UI** 的 Flutter 相机选择器,可以单独运行, 21 | 同时是 [wechat_assets_picker][wechat_assets_picker pub] 的扩展。 22 | 23 | 当前的界面设计基于的微信版本:**8.0.49** 24 | 界面更新将在微信版本更新后随时进行跟进。 25 | 26 | 查看 [迁移指南][] 了解如何从破坏性改动中迁移为可用代码。 27 | 28 | ## 版本兼容 29 | 30 | 该插件仅保证能与 **stable 渠道的 Flutter SDK** 配合使用。 31 | 我们不会为其他渠道的 Flutter SDK 做实时支持。 32 | 33 | | | 2.8.0 | 3.3.0 | 3.16.0 | 34 | |--------|:-----:|:-----:|:------:| 35 | | 4.2.0+ | ❌ | ❌ | ✅ | 36 | | 4.0.0+ | ❌ | ✅ | ❌ | 37 | | 3.0.0+ | ✅ | ❌ | ❌ | 38 | 39 | ## 主要使用的 package 40 | 41 | 该插件基于这些优秀的 package 构建: 42 | 43 | | Name | Features | 44 | |:-----------------------------------|:------------| 45 | | [photo_manager][photo_manager pub] | 资源的基础抽象和管理。 | 46 | | [camera][camera pub] | 拍摄图片和视频。 | 47 | | [video_player][video_player pub] | 播放对应的视频和音频。 | 48 | 49 | 这些 package 在该插件中的实现已相对稳定。 50 | 如果你在使用中发现于它们相关的问题, 51 | 请先在本插件的问题跟踪中报告相关问题。 52 | 53 |
54 | 目录列表 55 | 56 | 57 | * [Flutter WeChat Camera Picker](#flutter-wechat-camera-picker) 58 | * [版本兼容](#版本兼容) 59 | * [主要使用的 package](#主要使用的-package) 60 | * [特性 ✨](#特性-) 61 | * [截图 📸](#截图-) 62 | * [开始前的注意事项 ‼️](#开始前的注意事项-) 63 | * [准备工作 🍭](#准备工作-) 64 | * [配置](#配置) 65 | * [特别提醒 📝](#特别提醒-) 66 | * [使用方法 📖](#使用方法-) 67 | * [国际化](#国际化) 68 | * [简单的使用方法](#简单的使用方法) 69 | * [使用配置](#使用配置) 70 | * [使用自定义的 `State`](#使用自定义的-state) 71 | * [常见问题 💭](#常见问题-) 72 | * [iOS 上的预览在旋转时行为诡异](#ios-上的预览在旋转时行为诡异) 73 | 74 |
75 | 76 | ## 特性 ✨ 77 | 78 | - ♿ 完整的无障碍支持,包括 **TalkBack** 和 **VoiceOver** 79 | - ♻️ 支持基于 `State` 重载的全量自定义 80 | - 🎏 完全可自定义的基于 `ThemeData` 的主题 81 | - 💚 复刻微信风格(甚至优化了更多的细节) 82 | - ⚡️ 根据配置调节的性能优化 83 | - 📷 支持拍照 84 | - 🎥 支持录像 85 | - ⏱ 支持限制录像时间 86 | - 🔍 支持录像时缩放 87 | - ☀️ 支持设置曝光参数 88 | - 🔍️ 支持捏合缩放 89 | - 💱 国际化支持 90 | - ⏪ RTL 语言支持 91 | - 🖾 支持自定义前景 widget 构建 92 | - 🕹️ 保存时拦截自定义操作 93 | 94 | ## 截图 📸 95 | 96 | | ![](https://pic.alexv525.com/202310181547760.jpg) | ![](https://pic.alexv525.com/202310181547670.jpg) | ![](https://pic.alexv525.com/202310181547132.jpg) | ![](https://pic.alexv525.com/202310181547726.jpg) | ![](https://pic.alexv525.com/202310181548711.jpg) | 97 | |---------------------------------------------------|---------------------------------------------------|---------------------------------------------------|---------------------------------------------------|---------------------------------------------------| 98 | 99 | ## 开始前的注意事项 ‼️ 100 | 101 | 在开始一切之前,请明确以下两点: 102 | - 由于理解差异和篇幅限制,并不是所有的内容都会明确地在文档中指出。 103 | 当你遇到没有找到需求和无法理解的概念时,请先运行项目的示例 example, 104 | 它可以解决 90% 的常见需求。 105 | - 该库与 [photo_manager][photo_manager pub] 有强关联性, 106 | 大部分方法的行为是由 photo_manager 进行控制的, 107 | 所以请尽可能地确保你了解以下两个类的概念: 108 | - 资源(图片/视频/音频) [`AssetEntity`](https://pub.flutter-io.cn/documentation/photo_manager/latest/photo_manager/AssetEntity-class.html) 109 | - 资源合集(相册或集合概念) [`AssetPathEntity`](https://pub.flutter-io.cn/documentation/photo_manager/latest/photo_manager/AssetPathEntity-class.html) 110 | 111 | 当你有与相关的 API 和行为的疑问时,你可以查看 112 | [photo_manager API 文档][] 了解更多细节。 113 | 114 | 众多使用场景都已包含在示例中。 115 | 在你提出任何问题之前,请仔细并完整地查看和使用示例。 116 | 117 | ## 准备工作 🍭 118 | 119 | 如果在 `flutter pub get` 时遇到了 `resolve conflict` 失败问题, 120 | 请使用 `dependency_overrides` 解决。 121 | 122 | ### 配置 123 | 124 | 执行 `flutter pub add wechat_camera_picker`, 125 | 或者将 `wechat_camera_picker` 手动添加至 `pubspec.yaml` 引用。 126 | ```yaml 127 | dependencies: 128 | wechat_camera_picker: ^latest_version 129 | ``` 130 | 131 | 最新的 **稳定** 版本是: 132 | [![pub package](https://img.shields.io/pub/v/wechat_camera_picker?logo=dart&label=stable&style=flat-square)](https://pub.flutter-io.cn/packages/wechat_camera_picker) 133 | 134 | 最新的 **开发** 版本是: 135 | [![pub package](https://img.shields.io/pub/v/wechat_camera_picker?color=9d00ff&include_prereleases&label=dev&logo=dart&style=flat-square)](https://pub.flutter-io.cn/packages/wechat_camera_picker) 136 | 137 | 运行前,按照这些步骤逐一配置: 138 | - [wechat_assets_picker#准备工作](https://github.com/fluttercandies/flutter_wechat_assets_picker/blob/master/README-ZH.md#preparing-for-use-) 139 | - [camera#installation](https://pub.flutter-io.cn/packages/camera#installation) 140 | 141 | 在你的代码中导入: 142 | 143 | ```dart 144 | import 'package:wechat_camera_picker/wechat_camera_picker.dart'; 145 | ``` 146 | 147 | ### 特别提醒 📝 148 | 149 | 1. 在 iOS 上使用 `NSPhotoLibraryAddUsageDescription` 时, 150 | 需要使用 `onEntitySaving` 或 `onXFileCaptured` 来处理捕获的文件, 151 | 在这种情况下 `AssetEntity` 不可用,使用其 ID 获取该资源将导致崩溃。 152 | 153 | ## 使用方法 📖 154 | 155 | ### 国际化 156 | 157 | 当你在选择资源的时候,package 会通过你的 `BuildContext` 158 | 读取 `Locale?`,返回对应语言的文字代理实现。 159 | 请确保你可以通过 `BuildContext` 获取到 `Locale`,否则将会 **默认展示中文文字**。 160 | 161 | 内置的语言文字实现有: 162 | * 简体中文 (默认) 163 | * English 164 | * Tiếng Việt 165 | 166 | 如果你想使用自定义或固定的文字实现,请通过 167 | `CameraPickerConfig.textDelegate` 传递调用。 168 | 169 | ### 简单的使用方法 170 | 171 | ```dart 172 | final AssetEntity? entity = await CameraPicker.pickFromCamera(context); 173 | ``` 174 | 175 | ### 使用配置 176 | 177 | 你可以使用 `CameraPickerConfig` 来调整选择时的行为。 178 | 179 | ```dart 180 | final AssetEntity? entity = await CameraPicker.pickFromCamera( 181 | context, 182 | pickerConfig: const CameraPickerConfig(), 183 | ); 184 | ``` 185 | 186 | `CameraPickerConfig` 的成员说明: 187 | 188 | | 参数名 | 类型 | 描述 | 默认值 | 189 | |-------------------------------|-------------------------------|----------------------------------------------------|--------------------------------------------| 190 | | enableRecording | `bool` | 选择器是否可以录像 | `false` | 191 | | onlyEnableRecording | `bool` | 选择器是否仅可以录像。只在 `enableRecording` 为 `true` 时有效。 | `false` | 192 | | enableTapRecording | `bool` | 选择器是否可以单击录像。只在 `onlyEnableRecording` 为 `true` 时生效。 | `false` | 193 | | enableAudio | `bool` | 选择器是否需要录制音频。只在 `enableRecording` 为 `true` 时有效。 | `true` | 194 | | enableSetExposure | `bool` | 用户是否可以在界面上通过点击设定曝光点 | `true` | 195 | | enableExposureControlOnPoint | `bool` | 用户是否可以根据已经设置的曝光点调节曝光度 | `true` | 196 | | enablePinchToZoom | `bool` | 用户是否可以在界面上双指缩放相机对焦 | `true` | 197 | | enablePullToZoomInRecord | `bool` | 用户是否可以在录制视频时上拉缩放 | `true` | 198 | | enableScaledPreview | `bool` | 拍摄过程中相机预览是否需要缩放 | `false` | 199 | | shouldDeletePreviewFile | `bool` | 返回页面时是否删除预览文件 | `false` | 200 | | shouldAutoPreviewVideo | `bool` | 在预览时是否直接播放视频 | `true` | 201 | | maximumRecordingDuration | `Duration?` | 录制视频最长时长 | `const Duration(seconds: 15)` | 202 | | minimumRecordingDuration | `Duration` | 录制视频最短时长 | `const Duration(seconds: 1)` | 203 | | theme | `ThemeData?` | 选择器的主题 | `CameraPicker.themeData(wechatThemeColor)` | 204 | | textDelegate | `CameraPickerTextDelegate?` | 控制部件中的文字实现 | `CameraPickerTextDelegate` | 205 | | resolutionPreset | `ResolutionPreset` | 相机的分辨率预设 | `ResolutionPreset.ultraHigh` | 206 | | cameraQuarterTurns | `int` | 摄像机视图顺时针旋转次数,每次 90 度 | `0` | 207 | | imageFormatGroup | `ImageFormatGroup` | 输出图像的格式描述 | `ImageFormatGroup.unknown` | 208 | | preferredLensDirection | `CameraLensDirection` | 首次使用相机时首选的镜头方向 | `CameraLensDirection.back` | 209 | | lockCaptureOrientation | `DeviceOrientation?` | 摄像机在拍摄时锁定的旋转角度 | null | 210 | | foregroundBuilder | `ForegroundBuilder?` | 覆盖在相机预览上方的前景构建 | null | 211 | | previewTransformBuilder | `PreviewTransformBuilder?` | 对相机预览做变换的构建 | null | 212 | | onEntitySaving | `EntitySaveCallback?` | 在查看器中保存图片时的回调 | null | 213 | | onError | `CameraErrorHandler?` | 拍摄照片过程中的自定义错误处理 | null | 214 | | onXFileCaptured | `XFileCapturedCallback?` | 拍摄文件生成后的回调 | null | 215 | | onMinimumRecordDurationNotMet | `VoidCallback?` | 录制时长未达到最小时长时的回调方法 | null | 216 | | onPickConfirmed | `void Function(AssetEntity)?` | 拍照或录像确认时的回调方法。 | null | 217 | | permissionRequestOption | `PermissionRequestOption?` | 使用 `photo_manager` 保存拍摄的文件的权限请求配置 | null | 218 | 219 | ### 使用自定义的 `State` 220 | 221 | 所有的用户界面都可以通过自定义 `State` 实现,包括: 222 | - `CameraPickerState` 223 | - `CameraPickerViewerState` 224 | 225 | 在完成 `State` 的重载后,可以在调用时进行构建,具体来说: 226 | - `CameraPicker.pickFromCamera(createPickerState: () => CustomCameraPickerState());` 227 | - `CameraPickerViewer.pushToViewer(..., createViewerState: () => CustomCameraPickerViewerState());` 228 | 229 | ## 常见问题 💭 230 | 231 | ### iOS 上的预览在旋转时行为诡异 232 | 233 | 目前,iOS 上的预览画面在旋转时并未正确地同步, 234 | 你可以在这个 issue 里了解更多相关的信息: 235 | https://github.com/flutter/flutter/issues/89216 。 236 | 除此之外的问题,你可以提交 issue 进行提问。 237 | 238 | [wechat_assets_picker pub]: https://pub.flutter-io.cn/packages/wechat_assets_picker 239 | [photo_manager pub]: https://pub.flutter-io.cn/packages/photo_manager 240 | [camera pub]: https://pub.flutter-io.cn/packages/camera 241 | [video_player pub]: https://pub.flutter-io.cn/packages/video_player 242 | [迁移指南]: https://github.com/fluttercandies/flutter_wechat_camera_picker/blob/main/guides/migration_guide.md 243 | [photo_manager API 文档]: https://pub.flutter-io.cn/documentation/photo_manager/latest/ 244 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | linter: 4 | rules: 5 | always_declare_return_types: true 6 | flutter_style_todos: true 7 | library_private_types_in_public_api: false 8 | prefer_final_fields: true 9 | prefer_final_in_for_each: true 10 | prefer_final_locals: true 11 | prefer_single_quotes: true 12 | require_trailing_commas: true 13 | sort_child_properties_last: true 14 | sort_constructors_first: true 15 | sort_unnamed_constructors_first: true 16 | unnecessary_late: true 17 | use_build_context_synchronously: false 18 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Exceptions to above rules. 43 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 44 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "54e66469a933b60ddf175f858f82eaeb97e48c8d" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 17 | base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 18 | - platform: ios 19 | create_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 20 | base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d 21 | 22 | # User provided section 23 | 24 | # List of Local paths (relative to this file) that should be 25 | # ignored by the migrate tool. 26 | # 27 | # Files that are not part of the templates will be ignored by default. 28 | unmanaged_files: 29 | - 'lib/main.dart' 30 | - 'ios/Runner.xcodeproj/project.pbxproj' 31 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # WeChat Camera Picker example 6 | 7 | This is the example for the `wechat_camera_picker` package. 8 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: ../analysis_options.yaml 2 | 3 | analyzer: 4 | exclude: 5 | - lib/l10n/gen/*.dart 6 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "kotlin-kapt" 5 | id "dev.flutter.flutter-gradle-plugin" 6 | } 7 | 8 | def localProperties = new Properties() 9 | def localPropertiesFile = rootProject.file('local.properties') 10 | if (localPropertiesFile.exists()) { 11 | localPropertiesFile.withReader('UTF-8') { reader -> 12 | localProperties.load(reader) 13 | } 14 | } 15 | 16 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 17 | if (flutterVersionCode == null) { 18 | flutterVersionCode = '1' 19 | } 20 | 21 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 22 | if (flutterVersionName == null) { 23 | flutterVersionName = '1.0' 24 | } 25 | 26 | android { 27 | namespace "com.fluttercandies.wechatCameraPickerExample" 28 | 29 | compileSdkVersion 34 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | defaultConfig { 36 | applicationId "com.fluttercandies.wechatCameraPickerExample" 37 | minSdkVersion 21 38 | targetSdkVersion 34 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | } 42 | 43 | kotlinOptions { 44 | jvmTarget = '17' 45 | } 46 | 47 | compileOptions { 48 | // Sets Java compatibility to Java 17 49 | sourceCompatibility JavaVersion.VERSION_17 50 | targetCompatibility JavaVersion.VERSION_17 51 | } 52 | 53 | signingConfigs { 54 | forAll { 55 | storeFile file("${rootDir.absolutePath}/key.jks") 56 | storePassword 'picker' 57 | keyAlias 'picker' 58 | keyPassword 'picker' 59 | } 60 | } 61 | 62 | buildTypes { 63 | debug { 64 | signingConfig signingConfigs.forAll 65 | } 66 | profile { 67 | signingConfig signingConfigs.forAll 68 | } 69 | release { 70 | signingConfig signingConfigs.forAll 71 | } 72 | } 73 | } 74 | 75 | flutter { 76 | source '../..' 77 | } 78 | 79 | dependencies { 80 | implementation 'com.github.bumptech.glide:glide:4.15.0' 81 | kapt 'com.github.bumptech.glide:compiler:4.15.0' 82 | } 83 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 19 | 27 | 28 | 29 | 30 | 31 | 35 | 38 | 39 | 41 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/fluttercandies/wechatCameraPickerExample/ExampleAppGlideModule.kt: -------------------------------------------------------------------------------- 1 | package com.fluttercandies.wechatCameraPickerExample 2 | 3 | import com.bumptech.glide.annotation.GlideModule 4 | import com.bumptech.glide.module.AppGlideModule 5 | 6 | @GlideModule 7 | class ExampleAppGlideModule : AppGlideModule() 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/fluttercandies/wechatCameraPickerExample/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.fluttercandies.wechatCameraPickerExample 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() 6 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = '../build' 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(':app') 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip 7 | -------------------------------------------------------------------------------- /example/android/key.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/android/key.jks -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | pluginManagement { 6 | def flutterSdkPath = { 7 | def properties = new Properties() 8 | file("local.properties").withInputStream { properties.load(it) } 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | return flutterSdkPath 12 | } 13 | settings.ext.flutterSdkPath = flutterSdkPath() 14 | 15 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") 16 | 17 | repositories { 18 | google() 19 | mavenCentral() 20 | gradlePluginPortal() 21 | } 22 | } 23 | 24 | plugins { 25 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 26 | id "com.android.application" version "8.2.2" apply false 27 | id "org.jetbrains.kotlin.android" version "1.9.20" apply false 28 | } 29 | 30 | include ":app" 31 | -------------------------------------------------------------------------------- /example/assets/flutter_candies_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/assets/flutter_candies_logo.png -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '12.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleDisplayName 14 | Wechat Camera Picker Example 15 | CFBundleName 16 | com.fluttercandies.wechatCameraPickerExample 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | NSCameraUsageDescription 28 | Take a photo for display 29 | NSMicrophoneUsageDescription 30 | Take a video for display 31 | NSPhotoLibraryUsageDescription 32 | Read and write your photos 33 | UILaunchStoryboardName 34 | LaunchScreen 35 | UIMainStoryboardFile 36 | Main 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationLandscapeLeft 41 | UIInterfaceOrientationLandscapeRight 42 | 43 | UISupportedInterfaceOrientations~ipad 44 | 45 | UIInterfaceOrientationPortrait 46 | UIInterfaceOrientationPortraitUpsideDown 47 | UIInterfaceOrientationLandscapeLeft 48 | UIInterfaceOrientationLandscapeRight 49 | 50 | CADisableMinimumFrameDurationOnPhone 51 | 52 | UIApplicationSupportsIndirectInputEvents 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/l10n.yaml: -------------------------------------------------------------------------------- 1 | arb-dir: lib/l10n 2 | # format: true 3 | output-class: AppLocalizations 4 | output-dir: lib/l10n/gen 5 | output-localization-file: app_localizations.dart 6 | template-arb-file: app_en.arb 7 | synthetic-package: false 8 | untranslated-messages-file: lib/l10n/gen/app_localizations_untranslated.json 9 | -------------------------------------------------------------------------------- /example/lib/extensions/color_extension.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The FlutterCandies author. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be found 3 | // in the LICENSE file. 4 | // ignore_for_file: deprecated_member_use 5 | 6 | import 'package:flutter/material.dart' 7 | show Color, Colors, HSLColor, MaterialColor; 8 | 9 | extension ColorExtension on Color { 10 | MaterialColor get swatch { 11 | return Colors.primaries.firstWhere( 12 | (Color c) => c.value == value, 13 | orElse: () => MaterialColor(value, getMaterialColorValues), 14 | ); 15 | } 16 | 17 | Color _swatchShade(int swatchValue) { 18 | return HSLColor.fromColor(this) 19 | .withLightness(1 - (swatchValue / 1000)) 20 | .toColor(); 21 | } 22 | 23 | Map get getMaterialColorValues { 24 | return { 25 | 50: _swatchShade(50), 26 | 100: _swatchShade(100), 27 | 200: _swatchShade(200), 28 | 300: _swatchShade(300), 29 | 400: _swatchShade(400), 30 | 500: _swatchShade(500), 31 | 600: _swatchShade(600), 32 | 700: _swatchShade(700), 33 | 800: _swatchShade(800), 34 | 900: _swatchShade(900), 35 | }; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /example/lib/extensions/l10n_extensions.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The FlutterCandies author. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be found 3 | // in the LICENSE file. 4 | 5 | import 'package:flutter/widgets.dart'; 6 | 7 | import '../l10n/gen/app_localizations.dart'; 8 | 9 | extension BuildContextExtension on BuildContext { 10 | AppLocalizations get l10n => AppLocalizations.of(this)!; 11 | } 12 | -------------------------------------------------------------------------------- /example/lib/l10n/app_en.arb: -------------------------------------------------------------------------------- 1 | { 2 | "@@locale": "en", 3 | "appTitle": "WeChat Camera Picker Demo", 4 | "appVersion": "Version: {version}", 5 | "appVersionUnknown": "unknown", 6 | "selectedAssetsText": "Selected Assets", 7 | "pickMethodNotice": "Pickers in this page are located at the {dist}, defined by `pickMethods`.", 8 | "pickMethodPhotosName": "Taking photos", 9 | "pickMethodPhotosDescription": "Use cameras only to take photos.", 10 | "pickMethodPhotosAndVideosName": "Taking photos and videos", 11 | "pickMethodPhotosAndVideosDescription": "Use cameras to take photos and videos.", 12 | "pickMethodVideosName": "Taking videos", 13 | "pickMethodVideosDescription": "Use cameras only to take videos.", 14 | "pickMethodVideosByTapName": "Taking videos by tap", 15 | "pickMethodVideosByTapDescription": "Use cameras only to take videos, but not with long-press, just a single tap.", 16 | "pickMethodSilenceRecordingName": "Silence recording", 17 | "pickMethodSilenceRecordingDescription": "Make recordings silent.", 18 | "pickMethodNoDurationLimitName": "No duration limit", 19 | "pickMethodNoDurationLimitDescription": "Record as long as you with (if your device stays alive)...", 20 | "pickMethodCustomizableThemeName": "Customizable theme (ThemeData)", 21 | "pickMethodCustomizableThemeDescription": "Picking assets with the light theme or with a different color.", 22 | "pickMethodRotateInTurnsName": "Rotate picker in turns", 23 | "pickMethodRotateInTurnsDescription": "Rotate the picker layout in quarter turns, without the camera preview.", 24 | "pickMethodScalingPreviewName": "Scaling for camera preview", 25 | "pickMethodScalingPreviewDescription": "Camera preview will be scaled to cover the whole screen of the device with the original aspect ratio.", 26 | "pickMethodLowerResolutionName": "Lower resolutions", 27 | "pickMethodLowerResolutionDescription": "Use a lower resolution preset might be helpful in some specific scenarios.", 28 | "pickMethodPreferFrontCameraName": "Prefer front camera", 29 | "pickMethodPreferFrontCameraDescription": "Use the front camera as the preferred lens direction if the device supports.", 30 | "pickMethodPreferFlashlightOnName": "Prefer flashlight always on", 31 | "pickMethodPreferFlashlightOnDescription": "Prefer to keep using the flashlight during captures.", 32 | "pickMethodForegroundBuilderName": "Foreground builder", 33 | "pickMethodForegroundBuilderDescription": "Build your widgets with the given CameraController on the top of the camera preview." 34 | } -------------------------------------------------------------------------------- /example/lib/l10n/app_zh.arb: -------------------------------------------------------------------------------- 1 | { 2 | "@@locale": "zh", 3 | "appTitle": "WeChat Camera Picker 示例", 4 | "appVersion": "版本:{version}", 5 | "appVersionUnknown": "未知", 6 | "selectedAssetsText": "已选的资源", 7 | "pickMethodNotice": "该页面的所有选择器的代码位于 {dist},由 `pickMethods` 定义。", 8 | "pickMethodPhotosName": "拍照", 9 | "pickMethodPhotosDescription": "使用相机拍照。", 10 | "pickMethodPhotosAndVideosName": "拍照和录像", 11 | "pickMethodPhotosAndVideosDescription": "使用相机进行拍照和录像。", 12 | "pickMethodVideosName": "录像", 13 | "pickMethodVideosDescription": "使用相机录像。", 14 | "pickMethodVideosByTapName": "轻触录像", 15 | "pickMethodVideosByTapDescription": "轻触录像按钮进行录像,而不是长按。", 16 | "pickMethodSilenceRecordingName": "静音录像", 17 | "pickMethodSilenceRecordingDescription": "录像时不录制声音。", 18 | "pickMethodNoDurationLimitName": "无时长限制录像", 19 | "pickMethodNoDurationLimitDescription": "想录多久,就录多久(只要手机健在)。", 20 | "pickMethodCustomizableThemeName": "自定义主题 (ThemeData)", 21 | "pickMethodCustomizableThemeDescription": "可以用亮色或其他颜色及自定义的主题进行选择。", 22 | "pickMethodRotateInTurnsName": "旋转选择器的布局", 23 | "pickMethodRotateInTurnsDescription": "顺时针旋转选择器的元素布局,不旋转相机视图。", 24 | "pickMethodScalingPreviewName": "缩放相机预览", 25 | "pickMethodScalingPreviewDescription": "相机预览视图会被放大到覆盖整个屏幕且保持原始的预览比例。", 26 | "pickMethodLowerResolutionName": "低分辨率拍照", 27 | "pickMethodLowerResolutionDescription": "某些情况或机型使用低分辨率拍照会有稳定性改善。", 28 | "pickMethodPreferFrontCameraName": "首选前置摄像头", 29 | "pickMethodPreferFrontCameraDescription": "在设备支持时首选使用前置摄像头。", 30 | "pickMethodPreferFlashlightOnName": "首选闪光灯始终启用", 31 | "pickMethodPreferFlashlightOnDescription": "在设备支持时首选闪光灯始终启用。", 32 | "pickMethodForegroundBuilderName": "构建前景", 33 | "pickMethodForegroundBuilderDescription": "通过 CameraController 构建在相机预览部分的前景 widget。" 34 | } -------------------------------------------------------------------------------- /example/lib/l10n/gen/app_localizations.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/widgets.dart'; 5 | import 'package:flutter_localizations/flutter_localizations.dart'; 6 | import 'package:intl/intl.dart' as intl; 7 | 8 | import 'app_localizations_en.dart'; 9 | import 'app_localizations_zh.dart'; 10 | 11 | // ignore_for_file: type=lint 12 | 13 | /// Callers can lookup localized strings with an instance of AppLocalizations 14 | /// returned by `AppLocalizations.of(context)`. 15 | /// 16 | /// Applications need to include `AppLocalizations.delegate()` in their app's 17 | /// `localizationDelegates` list, and the locales they support in the app's 18 | /// `supportedLocales` list. For example: 19 | /// 20 | /// ```dart 21 | /// import 'gen/app_localizations.dart'; 22 | /// 23 | /// return MaterialApp( 24 | /// localizationsDelegates: AppLocalizations.localizationsDelegates, 25 | /// supportedLocales: AppLocalizations.supportedLocales, 26 | /// home: MyApplicationHome(), 27 | /// ); 28 | /// ``` 29 | /// 30 | /// ## Update pubspec.yaml 31 | /// 32 | /// Please make sure to update your pubspec.yaml to include the following 33 | /// packages: 34 | /// 35 | /// ```yaml 36 | /// dependencies: 37 | /// # Internationalization support. 38 | /// flutter_localizations: 39 | /// sdk: flutter 40 | /// intl: any # Use the pinned version from flutter_localizations 41 | /// 42 | /// # Rest of dependencies 43 | /// ``` 44 | /// 45 | /// ## iOS Applications 46 | /// 47 | /// iOS applications define key application metadata, including supported 48 | /// locales, in an Info.plist file that is built into the application bundle. 49 | /// To configure the locales supported by your app, you’ll need to edit this 50 | /// file. 51 | /// 52 | /// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. 53 | /// Then, in the Project Navigator, open the Info.plist file under the Runner 54 | /// project’s Runner folder. 55 | /// 56 | /// Next, select the Information Property List item, select Add Item from the 57 | /// Editor menu, then select Localizations from the pop-up menu. 58 | /// 59 | /// Select and expand the newly-created Localizations item then, for each 60 | /// locale your application supports, add a new item and select the locale 61 | /// you wish to add from the pop-up menu in the Value field. This list should 62 | /// be consistent with the languages listed in the AppLocalizations.supportedLocales 63 | /// property. 64 | abstract class AppLocalizations { 65 | AppLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString()); 66 | 67 | final String localeName; 68 | 69 | static AppLocalizations? of(BuildContext context) { 70 | return Localizations.of(context, AppLocalizations); 71 | } 72 | 73 | static const LocalizationsDelegate delegate = _AppLocalizationsDelegate(); 74 | 75 | /// A list of this localizations delegate along with the default localizations 76 | /// delegates. 77 | /// 78 | /// Returns a list of localizations delegates containing this delegate along with 79 | /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, 80 | /// and GlobalWidgetsLocalizations.delegate. 81 | /// 82 | /// Additional delegates can be added by appending to this list in 83 | /// MaterialApp. This list does not have to be used at all if a custom list 84 | /// of delegates is preferred or required. 85 | static const List> localizationsDelegates = >[ 86 | delegate, 87 | GlobalMaterialLocalizations.delegate, 88 | GlobalCupertinoLocalizations.delegate, 89 | GlobalWidgetsLocalizations.delegate, 90 | ]; 91 | 92 | /// A list of this localizations delegate's supported locales. 93 | static const List supportedLocales = [ 94 | Locale('en'), 95 | Locale('zh') 96 | ]; 97 | 98 | /// No description provided for @appTitle. 99 | /// 100 | /// In en, this message translates to: 101 | /// **'WeChat Camera Picker Demo'** 102 | String get appTitle; 103 | 104 | /// No description provided for @appVersion. 105 | /// 106 | /// In en, this message translates to: 107 | /// **'Version: {version}'** 108 | String appVersion(Object version); 109 | 110 | /// No description provided for @appVersionUnknown. 111 | /// 112 | /// In en, this message translates to: 113 | /// **'unknown'** 114 | String get appVersionUnknown; 115 | 116 | /// No description provided for @selectedAssetsText. 117 | /// 118 | /// In en, this message translates to: 119 | /// **'Selected Assets'** 120 | String get selectedAssetsText; 121 | 122 | /// No description provided for @pickMethodNotice. 123 | /// 124 | /// In en, this message translates to: 125 | /// **'Pickers in this page are located at the {dist}, defined by `pickMethods`.'** 126 | String pickMethodNotice(Object dist); 127 | 128 | /// No description provided for @pickMethodPhotosName. 129 | /// 130 | /// In en, this message translates to: 131 | /// **'Taking photos'** 132 | String get pickMethodPhotosName; 133 | 134 | /// No description provided for @pickMethodPhotosDescription. 135 | /// 136 | /// In en, this message translates to: 137 | /// **'Use cameras only to take photos.'** 138 | String get pickMethodPhotosDescription; 139 | 140 | /// No description provided for @pickMethodPhotosAndVideosName. 141 | /// 142 | /// In en, this message translates to: 143 | /// **'Taking photos and videos'** 144 | String get pickMethodPhotosAndVideosName; 145 | 146 | /// No description provided for @pickMethodPhotosAndVideosDescription. 147 | /// 148 | /// In en, this message translates to: 149 | /// **'Use cameras to take photos and videos.'** 150 | String get pickMethodPhotosAndVideosDescription; 151 | 152 | /// No description provided for @pickMethodVideosName. 153 | /// 154 | /// In en, this message translates to: 155 | /// **'Taking videos'** 156 | String get pickMethodVideosName; 157 | 158 | /// No description provided for @pickMethodVideosDescription. 159 | /// 160 | /// In en, this message translates to: 161 | /// **'Use cameras only to take videos.'** 162 | String get pickMethodVideosDescription; 163 | 164 | /// No description provided for @pickMethodVideosByTapName. 165 | /// 166 | /// In en, this message translates to: 167 | /// **'Taking videos by tap'** 168 | String get pickMethodVideosByTapName; 169 | 170 | /// No description provided for @pickMethodVideosByTapDescription. 171 | /// 172 | /// In en, this message translates to: 173 | /// **'Use cameras only to take videos, but not with long-press, just a single tap.'** 174 | String get pickMethodVideosByTapDescription; 175 | 176 | /// No description provided for @pickMethodSilenceRecordingName. 177 | /// 178 | /// In en, this message translates to: 179 | /// **'Silence recording'** 180 | String get pickMethodSilenceRecordingName; 181 | 182 | /// No description provided for @pickMethodSilenceRecordingDescription. 183 | /// 184 | /// In en, this message translates to: 185 | /// **'Make recordings silent.'** 186 | String get pickMethodSilenceRecordingDescription; 187 | 188 | /// No description provided for @pickMethodNoDurationLimitName. 189 | /// 190 | /// In en, this message translates to: 191 | /// **'No duration limit'** 192 | String get pickMethodNoDurationLimitName; 193 | 194 | /// No description provided for @pickMethodNoDurationLimitDescription. 195 | /// 196 | /// In en, this message translates to: 197 | /// **'Record as long as you with (if your device stays alive)...'** 198 | String get pickMethodNoDurationLimitDescription; 199 | 200 | /// No description provided for @pickMethodCustomizableThemeName. 201 | /// 202 | /// In en, this message translates to: 203 | /// **'Customizable theme (ThemeData)'** 204 | String get pickMethodCustomizableThemeName; 205 | 206 | /// No description provided for @pickMethodCustomizableThemeDescription. 207 | /// 208 | /// In en, this message translates to: 209 | /// **'Picking assets with the light theme or with a different color.'** 210 | String get pickMethodCustomizableThemeDescription; 211 | 212 | /// No description provided for @pickMethodRotateInTurnsName. 213 | /// 214 | /// In en, this message translates to: 215 | /// **'Rotate picker in turns'** 216 | String get pickMethodRotateInTurnsName; 217 | 218 | /// No description provided for @pickMethodRotateInTurnsDescription. 219 | /// 220 | /// In en, this message translates to: 221 | /// **'Rotate the picker layout in quarter turns, without the camera preview.'** 222 | String get pickMethodRotateInTurnsDescription; 223 | 224 | /// No description provided for @pickMethodScalingPreviewName. 225 | /// 226 | /// In en, this message translates to: 227 | /// **'Scaling for camera preview'** 228 | String get pickMethodScalingPreviewName; 229 | 230 | /// No description provided for @pickMethodScalingPreviewDescription. 231 | /// 232 | /// In en, this message translates to: 233 | /// **'Camera preview will be scaled to cover the whole screen of the device with the original aspect ratio.'** 234 | String get pickMethodScalingPreviewDescription; 235 | 236 | /// No description provided for @pickMethodLowerResolutionName. 237 | /// 238 | /// In en, this message translates to: 239 | /// **'Lower resolutions'** 240 | String get pickMethodLowerResolutionName; 241 | 242 | /// No description provided for @pickMethodLowerResolutionDescription. 243 | /// 244 | /// In en, this message translates to: 245 | /// **'Use a lower resolution preset might be helpful in some specific scenarios.'** 246 | String get pickMethodLowerResolutionDescription; 247 | 248 | /// No description provided for @pickMethodPreferFrontCameraName. 249 | /// 250 | /// In en, this message translates to: 251 | /// **'Prefer front camera'** 252 | String get pickMethodPreferFrontCameraName; 253 | 254 | /// No description provided for @pickMethodPreferFrontCameraDescription. 255 | /// 256 | /// In en, this message translates to: 257 | /// **'Use the front camera as the preferred lens direction if the device supports.'** 258 | String get pickMethodPreferFrontCameraDescription; 259 | 260 | /// No description provided for @pickMethodPreferFlashlightOnName. 261 | /// 262 | /// In en, this message translates to: 263 | /// **'Prefer flashlight always on'** 264 | String get pickMethodPreferFlashlightOnName; 265 | 266 | /// No description provided for @pickMethodPreferFlashlightOnDescription. 267 | /// 268 | /// In en, this message translates to: 269 | /// **'Prefer to keep using the flashlight during captures.'** 270 | String get pickMethodPreferFlashlightOnDescription; 271 | 272 | /// No description provided for @pickMethodForegroundBuilderName. 273 | /// 274 | /// In en, this message translates to: 275 | /// **'Foreground builder'** 276 | String get pickMethodForegroundBuilderName; 277 | 278 | /// No description provided for @pickMethodForegroundBuilderDescription. 279 | /// 280 | /// In en, this message translates to: 281 | /// **'Build your widgets with the given CameraController on the top of the camera preview.'** 282 | String get pickMethodForegroundBuilderDescription; 283 | } 284 | 285 | class _AppLocalizationsDelegate extends LocalizationsDelegate { 286 | const _AppLocalizationsDelegate(); 287 | 288 | @override 289 | Future load(Locale locale) { 290 | return SynchronousFuture(lookupAppLocalizations(locale)); 291 | } 292 | 293 | @override 294 | bool isSupported(Locale locale) => ['en', 'zh'].contains(locale.languageCode); 295 | 296 | @override 297 | bool shouldReload(_AppLocalizationsDelegate old) => false; 298 | } 299 | 300 | AppLocalizations lookupAppLocalizations(Locale locale) { 301 | 302 | 303 | // Lookup logic when only language code is specified. 304 | switch (locale.languageCode) { 305 | case 'en': return AppLocalizationsEn(); 306 | case 'zh': return AppLocalizationsZh(); 307 | } 308 | 309 | throw FlutterError( 310 | 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' 311 | 'an issue with the localizations generation tool. Please file an issue ' 312 | 'on GitHub with a reproducible sample app and the gen-l10n configuration ' 313 | 'that was used.' 314 | ); 315 | } 316 | -------------------------------------------------------------------------------- /example/lib/l10n/gen/app_localizations_en.dart: -------------------------------------------------------------------------------- 1 | import 'app_localizations.dart'; 2 | 3 | // ignore_for_file: type=lint 4 | 5 | /// The translations for English (`en`). 6 | class AppLocalizationsEn extends AppLocalizations { 7 | AppLocalizationsEn([String locale = 'en']) : super(locale); 8 | 9 | @override 10 | String get appTitle => 'WeChat Camera Picker Demo'; 11 | 12 | @override 13 | String appVersion(Object version) { 14 | return 'Version: $version'; 15 | } 16 | 17 | @override 18 | String get appVersionUnknown => 'unknown'; 19 | 20 | @override 21 | String get selectedAssetsText => 'Selected Assets'; 22 | 23 | @override 24 | String pickMethodNotice(Object dist) { 25 | return 'Pickers in this page are located at the $dist, defined by `pickMethods`.'; 26 | } 27 | 28 | @override 29 | String get pickMethodPhotosName => 'Taking photos'; 30 | 31 | @override 32 | String get pickMethodPhotosDescription => 'Use cameras only to take photos.'; 33 | 34 | @override 35 | String get pickMethodPhotosAndVideosName => 'Taking photos and videos'; 36 | 37 | @override 38 | String get pickMethodPhotosAndVideosDescription => 'Use cameras to take photos and videos.'; 39 | 40 | @override 41 | String get pickMethodVideosName => 'Taking videos'; 42 | 43 | @override 44 | String get pickMethodVideosDescription => 'Use cameras only to take videos.'; 45 | 46 | @override 47 | String get pickMethodVideosByTapName => 'Taking videos by tap'; 48 | 49 | @override 50 | String get pickMethodVideosByTapDescription => 'Use cameras only to take videos, but not with long-press, just a single tap.'; 51 | 52 | @override 53 | String get pickMethodSilenceRecordingName => 'Silence recording'; 54 | 55 | @override 56 | String get pickMethodSilenceRecordingDescription => 'Make recordings silent.'; 57 | 58 | @override 59 | String get pickMethodNoDurationLimitName => 'No duration limit'; 60 | 61 | @override 62 | String get pickMethodNoDurationLimitDescription => 'Record as long as you with (if your device stays alive)...'; 63 | 64 | @override 65 | String get pickMethodCustomizableThemeName => 'Customizable theme (ThemeData)'; 66 | 67 | @override 68 | String get pickMethodCustomizableThemeDescription => 'Picking assets with the light theme or with a different color.'; 69 | 70 | @override 71 | String get pickMethodRotateInTurnsName => 'Rotate picker in turns'; 72 | 73 | @override 74 | String get pickMethodRotateInTurnsDescription => 'Rotate the picker layout in quarter turns, without the camera preview.'; 75 | 76 | @override 77 | String get pickMethodScalingPreviewName => 'Scaling for camera preview'; 78 | 79 | @override 80 | String get pickMethodScalingPreviewDescription => 'Camera preview will be scaled to cover the whole screen of the device with the original aspect ratio.'; 81 | 82 | @override 83 | String get pickMethodLowerResolutionName => 'Lower resolutions'; 84 | 85 | @override 86 | String get pickMethodLowerResolutionDescription => 'Use a lower resolution preset might be helpful in some specific scenarios.'; 87 | 88 | @override 89 | String get pickMethodPreferFrontCameraName => 'Prefer front camera'; 90 | 91 | @override 92 | String get pickMethodPreferFrontCameraDescription => 'Use the front camera as the preferred lens direction if the device supports.'; 93 | 94 | @override 95 | String get pickMethodPreferFlashlightOnName => 'Prefer flashlight always on'; 96 | 97 | @override 98 | String get pickMethodPreferFlashlightOnDescription => 'Prefer to keep using the flashlight during captures.'; 99 | 100 | @override 101 | String get pickMethodForegroundBuilderName => 'Foreground builder'; 102 | 103 | @override 104 | String get pickMethodForegroundBuilderDescription => 'Build your widgets with the given CameraController on the top of the camera preview.'; 105 | } 106 | -------------------------------------------------------------------------------- /example/lib/l10n/gen/app_localizations_untranslated.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /example/lib/l10n/gen/app_localizations_zh.dart: -------------------------------------------------------------------------------- 1 | import 'app_localizations.dart'; 2 | 3 | // ignore_for_file: type=lint 4 | 5 | /// The translations for Chinese (`zh`). 6 | class AppLocalizationsZh extends AppLocalizations { 7 | AppLocalizationsZh([String locale = 'zh']) : super(locale); 8 | 9 | @override 10 | String get appTitle => 'WeChat Camera Picker 示例'; 11 | 12 | @override 13 | String appVersion(Object version) { 14 | return '版本:$version'; 15 | } 16 | 17 | @override 18 | String get appVersionUnknown => '未知'; 19 | 20 | @override 21 | String get selectedAssetsText => '已选的资源'; 22 | 23 | @override 24 | String pickMethodNotice(Object dist) { 25 | return '该页面的所有选择器的代码位于 $dist,由 `pickMethods` 定义。'; 26 | } 27 | 28 | @override 29 | String get pickMethodPhotosName => '拍照'; 30 | 31 | @override 32 | String get pickMethodPhotosDescription => '使用相机拍照。'; 33 | 34 | @override 35 | String get pickMethodPhotosAndVideosName => '拍照和录像'; 36 | 37 | @override 38 | String get pickMethodPhotosAndVideosDescription => '使用相机进行拍照和录像。'; 39 | 40 | @override 41 | String get pickMethodVideosName => '录像'; 42 | 43 | @override 44 | String get pickMethodVideosDescription => '使用相机录像。'; 45 | 46 | @override 47 | String get pickMethodVideosByTapName => '轻触录像'; 48 | 49 | @override 50 | String get pickMethodVideosByTapDescription => '轻触录像按钮进行录像,而不是长按。'; 51 | 52 | @override 53 | String get pickMethodSilenceRecordingName => '静音录像'; 54 | 55 | @override 56 | String get pickMethodSilenceRecordingDescription => '录像时不录制声音。'; 57 | 58 | @override 59 | String get pickMethodNoDurationLimitName => '无时长限制录像'; 60 | 61 | @override 62 | String get pickMethodNoDurationLimitDescription => '想录多久,就录多久(只要手机健在)。'; 63 | 64 | @override 65 | String get pickMethodCustomizableThemeName => '自定义主题 (ThemeData)'; 66 | 67 | @override 68 | String get pickMethodCustomizableThemeDescription => '可以用亮色或其他颜色及自定义的主题进行选择。'; 69 | 70 | @override 71 | String get pickMethodRotateInTurnsName => '旋转选择器的布局'; 72 | 73 | @override 74 | String get pickMethodRotateInTurnsDescription => '顺时针旋转选择器的元素布局,不旋转相机视图。'; 75 | 76 | @override 77 | String get pickMethodScalingPreviewName => '缩放相机预览'; 78 | 79 | @override 80 | String get pickMethodScalingPreviewDescription => '相机预览视图会被放大到覆盖整个屏幕且保持原始的预览比例。'; 81 | 82 | @override 83 | String get pickMethodLowerResolutionName => '低分辨率拍照'; 84 | 85 | @override 86 | String get pickMethodLowerResolutionDescription => '某些情况或机型使用低分辨率拍照会有稳定性改善。'; 87 | 88 | @override 89 | String get pickMethodPreferFrontCameraName => '首选前置摄像头'; 90 | 91 | @override 92 | String get pickMethodPreferFrontCameraDescription => '在设备支持时首选使用前置摄像头。'; 93 | 94 | @override 95 | String get pickMethodPreferFlashlightOnName => '首选闪光灯始终启用'; 96 | 97 | @override 98 | String get pickMethodPreferFlashlightOnDescription => '在设备支持时首选闪光灯始终启用。'; 99 | 100 | @override 101 | String get pickMethodForegroundBuilderName => '构建前景'; 102 | 103 | @override 104 | String get pickMethodForegroundBuilderDescription => '通过 CameraController 构建在相机预览部分的前景 widget。'; 105 | } 106 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The FlutterCandies author. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be found 3 | // in the LICENSE file. 4 | 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter/services.dart'; 7 | 8 | import 'extensions/color_extension.dart'; 9 | import 'extensions/l10n_extensions.dart'; 10 | import 'l10n/gen/app_localizations.dart'; 11 | import 'pages/splash_page.dart'; 12 | 13 | const Color themeColor = Color(0xff00bc56); 14 | 15 | /// The mock size is used for integration tests. 16 | /// Changing this requires at least a hot-restart. 17 | const Size? mockSize = null; 18 | 19 | String? packageVersion; 20 | 21 | void main() { 22 | runApp(const MyApp()); 23 | SystemChrome.setSystemUIOverlayStyle( 24 | SystemUiOverlayStyle.dark.copyWith(statusBarColor: Colors.transparent), 25 | ); 26 | } 27 | 28 | class MyApp extends StatelessWidget { 29 | const MyApp({super.key}); 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return MaterialApp( 34 | onGenerateTitle: (BuildContext context) => context.l10n.appTitle, 35 | theme: ThemeData( 36 | brightness: Brightness.light, 37 | primarySwatch: themeColor.swatch, 38 | textSelectionTheme: const TextSelectionThemeData( 39 | cursorColor: themeColor, 40 | ), 41 | ), 42 | darkTheme: ThemeData( 43 | brightness: Brightness.dark, 44 | primarySwatch: themeColor.swatch, 45 | textSelectionTheme: const TextSelectionThemeData( 46 | cursorColor: themeColor, 47 | ), 48 | ), 49 | home: const SplashPage(), 50 | localizationsDelegates: AppLocalizations.localizationsDelegates, 51 | supportedLocales: AppLocalizations.supportedLocales, 52 | builder: (context, child) { 53 | if (mockSize == null) { 54 | return child!; 55 | } 56 | final mq = MediaQuery.of(context).copyWith(size: mockSize); 57 | return MediaQuery( 58 | data: mq, 59 | child: Align( 60 | alignment: Alignment.topCenter, 61 | child: SizedBox.fromSize(size: mockSize, child: child), 62 | ), 63 | ); 64 | }, 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /example/lib/models/picker_method.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The FlutterCandies author. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be found 3 | // in the LICENSE file. 4 | 5 | import 'package:flutter/material.dart'; 6 | import 'package:wechat_camera_picker/wechat_camera_picker.dart'; 7 | 8 | import '../extensions/l10n_extensions.dart'; 9 | 10 | /// Provide common usages of the picker. 11 | /// 提供常见的选择器调用方式。 12 | List pickMethods(BuildContext context) { 13 | return [ 14 | PickMethod( 15 | icon: '📷', 16 | name: context.l10n.pickMethodPhotosName, 17 | description: context.l10n.pickMethodPhotosDescription, 18 | method: (BuildContext context) => CameraPicker.pickFromCamera(context), 19 | ), 20 | PickMethod( 21 | icon: '📹', 22 | name: context.l10n.pickMethodPhotosAndVideosName, 23 | description: context.l10n.pickMethodPhotosAndVideosDescription, 24 | method: (BuildContext context) => CameraPicker.pickFromCamera( 25 | context, 26 | pickerConfig: const CameraPickerConfig(enableRecording: true), 27 | ), 28 | ), 29 | PickMethod( 30 | icon: '🎥', 31 | name: context.l10n.pickMethodVideosName, 32 | description: context.l10n.pickMethodVideosDescription, 33 | method: (BuildContext context) => CameraPicker.pickFromCamera( 34 | context, 35 | pickerConfig: const CameraPickerConfig( 36 | enableRecording: true, 37 | onlyEnableRecording: true, 38 | ), 39 | ), 40 | ), 41 | PickMethod( 42 | icon: '📽', 43 | name: context.l10n.pickMethodVideosByTapName, 44 | description: context.l10n.pickMethodVideosByTapDescription, 45 | method: (BuildContext context) => CameraPicker.pickFromCamera( 46 | context, 47 | pickerConfig: const CameraPickerConfig( 48 | enableRecording: true, 49 | onlyEnableRecording: true, 50 | enableTapRecording: true, 51 | ), 52 | ), 53 | ), 54 | PickMethod( 55 | icon: '🈲', 56 | name: context.l10n.pickMethodSilenceRecordingName, 57 | description: context.l10n.pickMethodSilenceRecordingDescription, 58 | method: (BuildContext context) => CameraPicker.pickFromCamera( 59 | context, 60 | pickerConfig: const CameraPickerConfig( 61 | enableRecording: true, 62 | onlyEnableRecording: true, 63 | enableTapRecording: true, 64 | enableAudio: false, 65 | ), 66 | ), 67 | ), 68 | PickMethod( 69 | icon: '⏳', 70 | name: context.l10n.pickMethodNoDurationLimitName, 71 | description: context.l10n.pickMethodNoDurationLimitDescription, 72 | method: (BuildContext context) => CameraPicker.pickFromCamera( 73 | context, 74 | pickerConfig: const CameraPickerConfig( 75 | enableRecording: true, 76 | onlyEnableRecording: true, 77 | enableTapRecording: true, 78 | maximumRecordingDuration: null, 79 | ), 80 | ), 81 | ), 82 | PickMethod( 83 | icon: '🎨', 84 | name: context.l10n.pickMethodCustomizableThemeName, 85 | description: context.l10n.pickMethodCustomizableThemeDescription, 86 | method: (BuildContext context) => CameraPicker.pickFromCamera( 87 | context, 88 | pickerConfig: CameraPickerConfig( 89 | theme: CameraPicker.themeData(Colors.blue), 90 | ), 91 | ), 92 | ), 93 | PickMethod( 94 | icon: '↩️', 95 | name: context.l10n.pickMethodRotateInTurnsName, 96 | description: context.l10n.pickMethodRotateInTurnsDescription, 97 | method: (BuildContext context) => CameraPicker.pickFromCamera( 98 | context, 99 | pickerConfig: const CameraPickerConfig(cameraQuarterTurns: 1), 100 | ), 101 | ), 102 | PickMethod( 103 | icon: '🔍', 104 | name: context.l10n.pickMethodScalingPreviewName, 105 | description: context.l10n.pickMethodScalingPreviewDescription, 106 | method: (BuildContext context) => CameraPicker.pickFromCamera( 107 | context, 108 | pickerConfig: const CameraPickerConfig(enableScaledPreview: true), 109 | ), 110 | ), 111 | PickMethod( 112 | icon: '🌀', 113 | name: context.l10n.pickMethodLowerResolutionName, 114 | description: context.l10n.pickMethodLowerResolutionDescription, 115 | method: (BuildContext context) => CameraPicker.pickFromCamera( 116 | context, 117 | pickerConfig: const CameraPickerConfig( 118 | resolutionPreset: ResolutionPreset.low, 119 | ), 120 | ), 121 | ), 122 | PickMethod( 123 | icon: '🤳', 124 | name: context.l10n.pickMethodPreferFrontCameraName, 125 | description: context.l10n.pickMethodPreferFrontCameraDescription, 126 | method: (BuildContext context) => CameraPicker.pickFromCamera( 127 | context, 128 | pickerConfig: const CameraPickerConfig( 129 | preferredLensDirection: CameraLensDirection.front, 130 | ), 131 | ), 132 | ), 133 | PickMethod( 134 | icon: '📸', 135 | name: context.l10n.pickMethodPreferFlashlightOnName, 136 | description: context.l10n.pickMethodPreferFlashlightOnDescription, 137 | method: (BuildContext context) => CameraPicker.pickFromCamera( 138 | context, 139 | pickerConfig: const CameraPickerConfig( 140 | preferredFlashMode: FlashMode.always, 141 | ), 142 | ), 143 | ), 144 | PickMethod( 145 | icon: '🪄', 146 | name: context.l10n.pickMethodForegroundBuilderName, 147 | description: context.l10n.pickMethodForegroundBuilderDescription, 148 | method: (BuildContext context) => CameraPicker.pickFromCamera( 149 | context, 150 | pickerConfig: CameraPickerConfig( 151 | foregroundBuilder: ( 152 | BuildContext context, 153 | CameraController? controller, 154 | ) { 155 | return Center( 156 | child: Text( 157 | controller == null 158 | ? 'Waiting for initialize...' 159 | : '${controller.description.lensDirection}', 160 | style: const TextStyle(color: Colors.white), 161 | ), 162 | ); 163 | }, 164 | ), 165 | ), 166 | ), 167 | ]; 168 | } 169 | 170 | /// Define a regular pick method. 171 | final class PickMethod { 172 | const PickMethod({ 173 | required this.icon, 174 | required this.name, 175 | required this.description, 176 | required this.method, 177 | this.onLongPress, 178 | }); 179 | 180 | final String icon; 181 | final String name; 182 | final String description; 183 | 184 | /// The core function that defines how to use the picker. 185 | final Future Function(BuildContext context) method; 186 | 187 | final GestureLongPressCallback? onLongPress; 188 | } 189 | -------------------------------------------------------------------------------- /example/lib/pages/home_page.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The FlutterCandies author. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be found 3 | // in the LICENSE file. 4 | 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter/semantics.dart'; 7 | import 'package:flutter/services.dart'; 8 | import 'package:wechat_camera_picker/wechat_camera_picker.dart'; 9 | 10 | import '../extensions/l10n_extensions.dart'; 11 | import '../main.dart'; 12 | import '../models/picker_method.dart'; 13 | import '../widgets/method_list_view.dart'; 14 | import '../widgets/selected_assets_list_view.dart'; 15 | 16 | class HomePage extends StatefulWidget { 17 | const HomePage({super.key}); 18 | 19 | @override 20 | State createState() => _MyHomePageState(); 21 | } 22 | 23 | class _MyHomePageState extends State { 24 | final ValueNotifier isDisplayingDetail = ValueNotifier(true); 25 | List assets = []; 26 | 27 | Future selectAssets(PickMethod model) async { 28 | final result = await model.method(context); 29 | if (result != null) { 30 | assets = [...assets, result]; 31 | if (mounted) { 32 | setState(() {}); 33 | } 34 | } 35 | } 36 | 37 | Widget header(BuildContext context) { 38 | return Container( 39 | margin: const EdgeInsetsDirectional.only(top: 30), 40 | padding: const EdgeInsets.symmetric(horizontal: 20.0), 41 | height: 60, 42 | child: Row( 43 | mainAxisAlignment: MainAxisAlignment.center, 44 | children: [ 45 | AspectRatio( 46 | aspectRatio: 1, 47 | child: Hero( 48 | tag: 'LOGO', 49 | child: Image.asset('assets/flutter_candies_logo.png'), 50 | ), 51 | ), 52 | const SizedBox(width: 10), 53 | Flexible( 54 | child: Column( 55 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 56 | crossAxisAlignment: CrossAxisAlignment.start, 57 | children: [ 58 | Semantics( 59 | sortKey: const OrdinalSortKey(0), 60 | child: FittedBox( 61 | child: Text( 62 | context.l10n.appTitle, 63 | style: Theme.of(context).textTheme.titleLarge, 64 | overflow: TextOverflow.fade, 65 | maxLines: 1, 66 | ), 67 | ), 68 | ), 69 | Semantics( 70 | sortKey: const OrdinalSortKey(0.1), 71 | child: Text( 72 | context.l10n.appVersion( 73 | packageVersion ?? context.l10n.appVersionUnknown, 74 | ), 75 | style: Theme.of(context).textTheme.bodySmall, 76 | ), 77 | ), 78 | ], 79 | ), 80 | ), 81 | const SizedBox(width: 20), 82 | ], 83 | ), 84 | ); 85 | } 86 | 87 | @override 88 | Widget build(BuildContext context) { 89 | return AnnotatedRegion( 90 | value: Theme.of(context).brightness == Brightness.dark 91 | ? SystemUiOverlayStyle.light 92 | : SystemUiOverlayStyle.dark, 93 | child: Scaffold( 94 | body: SafeArea( 95 | child: Column( 96 | children: [ 97 | header(context), 98 | Padding( 99 | padding: const EdgeInsets.all(20.0), 100 | child: Text( 101 | context.l10n.pickMethodNotice( 102 | 'lib/models/picker_method.dart', 103 | ), 104 | ), 105 | ), 106 | Expanded( 107 | child: MethodListView( 108 | pickMethods: pickMethods(context), 109 | onSelectMethod: selectAssets, 110 | ), 111 | ), 112 | if (assets.isNotEmpty) 113 | SelectedAssetsListView( 114 | assets: assets, 115 | isDisplayingDetail: isDisplayingDetail, 116 | onRemoveAsset: (int index) { 117 | assets.removeAt(index); 118 | setState(() {}); 119 | }, 120 | ), 121 | ], 122 | ), 123 | ), 124 | ), 125 | ); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /example/lib/pages/splash_page.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The FlutterCandies author. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be found 3 | // in the LICENSE file. 4 | 5 | import 'package:flutter/material.dart'; 6 | import 'package:package_info_plus/package_info_plus.dart'; 7 | 8 | import '../main.dart'; 9 | import 'home_page.dart'; 10 | 11 | class SplashPage extends StatefulWidget { 12 | const SplashPage({super.key}); 13 | 14 | @override 15 | State createState() => _SplashPageState(); 16 | } 17 | 18 | class _SplashPageState extends State { 19 | @override 20 | void initState() { 21 | super.initState(); 22 | init(); 23 | } 24 | 25 | Future init() async { 26 | try { 27 | final PackageInfo info = await PackageInfo.fromPlatform(); 28 | packageVersion = info.version; 29 | } catch (_) {} 30 | await Future.delayed(const Duration(seconds: 1)); 31 | if (mounted) { 32 | Navigator.of(context).pushReplacement( 33 | PageRouteBuilder( 34 | pageBuilder: (_, __, ___) => const HomePage(), 35 | transitionsBuilder: (_, Animation a, __, Widget child) { 36 | return FadeTransition(opacity: a, child: child); 37 | }, 38 | transitionDuration: const Duration(seconds: 1), 39 | ), 40 | ); 41 | } 42 | } 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | return Material( 47 | color: Theme.of(context).canvasColor, 48 | child: Center( 49 | child: Hero( 50 | tag: 'LOGO', 51 | child: Image.asset('assets/flutter_candies_logo.png', width: 150.0), 52 | ), 53 | ), 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /example/lib/widgets/asset_widget_builder.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The FlutterCandies author. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be found 3 | // in the LICENSE file. 4 | 5 | import 'package:flutter/material.dart'; 6 | import 'package:wechat_camera_picker/wechat_camera_picker.dart'; 7 | 8 | class AssetWidgetBuilder extends StatelessWidget { 9 | const AssetWidgetBuilder({ 10 | super.key, 11 | required this.entity, 12 | required this.isDisplayingDetail, 13 | }); 14 | 15 | final AssetEntity entity; 16 | final bool isDisplayingDetail; 17 | 18 | Widget _audioAssetWidget(BuildContext context) { 19 | return ColoredBox( 20 | color: Theme.of(context).dividerColor, 21 | child: Stack( 22 | children: [ 23 | AnimatedPositionedDirectional( 24 | duration: kThemeAnimationDuration, 25 | top: 0.0, 26 | start: 0.0, 27 | end: 0.0, 28 | bottom: isDisplayingDetail ? 20.0 : 0.0, 29 | child: Center( 30 | child: Icon( 31 | Icons.audiotrack, 32 | size: isDisplayingDetail ? 24.0 : 16.0, 33 | ), 34 | ), 35 | ), 36 | AnimatedPositionedDirectional( 37 | duration: kThemeAnimationDuration, 38 | start: 0.0, 39 | end: 0.0, 40 | bottom: isDisplayingDetail ? 0.0 : -20.0, 41 | height: 20.0, 42 | child: Text( 43 | entity.title ?? '', 44 | style: const TextStyle(height: 1.0, fontSize: 10.0), 45 | maxLines: 1, 46 | overflow: TextOverflow.ellipsis, 47 | textAlign: TextAlign.center, 48 | ), 49 | ), 50 | ], 51 | ), 52 | ); 53 | } 54 | 55 | Widget _imageAssetWidget(BuildContext context) { 56 | return Image( 57 | image: AssetEntityImageProvider(entity, isOriginal: false), 58 | fit: BoxFit.cover, 59 | ); 60 | } 61 | 62 | Widget _videoAssetWidget(BuildContext context) { 63 | return Stack( 64 | children: [ 65 | Positioned.fill(child: _imageAssetWidget(context)), 66 | ColoredBox( 67 | // ignore: deprecated_member_use 68 | color: Theme.of(context).dividerColor.withOpacity(0.3), 69 | child: Center( 70 | child: Icon( 71 | Icons.video_library, 72 | color: Colors.white, 73 | size: isDisplayingDetail ? 24.0 : 16.0, 74 | ), 75 | ), 76 | ), 77 | ], 78 | ); 79 | } 80 | 81 | @override 82 | Widget build(BuildContext context) { 83 | switch (entity.type) { 84 | case AssetType.audio: 85 | return _audioAssetWidget(context); 86 | case AssetType.video: 87 | return _videoAssetWidget(context); 88 | case AssetType.image: 89 | case AssetType.other: 90 | return _imageAssetWidget(context); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /example/lib/widgets/method_list_view.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The FlutterCandies author. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be found 3 | // in the LICENSE file. 4 | 5 | import 'package:flutter/material.dart'; 6 | 7 | import '../models/picker_method.dart'; 8 | 9 | class MethodListView extends StatefulWidget { 10 | const MethodListView({ 11 | super.key, 12 | required this.pickMethods, 13 | required this.onSelectMethod, 14 | }); 15 | 16 | final List pickMethods; 17 | final void Function(PickMethod method) onSelectMethod; 18 | 19 | @override 20 | State createState() => _MethodListViewState(); 21 | } 22 | 23 | class _MethodListViewState extends State { 24 | final ScrollController _controller = ScrollController(); 25 | 26 | Widget methodItemBuilder(BuildContext context, int index) { 27 | final PickMethod model = widget.pickMethods[index]; 28 | return InkWell( 29 | onTap: () => widget.onSelectMethod(model), 30 | onLongPress: model.onLongPress, 31 | child: Container( 32 | padding: const EdgeInsets.symmetric( 33 | horizontal: 20.0, 34 | vertical: 10.0, 35 | ), 36 | child: Row( 37 | children: [ 38 | Container( 39 | margin: const EdgeInsets.all(2.0), 40 | width: 48, 41 | height: 48, 42 | child: Center( 43 | child: ExcludeSemantics( 44 | child: Text( 45 | model.icon, 46 | style: const TextStyle(fontSize: 28.0), 47 | ), 48 | ), 49 | ), 50 | ), 51 | const SizedBox(width: 12.0), 52 | Expanded( 53 | child: Column( 54 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 55 | crossAxisAlignment: CrossAxisAlignment.start, 56 | children: [ 57 | Text( 58 | model.name, 59 | style: const TextStyle( 60 | fontSize: 18.0, 61 | fontWeight: FontWeight.bold, 62 | ), 63 | maxLines: 1, 64 | overflow: TextOverflow.ellipsis, 65 | ), 66 | const SizedBox(height: 5), 67 | Text( 68 | model.description, 69 | style: Theme.of(context).textTheme.bodySmall, 70 | ), 71 | ], 72 | ), 73 | ), 74 | const Icon(Icons.chevron_right, color: Colors.grey), 75 | ], 76 | ), 77 | ), 78 | ); 79 | } 80 | 81 | @override 82 | Widget build(BuildContext context) { 83 | return Scrollbar( 84 | controller: _controller, 85 | thumbVisibility: true, 86 | radius: const Radius.circular(999), 87 | child: ListView.builder( 88 | controller: _controller, 89 | padding: const EdgeInsets.symmetric(horizontal: 10).add( 90 | const EdgeInsets.only(top: 10, bottom: 20), 91 | ), 92 | itemCount: widget.pickMethods.length, 93 | itemBuilder: methodItemBuilder, 94 | ), 95 | ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /example/lib/widgets/preview_asset_widget.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The FlutterCandies author. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be found 3 | // in the LICENSE file. 4 | 5 | import 'dart:io'; 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:video_player/video_player.dart'; 9 | import 'package:wechat_camera_picker/wechat_camera_picker.dart'; 10 | 11 | class PreviewAssetWidget extends StatefulWidget { 12 | const PreviewAssetWidget(this.asset, {super.key}); 13 | 14 | final AssetEntity asset; 15 | 16 | @override 17 | State createState() => _PreviewAssetWidgetState(); 18 | } 19 | 20 | class _PreviewAssetWidgetState extends State { 21 | bool get _isVideo => widget.asset.type == AssetType.video; 22 | Object? _error; 23 | VideoPlayerController? _playerController; 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | if (_isVideo) { 29 | _initializeController(); 30 | } 31 | } 32 | 33 | @override 34 | void dispose() { 35 | _playerController?.dispose(); 36 | super.dispose(); 37 | } 38 | 39 | Future _initializeController() async { 40 | final String? url = await widget.asset.getMediaUrl(); 41 | if (url == null) { 42 | _error = StateError('The media URL of the preview asset is null.'); 43 | return; 44 | } 45 | final VideoPlayerController controller; 46 | final Uri uri = Uri.parse(url); 47 | if (Platform.isAndroid) { 48 | controller = VideoPlayerController.contentUri(uri); 49 | } else { 50 | controller = VideoPlayerController.networkUrl(uri); 51 | } 52 | _playerController = controller; 53 | try { 54 | await controller.initialize(); 55 | controller 56 | ..setLooping(true) 57 | ..play(); 58 | } catch (e) { 59 | _error = e; 60 | } finally { 61 | if (mounted) { 62 | setState(() {}); 63 | } 64 | } 65 | } 66 | 67 | Widget _buildImage(BuildContext context) { 68 | return AssetEntityImage(widget.asset); 69 | } 70 | 71 | Widget _buildVideo(BuildContext context) { 72 | final VideoPlayerController? controller = _playerController; 73 | if (controller == null) { 74 | return const CircularProgressIndicator(); 75 | } 76 | return AspectRatio( 77 | aspectRatio: controller.value.aspectRatio, 78 | child: VideoPlayer(controller), 79 | ); 80 | } 81 | 82 | @override 83 | Widget build(BuildContext context) { 84 | if (_error != null) { 85 | return Text('$_error', style: const TextStyle(color: Colors.white)); 86 | } 87 | if (_isVideo) { 88 | return _buildVideo(context); 89 | } 90 | return _buildImage(context); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /example/lib/widgets/selected_assets_list_view.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The FlutterCandies author. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be found 3 | // in the LICENSE file. 4 | 5 | import 'package:flutter/material.dart'; 6 | import 'package:wechat_camera_picker/wechat_camera_picker.dart'; 7 | 8 | import '../extensions/l10n_extensions.dart'; 9 | import 'asset_widget_builder.dart'; 10 | import 'preview_asset_widget.dart'; 11 | 12 | class SelectedAssetsListView extends StatelessWidget { 13 | const SelectedAssetsListView({ 14 | super.key, 15 | required this.assets, 16 | required this.isDisplayingDetail, 17 | required this.onRemoveAsset, 18 | }); 19 | 20 | final List assets; 21 | final ValueNotifier isDisplayingDetail; 22 | final void Function(int index) onRemoveAsset; 23 | 24 | Widget _selectedAssetWidget(BuildContext context, int index) { 25 | final AssetEntity asset = assets.elementAt(index); 26 | return ValueListenableBuilder( 27 | valueListenable: isDisplayingDetail, 28 | builder: (_, bool value, __) => GestureDetector( 29 | onTap: () async { 30 | if (value) { 31 | showDialog( 32 | context: context, 33 | builder: (BuildContext context) => GestureDetector( 34 | onTap: Navigator.of(context).pop, 35 | child: Center(child: PreviewAssetWidget(asset)), 36 | ), 37 | ); 38 | } 39 | }, 40 | child: RepaintBoundary( 41 | child: ClipRRect( 42 | borderRadius: BorderRadius.circular(8.0), 43 | child: AssetWidgetBuilder( 44 | entity: asset, 45 | isDisplayingDetail: value, 46 | ), 47 | ), 48 | ), 49 | ), 50 | ); 51 | } 52 | 53 | Widget _selectedAssetDeleteButton(BuildContext context, int index) { 54 | return GestureDetector( 55 | onTap: () => onRemoveAsset(index), 56 | child: DecoratedBox( 57 | decoration: BoxDecoration( 58 | borderRadius: BorderRadius.circular(4.0), 59 | // ignore: deprecated_member_use 60 | color: Theme.of(context).canvasColor.withOpacity(0.5), 61 | ), 62 | child: const Icon(Icons.close, size: 18.0), 63 | ), 64 | ); 65 | } 66 | 67 | Widget selectedAssetsListView(BuildContext context) { 68 | return Expanded( 69 | child: ListView.builder( 70 | shrinkWrap: true, 71 | physics: const BouncingScrollPhysics(), 72 | padding: const EdgeInsets.symmetric(horizontal: 8.0), 73 | scrollDirection: Axis.horizontal, 74 | itemCount: assets.length, 75 | itemBuilder: (BuildContext context, int index) { 76 | return Padding( 77 | padding: const EdgeInsets.symmetric( 78 | horizontal: 8.0, 79 | vertical: 16.0, 80 | ), 81 | child: AspectRatio( 82 | aspectRatio: 1.0, 83 | child: Stack( 84 | children: [ 85 | Positioned.fill(child: _selectedAssetWidget(context, index)), 86 | ValueListenableBuilder( 87 | valueListenable: isDisplayingDetail, 88 | builder: (_, bool value, __) => AnimatedPositioned( 89 | duration: kThemeAnimationDuration, 90 | top: value ? 6.0 : -30.0, 91 | right: value ? 6.0 : -30.0, 92 | child: _selectedAssetDeleteButton(context, index), 93 | ), 94 | ), 95 | ], 96 | ), 97 | ), 98 | ); 99 | }, 100 | ), 101 | ); 102 | } 103 | 104 | @override 105 | Widget build(BuildContext context) { 106 | return ValueListenableBuilder( 107 | valueListenable: isDisplayingDetail, 108 | builder: (_, bool value, __) => AnimatedContainer( 109 | duration: kThemeChangeDuration, 110 | curve: Curves.easeInOut, 111 | height: assets.isNotEmpty 112 | ? value 113 | ? 120.0 114 | : 80.0 115 | : 40.0, 116 | child: Column( 117 | children: [ 118 | SizedBox( 119 | height: 20.0, 120 | child: GestureDetector( 121 | onTap: () { 122 | if (assets.isNotEmpty) { 123 | isDisplayingDetail.value = !isDisplayingDetail.value; 124 | } 125 | }, 126 | child: Row( 127 | mainAxisSize: MainAxisSize.min, 128 | children: [ 129 | Text(context.l10n.selectedAssetsText), 130 | if (assets.isNotEmpty) 131 | Padding( 132 | padding: const EdgeInsetsDirectional.only(start: 10.0), 133 | child: Icon( 134 | value ? Icons.arrow_downward : Icons.arrow_upward, 135 | size: 18.0, 136 | ), 137 | ), 138 | ], 139 | ), 140 | ), 141 | ), 142 | selectedAssetsListView(context), 143 | ], 144 | ), 145 | ), 146 | ); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: wechat_camera_picker_demo 2 | description: A new Flutter project. 3 | version: 4.4.0+44 4 | publish_to: none 5 | 6 | environment: 7 | sdk: ^3.3.0 8 | flutter: '>=3.19.0' 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | flutter_localizations: 14 | sdk: flutter 15 | intl: '>=0.17.0 <1.0.0' 16 | 17 | wechat_camera_picker: 18 | path: ../ 19 | 20 | package_info_plus: '>=7.0.0 <9.0.0' 21 | video_player: ^2.7.0 22 | 23 | dev_dependencies: 24 | flutter_lints: any 25 | 26 | flutter: 27 | uses-material-design: true 28 | assets: 29 | - assets/ 30 | -------------------------------------------------------------------------------- /guides/migration_guide.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # Migration Guide 6 | 7 | This document gathered all breaking changes and migrations requirement between major versions. 8 | 9 | ## Versions 10 | 11 | - [4.0.0](#400) 12 | 13 | ## 4.0.0 14 | 15 | ### Summary 16 | 17 | > If you don't extend your customized `CameraPickerState` 18 | > or you didn't override below methods, you can stop reading. 19 | 20 | In order to sync the UI details with the latest WeChat style (v8.3.0), 21 | few naming or signatures of methods are changed. including: 22 | - `restartDisplayModeDisplayTimer` 23 | - `buildBackButton` 24 | - `buildCameraPreview` 25 | - `buildCaptureButton` 26 | - `buildFocusingPoint` 27 | - `buildForegroundBody` 28 | 29 | ### Details 30 | 31 | - `restartDisplayModeDisplayTimer` is renamed to `restartExposureModeDisplayTimer`. 32 | - `buildBackButton` no more requires `BoxConstraints constraints` as an argument, 33 | the signature is `Widget buildBackButton(BuildContext context)` from now on. 34 | - `buildCameraPreview` no more requires `DeviceOrientation orientation` as an argument 35 | since the implementation does not really use it. 36 | It now requires `CameraValue cameraValue` as an argument. So the signature becomes: 37 | ```dart 38 | Widget buildCameraPreview({ 39 | required BuildContext context, 40 | required CameraValue cameraValue, 41 | required BoxConstraints constraints, 42 | }) 43 | ``` 44 | - `buildCaptureButton` now requires `BuildContext context` as an argument. So the signature becomes: 45 | ```dart 46 | Widget buildCaptureButton(BuildContext context, BoxConstraints constraints) 47 | ``` 48 | - `buildFocusingPoint` now adds `int quarterTurns` to make internal quarter turns. 49 | So the signature becomes: 50 | ```dart 51 | Widget buildFocusingPoint({ 52 | required CameraValue cameraValue, 53 | required BoxConstraints constraints, 54 | int quarterTurns = 0, 55 | }) 56 | ``` 57 | - `buildForegroundBody` now adds `DeviceOrientation? deviceOrientation` 58 | to make responsive layouts according to the device orientation. 59 | So the signature becomes: 60 | ```dart 61 | Widget buildForegroundBody( 62 | BuildContext context, 63 | BoxConstraints constraints, 64 | DeviceOrientation? deviceOrientation, 65 | ) 66 | ``` 67 | -------------------------------------------------------------------------------- /lib/src/constants/config.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The FlutterCandies author. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be found 3 | // in the LICENSE file. 4 | 5 | import 'package:camera/camera.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter/services.dart'; 8 | import 'package:photo_manager/photo_manager.dart'; 9 | 10 | import '../delegates/camera_picker_text_delegate.dart'; 11 | import 'type_defs.dart'; 12 | 13 | /// {@template wechat_camera_picker.CameraPickerConfig} 14 | /// Configurations for the [CameraPicker]. 15 | /// [CameraPicker] 的配置项 16 | /// {@endtemplate} 17 | final class CameraPickerConfig { 18 | const CameraPickerConfig({ 19 | this.enableRecording = false, 20 | this.onlyEnableRecording = false, 21 | this.enableTapRecording = false, 22 | this.enableAudio = true, 23 | this.enableSetExposure = true, 24 | this.enableExposureControlOnPoint = true, 25 | this.enablePinchToZoom = true, 26 | this.enablePullToZoomInRecord = true, 27 | this.enableScaledPreview = false, 28 | this.shouldDeletePreviewFile = false, 29 | this.shouldAutoPreviewVideo = true, 30 | this.maximumRecordingDuration = const Duration(seconds: 15), 31 | this.minimumRecordingDuration = const Duration(seconds: 1), 32 | this.theme, 33 | this.textDelegate, 34 | this.cameraQuarterTurns = 0, 35 | this.resolutionPreset = ResolutionPreset.ultraHigh, 36 | this.imageFormatGroup = ImageFormatGroup.unknown, 37 | this.preferredLensDirection = CameraLensDirection.back, 38 | this.preferredFlashMode = FlashMode.off, 39 | this.lockCaptureOrientation, 40 | this.foregroundBuilder, 41 | this.previewTransformBuilder, 42 | this.onEntitySaving, 43 | this.onError, 44 | this.onXFileCaptured, 45 | this.onMinimumRecordDurationNotMet, 46 | this.onPickConfirmed, 47 | this.permissionRequestOption, 48 | }) : assert( 49 | enableRecording == true || onlyEnableRecording != true, 50 | 'Recording mode error.', 51 | ); 52 | 53 | /// Whether the picker can record video. 54 | /// 选择器是否可以录像 55 | final bool enableRecording; 56 | 57 | /// Whether the picker can record video only. 58 | /// 选择器是否只可以录像 59 | final bool onlyEnableRecording; 60 | 61 | /// Whether allow the record can start with single tap. 62 | /// 选择器是否可以单击录像 63 | /// 64 | /// It only works when [onlyEnableRecording] is true. 65 | /// 仅在 [onlyEnableRecording] 为 true 时生效。 66 | final bool enableTapRecording; 67 | 68 | /// Whether the picker should record audio. 69 | /// 选择器录像时是否需要录制声音 70 | final bool enableAudio; 71 | 72 | /// Whether users can set the exposure point by tapping. 73 | /// 用户是否可以在界面上通过点击设定曝光点 74 | final bool enableSetExposure; 75 | 76 | /// Whether users can adjust exposure according to the set point. 77 | /// 用户是否可以根据已经设置的曝光点调节曝光度 78 | final bool enableExposureControlOnPoint; 79 | 80 | /// Whether users can zoom the camera by pinch. 81 | /// 用户是否可以在界面上双指缩放相机对焦 82 | final bool enablePinchToZoom; 83 | 84 | /// Whether users can zoom by pulling up when recording video. 85 | /// 用户是否可以在录制视频时上拉缩放 86 | final bool enablePullToZoomInRecord; 87 | 88 | /// Whether the camera preview should be scaled during captures. 89 | /// 拍摄过程中相机预览是否需要缩放 90 | final bool enableScaledPreview; 91 | 92 | /// {@template wechat_camera_picker.shouldDeletePreviewFile} 93 | /// Whether the preview file will be delete when pop. 94 | /// 返回页面时是否删除预览文件 95 | /// {@endtemplate} 96 | final bool shouldDeletePreviewFile; 97 | 98 | /// {@template wechat_camera_picker.shouldAutoPreviewVideo} 99 | /// Whether the video should be played instantly in the preview. 100 | /// 在预览时是否直接播放视频 101 | /// {@endtemplate} 102 | final bool shouldAutoPreviewVideo; 103 | 104 | /// The maximum duration of the video recording process. 105 | /// 录制视频最长时长 106 | /// 107 | /// Defaults to 15 seconds, allow `null` for unrestricted video recording. 108 | /// 默认为 15 秒,可以使用 `null` 来设置无限制的视频录制 109 | final Duration? maximumRecordingDuration; 110 | 111 | /// The minimum duration of the video recording process. 112 | /// 录制视频最短时长。 113 | /// 114 | /// Defaults to and cannot be lower than 1 second. 115 | /// 默认且不能少于 1 秒。 116 | final Duration minimumRecordingDuration; 117 | 118 | /// Theme data for the picker. 119 | /// 选择器的主题 120 | final ThemeData? theme; 121 | 122 | /// The number of clockwise quarter turns the camera view should be rotated. 123 | /// 摄像机视图顺时针旋转次数,每次90度 124 | final int cameraQuarterTurns; 125 | 126 | /// Text delegate that controls text in widgets. 127 | /// 控制部件中的文字实现 128 | final CameraPickerTextDelegate? textDelegate; 129 | 130 | /// Present resolution for the camera. 131 | /// 相机的分辨率预设 132 | final ResolutionPreset resolutionPreset; 133 | 134 | /// The [ImageFormatGroup] describes the output of the raw image format. 135 | /// 输出图像的格式描述 136 | final ImageFormatGroup imageFormatGroup; 137 | 138 | /// Which lens direction is preferred when first using the camera, 139 | /// typically with the front or the back direction. 140 | /// 首次使用相机时首选的镜头方向,通常是前置或后置。 141 | final CameraLensDirection preferredLensDirection; 142 | 143 | /// {@macro wechat_camera_picker.ForegroundBuilder} 144 | final ForegroundBuilder? foregroundBuilder; 145 | 146 | /// {@macro wechat_camera_picker.PreviewTransformBuilder} 147 | final PreviewTransformBuilder? previewTransformBuilder; 148 | 149 | /// Whether the camera should be locked to the specific orientation 150 | /// during captures. 151 | /// 摄像机在拍摄时锁定的旋转角度 152 | final DeviceOrientation? lockCaptureOrientation; 153 | 154 | /// Which flash mode is preferred when first using the camera, 155 | /// typically with the auto mode. 156 | /// 首次使用相机时首选的闪光灯,通常是自动模式。 157 | final FlashMode preferredFlashMode; 158 | 159 | /// {@macro wechat_camera_picker.EntitySaveCallback} 160 | final EntitySaveCallback? onEntitySaving; 161 | 162 | /// {@macro wechat_camera_picker.CameraErrorHandler} 163 | final CameraErrorHandler? onError; 164 | 165 | /// {@macro wechat_camera_picker.XFileCapturedCallback} 166 | final XFileCapturedCallback? onXFileCaptured; 167 | 168 | /// The callback when the recording is not met the minimum recording duration. 169 | /// 录制时长未达到最小时长时的回调方法。 170 | final VoidCallback? onMinimumRecordDurationNotMet; 171 | 172 | /// The callback when the picture or the video is confirmed as picked. 173 | /// 拍照或录像确认时的回调方法。 174 | final void Function(AssetEntity)? onPickConfirmed; 175 | 176 | /// The permission request option when saving the captured file using 177 | /// the `photo_manager` package. 178 | /// 使用 `photo_manager` 保存拍摄的文件的权限请求配置。 179 | final PermissionRequestOption? permissionRequestOption; 180 | } 181 | -------------------------------------------------------------------------------- /lib/src/constants/enums.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The FlutterCandies author. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be found 3 | // in the LICENSE file. 4 | 5 | /// Two types for the viewer: image and video. 6 | /// 两种预览类型:图片和视频 7 | enum CameraPickerViewType { image, video } 8 | -------------------------------------------------------------------------------- /lib/src/constants/type_defs.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The FlutterCandies author. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be found 3 | // in the LICENSE file. 4 | 5 | import 'dart:async' show FutureOr; 6 | import 'dart:io' show File; 7 | 8 | import 'package:camera/camera.dart' show CameraController, CameraValue, XFile; 9 | import 'package:flutter/widgets.dart' show BuildContext, Widget; 10 | 11 | import 'enums.dart'; 12 | 13 | /// {@template wechat_camera_picker.EntitySaveCallback} 14 | /// The callback type define for saving entity in the viewer. 15 | /// 在查看器中保存图片时的回调 16 | /// 17 | /// ```dart 18 | /// Future _onEntitySaving() async { 19 | /// File? _fileToBeHandle; 20 | /// await CameraPicker.pickFromCamera( 21 | /// context, 22 | /// pickerConfig: CameraPickerConfig( 23 | /// onEntitySaving: ( 24 | /// BuildContext context, 25 | /// CameraPickerViewType viewType, 26 | /// File file, 27 | /// ) { 28 | /// // Pass the file out of the saving method's scope. 29 | /// _fileToBeHandle = file; 30 | /// // Pop twice without any result to exit the picker. 31 | /// Navigator.of(context)..pop()..pop(); 32 | /// }, 33 | /// ), 34 | /// ); 35 | /// // Continue your custom flow here. 36 | /// print(_fileToBeHandle?.path); 37 | /// } 38 | /// ``` 39 | /// 40 | /// ### Notice about the implementation 41 | /// * After the callback is implemented, the default saving method 42 | /// won't called anymore. 43 | /// * Don't call `Navigator.of(context).pop/maybePop` without popping `null` or 44 | /// `AssetEntity`, otherwise there will be a type cast error occurred. 45 | /// 46 | /// ### 在实现时需要注意 47 | /// * 实现该方法后,原本的保存方法不会再被调用; 48 | /// * 不要使用 `Navigator.of(context).pop/maybePop` 返回 `null` 或 `AssetEntity` 49 | /// 以外类型的内容,否则会抛出类型转换异常。 50 | /// {@endtemplate} 51 | typedef EntitySaveCallback = FutureOr Function( 52 | BuildContext context, 53 | CameraPickerViewType viewType, 54 | File file, 55 | ); 56 | 57 | /// {@template wechat_camera_picker.CameraErrorHandler} 58 | /// The error handler when any error occurred during the picking process. 59 | /// 拍摄照片过程中的自定义错误处理 60 | /// {@endtemplate} 61 | typedef CameraErrorHandler = void Function( 62 | Object error, 63 | StackTrace? stackTrace, 64 | ); 65 | 66 | /// {@template wechat_camera_picker.ForegroundBuilder} 67 | /// Build the foreground/overlay widget with the given [CameraValue]. 68 | /// 根据给定的 [CameraValue] 构建自定义的前景 widget 69 | /// 70 | /// The `controller` will be null until initialized. 71 | /// 在 [CameraController] 完成初始化前,`controller` 将为空。 72 | /// {@endtemplate} 73 | typedef ForegroundBuilder = Widget Function( 74 | BuildContext context, 75 | CameraController? controller, 76 | ); 77 | 78 | /// {@template wechat_camera_picker.PreviewTransformBuilder} 79 | /// Build the transformed widget with the given [CameraController]. 80 | /// 根据给定的 [CameraController] 构建自定义的变换 widget 81 | /// {@endtemplate} 82 | typedef PreviewTransformBuilder = Widget? Function( 83 | BuildContext context, 84 | CameraController controller, 85 | Widget child, 86 | ); 87 | 88 | /// {@template wechat_camera_picker.XFileCapturedCallback} 89 | /// The callback type definition when the XFile is captured by the camera. 90 | /// 拍摄文件生成后的回调 91 | /// 92 | /// Return `true` if it has handled arguments. 93 | /// 如果在回调中已经进行了处理,请返回 `true`。 94 | /// 95 | /// ```dart 96 | /// Future _onXFileCaptured() async { 97 | /// XFile? _fileToBeHandle; 98 | /// await CameraPicker.pickFromCamera( 99 | /// context, 100 | /// pickerConfig: CameraPickerConfig( 101 | /// onXFileCaptured: (XFile file, CameraPickerViewType viewType) { 102 | /// // Pass the file out of the saving method's scope. 103 | /// _fileToBeHandle = file; 104 | /// // Pop twice without any result to exit the picker. 105 | /// Navigator.of(context).pop(); 106 | /// }, 107 | /// ), 108 | /// ); 109 | /// // Continue your custom flow here. 110 | /// print(_fileToBeHandle?.path); 111 | /// } 112 | /// ``` 113 | /// 114 | /// ### Notice about the implementation 115 | /// * After the callback is implemented and returned `true`, 116 | /// the default viewer page will not be presented anymore. 117 | /// 118 | /// ### 在实现时需要注意 119 | /// * 实现了该方法且返回 `true` 后,默认的预览页面不会再出现。 120 | /// {@endtemplate} 121 | typedef XFileCapturedCallback = bool Function(XFile, CameraPickerViewType); 122 | -------------------------------------------------------------------------------- /lib/src/delegates/camera_picker_text_delegate.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The FlutterCandies author. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be found 3 | // in the LICENSE file. 4 | 5 | import 'package:camera/camera.dart' show CameraLensDirection, FlashMode; 6 | import 'package:flutter/rendering.dart'; 7 | 8 | /// All text delegates. 9 | const List cameraPickerTextDelegates = 10 | [ 11 | CameraPickerTextDelegate(), 12 | EnglishCameraPickerTextDelegate(), 13 | VietnameseCameraPickerTextDelegate(), 14 | ]; 15 | 16 | /// Obtain the text delegate from the given locale. 17 | CameraPickerTextDelegate cameraPickerTextDelegateFromLocale(Locale? locale) { 18 | if (locale == null) { 19 | return const CameraPickerTextDelegate(); 20 | } 21 | final String languageCode = locale.languageCode.toLowerCase(); 22 | for (final CameraPickerTextDelegate delegate in cameraPickerTextDelegates) { 23 | if (delegate.languageCode == languageCode) { 24 | return delegate; 25 | } 26 | } 27 | return const CameraPickerTextDelegate(); 28 | } 29 | 30 | /// Text delegate implemented with Chinese. 31 | /// 中文文字实现 32 | class CameraPickerTextDelegate { 33 | const CameraPickerTextDelegate(); 34 | 35 | String get languageCode => 'zh'; 36 | 37 | /// Confirm string for the confirm button. 38 | /// 确认按钮的字段 39 | String get confirm => '确认'; 40 | 41 | /// Tips above the shooting button before shooting. 42 | /// 拍摄前确认按钮上方的提示文字 43 | String get shootingTips => '轻触拍照'; 44 | 45 | /// Tips with recording above the shooting button before shooting. 46 | /// 拍摄前确认按钮上方的提示文字(带录像) 47 | String get shootingWithRecordingTips => '轻触拍照,长按摄像'; 48 | 49 | /// Tips with only recording above the shooting button before shooting. 50 | /// 拍摄前确认按钮上方的提示文字(仅录像) 51 | String get shootingOnlyRecordingTips => '长按摄像'; 52 | 53 | /// Tips with tap recording above the shooting button before shooting. 54 | /// 拍摄前确认按钮上方的提示文字(点击录像) 55 | String get shootingTapRecordingTips => '轻触摄像'; 56 | 57 | /// Load failed string for item. 58 | /// 资源加载失败时的字段 59 | String get loadFailed => '加载失败'; 60 | 61 | /// Default loading string for the dialog. 62 | /// 加载中弹窗的默认文字 63 | String get loading => '加载中…'; 64 | 65 | /// Saving string for the dialog. 66 | /// 保存中弹窗的默认文字 67 | String get saving => '保存中…'; 68 | 69 | /// Semantics fields. 70 | /// 71 | /// Fields below are only for semantics usage. For customizable these fields, 72 | /// head over to [EnglishCameraPickerTextDelegate] for better understanding. 73 | String get sActionManuallyFocusHint => '手动聚焦'; 74 | 75 | String get sActionPreviewHint => '预览'; 76 | 77 | String get sActionRecordHint => '录像'; 78 | 79 | String get sActionShootHint => '拍照'; 80 | 81 | String get sActionShootingButtonTooltip => '拍照按钮'; 82 | 83 | String get sActionStopRecordingHint => '停止录像'; 84 | 85 | String sCameraLensDirectionLabel(CameraLensDirection value) { 86 | switch (value) { 87 | case CameraLensDirection.front: 88 | return '前置'; 89 | case CameraLensDirection.back: 90 | return '后置'; 91 | case CameraLensDirection.external: 92 | return '外置'; 93 | } 94 | } 95 | 96 | String? sCameraPreviewLabel(CameraLensDirection? value) { 97 | if (value == null) { 98 | return null; 99 | } 100 | return '${sCameraLensDirectionLabel(value)}画面预览'; 101 | } 102 | 103 | String sFlashModeLabel(FlashMode mode) { 104 | final String modeString; 105 | switch (mode) { 106 | case FlashMode.off: 107 | modeString = '关闭'; 108 | break; 109 | case FlashMode.auto: 110 | modeString = '自动'; 111 | break; 112 | case FlashMode.always: 113 | modeString = '拍照时闪光'; 114 | break; 115 | case FlashMode.torch: 116 | modeString = '始终闪光'; 117 | break; 118 | } 119 | return '闪光模式: $modeString'; 120 | } 121 | 122 | String sSwitchCameraLensDirectionLabel(CameraLensDirection value) { 123 | return '切换至${sCameraLensDirectionLabel(value)}摄像头'; 124 | } 125 | } 126 | 127 | /// Text delegate implements with English. 128 | class EnglishCameraPickerTextDelegate extends CameraPickerTextDelegate { 129 | const EnglishCameraPickerTextDelegate(); 130 | 131 | @override 132 | String get languageCode => 'en'; 133 | 134 | @override 135 | String get confirm => 'Confirm'; 136 | 137 | @override 138 | String get shootingTips => 'Tap to take photo.'; 139 | 140 | @override 141 | String get shootingWithRecordingTips => 142 | 'Tap to take photo. Long press to record video.'; 143 | 144 | @override 145 | String get shootingOnlyRecordingTips => 'Long press to record video.'; 146 | 147 | @override 148 | String get shootingTapRecordingTips => 'Tap to record video.'; 149 | 150 | @override 151 | String get loadFailed => 'Load failed'; 152 | 153 | @override 154 | String get loading => 'Loading...'; 155 | 156 | @override 157 | String get saving => 'Saving...'; 158 | 159 | @override 160 | String get sActionManuallyFocusHint => 'manually focus'; 161 | 162 | @override 163 | String get sActionPreviewHint => 'preview'; 164 | 165 | @override 166 | String get sActionRecordHint => 'record'; 167 | 168 | @override 169 | String get sActionShootHint => 'take picture'; 170 | 171 | @override 172 | String get sActionShootingButtonTooltip => 'shooting button'; 173 | 174 | @override 175 | String get sActionStopRecordingHint => 'stop recording'; 176 | 177 | @override 178 | String sCameraLensDirectionLabel(CameraLensDirection value) => value.name; 179 | 180 | @override 181 | String? sCameraPreviewLabel(CameraLensDirection? value) { 182 | if (value == null) { 183 | return null; 184 | } 185 | return '${sCameraLensDirectionLabel(value)} camera preview'; 186 | } 187 | 188 | @override 189 | String sFlashModeLabel(FlashMode mode) => 'Flash mode: ${mode.name}'; 190 | 191 | @override 192 | String sSwitchCameraLensDirectionLabel(CameraLensDirection value) => 193 | 'Switch to the ${sCameraLensDirectionLabel(value)} camera'; 194 | } 195 | 196 | /// Text delegate implemented with Vietnamese. 197 | /// Dịch tiếng Việt 198 | class VietnameseCameraPickerTextDelegate extends CameraPickerTextDelegate { 199 | const VietnameseCameraPickerTextDelegate(); 200 | 201 | @override 202 | String get languageCode => 'vi'; 203 | 204 | @override 205 | String get confirm => 'Xác nhận'; 206 | 207 | @override 208 | String get shootingTips => 'Chạm để chụp ảnh.'; 209 | 210 | @override 211 | String get shootingWithRecordingTips => 212 | 'Chạm để chụp ảnh. Giữ để quay video.'; 213 | 214 | @override 215 | String get shootingOnlyRecordingTips => 'Giữ để quay video.'; 216 | 217 | @override 218 | String get shootingTapRecordingTips => 'Chạm để quay video.'; 219 | 220 | @override 221 | String get loadFailed => 'Tải thất bại'; 222 | 223 | @override 224 | String get loading => 'Đang tải...'; 225 | 226 | @override 227 | String get saving => 'Đang lưu...'; 228 | 229 | @override 230 | String get sActionManuallyFocusHint => 'lấy nét bằng tay'; 231 | 232 | @override 233 | String get sActionPreviewHint => 'xem trước'; 234 | 235 | @override 236 | String get sActionRecordHint => 'quay'; 237 | 238 | @override 239 | String get sActionShootHint => 'chụp'; 240 | 241 | @override 242 | String get sActionShootingButtonTooltip => 'nút chụp'; 243 | 244 | @override 245 | String get sActionStopRecordingHint => 'dừng quay'; 246 | 247 | @override 248 | String sCameraLensDirectionLabel(CameraLensDirection value) { 249 | switch (value) { 250 | case CameraLensDirection.front: 251 | return 'trước'; 252 | case CameraLensDirection.back: 253 | return 'sau'; 254 | case CameraLensDirection.external: 255 | return 'ngoài'; 256 | } 257 | } 258 | 259 | @override 260 | String? sCameraPreviewLabel(CameraLensDirection? value) { 261 | if (value == null) { 262 | return null; 263 | } 264 | return 'Xem trước camera ${sCameraLensDirectionLabel(value)}'; 265 | } 266 | 267 | @override 268 | String sFlashModeLabel(FlashMode mode) { 269 | final String modeString; 270 | switch (mode) { 271 | case FlashMode.off: 272 | modeString = 'Tắt'; 273 | break; 274 | case FlashMode.auto: 275 | modeString = 'Tự động'; 276 | break; 277 | case FlashMode.always: 278 | modeString = 'Luôn bật đèn flash khi chụp ảnh'; 279 | break; 280 | case FlashMode.torch: 281 | modeString = 'Luôn bật đèn flash'; 282 | break; 283 | } 284 | return 'Chế độ đèn flash: $modeString'; 285 | } 286 | 287 | @override 288 | String sSwitchCameraLensDirectionLabel(CameraLensDirection value) => 289 | 'Chuyển sang camera ${sCameraLensDirectionLabel(value)}'; 290 | } 291 | -------------------------------------------------------------------------------- /lib/src/internals/methods.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The FlutterCandies author. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be found 3 | // in the LICENSE file. 4 | 5 | import 'dart:developer'; 6 | 7 | import 'package:flutter/foundation.dart'; 8 | 9 | import '../constants/type_defs.dart'; 10 | 11 | /// Log only in debug mode. 12 | /// 只在调试模式打印 13 | void realDebugPrint(dynamic message) { 14 | if (!kReleaseMode) { 15 | log('$message', name: 'CameraPicker - LOG'); 16 | } 17 | } 18 | 19 | void handleErrorWithHandler( 20 | Object e, 21 | StackTrace s, 22 | CameraErrorHandler? handler, 23 | ) { 24 | if (handler != null) { 25 | handler(e, s); 26 | return; 27 | } 28 | Error.throwWithStackTrace(e, s); 29 | } 30 | 31 | T? ambiguate(T value) => value; 32 | -------------------------------------------------------------------------------- /lib/src/internals/singleton.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The FlutterCandies author. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be found 3 | // in the LICENSE file. 4 | 5 | import '../delegates/camera_picker_text_delegate.dart'; 6 | 7 | export 'package:photo_manager/photo_manager.dart'; 8 | 9 | final class Singleton { 10 | const Singleton._(); 11 | 12 | static CameraPickerTextDelegate textDelegate = 13 | const CameraPickerTextDelegate(); 14 | } 15 | -------------------------------------------------------------------------------- /lib/src/states/camera_picker_viewer_state.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The FlutterCandies author. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be found 3 | // in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:io'; 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter/semantics.dart'; 10 | import 'package:path/path.dart' as path; 11 | import 'package:video_player/video_player.dart'; 12 | import 'package:wechat_picker_library/wechat_picker_library.dart'; 13 | 14 | import '../constants/config.dart'; 15 | import '../internals/singleton.dart'; 16 | import '../constants/enums.dart'; 17 | import '../constants/type_defs.dart'; 18 | import '../internals/methods.dart'; 19 | import '../widgets/camera_picker.dart'; 20 | import '../widgets/camera_picker_viewer.dart'; 21 | 22 | class CameraPickerViewerState extends State { 23 | CameraPickerConfig get pickerConfig => widget.pickerConfig; 24 | 25 | /// Whether the player is playing. 26 | /// 播放器是否在播放 27 | final isPlaying = ValueNotifier(false); 28 | 29 | late final theme = 30 | pickerConfig.theme ?? CameraPicker.themeData(defaultThemeColorWeChat); 31 | 32 | /// Construct an [File] instance through [previewXFile]. 33 | /// 通过 [previewXFile] 构建 [File] 实例。 34 | late final previewFile = File(widget.previewXFile.path); 35 | 36 | /// Controller for the video player. 37 | /// 视频播放的控制器 38 | late final videoController = VideoPlayerController.file(previewFile); 39 | 40 | /// Whether the controller is playing. 41 | /// 播放控制器是否在播放 42 | bool get isControllerPlaying => videoController.value.isPlaying; 43 | 44 | /// Whether the controller has initialized. 45 | /// 控制器是否已初始化 46 | late bool hasLoaded = widget.viewType == CameraPickerViewType.image; 47 | 48 | /// Whether there's any error when initialize the video controller. 49 | /// 初始化视频控制器时是否发生错误 50 | bool hasErrorWhenInitializing = false; 51 | 52 | /// Whether the saving process is ongoing. 53 | bool isSavingEntity = false; 54 | 55 | CameraErrorHandler? get onError => pickerConfig.onError; 56 | 57 | @override 58 | void initState() { 59 | super.initState(); 60 | if (widget.viewType == CameraPickerViewType.video) { 61 | initializeVideoPlayerController(); 62 | } 63 | } 64 | 65 | @override 66 | void dispose() { 67 | videoController 68 | ..removeListener(videoControllerListener) 69 | ..pause() 70 | ..dispose(); 71 | super.dispose(); 72 | } 73 | 74 | Future initializeVideoPlayerController() async { 75 | try { 76 | await videoController.initialize(); 77 | videoController.addListener(videoControllerListener); 78 | hasLoaded = true; 79 | if (pickerConfig.shouldAutoPreviewVideo) { 80 | videoController.play(); 81 | videoController.setLooping(true); 82 | } 83 | } catch (e, s) { 84 | hasErrorWhenInitializing = true; 85 | realDebugPrint('Error when initializing video controller: $e'); 86 | handleErrorWithHandler(e, s, onError); 87 | } finally { 88 | safeSetState(() {}); 89 | } 90 | } 91 | 92 | /// Listener for the video player. 93 | /// 播放器的监听方法 94 | void videoControllerListener() { 95 | if (isControllerPlaying != isPlaying.value) { 96 | isPlaying.value = isControllerPlaying; 97 | } 98 | } 99 | 100 | /// Callback for the play button. 101 | /// 播放按钮的回调 102 | /// 103 | /// Normally it only switches play state for the player. If the video reaches 104 | /// the end, then click the button will make the video replay. 105 | /// 一般来说按钮只切换播放暂停。当视频播放结束时,点击按钮将从头开始播放。 106 | Future playButtonCallback() async { 107 | try { 108 | if (isPlaying.value) { 109 | videoController.pause(); 110 | } else { 111 | if (videoController.value.duration == videoController.value.position) { 112 | videoController.seekTo(Duration.zero); 113 | } 114 | videoController 115 | ..play() 116 | ..setLooping(true); 117 | } 118 | } catch (e, s) { 119 | handleErrorWithHandler(e, s, onError); 120 | } 121 | } 122 | 123 | /// If [CameraPickerConfig.shouldDeletePreviewFile] is true, the preview file 124 | /// will be deleted after unused. 125 | /// 126 | /// [CameraPickerConfig.onEntitySaving] will reference the file, we don't want 127 | /// the file to be deleted in this case too. 128 | void deletePreviewFileIfConfigured() { 129 | if (pickerConfig.shouldDeletePreviewFile && 130 | pickerConfig.onEntitySaving != null && 131 | previewFile.existsSync()) { 132 | previewFile.delete().catchError((e, s) { 133 | handleErrorWithHandler(e, s, onError); 134 | return previewFile; 135 | }); 136 | } 137 | } 138 | 139 | /// When users confirm to use the taken file, create the [AssetEntity]. 140 | /// While the entity might returned null, there's no side effects if popping `null` 141 | /// because the parent picker will ignore it. 142 | Future createAssetEntityAndPop() async { 143 | if (isSavingEntity) { 144 | return; 145 | } 146 | setState(() { 147 | isSavingEntity = true; 148 | }); 149 | 150 | // Handle the explicitly entity saving method. 151 | if (pickerConfig.onEntitySaving != null) { 152 | try { 153 | await pickerConfig.onEntitySaving!( 154 | context, 155 | widget.viewType, 156 | previewFile, 157 | ); 158 | } catch (e, s) { 159 | handleErrorWithHandler(e, s, onError); 160 | } finally { 161 | safeSetState(() { 162 | isSavingEntity = false; 163 | }); 164 | } 165 | return; 166 | } 167 | 168 | AssetEntity? entity; 169 | try { 170 | final ps = await PhotoManager.requestPermissionExtend( 171 | requestOption: pickerConfig.permissionRequestOption ?? 172 | PermissionRequestOption( 173 | iosAccessLevel: IosAccessLevel.addOnly, 174 | androidPermission: AndroidPermission( 175 | type: switch (( 176 | pickerConfig.enableRecording, 177 | pickerConfig.enableTapRecording 178 | )) { 179 | (true, false) => RequestType.common, 180 | (true, true) => RequestType.video, 181 | (false, _) => RequestType.image, 182 | }, 183 | mediaLocation: false, 184 | ), 185 | ), 186 | ); 187 | if (ps == PermissionState.authorized || ps == PermissionState.limited) { 188 | final filePath = previewFile.path; 189 | switch (widget.viewType) { 190 | case CameraPickerViewType.image: 191 | entity = await PhotoManager.editor.saveImageWithPath( 192 | filePath, 193 | title: path.basename(filePath), 194 | ); 195 | break; 196 | case CameraPickerViewType.video: 197 | entity = await PhotoManager.editor.saveVideo( 198 | previewFile, 199 | title: path.basename(filePath), 200 | ); 201 | break; 202 | } 203 | deletePreviewFileIfConfigured(); 204 | return; 205 | } 206 | handleErrorWithHandler( 207 | StateError( 208 | 'Permission is not fully granted to save the captured file.', 209 | ), 210 | StackTrace.current, 211 | onError, 212 | ); 213 | } catch (e, s) { 214 | realDebugPrint('Saving entity failed: $e'); 215 | handleErrorWithHandler(e, s, onError); 216 | } finally { 217 | safeSetState(() { 218 | isSavingEntity = false; 219 | }); 220 | if (mounted) { 221 | Navigator.of(context).pop(entity); 222 | } 223 | } 224 | } 225 | 226 | /// The back button for the preview section. 227 | /// 预览区的返回按钮 228 | Widget buildBackButton(BuildContext context) { 229 | return Semantics( 230 | sortKey: const OrdinalSortKey(0), 231 | child: IconButton( 232 | onPressed: () { 233 | if (isSavingEntity) { 234 | return; 235 | } 236 | Navigator.of(context).pop(); 237 | }, 238 | padding: EdgeInsets.zero, 239 | constraints: BoxConstraints.tight(const Size.square(28)), 240 | tooltip: MaterialLocalizations.of(context).backButtonTooltip, 241 | iconSize: 18, 242 | icon: Container( 243 | padding: const EdgeInsets.all(5), 244 | decoration: const BoxDecoration( 245 | color: Colors.white, 246 | shape: BoxShape.circle, 247 | ), 248 | child: const Icon( 249 | Icons.keyboard_return_rounded, 250 | color: Colors.black, 251 | ), 252 | ), 253 | ), 254 | ); 255 | } 256 | 257 | Widget buildPreview(BuildContext context) { 258 | final Widget builder; 259 | if (widget.viewType == CameraPickerViewType.video) { 260 | builder = Stack( 261 | children: [ 262 | Center( 263 | child: AspectRatio( 264 | aspectRatio: videoController.value.aspectRatio, 265 | child: VideoPlayer(videoController), 266 | ), 267 | ), 268 | buildPlayControlButton(context), 269 | ], 270 | ); 271 | } else { 272 | builder = Image.file(previewFile); 273 | } 274 | return MergeSemantics( 275 | child: Semantics( 276 | label: Singleton.textDelegate.sActionPreviewHint, 277 | image: true, 278 | onTapHint: Singleton.textDelegate.sActionPreviewHint, 279 | sortKey: const OrdinalSortKey(1), 280 | child: builder, 281 | ), 282 | ); 283 | } 284 | 285 | /// The confirm button for the preview section. 286 | /// 预览区的确认按钮 287 | Widget buildConfirmButton(BuildContext context) { 288 | return MaterialButton( 289 | minWidth: 20, 290 | height: 32, 291 | padding: const EdgeInsets.symmetric(horizontal: 20), 292 | color: Theme.of(context).colorScheme.secondary, 293 | shape: RoundedRectangleBorder( 294 | borderRadius: BorderRadius.circular(3), 295 | ), 296 | onPressed: createAssetEntityAndPop, 297 | materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, 298 | child: Text( 299 | Singleton.textDelegate.confirm, 300 | style: TextStyle( 301 | color: Theme.of(context).textTheme.bodyLarge?.color, 302 | fontSize: 17, 303 | fontWeight: FontWeight.normal, 304 | ), 305 | ), 306 | ); 307 | } 308 | 309 | /// A play control button the video playing process. 310 | /// 控制视频播放的按钮 311 | Widget buildPlayControlButton(BuildContext context) { 312 | return ValueListenableBuilder( 313 | valueListenable: isPlaying, 314 | builder: (_, bool value, Widget? child) => GestureDetector( 315 | behavior: HitTestBehavior.opaque, 316 | onTap: value ? playButtonCallback : null, 317 | child: Center( 318 | child: AnimatedOpacity( 319 | duration: kThemeAnimationDuration, 320 | opacity: value ? 0 : 1, 321 | child: GestureDetector( 322 | onTap: playButtonCallback, 323 | child: DecoratedBox( 324 | decoration: const BoxDecoration( 325 | boxShadow: [BoxShadow(color: Colors.black12)], 326 | shape: BoxShape.circle, 327 | ), 328 | child: Icon( 329 | value ? Icons.pause_circle_outline : Icons.play_circle_filled, 330 | size: 70, 331 | color: Colors.white, 332 | ), 333 | ), 334 | ), 335 | ), 336 | ), 337 | ), 338 | ); 339 | } 340 | 341 | /// Actions section for the viewer. Including 'back' and 'confirm' button. 342 | /// 预览的操作区。包括"返回"和"确定"按钮。 343 | Widget buildForeground(BuildContext context) { 344 | return SafeArea( 345 | child: Padding( 346 | padding: const EdgeInsetsDirectional.only( 347 | start: 12.0, 348 | end: 12.0, 349 | bottom: 12.0, 350 | ), 351 | child: Column( 352 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 353 | children: [ 354 | Semantics( 355 | sortKey: const OrdinalSortKey(0), 356 | child: Align( 357 | alignment: AlignmentDirectional.centerStart, 358 | child: buildBackButton(context), 359 | ), 360 | ), 361 | Semantics( 362 | sortKey: const OrdinalSortKey(2), 363 | child: Align( 364 | alignment: AlignmentDirectional.centerEnd, 365 | child: buildConfirmButton(context), 366 | ), 367 | ), 368 | ], 369 | ), 370 | ), 371 | ); 372 | } 373 | 374 | Widget buildLoading(BuildContext context) { 375 | return IgnorePointer( 376 | child: AnimatedOpacity( 377 | duration: kThemeAnimationDuration, 378 | opacity: isSavingEntity ? 1 : 0, 379 | child: LoadingIndicator(tip: Singleton.textDelegate.saving), 380 | ), 381 | ); 382 | } 383 | 384 | @override 385 | Widget build(BuildContext context) { 386 | if (hasErrorWhenInitializing) { 387 | return Center( 388 | child: Text( 389 | Singleton.textDelegate.loadFailed, 390 | style: const TextStyle(inherit: false), 391 | ), 392 | ); 393 | } 394 | if (!hasLoaded) { 395 | return const SizedBox.shrink(); 396 | } 397 | return PopScope( 398 | canPop: true, 399 | // ignore: deprecated_member_use 400 | onPopInvoked: (didPop) { 401 | if (didPop) { 402 | deletePreviewFileIfConfigured(); 403 | } 404 | }, 405 | child: Theme( 406 | data: theme, 407 | child: Builder( 408 | builder: (context) => Material( 409 | color: Colors.black, 410 | child: Stack( 411 | fit: StackFit.expand, 412 | children: [ 413 | buildPreview(context), 414 | buildForeground(context), 415 | if (isSavingEntity) buildLoading(context), 416 | ], 417 | ), 418 | ), 419 | ), 420 | ), 421 | ); 422 | } 423 | } 424 | -------------------------------------------------------------------------------- /lib/src/widgets/camera_focus_point.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The FlutterCandies author. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be found 3 | // in the LICENSE file. 4 | 5 | import 'package:flutter/material.dart'; 6 | import 'package:wechat_picker_library/wechat_picker_library.dart'; 7 | 8 | final class CameraFocusPoint extends StatelessWidget { 9 | const CameraFocusPoint({ 10 | super.key, 11 | required this.size, 12 | required this.color, 13 | }); 14 | 15 | final double size; 16 | final Color? color; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return TweenAnimationBuilder2( 21 | firstTween: Tween(begin: 0, end: 1), 22 | secondTween: Tween(begin: 1.5, end: 1), 23 | secondTweenCurve: Curves.easeOutBack, 24 | secondTweenDuration: const Duration(milliseconds: 400), 25 | builder: (_, double opacity, double scale) => Opacity( 26 | opacity: opacity, 27 | child: Transform.scale( 28 | scale: scale, 29 | child: SizedBox.fromSize( 30 | size: Size.square(size), 31 | child: CustomPaint( 32 | painter: CameraFocusPointPainter(size: size, color: color), 33 | ), 34 | ), 35 | ), 36 | ), 37 | ); 38 | } 39 | } 40 | 41 | /// A [CustomPaint] that draws the exposure point with four arcs and one circle. 42 | /// 包含了四条弧及一个圆的曝光点绘制。 43 | final class CameraFocusPointPainter extends CustomPainter { 44 | const CameraFocusPointPainter({ 45 | required this.size, 46 | required this.color, 47 | this.radius = 2, 48 | this.strokeWidth = 2, 49 | }) : assert(size > 0); 50 | 51 | final double size; 52 | final double radius; 53 | final double strokeWidth; 54 | final Color? color; 55 | 56 | Radius get _circularRadius => Radius.circular(radius); 57 | 58 | @override 59 | void paint(Canvas canvas, Size size) { 60 | final Size dividedSize = size / 3; 61 | final double lineLength = dividedSize.width - radius; 62 | final Paint paint = Paint() 63 | ..style = PaintingStyle.stroke 64 | ..strokeWidth = strokeWidth; 65 | if (color != null) { 66 | paint.color = color!; 67 | } 68 | 69 | final Path path = Path() 70 | // Move to the start of the arc-line group at the left-top. 71 | ..moveTo(0, dividedSize.height) 72 | // Draw arc-line group from the left-top. 73 | ..relativeLineTo(0, -lineLength) 74 | ..relativeArcToPoint(Offset(radius, -radius), radius: _circularRadius) 75 | ..relativeLineTo(lineLength, 0) 76 | // Move to the start of the arc-line group at the right-top. 77 | ..relativeMoveTo(dividedSize.width, 0) 78 | // Draw arc-line group from the right-top. 79 | ..relativeLineTo(lineLength, 0) 80 | ..relativeArcToPoint(Offset(radius, radius), radius: _circularRadius) 81 | ..relativeLineTo(0, lineLength) 82 | // Move to the start of the arc-line group at the right-bottom. 83 | ..relativeMoveTo(0, dividedSize.height) 84 | // Draw arc-line group from the right-bottom. 85 | ..relativeLineTo(0, lineLength) 86 | ..relativeArcToPoint(Offset(-radius, radius), radius: _circularRadius) 87 | ..relativeLineTo(-lineLength, 0) 88 | // Move to the start of the arc-line group at the left-bottom. 89 | ..relativeMoveTo(-dividedSize.width, 0) 90 | // Draw arc-line group from the left-bottom. 91 | ..relativeLineTo(-lineLength, 0) 92 | ..relativeArcToPoint(Offset(-radius, -radius), radius: _circularRadius) 93 | ..relativeLineTo(0, -lineLength) 94 | // Move to the start of the arc-line group at the left-top. 95 | ..relativeMoveTo(0, -dividedSize.height) 96 | ..close(); 97 | canvas 98 | ..drawPath(path, paint) 99 | // Draw the center circle. 100 | ..drawCircle( 101 | Offset(size.width / 2, size.height / 2), 102 | dividedSize.width / 2, 103 | paint, 104 | ); 105 | } 106 | 107 | @override 108 | bool shouldRepaint(CameraFocusPointPainter oldDelegate) { 109 | return oldDelegate.size != size || 110 | oldDelegate.radius != radius || 111 | oldDelegate.strokeWidth != strokeWidth || 112 | oldDelegate.color != color; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /lib/src/widgets/camera_picker.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The FlutterCandies author. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be found 3 | // in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:camera/camera.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter/services.dart'; 10 | 11 | import '../constants/config.dart'; 12 | import '../internals/singleton.dart'; 13 | import '../states/camera_picker_state.dart'; 14 | 15 | import 'camera_picker_page_route.dart'; 16 | 17 | /// The camera picker widget. 18 | /// 拍照选择器。 19 | /// 20 | /// The picker provides create an [AssetEntity] through the [CameraController]. 21 | /// 该选择器可以通过 [CameraController] 创建 [AssetEntity]。 22 | class CameraPicker extends StatefulWidget { 23 | const CameraPicker({ 24 | super.key, 25 | this.pickerConfig = const CameraPickerConfig(), 26 | this.createPickerState, 27 | this.locale, 28 | }); 29 | 30 | /// {@macro wechat_camera_picker.CameraPickerConfig} 31 | final CameraPickerConfig pickerConfig; 32 | 33 | /// Creates a customized [CameraPickerState]. 34 | /// 构建一个自定义的 [CameraPickerState]。 35 | final CameraPickerState Function()? createPickerState; 36 | 37 | /// The [Locale] to determine text delegates for the picker. 38 | final Locale? locale; 39 | 40 | /// Static method to create [AssetEntity] through camera. 41 | /// 通过相机创建 [AssetEntity] 的静态方法 42 | static Future pickFromCamera( 43 | BuildContext context, { 44 | CameraPickerConfig pickerConfig = const CameraPickerConfig(), 45 | CameraPickerState Function()? createPickerState, 46 | bool useRootNavigator = true, 47 | CameraPickerPageRoute Function(Widget picker)? 48 | pageRouteBuilder, 49 | Locale? locale, 50 | }) { 51 | final Widget picker = CameraPicker( 52 | pickerConfig: pickerConfig, 53 | createPickerState: createPickerState, 54 | locale: locale, 55 | ); 56 | return Navigator.of( 57 | context, 58 | rootNavigator: useRootNavigator, 59 | ).push( 60 | pageRouteBuilder?.call(picker) ?? 61 | CameraPickerPageRoute(builder: (_) => picker), 62 | ); 63 | } 64 | 65 | /// Build a dark theme according to the theme color. 66 | /// 通过主题色构建一个默认的暗黑主题 67 | static ThemeData themeData(Color themeColor) { 68 | return ThemeData.dark().copyWith( 69 | primaryColor: Colors.grey[900], 70 | primaryColorLight: Colors.grey[900], 71 | primaryColorDark: Colors.grey[900], 72 | canvasColor: Colors.grey[850], 73 | scaffoldBackgroundColor: Colors.grey[900], 74 | cardColor: Colors.grey[900], 75 | highlightColor: Colors.transparent, 76 | textSelectionTheme: TextSelectionThemeData( 77 | cursorColor: themeColor, 78 | selectionColor: themeColor.withAlpha(100), 79 | selectionHandleColor: themeColor, 80 | ), 81 | // ignore: deprecated_member_use 82 | indicatorColor: themeColor, 83 | appBarTheme: const AppBarTheme( 84 | systemOverlayStyle: SystemUiOverlayStyle( 85 | statusBarBrightness: Brightness.dark, 86 | statusBarIconBrightness: Brightness.light, 87 | ), 88 | elevation: 0, 89 | ), 90 | buttonTheme: ButtonThemeData(buttonColor: themeColor), 91 | colorScheme: ColorScheme( 92 | primary: Colors.grey[900]!, 93 | primaryContainer: Colors.grey[900], 94 | secondary: themeColor, 95 | secondaryContainer: themeColor, 96 | // ignore: deprecated_member_use 97 | background: Colors.grey[900]!, 98 | surface: Colors.grey[900]!, 99 | brightness: Brightness.dark, 100 | error: const Color(0xffcf6679), 101 | onPrimary: Colors.black, 102 | onSecondary: Colors.black, 103 | onSurface: Colors.white, 104 | // ignore: deprecated_member_use 105 | onBackground: Colors.white, 106 | onError: Colors.black, 107 | ), 108 | ); 109 | } 110 | 111 | @override 112 | CameraPickerState createState() => 113 | // ignore: no_logic_in_create_state 114 | createPickerState?.call() ?? CameraPickerState(); 115 | } 116 | -------------------------------------------------------------------------------- /lib/src/widgets/camera_picker_page_route.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The FlutterCandies author. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be found 3 | // in the LICENSE file. 4 | 5 | import 'package:flutter/material.dart'; 6 | 7 | /// Built a slide page transition for the picker. 8 | /// 为选择器构造一个上下进出的页面过渡动画 9 | class CameraPickerPageRoute extends PageRoute { 10 | CameraPickerPageRoute({ 11 | required this.builder, 12 | this.transitionCurve = Curves.easeIn, 13 | this.transitionDuration = const Duration(milliseconds: 300), 14 | this.barrierColor, 15 | this.barrierDismissible = false, 16 | this.barrierLabel, 17 | this.maintainState = true, 18 | this.opaque = true, 19 | this.canTransitionFromPredicate, 20 | super.fullscreenDialog, 21 | super.settings, 22 | }); 23 | 24 | final WidgetBuilder builder; 25 | 26 | final Curve transitionCurve; 27 | @override 28 | final Duration transitionDuration; 29 | 30 | @override 31 | final Color? barrierColor; 32 | @override 33 | final bool barrierDismissible; 34 | @override 35 | final String? barrierLabel; 36 | @override 37 | final bool opaque; 38 | @override 39 | final bool maintainState; 40 | 41 | final bool Function(TransitionRoute)? canTransitionFromPredicate; 42 | 43 | @override 44 | bool canTransitionFrom(TransitionRoute previousRoute) => 45 | canTransitionFromPredicate?.call(previousRoute) ?? false; 46 | 47 | @override 48 | Widget buildPage( 49 | BuildContext context, 50 | Animation animation, 51 | Animation secondaryAnimation, 52 | ) { 53 | return builder(context); 54 | } 55 | 56 | @override 57 | Widget buildTransitions( 58 | BuildContext context, 59 | Animation animation, 60 | Animation secondaryAnimation, 61 | Widget child, 62 | ) { 63 | return SlideTransition( 64 | position: Tween( 65 | begin: const Offset(0, 1), 66 | end: Offset.zero, 67 | ).animate( 68 | CurvedAnimation(curve: transitionCurve, parent: animation), 69 | ), 70 | child: child, 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/src/widgets/camera_picker_viewer.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The FlutterCandies author. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be found 3 | // in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:camera/camera.dart'; 8 | import 'package:flutter/material.dart'; 9 | 10 | import '../constants/config.dart'; 11 | import '../internals/singleton.dart'; 12 | import '../constants/enums.dart'; 13 | import '../states/camera_picker_viewer_state.dart'; 14 | 15 | class CameraPickerViewer extends StatefulWidget { 16 | const CameraPickerViewer._({ 17 | // ignore: unused_element 18 | super.key, 19 | required this.viewType, 20 | required this.previewXFile, 21 | required this.pickerConfig, 22 | this.createViewerState, 23 | }); 24 | 25 | /// The type of the viewer. (Image | Video) 26 | /// 预览的类型(图片或视频) 27 | final CameraPickerViewType viewType; 28 | 29 | /// The [XFile] of the preview file. 30 | /// 预览文件的 [XFile] 实例 31 | final XFile previewXFile; 32 | 33 | /// {@macro wechat_camera_picker.CameraPickerConfig} 34 | final CameraPickerConfig pickerConfig; 35 | 36 | /// Creates a customized [CameraPickerViewerState]. 37 | /// 构建一个自定义的 [CameraPickerViewerState]。 38 | final CameraPickerViewerState Function()? createViewerState; 39 | 40 | /// Static method to push with the navigator. 41 | /// 跳转至选择预览的静态方法 42 | static Future pushToViewer( 43 | BuildContext context, { 44 | Key? key, 45 | required CameraPickerConfig pickerConfig, 46 | required CameraPickerViewType viewType, 47 | required XFile previewXFile, 48 | CameraPickerViewerState Function()? createViewerState, 49 | bool useRootNavigator = true, 50 | }) { 51 | return Navigator.of( 52 | context, 53 | rootNavigator: useRootNavigator, 54 | ).push( 55 | PageRouteBuilder( 56 | pageBuilder: (_, __, ___) => CameraPickerViewer._( 57 | key: key, 58 | viewType: viewType, 59 | previewXFile: previewXFile, 60 | pickerConfig: pickerConfig, 61 | createViewerState: createViewerState, 62 | ), 63 | transitionsBuilder: ( 64 | BuildContext context, 65 | Animation animation, 66 | Animation secondaryAnimation, 67 | Widget child, 68 | ) { 69 | return FadeTransition(opacity: animation, child: child); 70 | }, 71 | ), 72 | ); 73 | } 74 | 75 | @override 76 | CameraPickerViewerState createState() => 77 | // ignore: no_logic_in_create_state 78 | createViewerState?.call() ?? CameraPickerViewerState(); 79 | } 80 | -------------------------------------------------------------------------------- /lib/src/widgets/camera_progress_button.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The FlutterCandies author. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be found 3 | // in the LICENSE file. 4 | 5 | import 'package:flutter/material.dart'; 6 | import 'package:wechat_picker_library/wechat_picker_library.dart'; 7 | 8 | import '../internals/methods.dart'; 9 | 10 | final class CameraProgressButton extends StatefulWidget { 11 | const CameraProgressButton({ 12 | super.key, 13 | required this.isAnimating, 14 | required this.size, 15 | required this.ringsWidth, 16 | this.ringsColor = defaultThemeColorWeChat, 17 | this.duration = const Duration(seconds: 15), 18 | }); 19 | 20 | final bool isAnimating; 21 | final Size size; 22 | final double ringsWidth; 23 | final Color ringsColor; 24 | final Duration duration; 25 | 26 | @override 27 | State createState() => _CircleProgressState(); 28 | } 29 | 30 | class _CircleProgressState extends State 31 | with SingleTickerProviderStateMixin { 32 | late final AnimationController progressController; 33 | 34 | @override 35 | void initState() { 36 | super.initState(); 37 | progressController = AnimationController( 38 | duration: widget.duration, 39 | vsync: this, 40 | ); 41 | ambiguate(WidgetsBinding.instance)?.addPostFrameCallback((_) { 42 | if (widget.isAnimating) { 43 | progressController.forward(); 44 | } 45 | }); 46 | } 47 | 48 | @override 49 | void didUpdateWidget(CameraProgressButton oldWidget) { 50 | super.didUpdateWidget(oldWidget); 51 | if (widget.isAnimating != oldWidget.isAnimating) { 52 | if (widget.isAnimating) { 53 | progressController.forward(); 54 | } else { 55 | progressController.stop(); 56 | } 57 | } 58 | } 59 | 60 | @override 61 | void dispose() { 62 | progressController.dispose(); 63 | super.dispose(); 64 | } 65 | 66 | @override 67 | Widget build(BuildContext context) { 68 | if (!widget.isAnimating) { 69 | return const SizedBox.shrink(); 70 | } 71 | return SizedBox.fromSize( 72 | size: widget.size, 73 | child: RepaintBoundary( 74 | child: AnimatedBuilder( 75 | animation: progressController, 76 | builder: (_, __) => CircularProgressIndicator( 77 | color: widget.ringsColor, 78 | strokeWidth: widget.ringsWidth, 79 | value: progressController.value, 80 | ), 81 | ), 82 | ), 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/wechat_camera_picker.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The FlutterCandies author. All rights reserved. 2 | // Use of this source code is governed by an Apache license that can be found 3 | // in the LICENSE file. 4 | 5 | library; 6 | 7 | export 'package:camera/camera.dart'; 8 | export 'package:photo_manager/photo_manager.dart'; 9 | export 'package:photo_manager_image_provider/photo_manager_image_provider.dart'; 10 | 11 | export 'src/constants/config.dart'; 12 | export 'src/constants/enums.dart'; 13 | export 'src/constants/type_defs.dart'; 14 | 15 | export 'src/delegates/camera_picker_text_delegate.dart'; 16 | 17 | export 'src/states/camera_picker_state.dart'; 18 | export 'src/states/camera_picker_viewer_state.dart'; 19 | 20 | export 'src/widgets/camera_focus_point.dart'; 21 | export 'src/widgets/camera_picker.dart'; 22 | export 'src/widgets/camera_picker_page_route.dart'; 23 | export 'src/widgets/camera_picker_viewer.dart'; 24 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: wechat_camera_picker 2 | version: 4.4.0 3 | description: | 4 | A camera picker for Flutter projects based on WeChat's UI, 5 | which is also a separate runnable extension to the 6 | wechat_assets_picker. 7 | topics: 8 | - picker 9 | - camera 10 | - image 11 | - video 12 | - wechat 13 | 14 | repository: https://github.com/fluttercandies/flutter_wechat_camera_picker 15 | issue_tracker: https://github.com/fluttercandies/flutter_wechat_camera_picker/issues 16 | 17 | environment: 18 | sdk: ^3.2.0 19 | flutter: '>=3.16.0' 20 | 21 | dependencies: 22 | flutter: 23 | sdk: flutter 24 | 25 | wechat_picker_library: ^1.0.2 26 | 27 | camera: ^0.10.0 28 | camera_android: ^0.10.9+6 29 | camera_platform_interface: ^2.1.5 30 | 31 | collection: '>=1.18.0 <2.0.0' 32 | path: ^1.8.0 33 | photo_manager: ^3.2.3 34 | photo_manager_image_provider: ^2.0.0 35 | sensors_plus: '>=4.0.0 <7.0.0' 36 | video_player: ^2.7.0 37 | 38 | dev_dependencies: 39 | flutter_lints: any 40 | 41 | screenshots: 42 | - description: 'Screenshot 1' 43 | path: screenshots/README_1.jpg 44 | - description: 'Screenshot 2' 45 | path: screenshots/README_2.jpg 46 | - description: 'Screenshot 3' 47 | path: screenshots/README_3.jpg 48 | - description: 'Screenshot 4' 49 | path: screenshots/README_4.jpg 50 | - description: 'Screenshot 5' 51 | path: screenshots/README_5.jpg 52 | -------------------------------------------------------------------------------- /screenshots/README_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/screenshots/README_1.jpg -------------------------------------------------------------------------------- /screenshots/README_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/screenshots/README_2.jpg -------------------------------------------------------------------------------- /screenshots/README_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/screenshots/README_3.jpg -------------------------------------------------------------------------------- /screenshots/README_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/screenshots/README_4.jpg -------------------------------------------------------------------------------- /screenshots/README_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_camera_picker/c98ef36bab31c1b1e38a7ddb35ee979e24570c1f/screenshots/README_5.jpg --------------------------------------------------------------------------------