├── .all-contributorsrc ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug-report--bug---.md │ ├── config.yml │ └── feature-request-------.md ├── jetbrains-variant.png ├── no-response.yml └── workflows │ ├── publish.yml │ └── runnable.yml ├── .gitignore ├── .metadata ├── .pubignore ├── CHANGELOG.md ├── CODEOWNERS ├── LICENSE ├── README-ZH.md ├── README.md ├── _config.yml ├── analysis_options.yaml ├── assets └── icon │ └── indicator-live-photos.png ├── example ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── fluttercandies │ │ │ │ └── wechatAssetsPickerExample │ │ │ │ ├── ExampleAppGlideModule.java │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ ├── 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 │ ├── constants │ │ ├── custom_pick_method.dart │ │ ├── extensions.dart │ │ └── picker_method.dart │ ├── customs │ │ ├── CONTRIBUTING.md │ │ ├── custom_picker_page.dart │ │ └── pickers │ │ │ ├── directory_file_asset_picker.dart │ │ │ ├── insta_asset_picker.dart │ │ │ └── multi_tabs_assets_picker.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 │ ├── pages │ │ ├── home_page.dart │ │ ├── multi_assets_page.dart │ │ ├── page_mixin.dart │ │ ├── single_assets_page.dart │ │ └── splash_page.dart │ └── widgets │ │ ├── asset_widget_builder.dart │ │ ├── method_list_view.dart │ │ └── selected_assets_list_view.dart ├── macos │ ├── .gitignore │ ├── Flutter │ │ ├── Flutter-Debug.xcconfig │ │ ├── Flutter-Release.xcconfig │ │ └── GeneratedPluginRegistrant.swift │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── app_icon_1024.png │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_256.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_512.png │ │ │ └── app_icon_64.png │ │ ├── Base.lproj │ │ └── MainMenu.xib │ │ ├── Configs │ │ ├── AppInfo.xcconfig │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── Warnings.xcconfig │ │ ├── DebugProfile.entitlements │ │ ├── Info.plist │ │ ├── MainFlutterWindow.swift │ │ └── Release.entitlements └── pubspec.yaml ├── guides └── migration_guide.md ├── lib ├── src │ ├── constants │ │ ├── config.dart │ │ ├── constants.dart │ │ ├── custom_scroll_physics.dart │ │ ├── enums.dart │ │ └── typedefs.dart │ ├── delegates │ │ ├── asset_grid_drag_selection_coordinator.dart │ │ ├── asset_picker_builder_delegate.dart │ │ ├── asset_picker_delegate.dart │ │ ├── asset_picker_text_delegate.dart │ │ ├── asset_picker_viewer_builder_delegate.dart │ │ └── sort_path_delegate.dart │ ├── internals │ │ └── singleton.dart │ ├── models │ │ └── path_wrapper.dart │ ├── provider │ │ ├── asset_picker_provider.dart │ │ └── asset_picker_viewer_provider.dart │ └── widget │ │ ├── asset_picker.dart │ │ ├── asset_picker_app_bar.dart │ │ ├── asset_picker_page_route.dart │ │ ├── asset_picker_viewer.dart │ │ └── builder │ │ ├── asset_entity_grid_item_builder.dart │ │ ├── audio_page_builder.dart │ │ ├── fade_image_builder.dart │ │ ├── image_page_builder.dart │ │ └── video_page_builder.dart └── wechat_assets_picker.dart ├── pubspec.yaml ├── screenshots ├── README_1.webp ├── README_2.webp ├── README_3.webp ├── README_4.webp ├── README_5.webp ├── README_6.webp ├── README_7.webp ├── README_8.webp └── README_9.webp └── test ├── config_test.dart ├── delegates_test.dart ├── providers_test.dart ├── test_utils.dart └── widget_test.dart /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: AlexV525 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://www.alexv525.com/wechat.png', 'https://www.alexv525.com/alipay.jpg'] 13 | -------------------------------------------------------------------------------- /.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/jetbrains-variant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/.github/jetbrains-variant.png -------------------------------------------------------------------------------- /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-no-response - https://github.com/probot/no-response 2 | 3 | # Number of days of inactivity before an Issue is closed for lack of response 4 | daysUntilClose: 14 5 | # Label requiring a response 6 | responseRequiredLabel: "await response" 7 | # Comment to post when closing an Issue for lack of response. Set to `false` to disable 8 | closeComment: > 9 | This issue has been automatically closed because there has been no response 10 | to our request for more information from the original author. Please reach 11 | out if you have or find the answers we need so that we can investigate further. 12 | -------------------------------------------------------------------------------- /.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 | - name: Checkout repo 13 | uses: actions/checkout@v4 14 | - name: Publish 15 | uses: k-paxian/dart-package-publisher@master 16 | with: 17 | credentialJson: ${{ secrets.CREDENTIAL_JSON }} 18 | flutter: true 19 | skipTests: true 20 | -------------------------------------------------------------------------------- /.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 | 11 | env: 12 | MINIMUM_FLUTTER_VERSION: '3.22.3' 13 | 14 | jobs: 15 | analyze: 16 | name: Analyze on ${{ matrix.os }} with ${{ matrix.flutter-version }} Flutter 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | matrix: 20 | os: [ ubuntu-latest ] 21 | flutter-version: [ min, latest ] 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: actions/setup-java@v4 25 | with: 26 | distribution: 'zulu' 27 | java-version: '17' 28 | - uses: flutter-actions/setup-flutter@v4 29 | with: 30 | channel: stable 31 | version: ${{ matrix.flutter-version == 'min' && env.MINIMUM_FLUTTER_VERSION || 'latest' }} 32 | - name: Log Dart/Flutter versions 33 | run: | 34 | dart --version 35 | flutter --version 36 | - name: Prepare dependencies 37 | run: flutter pub get 38 | - name: Check Dart code formatting 39 | if: matrix.flutter-version != 'min' 40 | run: dart format . -o none --set-exit-if-changed 41 | - name: Analyze Dart code 42 | run: flutter analyze . 43 | - name: Run tests 44 | run: flutter test 45 | - name: Publish dry-run 46 | if: matrix.flutter-version != 'min' 47 | run: dart pub publish --dry-run 48 | - name: Generate docs 49 | if: matrix.flutter-version != 'min' 50 | run: | 51 | dart pub global activate dartdoc 52 | dart pub global run dartdoc 53 | 54 | test_iOS: 55 | needs: analyze 56 | name: Test iOS with ${{ matrix.flutter-version }} Flutter 57 | runs-on: macos-latest 58 | strategy: 59 | matrix: 60 | flutter-version: [ min, latest ] 61 | steps: 62 | - uses: actions/checkout@v4 63 | - uses: actions/setup-java@v4 64 | with: 65 | distribution: 'zulu' 66 | java-version: '17' 67 | - uses: flutter-actions/setup-flutter@v4 68 | with: 69 | channel: stable 70 | version: ${{ matrix.flutter-version == 'min' && env.MINIMUM_FLUTTER_VERSION || 'latest' }} 71 | - run: dart --version 72 | - run: flutter --version 73 | - run: flutter pub get 74 | - run: cd example; flutter build ios --no-codesign 75 | 76 | test_android: 77 | needs: analyze 78 | name: Test Android with ${{ matrix.flutter-version }} Flutter 79 | runs-on: ubuntu-latest 80 | strategy: 81 | matrix: 82 | flutter-version: [ min, latest ] 83 | steps: 84 | - uses: actions/checkout@v4 85 | - uses: actions/setup-java@v4 86 | with: 87 | distribution: 'zulu' 88 | java-version: '17' 89 | - uses: flutter-actions/setup-flutter@v4 90 | with: 91 | cache: true 92 | channel: stable 93 | version: ${{ matrix.flutter-version == 'min' && env.MINIMUM_FLUTTER_VERSION || 'latest' }} 94 | - run: dart --version 95 | - run: flutter --version 96 | - run: flutter pub get 97 | - run: flutter build apk --debug 98 | working-directory: ${{ github.workspace }}/example 99 | -------------------------------------------------------------------------------- /.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 | devtools_options.yaml 33 | 34 | # Android related 35 | **/android/**/gradle-wrapper.jar 36 | **/android/.gradle 37 | **/android/captures/ 38 | **/android/gradlew 39 | **/android/gradlew.bat 40 | **/android/local.properties 41 | **/android/**/GeneratedPluginRegistrant.java 42 | 43 | # iOS/XCode related 44 | **/ios/**/*.mode1v3 45 | **/ios/**/*.mode2v3 46 | **/ios/**/*.moved-aside 47 | **/ios/**/*.pbxuser 48 | **/ios/**/*.perspectivev3 49 | **/ios/**/*sync/ 50 | **/ios/**/.sconsign.dblite 51 | **/ios/**/.tags* 52 | **/ios/**/.vagrant/ 53 | **/ios/**/DerivedData/ 54 | **/ios/**/Icon? 55 | **/ios/**/Pods/ 56 | **/ios/**/.symlinks/ 57 | **/ios/**/profile 58 | **/ios/**/xcuserdata 59 | **/ios/.generated/ 60 | **/ios/Flutter/App.framework 61 | **/ios/Flutter/Flutter.framework 62 | **/ios/Flutter/Flutter.podspec 63 | **/ios/Flutter/Generated.xcconfig 64 | **/ios/Flutter/app.flx 65 | **/ios/Flutter/app.zip 66 | **/ios/Flutter/flutter_assets/ 67 | **/ios/Flutter/flutter_export_environment.sh 68 | **/ios/ServiceDefinitions.json 69 | **/ios/Runner/GeneratedPluginRegistrant.* 70 | 71 | # Exceptions to above rules. 72 | !**/ios/**/default.mode1v3 73 | !**/ios/**/default.mode2v3 74 | !**/ios/**/default.pbxuser 75 | !**/ios/**/default.perspectivev3 76 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 77 | *.lock 78 | .env -------------------------------------------------------------------------------- /.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: 0e6818650f2e3798c342f4be293693737e35a453 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /.pubignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | *.lock 12 | .env 13 | _config.yml 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | .dart_tool/ 29 | .flutter-plugins 30 | .flutter-plugins-dependencies 31 | .packages 32 | .pub-cache/ 33 | .pub/ 34 | build/ 35 | 36 | # Android related 37 | **/android/**/gradle-wrapper.jar 38 | **/android/.gradle 39 | **/android/captures/ 40 | **/android/gradlew 41 | **/android/gradlew.bat 42 | **/android/local.properties 43 | **/android/**/GeneratedPluginRegistrant.java 44 | 45 | # iOS/XCode related 46 | **/ios/**/*.mode1v3 47 | **/ios/**/*.mode2v3 48 | **/ios/**/*.moved-aside 49 | **/ios/**/*.pbxuser 50 | **/ios/**/*.perspectivev3 51 | **/ios/**/*sync/ 52 | **/ios/**/.sconsign.dblite 53 | **/ios/**/.tags* 54 | **/ios/**/.vagrant/ 55 | **/ios/**/DerivedData/ 56 | **/ios/**/Icon? 57 | **/ios/**/Pods/ 58 | **/ios/**/.symlinks/ 59 | **/ios/**/profile 60 | **/ios/**/xcuserdata 61 | **/ios/.generated/ 62 | **/ios/Flutter/App.framework 63 | **/ios/Flutter/Flutter.framework 64 | **/ios/Flutter/Flutter.podspec 65 | **/ios/Flutter/Generated.xcconfig 66 | **/ios/Flutter/app.flx 67 | **/ios/Flutter/app.zip 68 | **/ios/Flutter/flutter_assets/ 69 | **/ios/Flutter/flutter_export_environment.sh 70 | **/ios/ServiceDefinitions.json 71 | **/ios/Runner/GeneratedPluginRegistrant.* 72 | 73 | # Exceptions to above rules. 74 | !**/ios/**/default.mode1v3 75 | !**/ios/**/default.mode2v3 76 | !**/ios/**/default.pbxuser 77 | !**/ios/**/default.perspectivev3 78 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 79 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @AlexV525 @LeGoffMael 2 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | analyzer: 4 | errors: 5 | deprecated_member_use: ignore 6 | 7 | linter: 8 | rules: 9 | always_declare_return_types: true 10 | always_put_control_body_on_new_line: true 11 | avoid_print: true 12 | avoid_renaming_method_parameters: true 13 | avoid_unnecessary_containers: true 14 | avoid_void_async: true 15 | curly_braces_in_flow_control_structures: true 16 | directives_ordering: true 17 | flutter_style_todos: true 18 | library_private_types_in_public_api: false 19 | overridden_fields: false 20 | prefer_const_constructors: true 21 | prefer_const_constructors_in_immutables: false 22 | prefer_final_fields: true 23 | prefer_final_in_for_each: true 24 | prefer_final_locals: true 25 | prefer_single_quotes: true 26 | require_trailing_commas: true 27 | sort_child_properties_last: true 28 | sort_constructors_first: true 29 | sort_unnamed_constructors_first: true 30 | unnecessary_await_in_return: true 31 | unnecessary_breaks: true 32 | unnecessary_late: true 33 | unnecessary_parenthesis: true 34 | use_build_context_synchronously: false 35 | void_checks: true 36 | -------------------------------------------------------------------------------- /assets/icon/indicator-live-photos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/assets/icon/indicator-live-photos.png -------------------------------------------------------------------------------- /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 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | -------------------------------------------------------------------------------- /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: macos 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 Assets Picker example 6 | 7 | This is the example for the `wechat_assets_picker` package. 8 | 9 | We've put multiple common usage 10 | with the packages in the example. 11 | You can both found `List pickMethods` in 12 | [here](lib/pages/multi_assets_page.dart) 13 | and [here](lib/pages/single_assets_page.dart), 14 | which provide methods in multiple picking and single picking mode. 15 | Assets will be stored temporary and displayed at the below of the page. 16 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: ../analysis_options.yaml 2 | 3 | analyzer: 4 | exclude: 5 | - lib/l10n/gen/*.dart 6 | 7 | linter: 8 | rules: 9 | use_build_context_synchronously: false -------------------------------------------------------------------------------- /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 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. 6 | id "dev.flutter.flutter-gradle-plugin" 7 | } 8 | 9 | def flutterVersionCode = null 10 | def flutterVersionName = null 11 | if (flutter.hasProperty('versionCode')) { 12 | flutterVersionCode = flutter.versionCode 13 | } 14 | if (flutter.hasProperty('versionName')) { 15 | flutterVersionName = flutter.versionName 16 | } 17 | 18 | if (flutterVersionCode == null || flutterVersionName == null) { 19 | def localProperties = new Properties() 20 | rootProject.file('local.properties').withReader('UTF-8') { reader -> 21 | localProperties.load(reader) 22 | } 23 | 24 | if (flutterVersionCode == null) { 25 | flutterVersionCode = localProperties.getProperty('flutter.versionCode') 26 | } 27 | if (flutterVersionName == null) { 28 | flutterVersionName = localProperties.getProperty('flutter.versionName') 29 | } 30 | } 31 | 32 | android { 33 | namespace "com.fluttercandies.wechatAssetsPickerExample" 34 | compileSdk flutter.compileSdkVersion 35 | ndkVersion flutter.ndkVersion 36 | 37 | defaultConfig { 38 | applicationId = "com.fluttercandies.wechatAssetsPickerExample" 39 | minSdk 21 40 | targetSdk flutter.targetSdkVersion 41 | versionCode flutterVersionCode.toInteger() 42 | versionName flutterVersionName 43 | } 44 | 45 | sourceSets { 46 | main.java.srcDirs += 'src/main/kotlin' 47 | } 48 | 49 | kotlinOptions { 50 | jvmTarget = '17' 51 | } 52 | 53 | compileOptions { 54 | // Sets Java compatibility to Java 17 55 | sourceCompatibility JavaVersion.VERSION_17 56 | targetCompatibility JavaVersion.VERSION_17 57 | } 58 | 59 | signingConfigs { 60 | forAll { 61 | storeFile file("${rootDir.absolutePath}/key.jks") 62 | storePassword 'picker' 63 | keyAlias 'picker' 64 | keyPassword 'picker' 65 | } 66 | } 67 | 68 | buildTypes { 69 | debug { 70 | signingConfig signingConfigs.forAll 71 | } 72 | profile { 73 | signingConfig signingConfigs.forAll 74 | } 75 | release { 76 | signingConfig signingConfigs.forAll 77 | } 78 | } 79 | } 80 | 81 | flutter { 82 | source '../..' 83 | } 84 | 85 | dependencies { 86 | implementation 'com.github.bumptech.glide:glide:4.15.0' 87 | kapt 'com.github.bumptech.glide:compiler:4.15.0' 88 | } 89 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 25 | 29 | 33 | 34 | 35 | 36 | 37 | 38 | 40 | 43 | 44 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/fluttercandies/wechatAssetsPickerExample/ExampleAppGlideModule.java: -------------------------------------------------------------------------------- 1 | package com.fluttercandies.wechatAssetsPickerExample; 2 | 3 | import com.bumptech.glide.annotation.GlideModule; 4 | import com.bumptech.glide.module.AppGlideModule; 5 | 6 | @GlideModule 7 | public class ExampleAppGlideModule extends AppGlideModule { 8 | } -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/fluttercandies/wechatAssetsPickerExample/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.fluttercandies.wechatAssetsPickerExample 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | // The default activity that extends flutter's activity. 7 | } 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /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_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/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_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/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_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/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_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/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_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /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=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 6 | -------------------------------------------------------------------------------- /example/android/key.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/example/android/key.jks -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version "8.9.1" apply false 22 | id "com.android.library" version "8.9.1" apply false 23 | id "org.jetbrains.kotlin.android" version "2.1.20" apply false 24 | } 25 | 26 | include ":app" 27 | -------------------------------------------------------------------------------- /example/assets/flutter_candies_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/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 | 31 | 32 | 38 | 39 | 40 | 41 | 44 | 50 | 51 | 52 | 53 | 54 | 66 | 68 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /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 | "idiom":"iphone", 5 | "size":"20x20", 6 | "scale":"2x", 7 | "filename":"Icon-App-20x20@2x.png" 8 | }, 9 | { 10 | "idiom":"iphone", 11 | "size":"20x20", 12 | "scale":"3x", 13 | "filename":"Icon-App-20x20@3x.png" 14 | }, 15 | { 16 | "idiom":"iphone", 17 | "size":"29x29", 18 | "scale":"1x", 19 | "filename":"Icon-App-29x29@1x.png" 20 | }, 21 | { 22 | "idiom":"iphone", 23 | "size":"29x29", 24 | "scale":"2x", 25 | "filename":"Icon-App-29x29@2x.png" 26 | }, 27 | { 28 | "idiom":"iphone", 29 | "size":"29x29", 30 | "scale":"3x", 31 | "filename":"Icon-App-29x29@3x.png" 32 | }, 33 | { 34 | "idiom":"iphone", 35 | "size":"40x40", 36 | "scale":"2x", 37 | "filename":"Icon-App-40x40@2x.png" 38 | }, 39 | { 40 | "idiom":"iphone", 41 | "size":"40x40", 42 | "scale":"3x", 43 | "filename":"Icon-App-40x40@3x.png" 44 | }, 45 | { 46 | "idiom":"iphone", 47 | "size":"60x60", 48 | "scale":"2x", 49 | "filename":"Icon-App-60x60@2x.png" 50 | }, 51 | { 52 | "idiom":"iphone", 53 | "size":"60x60", 54 | "scale":"3x", 55 | "filename":"Icon-App-60x60@3x.png" 56 | }, 57 | { 58 | "idiom":"iphone", 59 | "size":"76x76", 60 | "scale":"2x", 61 | "filename":"Icon-App-76x76@2x.png" 62 | }, 63 | { 64 | "idiom":"ipad", 65 | "size":"20x20", 66 | "scale":"1x", 67 | "filename":"Icon-App-20x20@1x.png" 68 | }, 69 | { 70 | "idiom":"ipad", 71 | "size":"20x20", 72 | "scale":"2x", 73 | "filename":"Icon-App-20x20@2x.png" 74 | }, 75 | { 76 | "idiom":"ipad", 77 | "size":"29x29", 78 | "scale":"1x", 79 | "filename":"Icon-App-29x29@1x.png" 80 | }, 81 | { 82 | "idiom":"ipad", 83 | "size":"29x29", 84 | "scale":"2x", 85 | "filename":"Icon-App-29x29@2x.png" 86 | }, 87 | { 88 | "idiom":"ipad", 89 | "size":"40x40", 90 | "scale":"1x", 91 | "filename":"Icon-App-40x40@1x.png" 92 | }, 93 | { 94 | "idiom":"ipad", 95 | "size":"40x40", 96 | "scale":"2x", 97 | "filename":"Icon-App-40x40@2x.png" 98 | }, 99 | { 100 | "idiom":"ipad", 101 | "size":"76x76", 102 | "scale":"1x", 103 | "filename":"Icon-App-76x76@1x.png" 104 | }, 105 | { 106 | "idiom":"ipad", 107 | "size":"76x76", 108 | "scale":"2x", 109 | "filename":"Icon-App-76x76@2x.png" 110 | }, 111 | { 112 | "idiom":"ipad", 113 | "size":"83.5x83.5", 114 | "scale":"2x", 115 | "filename":"Icon-App-83.5x83.5@2x.png" 116 | }, 117 | { 118 | "size" : "1024x1024", 119 | "idiom" : "ios-marketing", 120 | "scale" : "1x", 121 | "filename" : "ItunesArtwork@2x.png" 122 | } 123 | ], 124 | "info":{ 125 | "version":1, 126 | "author":"makeappicon" 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/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_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/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_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/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_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/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_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/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_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/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_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/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_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/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_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/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_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/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_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/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_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/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_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/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_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/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_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/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_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/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_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/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_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/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_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/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 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleDisplayName 10 | Wechat Assets Picker Example 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | flutter_wechat_assets_picker_example 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | $(FLUTTER_BUILD_NAME) 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | $(FLUTTER_BUILD_NUMBER) 27 | LSRequiresIPhoneOS 28 | 29 | NSCameraUsageDescription 30 | Take a photo for display 31 | NSMicrophoneUsageDescription 32 | Take a video for display 33 | NSPhotoLibraryUsageDescription 34 | Read your photos for display 35 | UIApplicationSupportsIndirectInputEvents 36 | 37 | UILaunchStoryboardName 38 | LaunchScreen 39 | UIMainStoryboardFile 40 | Main 41 | UISupportedInterfaceOrientations 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | UISupportedInterfaceOrientations~ipad 48 | 49 | UIInterfaceOrientationPortrait 50 | UIInterfaceOrientationPortraitUpsideDown 51 | UIInterfaceOrientationLandscapeLeft 52 | UIInterfaceOrientationLandscapeRight 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/constants/custom_pick_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/widgets.dart'; 6 | 7 | @immutable 8 | class CustomPickMethod { 9 | const CustomPickMethod({ 10 | required this.icon, 11 | required this.name, 12 | required this.description, 13 | required this.method, 14 | }); 15 | 16 | final String icon; 17 | final String name; 18 | final String description; 19 | final void Function(BuildContext context) method; 20 | } 21 | -------------------------------------------------------------------------------- /example/lib/constants/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/material.dart'; 6 | import 'package:wechat_assets_picker_demo/l10n/gen/app_localizations.dart'; 7 | 8 | extension BuildContextExtension on BuildContext { 9 | AppLocalizations get l10n => AppLocalizations.of(this)!; 10 | } 11 | 12 | extension BrightnessExtension on Brightness { 13 | bool get isDark => this == Brightness.dark; 14 | 15 | bool get isLight => this == Brightness.light; 16 | 17 | Brightness get reverse => 18 | this == Brightness.light ? Brightness.dark : Brightness.light; 19 | } 20 | 21 | extension ColorExtension on Color { 22 | MaterialColor get swatch => Colors.primaries.firstWhere( 23 | (Color c) => c.value == value, 24 | orElse: () => _swatch, 25 | ); 26 | 27 | Map get getMaterialColorValues => { 28 | 50: _swatchShade(50), 29 | 100: _swatchShade(100), 30 | 200: _swatchShade(200), 31 | 300: _swatchShade(300), 32 | 400: _swatchShade(400), 33 | 500: _swatchShade(500), 34 | 600: _swatchShade(600), 35 | 700: _swatchShade(700), 36 | 800: _swatchShade(800), 37 | 900: _swatchShade(900), 38 | }; 39 | 40 | MaterialColor get _swatch => MaterialColor(value, getMaterialColorValues); 41 | 42 | Color _swatchShade(int swatchValue) => HSLColor.fromColor(this) 43 | .withLightness(1 - (swatchValue / 1000)) 44 | .toColor(); 45 | } 46 | -------------------------------------------------------------------------------- /example/lib/customs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribute custom implementations 2 | 3 | This folder allows people to contribute their delegates, use case, or even a brand-new picker. 4 | 5 | To add your works, make sure you follow this instruction. 6 | 7 | ## Ensure that your picker is worth to contribute 8 | 9 | People usually have their own apps to build, and the thing like the picker is not always the same between apps. 10 | However, some patterns can be consumed using arguments and fields override, without creating a new picker/delegate. 11 | Please think twice before you confirmed it should be pushed as a new implementation example. 12 | 13 | ## Contribute steps 14 | 15 | 1. Fork this project. 16 | 2. Create a new branch named `custom-{write-your-implementation-name}`. 17 | 3. Create a new picker dart file named `{your_implementation_name}_asset_picker.dart` in the `pickers` folder. 18 | 4. Brought all your implementations into the file you've just created. 19 | 5. Add a `CustomPickMethod` at the `custom_picker_page.dart` file in `pickMethods` 20 | in order to build an entrance to your picker. 21 | 6. Submit your PR with proper explanations with how your picker works. 22 | -------------------------------------------------------------------------------- /example/lib/customs/custom_picker_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 | 7 | import '../constants/custom_pick_method.dart'; 8 | import '../constants/extensions.dart'; 9 | import 'pickers/directory_file_asset_picker.dart'; 10 | import 'pickers/insta_asset_picker.dart'; 11 | import 'pickers/multi_tabs_assets_picker.dart'; 12 | 13 | class CustomPickersPage extends StatefulWidget { 14 | const CustomPickersPage({super.key}); 15 | 16 | @override 17 | State createState() => _CustomPickerPageState(); 18 | } 19 | 20 | class _CustomPickerPageState extends State 21 | with AutomaticKeepAliveClientMixin { 22 | Widget tips(BuildContext context) { 23 | return Padding( 24 | padding: const EdgeInsets.all(20.0), 25 | child: Text(context.l10n.customPickerNotice), 26 | ); 27 | } 28 | 29 | List pickMethods(BuildContext context) { 30 | return [ 31 | CustomPickMethod( 32 | icon: '🗄', 33 | name: context.l10n.customPickerDirectoryAndFileName, 34 | description: context.l10n.customPickerDirectoryAndFileDescription, 35 | method: (BuildContext context) { 36 | Navigator.maybeOf(context)?.push( 37 | MaterialPageRoute( 38 | builder: (_) => const DirectoryFileAssetPicker(), 39 | ), 40 | ); 41 | }, 42 | ), 43 | CustomPickMethod( 44 | icon: '🔀', 45 | name: context.l10n.customPickerMultiTabName, 46 | description: context.l10n.customPickerMultiTabDescription, 47 | method: (BuildContext context) { 48 | Navigator.maybeOf(context)?.push( 49 | MaterialPageRoute( 50 | builder: (_) => const MultiTabAssetPicker(), 51 | ), 52 | ); 53 | }, 54 | ), 55 | CustomPickMethod( 56 | icon: '📷', 57 | name: context.l10n.customPickerInstagramLayoutName, 58 | description: context.l10n.customPickerInstagramLayoutDescription, 59 | method: (BuildContext context) => 60 | Navigator.maybeOf(context)?.push( 61 | MaterialPageRoute(builder: (_) => const InstaAssetPicker()), 62 | ), 63 | ), 64 | ]; 65 | } 66 | 67 | @override 68 | bool get wantKeepAlive => true; 69 | 70 | @override 71 | Widget build(BuildContext context) { 72 | super.build(context); 73 | return Column( 74 | children: [ 75 | tips(context), 76 | Expanded(child: _MethodListView(pickMethods: pickMethods(context))), 77 | ], 78 | ); 79 | } 80 | } 81 | 82 | class _MethodListView extends StatelessWidget { 83 | const _MethodListView({required this.pickMethods}); 84 | 85 | final List pickMethods; 86 | 87 | Widget methodItemBuilder(BuildContext context, int index) { 88 | final CustomPickMethod model = pickMethods[index]; 89 | return InkWell( 90 | onTap: () => model.method(context), 91 | child: Container( 92 | padding: const EdgeInsets.symmetric( 93 | horizontal: 30.0, 94 | vertical: 10.0, 95 | ), 96 | child: Row( 97 | children: [ 98 | Container( 99 | margin: const EdgeInsets.all(2.0), 100 | width: 48, 101 | height: 48, 102 | child: Center( 103 | child: Text( 104 | model.icon, 105 | style: const TextStyle(fontSize: 28.0), 106 | ), 107 | ), 108 | ), 109 | const SizedBox(width: 12.0), 110 | Expanded( 111 | child: Column( 112 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 113 | crossAxisAlignment: CrossAxisAlignment.start, 114 | children: [ 115 | Text( 116 | model.name, 117 | style: const TextStyle( 118 | fontSize: 18.0, 119 | fontWeight: FontWeight.bold, 120 | ), 121 | ), 122 | const SizedBox(height: 5), 123 | Text( 124 | model.description, 125 | style: Theme.of(context).textTheme.bodySmall, 126 | overflow: TextOverflow.fade, 127 | ), 128 | ], 129 | ), 130 | ), 131 | const Icon(Icons.chevron_right, color: Colors.grey), 132 | ], 133 | ), 134 | ), 135 | ); 136 | } 137 | 138 | @override 139 | Widget build(BuildContext context) { 140 | return ListView.builder( 141 | padding: const EdgeInsets.symmetric(vertical: 10.0), 142 | itemCount: pickMethods.length, 143 | itemBuilder: methodItemBuilder, 144 | ); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /example/lib/l10n/app_en.arb: -------------------------------------------------------------------------------- 1 | { 2 | "@@locale": "en", 3 | "appTitle": "WeChat Asset Picker Demo", 4 | "appVersion": "Version: {version}", 5 | "appVersionUnknown": "unknown", 6 | "navMulti": "Multi", 7 | "navSingle": "Single", 8 | "navCustom": "Custom", 9 | "selectedAssetsText": "Selected Assets", 10 | "pickMethodNotice": "Pickers in this page are located at the {dist}, defined by `pickMethods`.", 11 | "pickMethodImageName": "Image picker", 12 | "pickMethodImageDescription": "Only pick image from device.", 13 | "pickMethodVideoName": "Video picker", 14 | "pickMethodVideoDescription": "Only pick video from device. (Includes Live Photos on iOS and macOS.)", 15 | "pickMethodAudioName": "Audio picker", 16 | "pickMethodAudioDescription": "Only pick audio from device.", 17 | "pickMethodLivePhotoName": "Live Photo picker", 18 | "pickMethodLivePhotoDescription": "Only pick Live Photos from device.", 19 | "pickMethodCameraName": "Pick from camera", 20 | "pickMethodCameraDescription": "Allow to pick an asset through camera.", 21 | "pickMethodCameraAndStayName": "Pick from camera and stay", 22 | "pickMethodCameraAndStayDescription": "Take a photo or video with the camera picker, select the result and stay in the entities list.", 23 | "pickMethodCommonName": "Common picker", 24 | "pickMethodCommonDescription": "Pick images and videos.", 25 | "pickMethodThreeItemsGridName": "3 items grid", 26 | "pickMethodThreeItemsGridDescription": "Picker will served as 3 items on cross axis. (pageSize must be a multiple of the gridCount)", 27 | "pickMethodCustomFilterOptionsName": "Custom filter options", 28 | "pickMethodCustomFilterOptionsDescription": "Add filter options for the picker.", 29 | "pickMethodPrependItemName": "Prepend special item", 30 | "pickMethodPrependItemDescription": "A special item will prepend to the assets grid.", 31 | "pickMethodNoPreviewName": "No preview", 32 | "pickMethodNoPreviewDescription": "You cannot preview assets during the picking, the behavior is like the WhatsApp/MegaTok pattern.", 33 | "pickMethodKeepScrollOffsetName": "Keep scroll offset", 34 | "pickMethodKeepScrollOffsetDescription": "Pick assets from same scroll position.", 35 | "pickMethodChangeLanguagesName": "Change Languages", 36 | "pickMethodChangeLanguagesDescription": "Pass AssetPickerTextDelegate to change between languages (e.g. EnglishAssetPickerTextDelegate).", 37 | "pickMethodPreventGIFPickedName": "Prevent GIF being picked", 38 | "pickMethodPreventGIFPickedDescription": "Use selectPredicate to banned GIF picking when tapped.", 39 | "pickMethodCustomizableThemeName": "Customizable theme (ThemeData)", 40 | "pickMethodCustomizableThemeDescription": "Picking assets with the light theme or with a different color.", 41 | "pickMethodPathNameBuilderName": "Path name builder", 42 | "pickMethodPathNameBuilderDescription": "Add \uD83C\uDF6D after paths name.", 43 | "pickMethodWeChatMomentName": "WeChat Moment", 44 | "pickMethodWeChatMomentDescription": "Pick assets with images or only 1 video.", 45 | "pickMethodCustomImagePreviewThumbSizeName": "Custom image preview thumb size", 46 | "pickMethodCustomImagePreviewThumbSizeDescription": "You can reduce the thumb size to get faster load speed.", 47 | "customPickerNotice": "This page contains customized pickers with different asset types, different UI layouts, or some use case for specific apps. Contribute to add your custom picker are welcomed.\nPickers in this page are located at the lib/customs/pickers folder.", 48 | "customPickerCallThePickerButton": "\uD83C\uDF81 Call the Picker", 49 | "customPickerDirectoryAndFileName": "Directory+File picker", 50 | "customPickerDirectoryAndFileDescription": "This is a custom picker built for `File`.\nBy browsing this picker, we want you to know that you can build your own picker components using the entity's type you desired.\n\nIn this page, picker will grab files from `getApplicationDocumentsDirectory`, then check whether it contains images. Put files into the path to see how this custom picker work.", 51 | "customPickerMultiTabName": "Multi tab picker", 52 | "customPickerMultiTabDescription": "The picker contains multiple tab with different types of assets for the picking at the same time.", 53 | "customPickerMultiTabTab1": "All", 54 | "customPickerMultiTabTab2": "Videos", 55 | "customPickerMultiTabTab3": "Images", 56 | "customPickerInstagramLayoutName": "Instagram layout picker", 57 | "customPickerInstagramLayoutDescription": "The picker reproduces Instagram layout with preview and scroll animations. It's also published as the package insta_assets_picker." 58 | } -------------------------------------------------------------------------------- /example/lib/l10n/app_zh.arb: -------------------------------------------------------------------------------- 1 | { 2 | "@@locale": "zh", 3 | "appTitle": "WeChat Asset Picker 示例", 4 | "appVersion": "版本:{version}", 5 | "appVersionUnknown": "未知", 6 | "navMulti": "多选", 7 | "navSingle": "单选", 8 | "navCustom": "自定义", 9 | "selectedAssetsText": "已选的资源", 10 | "pickMethodNotice": "该页面的所有选择器的代码位于 {dist},由 `pickMethods` 定义。", 11 | "pickMethodCommonName": "常用选择", 12 | "pickMethodCommonDescription": "选择图片和视频。", 13 | "pickMethodImageName": "图片选择", 14 | "pickMethodImageDescription": "仅选择图片。", 15 | "pickMethodVideoName": "视频选择", 16 | "pickMethodVideoDescription": "仅选择视频。", 17 | "pickMethodAudioName": "音频选择", 18 | "pickMethodAudioDescription": "仅选择音频。", 19 | "pickMethodLivePhotoName": "实况图片选择", 20 | "pickMethodLivePhotoDescription": "仅选择实况图片。", 21 | "pickMethodCameraName": "从相机生成选择", 22 | "pickMethodCameraDescription": "通过相机拍照生成并选择资源", 23 | "pickMethodCameraAndStayName": "从相机生成选择并停留", 24 | "pickMethodCameraAndStayDescription": "通过相机拍照生成选择资源,并停留在选择界面。", 25 | "pickMethodThreeItemsGridName": "横向 3 格", 26 | "pickMethodThreeItemsGridDescription": "选择器每行为 3 格。(pageSize 必须为 gridCount 的倍数)", 27 | "pickMethodCustomFilterOptionsName": "自定义过滤条件", 28 | "pickMethodCustomFilterOptionsDescription": "为选择器添加自定义过滤条件。", 29 | "pickMethodPrependItemName": "往网格前插入 widget", 30 | "pickMethodPrependItemDescription": "网格的靠前位置会添加一个自定义的 widget。", 31 | "pickMethodNoPreviewName": "禁止预览", 32 | "pickMethodNoPreviewDescription": "无法预览选择的资源,与 WhatsApp/MegaTok 的行为类似。", 33 | "pickMethodKeepScrollOffsetName": "保持滚动位置", 34 | "pickMethodKeepScrollOffsetDescription": "可以从上次滚动到的位置再次开始选择。", 35 | "pickMethodChangeLanguagesName": "更改语言", 36 | "pickMethodChangeLanguagesDescription": "传入 AssetPickerTextDelegate 手动更改选择器的语言(例如 EnglishAssetPickerTextDelegate)。", 37 | "pickMethodPreventGIFPickedName": "禁止选择 GIF 图片", 38 | "pickMethodPreventGIFPickedDescription": "通过 selectPredicate 来禁止 GIF 图片在点击时被选择。", 39 | "pickMethodCustomizableThemeName": "自定义主题 (ThemeData)", 40 | "pickMethodCustomizableThemeDescription": "可以用亮色或其他颜色及自定义的主题进行选择。", 41 | "pickMethodPathNameBuilderName": "构建路径名称", 42 | "pickMethodPathNameBuilderDescription": "在路径后添加 \uD83C\uDF6D 进行自定义。", 43 | "pickMethodWeChatMomentName": "微信朋友圈模式", 44 | "pickMethodWeChatMomentDescription": "允许选择图片或仅 1 个视频。", 45 | "pickMethodCustomImagePreviewThumbSizeName": "自定义图片预览的缩略图大小", 46 | "pickMethodCustomImagePreviewThumbSizeDescription": "通过降低缩略图的质量来获得更快的加载速度。", 47 | "customPickerNotice": "本页面包含了多种方式、不同界面和特定应用的自定义选择器。欢迎贡献添加你自定义的选择器。\n该页面的所有选择器的代码位于 lib/customs/pickers 目录。", 48 | "customPickerCallThePickerButton": "\uD83C\uDF81 开始选择资源", 49 | "customPickerDirectoryAndFileName": "Directory+File 选择器", 50 | "customPickerDirectoryAndFileDescription": "为 `File` 构建的自定义选择器。\n通过阅读该选择器的源码,你可以学习如何完全以你自定义的资源类型来构建并选择器的界面。\n\n该选择器会从 `getApplicationDocumentsDirectory` 目录获取资源,然后检查它是否包含图片。你需要将图片放在该目录来查看选择器的效果。", 51 | "customPickerMultiTabName": "多 Tab 选择器", 52 | "customPickerMultiTabDescription": "该选择器会以多 Tab 的形式同时展示多种资源类型的选择器。", 53 | "customPickerMultiTabTab1": "全部", 54 | "customPickerMultiTabTab2": "视频", 55 | "customPickerMultiTabTab3": "图片", 56 | "customPickerInstagramLayoutName": "Instagram 布局的选择器", 57 | "customPickerInstagramLayoutDescription": "该选择器以 Instagram 的布局模式构建,在选择时可以同时预览。其已发布为单独的 package:insta_assets_picker。" 58 | } -------------------------------------------------------------------------------- /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 Asset 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 navMulti => 'Multi'; 22 | 23 | @override 24 | String get navSingle => 'Single'; 25 | 26 | @override 27 | String get navCustom => 'Custom'; 28 | 29 | @override 30 | String get selectedAssetsText => 'Selected Assets'; 31 | 32 | @override 33 | String pickMethodNotice(Object dist) { 34 | return 'Pickers in this page are located at the $dist, defined by `pickMethods`.'; 35 | } 36 | 37 | @override 38 | String get pickMethodImageName => 'Image picker'; 39 | 40 | @override 41 | String get pickMethodImageDescription => 'Only pick image from device.'; 42 | 43 | @override 44 | String get pickMethodVideoName => 'Video picker'; 45 | 46 | @override 47 | String get pickMethodVideoDescription => 48 | 'Only pick video from device. (Includes Live Photos on iOS and macOS.)'; 49 | 50 | @override 51 | String get pickMethodAudioName => 'Audio picker'; 52 | 53 | @override 54 | String get pickMethodAudioDescription => 'Only pick audio from device.'; 55 | 56 | @override 57 | String get pickMethodLivePhotoName => 'Live Photo picker'; 58 | 59 | @override 60 | String get pickMethodLivePhotoDescription => 61 | 'Only pick Live Photos from device.'; 62 | 63 | @override 64 | String get pickMethodCameraName => 'Pick from camera'; 65 | 66 | @override 67 | String get pickMethodCameraDescription => 68 | 'Allow to pick an asset through camera.'; 69 | 70 | @override 71 | String get pickMethodCameraAndStayName => 'Pick from camera and stay'; 72 | 73 | @override 74 | String get pickMethodCameraAndStayDescription => 75 | 'Take a photo or video with the camera picker, select the result and stay in the entities list.'; 76 | 77 | @override 78 | String get pickMethodCommonName => 'Common picker'; 79 | 80 | @override 81 | String get pickMethodCommonDescription => 'Pick images and videos.'; 82 | 83 | @override 84 | String get pickMethodThreeItemsGridName => '3 items grid'; 85 | 86 | @override 87 | String get pickMethodThreeItemsGridDescription => 88 | 'Picker will served as 3 items on cross axis. (pageSize must be a multiple of the gridCount)'; 89 | 90 | @override 91 | String get pickMethodCustomFilterOptionsName => 'Custom filter options'; 92 | 93 | @override 94 | String get pickMethodCustomFilterOptionsDescription => 95 | 'Add filter options for the picker.'; 96 | 97 | @override 98 | String get pickMethodPrependItemName => 'Prepend special item'; 99 | 100 | @override 101 | String get pickMethodPrependItemDescription => 102 | 'A special item will prepend to the assets grid.'; 103 | 104 | @override 105 | String get pickMethodNoPreviewName => 'No preview'; 106 | 107 | @override 108 | String get pickMethodNoPreviewDescription => 109 | 'You cannot preview assets during the picking, the behavior is like the WhatsApp/MegaTok pattern.'; 110 | 111 | @override 112 | String get pickMethodKeepScrollOffsetName => 'Keep scroll offset'; 113 | 114 | @override 115 | String get pickMethodKeepScrollOffsetDescription => 116 | 'Pick assets from same scroll position.'; 117 | 118 | @override 119 | String get pickMethodChangeLanguagesName => 'Change Languages'; 120 | 121 | @override 122 | String get pickMethodChangeLanguagesDescription => 123 | 'Pass AssetPickerTextDelegate to change between languages (e.g. EnglishAssetPickerTextDelegate).'; 124 | 125 | @override 126 | String get pickMethodPreventGIFPickedName => 'Prevent GIF being picked'; 127 | 128 | @override 129 | String get pickMethodPreventGIFPickedDescription => 130 | 'Use selectPredicate to banned GIF picking when tapped.'; 131 | 132 | @override 133 | String get pickMethodCustomizableThemeName => 134 | 'Customizable theme (ThemeData)'; 135 | 136 | @override 137 | String get pickMethodCustomizableThemeDescription => 138 | 'Picking assets with the light theme or with a different color.'; 139 | 140 | @override 141 | String get pickMethodPathNameBuilderName => 'Path name builder'; 142 | 143 | @override 144 | String get pickMethodPathNameBuilderDescription => 'Add 🍭 after paths name.'; 145 | 146 | @override 147 | String get pickMethodWeChatMomentName => 'WeChat Moment'; 148 | 149 | @override 150 | String get pickMethodWeChatMomentDescription => 151 | 'Pick assets with images or only 1 video.'; 152 | 153 | @override 154 | String get pickMethodCustomImagePreviewThumbSizeName => 155 | 'Custom image preview thumb size'; 156 | 157 | @override 158 | String get pickMethodCustomImagePreviewThumbSizeDescription => 159 | 'You can reduce the thumb size to get faster load speed.'; 160 | 161 | @override 162 | String get customPickerNotice => 163 | 'This page contains customized pickers with different asset types, different UI layouts, or some use case for specific apps. Contribute to add your custom picker are welcomed.\nPickers in this page are located at the lib/customs/pickers folder.'; 164 | 165 | @override 166 | String get customPickerCallThePickerButton => '🎁 Call the Picker'; 167 | 168 | @override 169 | String get customPickerDirectoryAndFileName => 'Directory+File picker'; 170 | 171 | @override 172 | String get customPickerDirectoryAndFileDescription => 173 | 'This is a custom picker built for `File`.\nBy browsing this picker, we want you to know that you can build your own picker components using the entity\'s type you desired.\n\nIn this page, picker will grab files from `getApplicationDocumentsDirectory`, then check whether it contains images. Put files into the path to see how this custom picker work.'; 174 | 175 | @override 176 | String get customPickerMultiTabName => 'Multi tab picker'; 177 | 178 | @override 179 | String get customPickerMultiTabDescription => 180 | 'The picker contains multiple tab with different types of assets for the picking at the same time.'; 181 | 182 | @override 183 | String get customPickerMultiTabTab1 => 'All'; 184 | 185 | @override 186 | String get customPickerMultiTabTab2 => 'Videos'; 187 | 188 | @override 189 | String get customPickerMultiTabTab3 => 'Images'; 190 | 191 | @override 192 | String get customPickerInstagramLayoutName => 'Instagram layout picker'; 193 | 194 | @override 195 | String get customPickerInstagramLayoutDescription => 196 | 'The picker reproduces Instagram layout with preview and scroll animations. It\'s also published as the package insta_assets_picker.'; 197 | } 198 | -------------------------------------------------------------------------------- /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 Asset 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 navMulti => '多选'; 22 | 23 | @override 24 | String get navSingle => '单选'; 25 | 26 | @override 27 | String get navCustom => '自定义'; 28 | 29 | @override 30 | String get selectedAssetsText => '已选的资源'; 31 | 32 | @override 33 | String pickMethodNotice(Object dist) { 34 | return '该页面的所有选择器的代码位于 $dist,由 `pickMethods` 定义。'; 35 | } 36 | 37 | @override 38 | String get pickMethodImageName => '图片选择'; 39 | 40 | @override 41 | String get pickMethodImageDescription => '仅选择图片。'; 42 | 43 | @override 44 | String get pickMethodVideoName => '视频选择'; 45 | 46 | @override 47 | String get pickMethodVideoDescription => '仅选择视频。'; 48 | 49 | @override 50 | String get pickMethodAudioName => '音频选择'; 51 | 52 | @override 53 | String get pickMethodAudioDescription => '仅选择音频。'; 54 | 55 | @override 56 | String get pickMethodLivePhotoName => '实况图片选择'; 57 | 58 | @override 59 | String get pickMethodLivePhotoDescription => '仅选择实况图片。'; 60 | 61 | @override 62 | String get pickMethodCameraName => '从相机生成选择'; 63 | 64 | @override 65 | String get pickMethodCameraDescription => '通过相机拍照生成并选择资源'; 66 | 67 | @override 68 | String get pickMethodCameraAndStayName => '从相机生成选择并停留'; 69 | 70 | @override 71 | String get pickMethodCameraAndStayDescription => '通过相机拍照生成选择资源,并停留在选择界面。'; 72 | 73 | @override 74 | String get pickMethodCommonName => '常用选择'; 75 | 76 | @override 77 | String get pickMethodCommonDescription => '选择图片和视频。'; 78 | 79 | @override 80 | String get pickMethodThreeItemsGridName => '横向 3 格'; 81 | 82 | @override 83 | String get pickMethodThreeItemsGridDescription => 84 | '选择器每行为 3 格。(pageSize 必须为 gridCount 的倍数)'; 85 | 86 | @override 87 | String get pickMethodCustomFilterOptionsName => '自定义过滤条件'; 88 | 89 | @override 90 | String get pickMethodCustomFilterOptionsDescription => '为选择器添加自定义过滤条件。'; 91 | 92 | @override 93 | String get pickMethodPrependItemName => '往网格前插入 widget'; 94 | 95 | @override 96 | String get pickMethodPrependItemDescription => '网格的靠前位置会添加一个自定义的 widget。'; 97 | 98 | @override 99 | String get pickMethodNoPreviewName => '禁止预览'; 100 | 101 | @override 102 | String get pickMethodNoPreviewDescription => 103 | '无法预览选择的资源,与 WhatsApp/MegaTok 的行为类似。'; 104 | 105 | @override 106 | String get pickMethodKeepScrollOffsetName => '保持滚动位置'; 107 | 108 | @override 109 | String get pickMethodKeepScrollOffsetDescription => '可以从上次滚动到的位置再次开始选择。'; 110 | 111 | @override 112 | String get pickMethodChangeLanguagesName => '更改语言'; 113 | 114 | @override 115 | String get pickMethodChangeLanguagesDescription => 116 | '传入 AssetPickerTextDelegate 手动更改选择器的语言(例如 EnglishAssetPickerTextDelegate)。'; 117 | 118 | @override 119 | String get pickMethodPreventGIFPickedName => '禁止选择 GIF 图片'; 120 | 121 | @override 122 | String get pickMethodPreventGIFPickedDescription => 123 | '通过 selectPredicate 来禁止 GIF 图片在点击时被选择。'; 124 | 125 | @override 126 | String get pickMethodCustomizableThemeName => '自定义主题 (ThemeData)'; 127 | 128 | @override 129 | String get pickMethodCustomizableThemeDescription => '可以用亮色或其他颜色及自定义的主题进行选择。'; 130 | 131 | @override 132 | String get pickMethodPathNameBuilderName => '构建路径名称'; 133 | 134 | @override 135 | String get pickMethodPathNameBuilderDescription => '在路径后添加 🍭 进行自定义。'; 136 | 137 | @override 138 | String get pickMethodWeChatMomentName => '微信朋友圈模式'; 139 | 140 | @override 141 | String get pickMethodWeChatMomentDescription => '允许选择图片或仅 1 个视频。'; 142 | 143 | @override 144 | String get pickMethodCustomImagePreviewThumbSizeName => '自定义图片预览的缩略图大小'; 145 | 146 | @override 147 | String get pickMethodCustomImagePreviewThumbSizeDescription => 148 | '通过降低缩略图的质量来获得更快的加载速度。'; 149 | 150 | @override 151 | String get customPickerNotice => 152 | '本页面包含了多种方式、不同界面和特定应用的自定义选择器。欢迎贡献添加你自定义的选择器。\n该页面的所有选择器的代码位于 lib/customs/pickers 目录。'; 153 | 154 | @override 155 | String get customPickerCallThePickerButton => '🎁 开始选择资源'; 156 | 157 | @override 158 | String get customPickerDirectoryAndFileName => 'Directory+File 选择器'; 159 | 160 | @override 161 | String get customPickerDirectoryAndFileDescription => 162 | '为 `File` 构建的自定义选择器。\n通过阅读该选择器的源码,你可以学习如何完全以你自定义的资源类型来构建并选择器的界面。\n\n该选择器会从 `getApplicationDocumentsDirectory` 目录获取资源,然后检查它是否包含图片。你需要将图片放在该目录来查看选择器的效果。'; 163 | 164 | @override 165 | String get customPickerMultiTabName => '多 Tab 选择器'; 166 | 167 | @override 168 | String get customPickerMultiTabDescription => 169 | '该选择器会以多 Tab 的形式同时展示多种资源类型的选择器。'; 170 | 171 | @override 172 | String get customPickerMultiTabTab1 => '全部'; 173 | 174 | @override 175 | String get customPickerMultiTabTab2 => '视频'; 176 | 177 | @override 178 | String get customPickerMultiTabTab3 => '图片'; 179 | 180 | @override 181 | String get customPickerInstagramLayoutName => 'Instagram 布局的选择器'; 182 | 183 | @override 184 | String get customPickerInstagramLayoutDescription => 185 | '该选择器以 Instagram 的布局模式构建,在选择时可以同时预览。其已发布为单独的 package:insta_assets_picker。'; 186 | } 187 | -------------------------------------------------------------------------------- /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 | import 'package:wechat_assets_picker/wechat_assets_picker.dart'; 8 | import 'package:wechat_assets_picker_demo/l10n/gen/app_localizations.dart'; 9 | 10 | import 'constants/extensions.dart'; 11 | import 'pages/splash_page.dart'; 12 | 13 | const Color themeColor = Color(0xff00bc56); 14 | 15 | String? packageVersion; 16 | 17 | void main() { 18 | runApp(const MyApp()); 19 | SystemChrome.setSystemUIOverlayStyle( 20 | SystemUiOverlayStyle.dark.copyWith(statusBarColor: Colors.transparent), 21 | ); 22 | AssetPicker.registerObserve(); 23 | // Enables logging with the photo_manager. 24 | PhotoManager.setLog(true); 25 | } 26 | 27 | class MyApp extends StatelessWidget { 28 | const MyApp({super.key}); 29 | 30 | ThemeData _buildTheme(Brightness brightness) { 31 | return ThemeData( 32 | brightness: brightness, 33 | primarySwatch: themeColor.swatch, 34 | textSelectionTheme: const TextSelectionThemeData(cursorColor: themeColor), 35 | ); 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return MaterialApp( 41 | onGenerateTitle: (context) => context.l10n.appTitle, 42 | theme: _buildTheme(Brightness.light), 43 | darkTheme: _buildTheme(Brightness.dark), 44 | home: const SplashPage(), 45 | builder: (BuildContext c, Widget? w) { 46 | return ScrollConfiguration( 47 | behavior: const NoGlowScrollBehavior(), 48 | child: w!, 49 | ); 50 | }, 51 | localizationsDelegates: AppLocalizations.localizationsDelegates, 52 | supportedLocales: AppLocalizations.supportedLocales, 53 | ); 54 | } 55 | } 56 | 57 | class NoGlowScrollBehavior extends ScrollBehavior { 58 | const NoGlowScrollBehavior(); 59 | 60 | @override 61 | Widget buildOverscrollIndicator( 62 | BuildContext context, 63 | Widget child, 64 | ScrollableDetails details, 65 | ) => 66 | child; 67 | } 68 | -------------------------------------------------------------------------------- /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 | 9 | import '../constants/extensions.dart'; 10 | import '../customs/custom_picker_page.dart'; 11 | import '../main.dart'; 12 | import 'multi_assets_page.dart'; 13 | import 'single_assets_page.dart'; 14 | 15 | class HomePage extends StatefulWidget { 16 | const HomePage({super.key}); 17 | 18 | @override 19 | State createState() => _HomePageState(); 20 | } 21 | 22 | class _HomePageState extends State { 23 | final PageController controller = PageController(); 24 | int currentIndex = 0; 25 | 26 | @override 27 | void initState() { 28 | super.initState(); 29 | controller.addListener(pageControllerListener); 30 | } 31 | 32 | void selectIndex(int index) { 33 | if (index == currentIndex) { 34 | return; 35 | } 36 | controller.animateToPage( 37 | index, 38 | duration: kThemeAnimationDuration, 39 | curve: Curves.easeInOut, 40 | ); 41 | } 42 | 43 | void pageControllerListener() { 44 | final int? currentPage = controller.page?.round(); 45 | if (currentPage != null && currentPage != currentIndex) { 46 | currentIndex = currentPage; 47 | if (mounted) { 48 | setState(() {}); 49 | } 50 | } 51 | } 52 | 53 | Widget header(BuildContext context) { 54 | return Container( 55 | margin: const EdgeInsetsDirectional.only(top: 30.0), 56 | height: 60.0, 57 | child: Row( 58 | mainAxisAlignment: MainAxisAlignment.center, 59 | children: [ 60 | AspectRatio( 61 | aspectRatio: 1.0, 62 | child: Hero( 63 | tag: 'LOGO', 64 | child: Image.asset('assets/flutter_candies_logo.png'), 65 | ), 66 | ), 67 | const SizedBox(width: 10.0), 68 | Column( 69 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 70 | crossAxisAlignment: CrossAxisAlignment.start, 71 | children: [ 72 | Semantics( 73 | sortKey: const OrdinalSortKey(0), 74 | child: Text( 75 | context.l10n.appTitle, 76 | style: Theme.of(context).textTheme.titleLarge, 77 | ), 78 | ), 79 | Semantics( 80 | sortKey: const OrdinalSortKey(0.1), 81 | child: Text( 82 | context.l10n.appVersion( 83 | packageVersion ?? context.l10n.appVersionUnknown, 84 | ), 85 | style: Theme.of(context).textTheme.bodySmall, 86 | ), 87 | ), 88 | ], 89 | ), 90 | const SizedBox(width: 20.0), 91 | ], 92 | ), 93 | ); 94 | } 95 | 96 | @override 97 | Widget build(BuildContext context) { 98 | return AnnotatedRegion( 99 | value: Theme.of(context).brightness == Brightness.dark 100 | ? SystemUiOverlayStyle.light 101 | : SystemUiOverlayStyle.dark, 102 | child: Scaffold( 103 | body: SafeArea( 104 | child: Column( 105 | children: [ 106 | header(context), 107 | Expanded( 108 | child: PageView( 109 | controller: controller, 110 | children: const [ 111 | MultiAssetsPage(), 112 | SingleAssetPage(), 113 | CustomPickersPage(), 114 | ], 115 | ), 116 | ), 117 | ], 118 | ), 119 | ), 120 | bottomNavigationBar: BottomNavigationBar( 121 | currentIndex: currentIndex, 122 | onTap: selectIndex, 123 | items: [ 124 | BottomNavigationBarItem( 125 | icon: const Icon(Icons.photo_library), 126 | label: context.l10n.navMulti, 127 | ), 128 | BottomNavigationBarItem( 129 | icon: const Icon(Icons.photo), 130 | label: context.l10n.navSingle, 131 | ), 132 | BottomNavigationBarItem( 133 | icon: const Icon(Icons.explore), 134 | label: context.l10n.navCustom, 135 | ), 136 | ], 137 | ), 138 | ), 139 | ); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /example/lib/pages/multi_assets_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 'dart:io'; 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:wechat_assets_picker/wechat_assets_picker.dart'; 9 | 10 | import '../constants/extensions.dart'; 11 | import '../constants/picker_method.dart'; 12 | import 'page_mixin.dart'; 13 | 14 | class MultiAssetsPage extends StatefulWidget { 15 | const MultiAssetsPage({super.key}); 16 | 17 | @override 18 | State createState() => _MultiAssetsPageState(); 19 | } 20 | 21 | class _MultiAssetsPageState extends State 22 | with AutomaticKeepAliveClientMixin, ExamplePageMixin { 23 | @override 24 | String get noticeText => 'lib/pages/multi_assets_page.dart'; 25 | 26 | @override 27 | int get maxAssetsCount => 9; 28 | 29 | /// Check each method's source code for more details. 30 | @override 31 | List pickMethods(BuildContext context) { 32 | return [ 33 | PickMethod.common(context, maxAssetsCount), 34 | PickMethod.image(context, maxAssetsCount), 35 | PickMethod.video(context, maxAssetsCount), 36 | PickMethod.audio(context, maxAssetsCount), 37 | if (Platform.isIOS || Platform.isMacOS) 38 | PickMethod.livePhoto(context, maxAssetsCount), 39 | PickMethod.camera( 40 | context: context, 41 | maxAssetsCount: maxAssetsCount, 42 | handleResult: (BuildContext context, AssetEntity result) => 43 | Navigator.maybeOf(context)?.pop([...assets, result]), 44 | ), 45 | PickMethod.cameraAndStay(context, maxAssetsCount), 46 | PickMethod.changeLanguages(context, maxAssetsCount), 47 | PickMethod.threeItemsGrid(context, maxAssetsCount), 48 | PickMethod.prependItem(context, maxAssetsCount), 49 | PickMethod( 50 | icon: '🎭', 51 | name: context.l10n.pickMethodWeChatMomentName, 52 | description: context.l10n.pickMethodWeChatMomentDescription, 53 | method: (BuildContext context, List assets) { 54 | return AssetPicker.pickAssets( 55 | context, 56 | pickerConfig: AssetPickerConfig( 57 | maxAssets: maxAssetsCount, 58 | specialPickerType: SpecialPickerType.wechatMoment, 59 | ), 60 | ); 61 | }, 62 | ), 63 | PickMethod.noPreview(context, maxAssetsCount), 64 | PickMethod.customizableTheme(context, maxAssetsCount), 65 | PickMethod.pathNameBuilder(context, maxAssetsCount), 66 | PickMethod.customFilterOptions(context, maxAssetsCount), 67 | PickMethod.preventGIFPicked(context, maxAssetsCount), 68 | PickMethod.keepScrollOffset( 69 | context: context, 70 | delegate: () => keepScrollDelegate!, 71 | onPermission: (PermissionState state) { 72 | keepScrollDelegate ??= DefaultAssetPickerBuilderDelegate( 73 | provider: keepScrollProvider, 74 | initialPermission: state, 75 | keepScrollOffset: true, 76 | ); 77 | }, 78 | onLongPress: () { 79 | keepScrollProvider.dispose(); 80 | keepScrollProvider = DefaultAssetPickerProvider(); 81 | keepScrollDelegate?.dispose(); 82 | keepScrollDelegate = null; 83 | ScaffoldMessenger.of(context).showSnackBar( 84 | const SnackBar(content: Text('Resources have been released')), 85 | ); 86 | }, 87 | ), 88 | PickMethod( 89 | icon: '🎚', 90 | name: context.l10n.pickMethodCustomImagePreviewThumbSizeName, 91 | description: 92 | context.l10n.pickMethodCustomImagePreviewThumbSizeDescription, 93 | method: (BuildContext context, List assets) { 94 | return AssetPicker.pickAssets( 95 | context, 96 | pickerConfig: AssetPickerConfig( 97 | maxAssets: maxAssetsCount, 98 | selectedAssets: assets, 99 | requestType: RequestType.image, 100 | gridThumbnailSize: const ThumbnailSize.square(80), 101 | previewThumbnailSize: const ThumbnailSize.square(150), 102 | ), 103 | ); 104 | }, 105 | ), 106 | ]; 107 | } 108 | 109 | @override 110 | bool get wantKeepAlive => true; 111 | } 112 | -------------------------------------------------------------------------------- /example/lib/pages/page_mixin.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 | import 'package:wechat_assets_picker/wechat_assets_picker.dart' 7 | show 8 | AssetEntity, 9 | DefaultAssetPickerProvider, 10 | DefaultAssetPickerBuilderDelegate; 11 | 12 | import '../constants/extensions.dart'; 13 | import '../constants/picker_method.dart'; 14 | import '../widgets/method_list_view.dart'; 15 | import '../widgets/selected_assets_list_view.dart'; 16 | 17 | @optionalTypeArgs 18 | mixin ExamplePageMixin on State { 19 | final ValueNotifier isDisplayingDetail = ValueNotifier(true); 20 | 21 | @override 22 | void dispose() { 23 | isDisplayingDetail.dispose(); 24 | super.dispose(); 25 | } 26 | 27 | int get maxAssetsCount; 28 | 29 | List assets = []; 30 | 31 | int get assetsLength => assets.length; 32 | 33 | List pickMethods(BuildContext context); 34 | 35 | String get noticeText; 36 | 37 | /// These fields are for the keep scroll position feature. 38 | late DefaultAssetPickerProvider keepScrollProvider = 39 | DefaultAssetPickerProvider(); 40 | DefaultAssetPickerBuilderDelegate? keepScrollDelegate; 41 | 42 | Future selectAssets(PickMethod model) async { 43 | final List? result = await model.method(context, assets); 44 | if (result != null) { 45 | assets = result.toList(); 46 | if (mounted) { 47 | setState(() {}); 48 | } 49 | } 50 | } 51 | 52 | void removeAsset(int index) { 53 | assets.removeAt(index); 54 | setState(() {}); 55 | } 56 | 57 | void onResult(List? result) { 58 | if (result != null && result != assets) { 59 | assets = result.toList(); 60 | if (mounted) { 61 | setState(() {}); 62 | } 63 | } 64 | } 65 | 66 | @override 67 | @mustCallSuper 68 | Widget build(BuildContext context) { 69 | super.build(context); 70 | return Column( 71 | children: [ 72 | Padding( 73 | padding: const EdgeInsets.all(20.0), 74 | child: Text(context.l10n.pickMethodNotice(noticeText)), 75 | ), 76 | Expanded( 77 | child: MethodListView( 78 | pickMethods: pickMethods(context), 79 | onSelectMethod: selectAssets, 80 | ), 81 | ), 82 | if (assets.isNotEmpty) 83 | SelectedAssetsListView( 84 | assets: assets, 85 | isDisplayingDetail: isDisplayingDetail, 86 | onResult: onResult, 87 | onRemoveAsset: removeAsset, 88 | ), 89 | ], 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /example/lib/pages/single_assets_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 'dart:io'; 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:wechat_assets_picker/wechat_assets_picker.dart' 9 | show AssetEntity; 10 | 11 | import '../constants/picker_method.dart'; 12 | import 'page_mixin.dart'; 13 | 14 | class SingleAssetPage extends StatefulWidget { 15 | const SingleAssetPage({super.key}); 16 | 17 | @override 18 | State createState() => _SingleAssetPageState(); 19 | } 20 | 21 | class _SingleAssetPageState extends State 22 | with AutomaticKeepAliveClientMixin, ExamplePageMixin { 23 | @override 24 | String get noticeText => 'lib/pages/single_assets_page.dart'; 25 | 26 | @override 27 | int get maxAssetsCount => 1; 28 | 29 | /// Check each method's source code for more details. 30 | @override 31 | List pickMethods(BuildContext context) { 32 | return [ 33 | PickMethod.common(context, maxAssetsCount), 34 | PickMethod.image(context, maxAssetsCount), 35 | PickMethod.video(context, maxAssetsCount), 36 | PickMethod.audio(context, maxAssetsCount), 37 | if (Platform.isIOS || Platform.isMacOS) 38 | PickMethod.livePhoto(context, maxAssetsCount), 39 | PickMethod.camera( 40 | context: context, 41 | maxAssetsCount: maxAssetsCount, 42 | handleResult: (BuildContext context, AssetEntity result) => 43 | Navigator.maybeOf(context)?.pop([result]), 44 | ), 45 | PickMethod.cameraAndStay(context, maxAssetsCount), 46 | PickMethod.changeLanguages(context, maxAssetsCount), 47 | PickMethod.threeItemsGrid(context, maxAssetsCount), 48 | PickMethod.prependItem(context, maxAssetsCount), 49 | PickMethod.customFilterOptions(context, maxAssetsCount), 50 | PickMethod.preventGIFPicked(context, maxAssetsCount), 51 | PickMethod.noPreview(context, maxAssetsCount), 52 | PickMethod.customizableTheme(context, maxAssetsCount), 53 | PickMethod.pathNameBuilder(context, maxAssetsCount), 54 | ]; 55 | } 56 | 57 | @override 58 | bool get wantKeepAlive => true; 59 | } 60 | -------------------------------------------------------------------------------- /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.maybeOf(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_assets_picker/wechat_assets_picker.dart' 7 | show AssetEntity, AssetEntityImageProvider, AssetType; 8 | 9 | class AssetWidgetBuilder extends StatelessWidget { 10 | const AssetWidgetBuilder({ 11 | super.key, 12 | required this.entity, 13 | required this.isDisplayingDetail, 14 | }); 15 | 16 | final AssetEntity entity; 17 | final bool isDisplayingDetail; 18 | 19 | Widget _audioAssetWidget(BuildContext context) { 20 | return ColoredBox( 21 | color: Theme.of(context).dividerColor, 22 | child: Stack( 23 | children: [ 24 | AnimatedPositionedDirectional( 25 | duration: kThemeAnimationDuration, 26 | top: 0.0, 27 | start: 0.0, 28 | end: 0.0, 29 | bottom: isDisplayingDetail ? 20.0 : 0.0, 30 | child: Center( 31 | child: Icon( 32 | Icons.audiotrack, 33 | size: isDisplayingDetail ? 24.0 : 16.0, 34 | ), 35 | ), 36 | ), 37 | AnimatedPositionedDirectional( 38 | duration: kThemeAnimationDuration, 39 | start: 0.0, 40 | end: 0.0, 41 | bottom: isDisplayingDetail ? 0.0 : -20.0, 42 | height: 20.0, 43 | child: Text( 44 | entity.title ?? '', 45 | style: const TextStyle(height: 1.0, fontSize: 10.0), 46 | maxLines: 1, 47 | overflow: TextOverflow.ellipsis, 48 | textAlign: TextAlign.center, 49 | ), 50 | ), 51 | ], 52 | ), 53 | ); 54 | } 55 | 56 | Widget _imageAssetWidget(BuildContext context) { 57 | return Image( 58 | image: AssetEntityImageProvider(entity, isOriginal: false), 59 | fit: BoxFit.cover, 60 | ); 61 | } 62 | 63 | Widget _videoAssetWidget(BuildContext context) { 64 | return Stack( 65 | children: [ 66 | Positioned.fill(child: _imageAssetWidget(context)), 67 | ColoredBox( 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 | return switch (entity.type) { 84 | AssetType.audio => _audioAssetWidget(context), 85 | AssetType.video => _videoAssetWidget(context), 86 | AssetType.image || AssetType.other => _imageAssetWidget(context), 87 | }; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /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 '../constants/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: Text( 44 | model.icon, 45 | style: const TextStyle(fontSize: 28.0), 46 | ), 47 | ), 48 | ), 49 | const SizedBox(width: 12.0), 50 | Expanded( 51 | child: Column( 52 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 53 | crossAxisAlignment: CrossAxisAlignment.start, 54 | children: [ 55 | Text( 56 | model.name, 57 | style: const TextStyle( 58 | fontSize: 18.0, 59 | fontWeight: FontWeight.bold, 60 | ), 61 | maxLines: 1, 62 | overflow: TextOverflow.ellipsis, 63 | ), 64 | const SizedBox(height: 5), 65 | Text( 66 | model.description, 67 | style: Theme.of(context).textTheme.bodySmall, 68 | ), 69 | ], 70 | ), 71 | ), 72 | const Icon(Icons.chevron_right, color: Colors.grey), 73 | ], 74 | ), 75 | ), 76 | ); 77 | } 78 | 79 | @override 80 | Widget build(BuildContext context) { 81 | return Padding( 82 | padding: const EdgeInsets.symmetric( 83 | horizontal: 10, 84 | ).copyWith(bottom: 10.0), 85 | child: Scrollbar( 86 | controller: _controller, 87 | thumbVisibility: true, 88 | radius: const Radius.circular(999), 89 | child: ListView.builder( 90 | controller: _controller, 91 | padding: const EdgeInsets.symmetric(vertical: 10.0), 92 | itemCount: widget.pickMethods.length, 93 | itemBuilder: methodItemBuilder, 94 | ), 95 | ), 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /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_assets_picker/wechat_assets_picker.dart' 7 | show AssetEntity, AssetPicker, AssetPickerViewer; 8 | import 'package:wechat_assets_picker_demo/constants/extensions.dart'; 9 | 10 | import '../main.dart' show themeColor; 11 | import 'asset_widget_builder.dart'; 12 | 13 | class SelectedAssetsListView extends StatelessWidget { 14 | const SelectedAssetsListView({ 15 | super.key, 16 | required this.assets, 17 | required this.isDisplayingDetail, 18 | required this.onResult, 19 | required this.onRemoveAsset, 20 | }); 21 | 22 | final List assets; 23 | final ValueNotifier isDisplayingDetail; 24 | final void Function(List? result) onResult; 25 | final void Function(int index) onRemoveAsset; 26 | 27 | Widget _selectedAssetWidget(BuildContext context, int index) { 28 | final AssetEntity asset = assets.elementAt(index); 29 | return ValueListenableBuilder( 30 | valueListenable: isDisplayingDetail, 31 | builder: (_, bool value, __) => GestureDetector( 32 | onTap: () async { 33 | if (value) { 34 | final List? result = 35 | await AssetPickerViewer.pushToViewer( 36 | context, 37 | currentIndex: index, 38 | previewAssets: assets, 39 | themeData: AssetPicker.themeData(themeColor), 40 | ); 41 | onResult(result); 42 | } 43 | }, 44 | child: RepaintBoundary( 45 | child: ClipRRect( 46 | borderRadius: BorderRadius.circular(8.0), 47 | child: AssetWidgetBuilder( 48 | entity: asset, 49 | isDisplayingDetail: value, 50 | ), 51 | ), 52 | ), 53 | ), 54 | ); 55 | } 56 | 57 | Widget _selectedAssetDeleteButton(BuildContext context, int index) { 58 | return GestureDetector( 59 | onTap: () => onRemoveAsset(index), 60 | child: DecoratedBox( 61 | decoration: BoxDecoration( 62 | borderRadius: BorderRadius.circular(4.0), 63 | color: Theme.of(context).canvasColor.withOpacity(0.5), 64 | ), 65 | child: const Icon(Icons.close, size: 18.0), 66 | ), 67 | ); 68 | } 69 | 70 | Widget selectedAssetsListView(BuildContext context) { 71 | return Expanded( 72 | child: ListView.builder( 73 | shrinkWrap: true, 74 | physics: const BouncingScrollPhysics(), 75 | padding: const EdgeInsets.symmetric(horizontal: 8.0), 76 | scrollDirection: Axis.horizontal, 77 | itemCount: assets.length, 78 | itemBuilder: (BuildContext context, int index) { 79 | return Padding( 80 | padding: const EdgeInsets.symmetric( 81 | horizontal: 8.0, 82 | vertical: 16.0, 83 | ), 84 | child: AspectRatio( 85 | aspectRatio: 1.0, 86 | child: Stack( 87 | children: [ 88 | Positioned.fill(child: _selectedAssetWidget(context, index)), 89 | ValueListenableBuilder( 90 | valueListenable: isDisplayingDetail, 91 | builder: (_, bool value, __) => AnimatedPositioned( 92 | duration: kThemeAnimationDuration, 93 | top: value ? 6.0 : -30.0, 94 | right: value ? 6.0 : -30.0, 95 | child: _selectedAssetDeleteButton(context, index), 96 | ), 97 | ), 98 | ], 99 | ), 100 | ), 101 | ); 102 | }, 103 | ), 104 | ); 105 | } 106 | 107 | @override 108 | Widget build(BuildContext context) { 109 | return ValueListenableBuilder( 110 | valueListenable: isDisplayingDetail, 111 | builder: (_, bool value, __) => AnimatedContainer( 112 | duration: kThemeChangeDuration, 113 | curve: Curves.easeInOut, 114 | height: assets.isNotEmpty 115 | ? value 116 | ? 120.0 117 | : 80.0 118 | : 40.0, 119 | child: Column( 120 | children: [ 121 | SizedBox( 122 | height: 20.0, 123 | child: GestureDetector( 124 | onTap: () { 125 | if (assets.isNotEmpty) { 126 | isDisplayingDetail.value = !isDisplayingDetail.value; 127 | } 128 | }, 129 | child: Row( 130 | mainAxisSize: MainAxisSize.min, 131 | children: [ 132 | Text(context.l10n.selectedAssetsText), 133 | if (assets.isNotEmpty) 134 | Padding( 135 | padding: const EdgeInsetsDirectional.only(start: 10.0), 136 | child: Icon( 137 | value ? Icons.arrow_downward : Icons.arrow_upward, 138 | size: 18.0, 139 | ), 140 | ), 141 | ], 142 | ), 143 | ), 144 | ), 145 | selectedAssetsListView(context), 146 | ], 147 | ), 148 | ), 149 | ); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /example/macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import package_info_plus 9 | import path_provider_foundation 10 | import photo_manager 11 | import video_player_avfoundation 12 | 13 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 14 | FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) 15 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 16 | PhotoManagerPlugin.register(with: registry.registrar(forPlugin: "PhotoManagerPlugin")) 17 | FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) 18 | } 19 | -------------------------------------------------------------------------------- /example/macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '11.0' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | end 35 | 36 | post_install do |installer| 37 | installer.pods_project.targets.each do |target| 38 | flutter_additional_macos_build_settings(target) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 64 | 66 | 72 | 73 | 74 | 75 | 81 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @main 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | 10 | override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { 11 | return true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /example/macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = Wechat Assets Picker Example 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.fluttercandies.wechatAssetsPickerExample 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2021 FlutterCandies. All rights reserved. 15 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /example/macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.client 10 | 11 | com.apple.security.network.server 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Wechat Assets Picker Example 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIconFile 12 | 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | $(FLUTTER_BUILD_NAME) 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSCameraUsageDescription 28 | Take a photo for display 29 | NSHumanReadableCopyright 30 | $(PRODUCT_COPYRIGHT) 31 | NSMainNibFile 32 | MainMenu 33 | NSMicrophoneUsageDescription 34 | Take a video for display 35 | NSPhotoLibraryUsageDescription 36 | Select your photos, videos, and audios 37 | NSPrincipalClass 38 | NSApplication 39 | 40 | 41 | -------------------------------------------------------------------------------- /example/macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: wechat_assets_picker_demo 2 | description: The demo project for the wechat_assets_picker package. 3 | version: 9.5.1+66 4 | publish_to: none 5 | 6 | environment: 7 | sdk: ^3.4.0 8 | flutter: '>=3.22.0' 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | flutter_localizations: 14 | sdk: flutter 15 | 16 | wechat_assets_picker: 17 | path: ../ 18 | wechat_camera_picker: ^4.2.0 19 | 20 | extended_image: any 21 | package_info_plus: '>=6.0.0 <9.0.0' 22 | path: ^1.8.0 23 | path_provider: ^2.0.15 24 | provider: any 25 | 26 | dev_dependencies: 27 | flutter_lints: any 28 | 29 | flutter: 30 | uses-material-design: true 31 | generate: true 32 | assets: 33 | - assets/ 34 | -------------------------------------------------------------------------------- /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:flutter/material.dart'; 6 | import 'package:photo_manager/photo_manager.dart'; 7 | 8 | import '../constants/typedefs.dart'; 9 | import '../delegates/asset_picker_text_delegate.dart'; 10 | import '../delegates/sort_path_delegate.dart'; 11 | import 'constants.dart'; 12 | import 'enums.dart'; 13 | 14 | class AssetPickerConfig { 15 | const AssetPickerConfig({ 16 | this.selectedAssets, 17 | this.maxAssets = defaultMaxAssetsCount, 18 | this.pageSize = defaultAssetsPerPage, 19 | this.gridThumbnailSize = defaultAssetGridPreviewSize, 20 | this.pathThumbnailSize = defaultPathThumbnailSize, 21 | this.previewThumbnailSize, 22 | this.requestType = RequestType.common, 23 | this.specialPickerType, 24 | this.keepScrollOffset = false, 25 | this.sortPathDelegate, 26 | this.sortPathsByModifiedDate = false, 27 | this.filterOptions, 28 | this.gridCount = 4, 29 | this.themeColor, 30 | this.pickerTheme, 31 | this.textDelegate, 32 | this.specialItemPosition = SpecialItemPosition.none, 33 | this.specialItemBuilder, 34 | this.loadingIndicatorBuilder, 35 | this.selectPredicate, 36 | this.shouldRevertGrid, 37 | this.limitedPermissionOverlayPredicate, 38 | this.pathNameBuilder, 39 | this.assetsChangeCallback, 40 | this.assetsChangeRefreshPredicate, 41 | this.shouldAutoplayPreview = false, 42 | this.dragToSelect, 43 | }) : assert( 44 | pickerTheme == null || themeColor == null, 45 | 'pickerTheme and themeColor cannot be set at the same time.', 46 | ), 47 | assert(maxAssets > 0, 'maxAssets must be greater than 0.'), 48 | assert(pageSize > 0, 'pageSize must be greater than 0.'), 49 | assert(gridCount > 0, 'gridCount must be greater than 0.'), 50 | assert( 51 | pageSize % gridCount == 0, 52 | 'pageSize must be a multiple of gridCount.', 53 | ), 54 | assert( 55 | specialPickerType != SpecialPickerType.wechatMoment || 56 | requestType == RequestType.common, 57 | 'SpecialPickerType.wechatMoment and requestType ' 58 | 'cannot be set at the same time.', 59 | ), 60 | assert( 61 | (specialItemBuilder == null && 62 | identical(specialItemPosition, SpecialItemPosition.none)) || 63 | (specialItemBuilder != null && 64 | !identical(specialItemPosition, SpecialItemPosition.none)), 65 | 'Custom item did not set properly.', 66 | ); 67 | 68 | /// Selected assets. 69 | /// 已选中的资源 70 | final List? selectedAssets; 71 | 72 | /// Maximum count for asset selection. 73 | /// 资源选择的最大数量 74 | final int maxAssets; 75 | 76 | /// Assets should be loaded per page. 77 | /// 资源选择的最大数量 78 | /// 79 | /// Use `null` to display all assets into a single grid. 80 | final int pageSize; 81 | 82 | /// Thumbnail size in the grid. 83 | /// 预览时网络的缩略图大小 84 | /// 85 | /// This only works on images and videos since other types does not have to 86 | /// request for the thumbnail data. The preview can speed up by reducing it. 87 | /// 该参数仅生效于图片和视频类型的资源,因为其他资源不需要请求缩略图数据。 88 | /// 预览图片的速度可以通过适当降低它的数值来提升。 89 | /// 90 | /// This cannot be `null` or a large value since you shouldn't use the 91 | /// original data for the grid. 92 | /// 该值不能为空或者非常大,因为在网格中使用原数据不是一个好的决定。 93 | final ThumbnailSize gridThumbnailSize; 94 | 95 | /// Thumbnail size for path selector. 96 | /// 路径选择器中缩略图的大小 97 | final ThumbnailSize pathThumbnailSize; 98 | 99 | /// Preview thumbnail size in the viewer. 100 | /// 预览时图片的缩略图大小 101 | /// 102 | /// This only works on images and videos since other types does not have to 103 | /// request for the thumbnail data. The preview can speed up by reducing it. 104 | /// 该参数仅生效于图片和视频类型的资源,因为其他资源不需要请求缩略图数据。 105 | /// 预览图片的速度可以通过适当降低它的数值来提升。 106 | /// 107 | /// Default is `null`, which will request the origin data. 108 | /// 默认为空,即读取原图。 109 | final ThumbnailSize? previewThumbnailSize; 110 | 111 | /// Request assets type. 112 | /// 请求的资源类型 113 | final RequestType requestType; 114 | 115 | /// The current special picker type for the picker. 116 | /// 当前特殊选择类型 117 | /// 118 | /// Several types which are special: 119 | /// * [SpecialPickerType.wechatMoment] When user selected video, 120 | /// no more images can be selected. 121 | /// * [SpecialPickerType.noPreview] Disable preview of asset; 122 | /// Clicking on an asset selects it. 123 | /// 124 | /// 这里包含一些特殊选择类型: 125 | /// * [SpecialPickerType.wechatMoment] 微信朋友圈模式。 126 | /// 当用户选择了视频,将不能选择图片。 127 | /// * [SpecialPickerType.noPreview] 禁用资源预览。 128 | /// 多选时单击资产将直接选中,单选时选中并返回。 129 | final SpecialPickerType? specialPickerType; 130 | 131 | /// Whether the picker should save the scroll offset between pushes and pops. 132 | /// 选择器是否可以从同样的位置开始选择 133 | final bool keepScrollOffset; 134 | 135 | /// @{macro wechat_assets_picker.delegates.SortPathDelegate} 136 | final SortPathDelegate? sortPathDelegate; 137 | 138 | /// {@template wechat_assets_picker.constants.AssetPickerConfig.sortPathsByModifiedDate} 139 | /// Whether to allow sort delegates to sort paths with 140 | /// [FilterOptionGroup.containsPathModified]. 141 | /// 是否结合 [FilterOptionGroup.containsPathModified] 进行路径排序 142 | /// {@endtemplate} 143 | final bool sortPathsByModifiedDate; 144 | 145 | /// Filter options for the picker. 146 | /// 选择器的筛选条件 147 | /// 148 | /// Will be merged into the base configuration. 149 | /// 将会与基础条件进行合并。 150 | final PMFilter? filterOptions; 151 | 152 | /// Assets count for the picker. 153 | /// 资源网格数 154 | final int gridCount; 155 | 156 | /// Main color for the picker. 157 | /// 选择器的主题色 158 | final Color? themeColor; 159 | 160 | /// Theme for the picker. 161 | /// 选择器的主题 162 | /// 163 | /// Usually the WeChat uses the dark version (dark background color) 164 | /// for the picker. However, some others want a light or a custom version. 165 | /// 通常情况下微信选择器使用的是暗色(暗色背景)的主题, 166 | /// 但某些情况下开发者需要亮色或自定义主题。 167 | final ThemeData? pickerTheme; 168 | 169 | final AssetPickerTextDelegate? textDelegate; 170 | 171 | /// Allow users set a special item in the picker with several positions. 172 | /// 允许用户在选择器中添加一个自定义item,并指定位置 173 | final SpecialItemPosition specialItemPosition; 174 | 175 | /// The widget builder for the the special item. 176 | /// 自定义item的构造方法 177 | final SpecialItemBuilder? specialItemBuilder; 178 | 179 | /// Indicates the loading status for the builder. 180 | /// 指示目前加载的状态 181 | final LoadingIndicatorBuilder? loadingIndicatorBuilder; 182 | 183 | /// {@macro wechat_assets_picker.AssetSelectPredicate} 184 | final AssetSelectPredicate? selectPredicate; 185 | 186 | /// Whether the assets grid should revert. 187 | /// 判断资源网格是否需要倒序排列 188 | /// 189 | /// [Null] means judging by Apple OS. 190 | /// 使用 [Null] 即使用是否为 Apple 系统进行判断。 191 | final bool? shouldRevertGrid; 192 | 193 | /// {@macro wechat_assets_picker.LimitedPermissionOverlayPredicate} 194 | final LimitedPermissionOverlayPredicate? limitedPermissionOverlayPredicate; 195 | 196 | /// {@macro wechat_assets_picker.PathNameBuilder} 197 | final PathNameBuilder? pathNameBuilder; 198 | 199 | /// {@macro wechat_assets_picker.AssetsChangeCallback} 200 | final AssetsChangeCallback? assetsChangeCallback; 201 | 202 | /// {@macro wechat_assets_picker.AssetsChangeRefreshPredicate} 203 | final AssetsChangeRefreshPredicate? 204 | assetsChangeRefreshPredicate; 205 | 206 | /// Whether the preview should auto play. 207 | /// 预览是否自动播放 208 | final bool shouldAutoplayPreview; 209 | 210 | /// {@template wechat_assets_picker.constants.AssetPickerConfig.dragToSelect} 211 | /// Whether assets selection can be done with drag gestures. 212 | /// 是否开启拖拽选择 213 | /// 214 | /// The feature enables by default if no accessibility service is being used. 215 | /// 在未使用辅助功能的情况下会默认启用该功能。 216 | /// {@endtemplate} 217 | final bool? dragToSelect; 218 | } 219 | -------------------------------------------------------------------------------- /lib/src/constants/constants.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:photo_manager/photo_manager.dart'; 6 | 7 | const String packageName = 'wechat_assets_picker'; 8 | 9 | const int defaultAssetsPerPage = 80; 10 | const int defaultMaxAssetsCount = 9; 11 | 12 | const ThumbnailSize defaultAssetGridPreviewSize = ThumbnailSize.square(200); 13 | const ThumbnailSize defaultPathThumbnailSize = ThumbnailSize.square(80); 14 | -------------------------------------------------------------------------------- /lib/src/constants/custom_scroll_physics.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/cupertino.dart'; 6 | import 'package:flutter/physics.dart' as physics show SpringDescription; 7 | 8 | class CustomBouncingScrollPhysics extends BouncingScrollPhysics { 9 | const CustomBouncingScrollPhysics({ 10 | super.parent, 11 | }); 12 | 13 | @override 14 | CustomBouncingScrollPhysics applyTo(ScrollPhysics? ancestor) { 15 | return CustomBouncingScrollPhysics(parent: buildParent(ancestor)); 16 | } 17 | 18 | @override 19 | physics.SpringDescription get spring { 20 | return physics.SpringDescription.withDampingRatio( 21 | mass: 0.5, 22 | stiffness: 400.0, 23 | ratio: 1.1, 24 | ); 25 | } 26 | } 27 | 28 | class CustomClampingScrollPhysics extends ClampingScrollPhysics { 29 | const CustomClampingScrollPhysics({ 30 | super.parent, 31 | }); 32 | 33 | @override 34 | CustomClampingScrollPhysics applyTo(ScrollPhysics? ancestor) { 35 | return CustomClampingScrollPhysics(parent: buildParent(ancestor)); 36 | } 37 | 38 | @override 39 | physics.SpringDescription get spring { 40 | return physics.SpringDescription.withDampingRatio( 41 | mass: 0.5, 42 | stiffness: 400.0, 43 | ratio: 1.1, 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /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 | /// Provide some special picker types to integrate 6 | /// un-common pick pattern. 7 | /// 提供一些特殊的选择器类型以整合非常规的选择行为。 8 | enum SpecialPickerType { 9 | /// WeChat Moments mode. 10 | /// 微信朋友圈模式 11 | /// 12 | /// The user can only select *one video* or *multiple images* at the same time, 13 | /// and those two asset types cannot be selected at the same time. 14 | /// 用户只可以选择 **一个视频** 或 **多个图片**,并且两种类型互斥。 15 | wechatMoment, 16 | 17 | /// Disable preview of assets. 18 | /// 禁用资源预览 19 | /// 20 | /// There is no preview mode when clicking grid items. 21 | /// In multiple select mode, any click (either on the select indicator or on 22 | /// the asset itself) will select the asset. 23 | /// In single select mode, any click directly selects the asset and returns. 24 | /// 用户在点击网格的 item 时无法进入预览。 25 | /// 在多选模式下无论点击选择指示还是 item 都将触发选择, 26 | /// 而在单选模式下将直接返回点击的资源。 27 | noPreview, 28 | } 29 | 30 | /// Provide an item slot for custom widget insertion. 31 | /// 提供一个自定义位置供特殊item放入资源列表中。 32 | enum SpecialItemPosition { 33 | /// Not insert to the list. 34 | /// 不放入列表 35 | none, 36 | 37 | /// Add as leading of the list. 38 | /// 在列表前放入 39 | prepend, 40 | 41 | /// Add as trailing of the list. 42 | /// 在列表后放入 43 | append, 44 | } 45 | -------------------------------------------------------------------------------- /lib/src/constants/typedefs.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:flutter/services.dart'; 8 | import 'package:flutter/widgets.dart'; 9 | import 'package:photo_manager/photo_manager.dart' show PermissionState; 10 | import 'package:provider/provider.dart'; 11 | 12 | /// Mirroring [ChangeNotifierProvider]. 13 | typedef CNP = ChangeNotifierProvider; 14 | 15 | /// {@template wechat_assets_picker.LoadingIndicatorBuilder} 16 | /// Build the loading indicator with the given `isAssetsEmpty`. 17 | /// 根据给定的 `isAssetsEmpty` 构建加载指示器。 18 | /// {@endtemplate} 19 | typedef LoadingIndicatorBuilder = Widget Function( 20 | BuildContext context, 21 | bool isAssetsEmpty, 22 | ); 23 | 24 | /// {@template wechat_asset_picker.SpecialItemBuilder} 25 | /// Build the special item with the given path and assets length. 26 | /// 根据给定的目录和资源数量构建特殊 item。 27 | /// {@endtemplate} 28 | typedef SpecialItemBuilder = Widget? Function( 29 | BuildContext context, 30 | Path? path, 31 | int length, 32 | ); 33 | 34 | /// {@template wechat_assets_picker.AssetSelectPredicate} 35 | /// Predicate whether an asset can be selected or unselected. 36 | /// 判断资源可否被选择。 37 | /// {@endtemplate} 38 | typedef AssetSelectPredicate = FutureOr Function( 39 | BuildContext context, 40 | Asset asset, 41 | bool isSelected, 42 | ); 43 | 44 | /// {@template wechat_assets_picker.LimitedPermissionOverlayPredicate} 45 | /// Predicate whether the limited permission overlay should be displayed. 46 | /// 判断有限的权限情况下是否展示提示页面。 47 | /// {@endtemplate} 48 | typedef LimitedPermissionOverlayPredicate = bool Function( 49 | PermissionState permissionState, 50 | ); 51 | 52 | /// {@template wechat_assets_picker.PathNameBuilder} 53 | /// Build customized path name. 54 | /// 构建自定义路径名称。 55 | /// {@endtemplate} 56 | typedef PathNameBuilder = String Function(Path path); 57 | 58 | /// {@template wechat_assets_picker.AssetsChangeCallback} 59 | /// The callback that will be called when the system notifies assets changes. 60 | /// 当系统通知资源变化时将调用的回调。 61 | /// {@endtemplate} 62 | typedef AssetsChangeCallback = void Function( 63 | PermissionState permission, 64 | MethodCall call, 65 | Path? path, 66 | ); 67 | 68 | /// {@template wechat_assets_picker.AssetsChangeRefreshPredicate} 69 | /// Whether assets changing should call refresh with the given `call` 70 | /// and the current selected `path`. 71 | /// 判断资源变化是否根据 `call` 和当前选中的 `path` 进行更新。 72 | /// {@endtemplate} 73 | typedef AssetsChangeRefreshPredicate = bool Function( 74 | PermissionState permission, 75 | MethodCall call, 76 | Path? path, 77 | ); 78 | -------------------------------------------------------------------------------- /lib/src/delegates/sort_path_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:photo_manager/photo_manager.dart'; 6 | 7 | import '../models/path_wrapper.dart'; 8 | 9 | /// @{template wechat_assets_picker.delegates.SortPathDelegate} 10 | /// Delegate to sort asset path entities. 11 | /// 用于资源路径排序的实现 12 | /// @{endtemplate} 13 | /// 14 | /// Define [sort] to sort the asset path list. 15 | /// Usually integrate with [List.sort]. 16 | /// 通过定义 [sort] 方法对资源路径列表进行排序。通常使用 [List.sort]。 17 | abstract class SortPathDelegate { 18 | const SortPathDelegate(); 19 | 20 | void sort(List> list); 21 | 22 | static const SortPathDelegate common = 23 | CommonSortPathDelegate(); 24 | } 25 | 26 | /// Common sort path delegate. 27 | /// 常用的路径排序实现 28 | /// 29 | /// This delegate will bring "Recent" (All photos), "Camera", "Screenshot(?s)" 30 | /// to the front of the paths list. 31 | /// 该实现会把“最近”、“相机”、“截图”排到列表头部。 32 | class CommonSortPathDelegate extends SortPathDelegate { 33 | const CommonSortPathDelegate(); 34 | 35 | @override 36 | void sort(List> list) { 37 | if (list.any( 38 | (PathWrapper e) => e.path.lastModified != null, 39 | )) { 40 | list.sort( 41 | (PathWrapper a, PathWrapper b) { 42 | if (a.path.lastModified == null || b.path.lastModified == null) { 43 | return 0; 44 | } 45 | if (b.path.lastModified!.isAfter(a.path.lastModified!)) { 46 | return 1; 47 | } 48 | return -1; 49 | }, 50 | ); 51 | } 52 | list.sort( 53 | (PathWrapper a, PathWrapper b) { 54 | if (a.path.isAll) { 55 | return -1; 56 | } 57 | if (b.path.isAll) { 58 | return 1; 59 | } 60 | if (_isCamera(a.path)) { 61 | return -1; 62 | } 63 | if (_isCamera(b.path)) { 64 | return 1; 65 | } 66 | if (_isScreenShot(a.path)) { 67 | return -1; 68 | } 69 | if (_isScreenShot(b.path)) { 70 | return 1; 71 | } 72 | return 0; 73 | }, 74 | ); 75 | } 76 | 77 | int otherSort(AssetPathEntity path1, AssetPathEntity path2) { 78 | return path1.name.compareTo(path2.name); 79 | } 80 | 81 | bool _isCamera(AssetPathEntity entity) { 82 | return entity.name == 'Camera'; 83 | } 84 | 85 | bool _isScreenShot(AssetPathEntity entity) { 86 | return entity.name == 'Screenshots' || entity.name == 'Screenshot'; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /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 'package:flutter/widgets.dart'; 6 | 7 | import '../delegates/asset_picker_text_delegate.dart'; 8 | import '../delegates/sort_path_delegate.dart'; 9 | 10 | /// Define an inner static singleton for picker libraries. 11 | class Singleton { 12 | const Singleton._(); 13 | 14 | static AssetPickerTextDelegate textDelegate = const AssetPickerTextDelegate(); 15 | static SortPathDelegate sortPathDelegate = SortPathDelegate.common; 16 | 17 | /// The last scroll position where the picker scrolled. 18 | /// 19 | /// See also: 20 | /// * [AssetPickerBuilderDelegate.keepScrollOffset] 21 | static ScrollPosition? scrollPosition; 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/models/path_wrapper.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:typed_data' as typed_data; 6 | 7 | import 'package:flutter/foundation.dart'; 8 | 9 | /// A wrapper that holds [Path] with it's nullable (late initialize) fields. 10 | /// 11 | /// The asset count usually backed by [AssetPathEntity.assetCountAsync], 12 | /// and the thumbnail usually backed by [AssetEntity.thumbnailData]. 13 | /// These methods are asynchronous and called separately for better performance. 14 | /// After calls, use [copyWith] to update paths to avoid unnecessary waits. 15 | @immutable 16 | class PathWrapper { 17 | const PathWrapper({ 18 | required this.path, 19 | this.assetCount, 20 | this.thumbnailData, 21 | }); 22 | 23 | /// Typically an [AssetPathEntity]. 24 | final Path path; 25 | 26 | /// The total asset count of the [path]. 27 | /// 28 | /// Nullability represents whether it's initialized. 29 | /// 30 | /// See also: 31 | /// * [AssetPathEntity.assetCountAsync] API document: 32 | /// https://pub.dev/documentation/photo_manager/latest/photo_manager/AssetPathEntity/assetCountAsync.html 33 | final int? assetCount; 34 | 35 | /// The thumbnail (first asset) data of the [path]. 36 | /// 37 | /// Nullability represents whether it's initialized. 38 | /// 39 | /// See also: 40 | /// * [AssetEntity.thumbnailData] API document: 41 | /// https://pub.dev/documentation/photo_manager/latest/photo_manager/AssetEntity/thumbnailData.html 42 | final typed_data.Uint8List? thumbnailData; 43 | 44 | /// Creates a modified copy of the object. 45 | /// 46 | /// Explicitly specified fields get the specified value, all other fields get 47 | /// the same value of the current object. 48 | PathWrapper copyWith({ 49 | int? assetCount, 50 | typed_data.Uint8List? thumbnailData, 51 | }) { 52 | return PathWrapper( 53 | path: path, 54 | assetCount: assetCount ?? this.assetCount, 55 | thumbnailData: thumbnailData ?? this.thumbnailData, 56 | ); 57 | } 58 | 59 | @override 60 | bool operator ==(Object other) { 61 | if (identical(this, other)) { 62 | return true; 63 | } 64 | if (other.runtimeType != runtimeType) { 65 | return false; 66 | } 67 | return other is PathWrapper && 68 | other.path == path && 69 | other.assetCount == assetCount && 70 | other.thumbnailData == thumbnailData; 71 | } 72 | 73 | @override 74 | int get hashCode => 75 | path.hashCode ^ assetCount.hashCode ^ thumbnailData.hashCode; 76 | 77 | @override 78 | String toString() { 79 | return '$runtimeType(' 80 | 'path: $path, ' 81 | 'assetCount: $assetCount, ' 82 | 'thumbnailData: ${thumbnailData?.runtimeType}' 83 | ')'; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/src/provider/asset_picker_viewer_provider.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 '../constants/constants.dart'; 8 | 9 | /// [ChangeNotifier] for assets picker viewer. 10 | /// 资源选择查看器的 provider model. 11 | class AssetPickerViewerProvider extends ChangeNotifier { 12 | /// Copy selected assets for editing when constructing. 13 | /// 构造时深拷贝已选择的资源集合,用于后续编辑。 14 | AssetPickerViewerProvider( 15 | List? assets, { 16 | this.maxAssets = defaultMaxAssetsCount, 17 | }) : assert(maxAssets > 0, 'maxAssets must be greater than 0.') { 18 | _currentlySelectedAssets = (assets ?? []).toList(); 19 | } 20 | 21 | /// Maximum count for asset selection. 22 | /// 资源选择的最大数量 23 | final int maxAssets; 24 | 25 | /// Selected assets in the viewer. 26 | /// 查看器中已选择的资源 27 | late List _currentlySelectedAssets; 28 | 29 | List get currentlySelectedAssets => _currentlySelectedAssets; 30 | 31 | set currentlySelectedAssets(List value) { 32 | if (value == _currentlySelectedAssets) { 33 | return; 34 | } 35 | _currentlySelectedAssets = value; 36 | notifyListeners(); 37 | } 38 | 39 | /// 选中资源是否为空 40 | bool get isSelectedNotEmpty => currentlySelectedAssets.isNotEmpty; 41 | 42 | /// Select asset. 43 | /// 选中资源 44 | void selectAsset(A item) { 45 | if (currentlySelectedAssets.length == maxAssets || 46 | currentlySelectedAssets.contains(item)) { 47 | return; 48 | } 49 | final List newList = _currentlySelectedAssets.toList()..add(item); 50 | currentlySelectedAssets = newList; 51 | } 52 | 53 | /// Un-select asset. 54 | /// 取消选中资源 55 | void unSelectAsset(A item) { 56 | if (currentlySelectedAssets.isEmpty || 57 | !currentlySelectedAssets.contains(item)) { 58 | return; 59 | } 60 | final List newList = _currentlySelectedAssets.toList()..remove(item); 61 | currentlySelectedAssets = newList; 62 | } 63 | 64 | @Deprecated('Use selectAsset instead. This will be removed in 10.0.0') 65 | void selectAssetEntity(A entity) { 66 | selectAsset(entity); 67 | } 68 | 69 | @Deprecated('Use unSelectAsset instead. This will be removed in 10.0.0') 70 | void unselectAssetEntity(A entity) { 71 | unSelectAsset(entity); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/src/widget/asset_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 | import 'dart:io'; 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter/services.dart'; 10 | import 'package:photo_manager/photo_manager.dart'; 11 | import 'package:wechat_picker_library/wechat_picker_library.dart'; 12 | 13 | import '../constants/config.dart'; 14 | import '../delegates/asset_picker_builder_delegate.dart'; 15 | import '../delegates/asset_picker_delegate.dart'; 16 | import '../provider/asset_picker_provider.dart'; 17 | import 'asset_picker_page_route.dart'; 18 | 19 | AssetPickerDelegate _pickerDelegate = const AssetPickerDelegate(); 20 | 21 | class AssetPicker extends StatefulWidget { 22 | const AssetPicker({ 23 | super.key, 24 | required this.permissionRequestOption, 25 | required this.builder, 26 | }); 27 | 28 | final PermissionRequestOption permissionRequestOption; 29 | final AssetPickerBuilderDelegate builder; 30 | 31 | /// Provide another [AssetPickerDelegate] which override with 32 | /// custom methods during handling the picking, 33 | /// e.g. to verify if arguments are properly set during picking calls. 34 | /// 35 | /// See also: 36 | /// * [AssetPickerDelegate] which is the default picker delegate. 37 | @visibleForTesting 38 | static void setPickerDelegate(AssetPickerDelegate delegate) { 39 | _pickerDelegate = delegate; 40 | } 41 | 42 | /// {@macro wechat_assets_picker.delegates.AssetPickerDelegate.permissionCheck} 43 | static Future permissionCheck({ 44 | PermissionRequestOption requestOption = const PermissionRequestOption(), 45 | }) { 46 | return _pickerDelegate.permissionCheck(requestOption: requestOption); 47 | } 48 | 49 | /// {@macro wechat_assets_picker.delegates.AssetPickerDelegate.pickAssets} 50 | static Future?> pickAssets( 51 | BuildContext context, { 52 | Key? key, 53 | PermissionRequestOption? permissionRequestOption, 54 | AssetPickerConfig pickerConfig = const AssetPickerConfig(), 55 | bool useRootNavigator = true, 56 | AssetPickerPageRouteBuilder>? pageRouteBuilder, 57 | }) { 58 | return _pickerDelegate.pickAssets( 59 | context, 60 | key: key, 61 | pickerConfig: pickerConfig, 62 | permissionRequestOption: permissionRequestOption, 63 | useRootNavigator: useRootNavigator, 64 | pageRouteBuilder: pageRouteBuilder, 65 | ); 66 | } 67 | 68 | /// {@macro wechat_assets_picker.delegates.AssetPickerDelegate.pickAssetsWithDelegate} 69 | static Future?> pickAssetsWithDelegate>( 71 | BuildContext context, { 72 | required AssetPickerBuilderDelegate delegate, 73 | PermissionRequestOption permissionRequestOption = 74 | const PermissionRequestOption(), 75 | Key? key, 76 | AssetPickerPageRouteBuilder>? pageRouteBuilder, 77 | bool useRootNavigator = true, 78 | }) { 79 | return _pickerDelegate.pickAssetsWithDelegate( 80 | context, 81 | key: key, 82 | delegate: delegate, 83 | permissionRequestOption: permissionRequestOption, 84 | useRootNavigator: useRootNavigator, 85 | pageRouteBuilder: pageRouteBuilder, 86 | ); 87 | } 88 | 89 | /// {@macro wechat_assets_picker.delegates.AssetPickerDelegate.registerObserve} 90 | static void registerObserve([ValueChanged? callback]) { 91 | _pickerDelegate.registerObserve(callback); 92 | } 93 | 94 | /// {@macro wechat_assets_picker.delegates.AssetPickerDelegate.unregisterObserve} 95 | static void unregisterObserve([ValueChanged? callback]) { 96 | _pickerDelegate.unregisterObserve(callback); 97 | } 98 | 99 | /// {@macro wechat_assets_picker.delegates.AssetPickerDelegate.themeData} 100 | static ThemeData themeData(Color? themeColor, {bool light = false}) { 101 | return _pickerDelegate.themeData(themeColor, light: light); 102 | } 103 | 104 | @override 105 | AssetPickerState createState() => 106 | AssetPickerState(); 107 | } 108 | 109 | class AssetPickerState extends State> 110 | with TickerProviderStateMixin, WidgetsBindingObserver { 111 | Completer? permissionStateLock; 112 | 113 | @override 114 | void initState() { 115 | super.initState(); 116 | WidgetsBinding.instance.addObserver(this); 117 | AssetPicker.registerObserve(_onAssetsUpdated); 118 | widget.builder.initState(this); 119 | } 120 | 121 | @override 122 | void didChangeAppLifecycleState(AppLifecycleState state) { 123 | super.didChangeAppLifecycleState(state); 124 | if (state == AppLifecycleState.resumed) { 125 | requestPermission().then((ps) { 126 | if (!mounted) { 127 | return; 128 | } 129 | widget.builder.permissionNotifier.value = ps; 130 | if (ps == PermissionState.limited && Platform.isAndroid) { 131 | _onAssetsUpdated(const MethodCall('')); 132 | } 133 | }); 134 | } 135 | } 136 | 137 | @override 138 | void dispose() { 139 | WidgetsBinding.instance.removeObserver(this); 140 | AssetPicker.unregisterObserve(_onAssetsUpdated); 141 | widget.builder.dispose(); 142 | super.dispose(); 143 | } 144 | 145 | Future _onAssetsUpdated(MethodCall call) { 146 | return widget.builder.onAssetsChanged(call, (VoidCallback fn) { 147 | fn(); 148 | safeSetState(() {}); 149 | }); 150 | } 151 | 152 | Future requestPermission() { 153 | if (permissionStateLock != null) { 154 | return permissionStateLock!.future; 155 | } 156 | final lock = Completer(); 157 | permissionStateLock = lock; 158 | Future( 159 | () => PhotoManager.requestPermissionExtend( 160 | requestOption: widget.permissionRequestOption, 161 | ), 162 | ).then(lock.complete).catchError(lock.completeError).whenComplete(() { 163 | permissionStateLock = null; 164 | }); 165 | return lock.future; 166 | } 167 | 168 | @override 169 | Widget build(BuildContext context) { 170 | return widget.builder.build(context); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /lib/src/widget/asset_picker_app_bar.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:ui' as ui; 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter/semantics.dart'; 9 | import 'package:flutter/services.dart'; 10 | import 'package:wechat_picker_library/wechat_picker_library.dart'; 11 | 12 | /// A custom app bar. 13 | /// 自定义的顶栏 14 | class AssetPickerAppBar extends StatelessWidget implements PreferredSizeWidget { 15 | const AssetPickerAppBar({ 16 | super.key, 17 | this.automaticallyImplyLeading = true, 18 | this.automaticallyImplyActions = true, 19 | this.brightness, 20 | this.title, 21 | this.leading, 22 | this.bottom, 23 | this.centerTitle = true, 24 | this.backgroundColor, 25 | this.elevation = 0, 26 | this.actions, 27 | this.actionsPadding, 28 | this.height, 29 | this.blurRadius = 0, 30 | this.iconTheme, 31 | this.semanticsBuilder, 32 | }); 33 | 34 | /// Title widget. Typically a [Text] widget. 35 | /// 标题部件 36 | final Widget? title; 37 | 38 | /// Leading widget. 39 | /// 头部部件 40 | final Widget? leading; 41 | 42 | /// Action widgets. 43 | /// 尾部操作部件 44 | final List? actions; 45 | 46 | /// This widget appears across the bottom of the app bar. 47 | /// 显示在顶栏下方的 widget 48 | final PreferredSizeWidget? bottom; 49 | 50 | /// Padding for actions. 51 | /// 尾部操作部分的内边距 52 | final EdgeInsetsGeometry? actionsPadding; 53 | 54 | /// Whether it should imply leading with [BackButton] automatically. 55 | /// 是否会自动检测并添加返回按钮至头部 56 | final bool automaticallyImplyLeading; 57 | 58 | /// Whether the [title] should be at the center. 59 | /// [title] 是否会在正中间 60 | final bool centerTitle; 61 | 62 | /// Whether it should imply actions size with [effectiveHeight]. 63 | /// 是否会自动使用 [effectiveHeight] 进行占位 64 | final bool automaticallyImplyActions; 65 | 66 | /// Background color. 67 | /// 背景颜色 68 | final Color? backgroundColor; 69 | 70 | /// Height of the app bar. 71 | /// 高度 72 | final double? height; 73 | 74 | /// Elevation to [Material]. 75 | /// 设置在 [Material] 的阴影 76 | final double elevation; 77 | 78 | /// The blur radius applies on the bar. 79 | /// 顶栏的高斯模糊值 80 | final double blurRadius; 81 | 82 | /// Set the brightness for the status bar's layer. 83 | /// 设置状态栏亮度层 84 | final Brightness? brightness; 85 | 86 | final IconThemeData? iconTheme; 87 | 88 | final Semantics Function(Widget appBar)? semanticsBuilder; 89 | 90 | bool canPop(BuildContext context) { 91 | if (Navigator.maybeOf(context)?.canPop() ?? false) { 92 | return automaticallyImplyLeading; 93 | } 94 | return false; 95 | } 96 | 97 | double get _barHeight => height ?? kToolbarHeight; 98 | 99 | double get effectiveHeight => 100 | _barHeight + (bottom?.preferredSize.height ?? 0); 101 | 102 | @override 103 | Size get preferredSize => Size.fromHeight(effectiveHeight); 104 | 105 | @override 106 | Widget build(BuildContext context) { 107 | final ThemeData theme = Theme.of(context); 108 | final AppBarTheme appBarTheme = theme.appBarTheme; 109 | 110 | final Widget? titleWidget; 111 | if (centerTitle) { 112 | titleWidget = Center(child: title); 113 | } else { 114 | titleWidget = title; 115 | } 116 | Widget child = Container( 117 | width: double.maxFinite, 118 | height: _barHeight + MediaQuery.paddingOf(context).top, 119 | padding: EdgeInsets.only(top: MediaQuery.paddingOf(context).top), 120 | child: Stack( 121 | children: [ 122 | if (canPop(context)) 123 | PositionedDirectional( 124 | top: 0.0, 125 | bottom: 0.0, 126 | child: IconTheme.merge( 127 | data: appBarTheme.iconTheme ?? theme.iconTheme, 128 | child: leading ?? const BackButton(), 129 | ), 130 | ), 131 | if (titleWidget != null) 132 | PositionedDirectional( 133 | top: 0.0, 134 | bottom: 0.0, 135 | start: canPop(context) ? _barHeight : 0.0, 136 | end: automaticallyImplyActions ? _barHeight : 0.0, 137 | child: Align( 138 | alignment: centerTitle 139 | ? Alignment.center 140 | : AlignmentDirectional.centerStart, 141 | child: DefaultTextStyle( 142 | style: appBarTheme.titleTextStyle ?? 143 | theme.textTheme.titleLarge!.copyWith(fontSize: 23.0), 144 | maxLines: 1, 145 | softWrap: false, 146 | overflow: TextOverflow.ellipsis, 147 | child: titleWidget, 148 | ), 149 | ), 150 | ), 151 | if (canPop(context) && (actions?.isEmpty ?? true)) 152 | SizedBox(width: _barHeight) 153 | else if (actions?.isNotEmpty ?? false) 154 | PositionedDirectional( 155 | top: 0.0, 156 | end: 0.0, 157 | height: _barHeight, 158 | child: IconTheme.merge( 159 | data: appBarTheme.actionsIconTheme ?? theme.iconTheme, 160 | child: Padding( 161 | padding: actionsPadding ?? EdgeInsets.zero, 162 | child: Row( 163 | mainAxisSize: MainAxisSize.min, 164 | children: actions!, 165 | ), 166 | ), 167 | ), 168 | ), 169 | ], 170 | ), 171 | ); 172 | 173 | if (bottom != null) { 174 | child = Column( 175 | mainAxisSize: MainAxisSize.min, 176 | children: [child, bottom!], 177 | ); 178 | } 179 | 180 | // Allow custom blur radius using [ui.ImageFilter.blur]. 181 | if (blurRadius > 0.0) { 182 | child = ClipRect( 183 | child: BackdropFilter( 184 | filter: ui.ImageFilter.blur(sigmaX: blurRadius, sigmaY: blurRadius), 185 | child: child, 186 | ), 187 | ); 188 | } 189 | 190 | /// Apply the icon theme data. 191 | child = IconTheme.merge( 192 | data: iconTheme ?? appBarTheme.iconTheme ?? theme.iconTheme, 193 | child: child, 194 | ); 195 | 196 | // Set [SystemUiOverlayStyle] according to the brightness. 197 | final Color effectiveBackgroundColor = backgroundColor ?? 198 | appBarTheme.backgroundColor ?? 199 | theme.colorScheme.surface; 200 | final Brightness effectiveBrightness = brightness ?? 201 | appBarTheme.systemOverlayStyle?.statusBarBrightness ?? 202 | theme.brightness; 203 | final SystemUiOverlayStyle overlayStyle = appBarTheme.systemOverlayStyle ?? 204 | SystemUiOverlayStyle( 205 | statusBarColor: effectiveBackgroundColor, 206 | systemNavigationBarIconBrightness: Brightness.light, 207 | statusBarIconBrightness: effectiveBrightness.reverse, 208 | statusBarBrightness: effectiveBrightness, 209 | ); 210 | child = AnnotatedRegion( 211 | value: overlayStyle, 212 | child: child, 213 | ); 214 | 215 | final Widget result = Material( 216 | // Wrap to ensure the child rendered correctly. 217 | color: Color.lerp( 218 | effectiveBackgroundColor, 219 | Colors.transparent, 220 | blurRadius > 0.0 ? 0.1 : 0.0, 221 | ), 222 | elevation: elevation, 223 | child: child, 224 | ); 225 | return semanticsBuilder?.call(result) ?? 226 | Semantics( 227 | sortKey: const OrdinalSortKey(0), 228 | explicitChildNodes: true, 229 | child: result, 230 | ); 231 | } 232 | } 233 | 234 | /// Wrapper for [AssetPickerAppBar]. Avoid elevation covered by body. 235 | /// 顶栏封装。防止内容块层级高于顶栏导致遮挡阴影。 236 | class AssetPickerAppBarWrapper extends StatelessWidget { 237 | const AssetPickerAppBarWrapper({ 238 | super.key, 239 | required this.appBar, 240 | required this.body, 241 | }); 242 | 243 | final AssetPickerAppBar appBar; 244 | final Widget body; 245 | 246 | @override 247 | Widget build(BuildContext context) { 248 | return Material( 249 | type: MaterialType.transparency, 250 | child: Stack( 251 | children: [ 252 | Positioned.fill( 253 | top: 254 | MediaQuery.paddingOf(context).top + appBar.preferredSize.height, 255 | child: MediaQuery.removePadding( 256 | context: context, 257 | removeTop: true, 258 | child: body, 259 | ), 260 | ), 261 | Positioned.fill(bottom: null, child: appBar), 262 | ], 263 | ), 264 | ); 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /lib/src/widget/asset_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 | /// Build [AssetPickerPageRoute] with the given generic type. 8 | /// 构建匹配泛型的 [AssetPickerPageRoute] 9 | typedef AssetPickerPageRouteBuilder = AssetPickerPageRoute Function( 10 | Widget picker, 11 | ); 12 | 13 | /// Built a slide page transition for the picker. 14 | /// 为选择器构造一个上下进出的页面过渡动画 15 | class AssetPickerPageRoute extends PageRoute { 16 | AssetPickerPageRoute({ 17 | required this.builder, 18 | this.transitionCurve = Curves.easeIn, 19 | this.transitionDuration = const Duration(milliseconds: 250), 20 | this.barrierColor, 21 | this.barrierDismissible = false, 22 | this.barrierLabel, 23 | this.maintainState = true, 24 | this.opaque = true, 25 | this.canTransitionFromPredicate, 26 | super.settings, 27 | }); 28 | 29 | final WidgetBuilder builder; 30 | 31 | final Curve transitionCurve; 32 | @override 33 | final Duration transitionDuration; 34 | 35 | @override 36 | final Color? barrierColor; 37 | @override 38 | final bool barrierDismissible; 39 | @override 40 | final String? barrierLabel; 41 | @override 42 | final bool opaque; 43 | @override 44 | final bool maintainState; 45 | 46 | final bool Function(TransitionRoute)? canTransitionFromPredicate; 47 | 48 | @override 49 | bool canTransitionFrom(TransitionRoute previousRoute) => 50 | canTransitionFromPredicate?.call(previousRoute) ?? false; 51 | 52 | @override 53 | Widget buildPage( 54 | BuildContext context, 55 | Animation animation, 56 | Animation secondaryAnimation, 57 | ) { 58 | return builder(context); 59 | } 60 | 61 | @override 62 | Widget buildTransitions( 63 | BuildContext context, 64 | Animation animation, 65 | Animation secondaryAnimation, 66 | Widget child, 67 | ) { 68 | return SlideTransition( 69 | position: Tween( 70 | begin: const Offset(0, 1), 71 | end: Offset.zero, 72 | ).animate( 73 | CurvedAnimation(curve: transitionCurve, parent: animation), 74 | ), 75 | child: ClipRect(child: child), // Clip the overflowed part. 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/src/widget/asset_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:flutter/material.dart'; 8 | import 'package:photo_manager/photo_manager.dart'; 9 | 10 | import '../constants/constants.dart'; 11 | import '../constants/enums.dart'; 12 | import '../constants/typedefs.dart'; 13 | import '../delegates/asset_picker_viewer_builder_delegate.dart'; 14 | import '../provider/asset_picker_provider.dart'; 15 | import '../provider/asset_picker_viewer_provider.dart'; 16 | import 'asset_picker.dart'; 17 | 18 | class AssetPickerViewer extends StatefulWidget { 19 | const AssetPickerViewer({ 20 | super.key, 21 | required this.builder, 22 | }); 23 | 24 | final AssetPickerViewerBuilderDelegate builder; 25 | 26 | @override 27 | AssetPickerViewerState createState() => 28 | AssetPickerViewerState(); 29 | 30 | /// Static method to push with the navigator. 31 | /// 跳转至选择预览的静态方法 32 | static Future?> pushToViewer( 33 | BuildContext context, { 34 | int currentIndex = 0, 35 | required List previewAssets, 36 | required ThemeData themeData, 37 | DefaultAssetPickerProvider? selectorProvider, 38 | ThumbnailSize? previewThumbnailSize, 39 | List? selectedAssets, 40 | SpecialPickerType? specialPickerType, 41 | int? maxAssets, 42 | bool shouldReversePreview = false, 43 | AssetSelectPredicate? selectPredicate, 44 | PermissionRequestOption permissionRequestOption = 45 | const PermissionRequestOption(), 46 | bool shouldAutoplayPreview = false, 47 | }) async { 48 | if (previewAssets.isEmpty) { 49 | throw StateError('Previewing empty assets is not allowed.'); 50 | } 51 | await AssetPicker.permissionCheck(requestOption: permissionRequestOption); 52 | final Widget viewer = AssetPickerViewer( 53 | builder: DefaultAssetPickerViewerBuilderDelegate( 54 | currentIndex: currentIndex, 55 | previewAssets: previewAssets, 56 | provider: selectedAssets != null 57 | ? AssetPickerViewerProvider( 58 | selectedAssets, 59 | maxAssets: maxAssets ?? 60 | selectorProvider?.maxAssets ?? 61 | defaultMaxAssetsCount, 62 | ) 63 | : null, 64 | themeData: themeData, 65 | previewThumbnailSize: previewThumbnailSize, 66 | specialPickerType: specialPickerType, 67 | selectedAssets: selectedAssets, 68 | selectorProvider: selectorProvider, 69 | maxAssets: maxAssets, 70 | shouldReversePreview: shouldReversePreview, 71 | selectPredicate: selectPredicate, 72 | shouldAutoplayPreview: shouldAutoplayPreview, 73 | ), 74 | ); 75 | final PageRouteBuilder> pageRoute = 76 | PageRouteBuilder>( 77 | pageBuilder: (_, __, ___) => viewer, 78 | transitionsBuilder: (_, Animation animation, __, Widget child) { 79 | return FadeTransition(opacity: animation, child: child); 80 | }, 81 | ); 82 | final List? result = 83 | await Navigator.maybeOf(context)?.push>(pageRoute); 84 | return result; 85 | } 86 | 87 | /// Call the viewer with provided delegate and provider. 88 | /// 通过指定的 [delegate] 调用查看器 89 | static Future?> pushToViewerWithDelegate( 90 | BuildContext context, { 91 | required AssetPickerViewerBuilderDelegate delegate, 92 | PermissionRequestOption permissionRequestOption = 93 | const PermissionRequestOption(), 94 | }) async { 95 | await AssetPicker.permissionCheck(requestOption: permissionRequestOption); 96 | final Widget viewer = AssetPickerViewer(builder: delegate); 97 | final PageRouteBuilder> pageRoute = PageRouteBuilder>( 98 | pageBuilder: (_, __, ___) => viewer, 99 | transitionsBuilder: (_, Animation animation, __, Widget child) { 100 | return FadeTransition(opacity: animation, child: child); 101 | }, 102 | ); 103 | final List? result = 104 | await Navigator.maybeOf(context)?.push>(pageRoute); 105 | return result; 106 | } 107 | } 108 | 109 | class AssetPickerViewerState 110 | extends State> 111 | with TickerProviderStateMixin { 112 | AssetPickerViewerBuilderDelegate get builder => widget.builder; 113 | 114 | @override 115 | void initState() { 116 | super.initState(); 117 | builder.initStateAndTicker(this, this); 118 | } 119 | 120 | @override 121 | void didUpdateWidget(covariant AssetPickerViewer oldWidget) { 122 | super.didUpdateWidget(oldWidget); 123 | builder.didUpdateViewer(this, oldWidget, widget); 124 | } 125 | 126 | @override 127 | void dispose() { 128 | builder.dispose(); 129 | super.dispose(); 130 | } 131 | 132 | @override 133 | Widget build(BuildContext context) { 134 | return builder.build(context); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /lib/src/widget/builder/asset_entity_grid_item_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:extended_image/extended_image.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:photo_manager_image_provider/photo_manager_image_provider.dart'; 8 | import 'package:wechat_picker_library/wechat_picker_library.dart'; 9 | 10 | import '../../internals/singleton.dart'; 11 | 12 | class AssetEntityGridItemBuilder extends StatefulWidget { 13 | const AssetEntityGridItemBuilder({ 14 | super.key, 15 | required this.image, 16 | required this.failedItemBuilder, 17 | }); 18 | 19 | final AssetEntityImageProvider image; 20 | final WidgetBuilder failedItemBuilder; 21 | 22 | @override 23 | AssetEntityGridItemWidgetState createState() => 24 | AssetEntityGridItemWidgetState(); 25 | } 26 | 27 | class AssetEntityGridItemWidgetState extends State { 28 | Widget? child; 29 | 30 | Widget get newChild { 31 | return ExtendedImage( 32 | image: widget.image, 33 | fit: BoxFit.cover, 34 | loadStateChanged: (ExtendedImageState state) => 35 | switch (state.extendedImageLoadState) { 36 | LoadState.loading => const ColoredBox(color: Color(0x10ffffff)), 37 | LoadState.completed => RepaintBoundary(child: state.completedWidget), 38 | LoadState.failed => widget.failedItemBuilder(context), 39 | }, 40 | ); 41 | } 42 | 43 | /// Item widgets when the thumbnail data load failed. 44 | /// 资源缩略数据加载失败时使用的部件 45 | Widget failedItemBuilder(BuildContext context) { 46 | return Center( 47 | child: ScaleText( 48 | Singleton.textDelegate.loadFailed, 49 | textAlign: TextAlign.center, 50 | style: const TextStyle(fontSize: 18.0), 51 | semanticsLabel: Singleton.textDelegate.semanticsTextDelegate.loadFailed, 52 | ), 53 | ); 54 | } 55 | 56 | @override 57 | Widget build(BuildContext context) { 58 | child ??= newChild; 59 | return child!; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/src/widget/builder/audio_page_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 'dart:async'; 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:photo_manager/photo_manager.dart'; 9 | import 'package:video_player/video_player.dart'; 10 | import 'package:wechat_picker_library/wechat_picker_library.dart'; 11 | 12 | import '../../constants/constants.dart'; 13 | import '../../internals/singleton.dart'; 14 | 15 | class AudioPageBuilder extends StatefulWidget { 16 | const AudioPageBuilder({ 17 | super.key, 18 | required this.asset, 19 | this.shouldAutoplayPreview = false, 20 | }); 21 | 22 | /// Asset currently displayed. 23 | /// 展示的资源 24 | final AssetEntity asset; 25 | 26 | /// Whether the preview should auto play. 27 | /// 预览是否自动播放 28 | final bool shouldAutoplayPreview; 29 | 30 | @override 31 | State createState() => _AudioPageBuilderState(); 32 | } 33 | 34 | class _AudioPageBuilderState extends State { 35 | /// A [StreamController] for current position of the [_controller]. 36 | /// 控制器当前的播放进度 37 | final StreamController durationStreamController = 38 | StreamController.broadcast(); 39 | 40 | /// Create a [VideoPlayerController] instance for the page builder state. 41 | /// 创建一个 [VideoPlayerController] 的实例 42 | VideoPlayerController get controller => _controller!; 43 | VideoPlayerController? _controller; 44 | 45 | /// Whether the audio loaded. 46 | /// 音频是否已经加载完成 47 | bool isLoaded = false; 48 | 49 | /// Whether the player is playing. 50 | /// 播放器是否在播放 51 | bool isPlaying = false; 52 | 53 | /// Whether the controller is playing. 54 | /// 播放控制器是否在播放 55 | bool get isControllerPlaying => _controller?.value.isPlaying == true; 56 | 57 | /// Duration of the audio. 58 | /// 音频的时长 59 | Duration assetDuration = Duration.zero; 60 | 61 | @override 62 | void initState() { 63 | super.initState(); 64 | openAudioFile(); 65 | } 66 | 67 | @override 68 | void didUpdateWidget(AudioPageBuilder oldWidget) { 69 | super.didUpdateWidget(oldWidget); 70 | if (widget.asset != oldWidget.asset) { 71 | _controller 72 | ?..removeListener(audioPlayerListener) 73 | ..pause() 74 | ..dispose(); 75 | isLoaded = false; 76 | isPlaying = false; 77 | assetDuration = Duration.zero; 78 | openAudioFile(); 79 | } 80 | } 81 | 82 | @override 83 | void dispose() { 84 | /// Stop and dispose player instance to stop playing 85 | /// when dispose (e.g. page switched). 86 | /// 状态销毁时停止并销毁实例(例如页面切换时) 87 | _controller 88 | ?..removeListener(audioPlayerListener) 89 | ..pause() 90 | ..dispose(); 91 | super.dispose(); 92 | } 93 | 94 | /// Load content url from the asset. 95 | /// 通过content地址加载资源 96 | Future openAudioFile() async { 97 | try { 98 | final String? url = await widget.asset.getMediaUrl(); 99 | assetDuration = Duration(seconds: widget.asset.duration); 100 | _controller = VideoPlayerController.networkUrl(Uri.parse(url!)); 101 | await controller.initialize(); 102 | controller.addListener(audioPlayerListener); 103 | if (widget.shouldAutoplayPreview) { 104 | controller.play(); 105 | } 106 | } catch (e, s) { 107 | FlutterError.presentError( 108 | FlutterErrorDetails( 109 | exception: e, 110 | stack: s, 111 | library: packageName, 112 | silent: true, 113 | ), 114 | ); 115 | } finally { 116 | isLoaded = true; 117 | safeSetState(() {}); 118 | } 119 | } 120 | 121 | /// Listener for the player. 122 | /// 播放器的监听方法 123 | void audioPlayerListener() { 124 | if (isControllerPlaying != isPlaying) { 125 | isPlaying = isControllerPlaying; 126 | safeSetState(() {}); 127 | } 128 | 129 | /// Add the current position into the stream. 130 | durationStreamController.add(controller.value.position); 131 | } 132 | 133 | void playButtonCallback() { 134 | if (isPlaying) { 135 | controller.pause(); 136 | } else { 137 | controller.play(); 138 | } 139 | } 140 | 141 | /// Title widget. 142 | /// 标题组件 143 | Widget get titleWidget { 144 | // Excluding audio title from semantics since the label already includes. 145 | return ExcludeSemantics( 146 | child: ScaleText( 147 | widget.asset.title ?? '', 148 | style: const TextStyle(fontSize: 20, fontWeight: FontWeight.normal), 149 | ), 150 | ); 151 | } 152 | 153 | /// Button to control audio play/pause. 154 | /// 控制音频播放或暂停的按钮 155 | Widget get audioControlButton { 156 | return GestureDetector( 157 | onTap: playButtonCallback, 158 | child: Container( 159 | margin: const EdgeInsets.all(20), 160 | decoration: const BoxDecoration( 161 | boxShadow: [BoxShadow(color: Colors.black12)], 162 | shape: BoxShape.circle, 163 | ), 164 | child: Icon( 165 | isPlaying ? Icons.pause_circle_outline : Icons.play_circle_filled, 166 | size: 70, 167 | ), 168 | ), 169 | ); 170 | } 171 | 172 | /// Duration indicator for the audio. 173 | /// 音频的时长指示器 174 | Widget get durationIndicator { 175 | final String Function(Duration) durationBuilder = 176 | Singleton.textDelegate.durationIndicatorBuilder; 177 | final String Function(Duration) semanticsDurationBuilder = 178 | Singleton.textDelegate.semanticsTextDelegate.durationIndicatorBuilder; 179 | return StreamBuilder( 180 | initialData: Duration.zero, 181 | stream: durationStreamController.stream, 182 | builder: (BuildContext _, AsyncSnapshot data) { 183 | return ScaleText( 184 | '${durationBuilder(data.data!)} / ${durationBuilder(assetDuration)}', 185 | style: const TextStyle( 186 | fontSize: 20, 187 | fontWeight: FontWeight.normal, 188 | ), 189 | semanticsLabel: '${semanticsDurationBuilder(data.data!)}' 190 | ' / ' 191 | '${semanticsDurationBuilder(assetDuration)}', 192 | ); 193 | }, 194 | ); 195 | } 196 | 197 | @override 198 | Widget build(BuildContext context) { 199 | return Semantics( 200 | onLongPress: playButtonCallback, 201 | onLongPressHint: 202 | Singleton.textDelegate.semanticsTextDelegate.sActionPlayHint, 203 | child: ColoredBox( 204 | color: context.theme.colorScheme.background, 205 | child: isLoaded 206 | ? Column( 207 | mainAxisAlignment: MainAxisAlignment.center, 208 | children: [ 209 | titleWidget, 210 | audioControlButton, 211 | durationIndicator, 212 | ], 213 | ) 214 | : const SizedBox.shrink(), 215 | ), 216 | ); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /lib/src/widget/builder/fade_image_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 | 7 | class FadeImageBuilder extends StatelessWidget { 8 | const FadeImageBuilder({super.key, required this.child}); 9 | 10 | final Widget child; 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return TweenAnimationBuilder( 15 | tween: Tween(begin: 0, end: 1), 16 | duration: const Duration(milliseconds: 150), 17 | builder: (_, double value, Widget? w) => Opacity( 18 | opacity: value, 19 | child: w, 20 | ), 21 | child: child, 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/wechat_assets_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 | // ignore: unnecessary_library_name 6 | library wechat_assets_picker; 7 | 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/constants.dart' hide packageName; 13 | export 'src/constants/enums.dart'; 14 | export 'src/constants/typedefs.dart'; 15 | 16 | export 'src/delegates/asset_picker_builder_delegate.dart'; 17 | export 'src/delegates/asset_picker_delegate.dart'; 18 | export 'src/delegates/asset_picker_text_delegate.dart'; 19 | export 'src/delegates/asset_picker_viewer_builder_delegate.dart'; 20 | export 'src/delegates/sort_path_delegate.dart'; 21 | 22 | export 'src/models/path_wrapper.dart'; 23 | 24 | export 'src/provider/asset_picker_provider.dart'; 25 | export 'src/provider/asset_picker_viewer_provider.dart'; 26 | 27 | export 'src/widget/asset_picker.dart'; 28 | export 'src/widget/asset_picker_app_bar.dart'; 29 | export 'src/widget/asset_picker_page_route.dart'; 30 | export 'src/widget/asset_picker_viewer.dart'; 31 | export 'src/widget/builder/asset_entity_grid_item_builder.dart'; 32 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: wechat_assets_picker 2 | version: 9.5.1 3 | description: | 4 | An image picker (also with videos and audio) 5 | for Flutter projects based on WeChat's UI, 6 | with full support for customization. 7 | topics: 8 | - picker 9 | - image 10 | - audio 11 | - video 12 | - wechat 13 | 14 | repository: https://github.com/fluttercandies/flutter_wechat_assets_picker 15 | issue_tracker: https://github.com/fluttercandies/flutter_wechat_assets_picker/issues 16 | 17 | environment: 18 | sdk: ^3.4.0 19 | flutter: '>=3.22.0' 20 | 21 | dependencies: 22 | flutter: 23 | sdk: flutter 24 | 25 | wechat_picker_library: ^1.0.5 26 | 27 | extended_image: '>=8.3.0 <11.0.0' 28 | photo_manager: ^3.5.0 29 | photo_manager_image_provider: ^2.2.0 30 | provider: ^6.0.5 31 | video_player: ^2.7.0 32 | visibility_detector: ^0.4.0 33 | 34 | dev_dependencies: 35 | flutter_lints: any 36 | flutter_localizations: 37 | sdk: flutter 38 | flutter_test: 39 | sdk: flutter 40 | 41 | flutter: 42 | assets: 43 | - 'assets/icon/' 44 | 45 | screenshots: 46 | - description: 'Screenshot 1' 47 | path: screenshots/README_1.webp 48 | - description: 'Screenshot 2' 49 | path: screenshots/README_2.webp 50 | - description: 'Screenshot 3' 51 | path: screenshots/README_3.webp 52 | - description: 'Screenshot 4' 53 | path: screenshots/README_4.webp 54 | - description: 'Screenshot 5' 55 | path: screenshots/README_5.webp 56 | - description: 'Screenshot 6' 57 | path: screenshots/README_6.webp 58 | - description: 'Screenshot 7' 59 | path: screenshots/README_7.webp 60 | - description: 'Screenshot 8' 61 | path: screenshots/README_8.webp 62 | - description: 'Screenshot 9' 63 | path: screenshots/README_9.webp 64 | -------------------------------------------------------------------------------- /screenshots/README_1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/screenshots/README_1.webp -------------------------------------------------------------------------------- /screenshots/README_2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/screenshots/README_2.webp -------------------------------------------------------------------------------- /screenshots/README_3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/screenshots/README_3.webp -------------------------------------------------------------------------------- /screenshots/README_4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/screenshots/README_4.webp -------------------------------------------------------------------------------- /screenshots/README_5.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/screenshots/README_5.webp -------------------------------------------------------------------------------- /screenshots/README_6.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/screenshots/README_6.webp -------------------------------------------------------------------------------- /screenshots/README_7.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/screenshots/README_7.webp -------------------------------------------------------------------------------- /screenshots/README_8.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/screenshots/README_8.webp -------------------------------------------------------------------------------- /screenshots/README_9.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluttercandies/flutter_wechat_assets_picker/7f06b77559b062e6683b920d1fbc59ac213ebb99/screenshots/README_9.webp -------------------------------------------------------------------------------- /test/config_test.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_test/flutter_test.dart'; 7 | import 'package:wechat_assets_picker/wechat_assets_picker.dart'; 8 | 9 | import 'test_utils.dart'; 10 | 11 | void main() { 12 | PhotoManager.withPlugin(TestPhotoManagerPlugin()); 13 | AssetPicker.setPickerDelegate(TestAssetPickerDelegate()); 14 | 15 | group('PathNameBuilder', () { 16 | testWidgets('called correctly', (WidgetTester tester) async { 17 | await tester.pumpWidget( 18 | defaultPickerTestApp( 19 | onButtonPressed: (BuildContext context) { 20 | AssetPicker.pickAssets( 21 | context, 22 | pickerConfig: AssetPickerConfig( 23 | pathNameBuilder: (AssetPathEntity p) => 'testPathNameBuilder', 24 | ), 25 | ); 26 | }, 27 | ), 28 | ); 29 | await tester.tap(defaultButtonFinder); 30 | await tester.pumpAndSettle(); 31 | await tester.tap(find.byIcon(Icons.keyboard_arrow_down)); 32 | await tester.pumpAndSettle(); 33 | expect(find.text('testPathNameBuilder'), findsOneWidget); 34 | }); 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /test/delegates_test.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/rendering.dart'; 6 | import 'package:flutter_test/flutter_test.dart'; 7 | import 'package:wechat_assets_picker/wechat_assets_picker.dart'; 8 | 9 | void main() { 10 | group(AssetPickerTextDelegate, () { 11 | test('returns the default when available', () { 12 | expect( 13 | assetPickerTextDelegateFromLocale(null), 14 | equals(const AssetPickerTextDelegate()), 15 | ); 16 | expect( 17 | assetPickerTextDelegateFromLocale(const Locale('zh')), 18 | equals(const AssetPickerTextDelegate()), 19 | ); 20 | expect( 21 | assetPickerTextDelegateFromLocale(const Locale('zxx')), 22 | equals(const AssetPickerTextDelegate()), 23 | ); 24 | }); 25 | 26 | test('each delegate can be obtained by its locale definition', () { 27 | for (final delegate in assetPickerTextDelegates) { 28 | final locale = Locale.fromSubtags( 29 | languageCode: delegate.languageCode, 30 | scriptCode: delegate.scriptCode, 31 | countryCode: delegate.countryCode, 32 | ); 33 | final matchedDelegate = assetPickerTextDelegateFromLocale(locale); 34 | expect(matchedDelegate, equals(delegate)); 35 | } 36 | }); 37 | }); 38 | 39 | test('Sort paths correctly', () { 40 | final List> paths = 41 | >[ 42 | PathWrapper( 43 | path: AssetPathEntity(id: 'id2', name: 'Screenshots'), 44 | ), 45 | PathWrapper( 46 | path: AssetPathEntity(id: 'id1', name: 'Camera'), 47 | ), 48 | PathWrapper( 49 | path: AssetPathEntity(id: 'id0', name: 'All', isAll: true), 50 | ), 51 | ]; 52 | SortPathDelegate.common.sort(paths); 53 | expect(paths[0], (PathWrapper e) => e.path.isAll); 54 | expect( 55 | paths[1], 56 | (PathWrapper e) => e.path.name == 'Camera', 57 | ); 58 | expect( 59 | paths[2], 60 | (PathWrapper e) => e.path.name == 'Screenshots', 61 | ); 62 | }); 63 | } 64 | -------------------------------------------------------------------------------- /test/providers_test.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_test/flutter_test.dart'; 7 | import 'package:wechat_assets_picker/wechat_assets_picker.dart'; 8 | 9 | import 'test_utils.dart'; 10 | 11 | void main() async { 12 | PhotoManager.withPlugin(TestPhotoManagerPlugin()); 13 | AssetPicker.setPickerDelegate(TestAssetPickerDelegate()); 14 | 15 | group('AssetPickerProvider', () { 16 | testWidgets('disposed correctly', (WidgetTester tester) async { 17 | await tester.pumpWidget( 18 | defaultPickerTestApp( 19 | onButtonPressed: (BuildContext context) { 20 | AssetPicker.pickAssets(context); 21 | }, 22 | ), 23 | ); 24 | await tester.tap(defaultButtonFinder); 25 | await tester.pumpAndSettle(); 26 | final Finder pickerFinder = find.byType( 27 | AssetPicker, 28 | ); 29 | final AssetPicker picker = tester.widget( 30 | pickerFinder, 31 | ); 32 | final DefaultAssetPickerProvider provider = 33 | (picker.builder as DefaultAssetPickerBuilderDelegate).provider; 34 | expect(provider, isA()); 35 | await tester.tap(find.widgetWithIcon(IconButton, Icons.close)); 36 | await tester.pumpAndSettle(); 37 | expect( 38 | () { 39 | provider.addListener(() {}); 40 | }, 41 | throwsA(isA()), 42 | ); 43 | }); 44 | 45 | /// Regression: https://github.com/fluttercandies/flutter_wechat_assets_picker/issues/427 46 | testWidgets( 47 | 'does not clear selected assets', 48 | (WidgetTester tester) async { 49 | final List selectedAssets = [testAssetEntity]; 50 | await tester.pumpWidget( 51 | defaultPickerTestApp( 52 | onButtonPressed: (BuildContext context) { 53 | AssetPicker.pickAssets( 54 | context, 55 | pickerConfig: AssetPickerConfig(selectedAssets: selectedAssets), 56 | ); 57 | }, 58 | ), 59 | ); 60 | await tester.tap(defaultButtonFinder); 61 | await tester.pumpAndSettle(); 62 | await tester.tap(find.widgetWithIcon(IconButton, Icons.close)); 63 | await tester.pumpAndSettle(); 64 | expect(selectedAssets, contains(testAssetEntity)); 65 | }, 66 | ); 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /test/test_utils.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/foundation.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_localizations/flutter_localizations.dart'; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | import 'package:wechat_assets_picker/wechat_assets_picker.dart'; 10 | 11 | const String _testButtonText = 'Picker test press'; 12 | 13 | final Finder defaultButtonFinder = find.widgetWithText( 14 | TextButton, 15 | _testButtonText, 16 | ); 17 | 18 | Widget defaultPickerTestApp({ 19 | void Function(BuildContext)? onButtonPressed, 20 | Locale locale = const Locale('zh'), 21 | }) { 22 | return MaterialApp( 23 | home: _DefaultHomePage(onButtonPressed), 24 | localizationsDelegates: const >[ 25 | GlobalWidgetsLocalizations.delegate, 26 | GlobalMaterialLocalizations.delegate, 27 | GlobalCupertinoLocalizations.delegate, 28 | ], 29 | supportedLocales: const [ 30 | Locale('zh'), 31 | Locale('en'), 32 | Locale('he'), 33 | Locale('de'), 34 | Locale('ru'), 35 | Locale('ja'), 36 | Locale('ar'), 37 | Locale('fr'), 38 | Locale('vi'), 39 | Locale('ko'), 40 | ], 41 | locale: locale, 42 | ); 43 | } 44 | 45 | class _DefaultHomePage extends StatelessWidget { 46 | const _DefaultHomePage(this.onButtonPressed); 47 | 48 | final void Function(BuildContext)? onButtonPressed; 49 | 50 | @override 51 | Widget build(BuildContext context) { 52 | return Scaffold( 53 | body: Center( 54 | child: TextButton( 55 | onPressed: () { 56 | onButtonPressed?.call(context); 57 | }, 58 | child: const Text(_testButtonText), 59 | ), 60 | ), 61 | ); 62 | } 63 | } 64 | 65 | class TestPhotoManagerPlugin extends PhotoManagerPlugin { 66 | @override 67 | Future requestPermissionExtend( 68 | PermissionRequestOption requestOption, 69 | ) { 70 | return SynchronousFuture(PermissionState.authorized); 71 | } 72 | } 73 | 74 | class TestAssetPickerDelegate extends AssetPickerDelegate { 75 | @override 76 | Future permissionCheck({ 77 | PermissionRequestOption requestOption = const PermissionRequestOption(), 78 | }) async { 79 | return SynchronousFuture(PermissionState.authorized); 80 | } 81 | 82 | @override 83 | Future?> pickAssets( 84 | BuildContext context, { 85 | Key? key, 86 | AssetPickerConfig pickerConfig = const AssetPickerConfig(), 87 | PermissionRequestOption? permissionRequestOption, 88 | bool useRootNavigator = true, 89 | AssetPickerPageRouteBuilder>? pageRouteBuilder, 90 | }) async { 91 | permissionRequestOption ??= PermissionRequestOption( 92 | androidPermission: AndroidPermission( 93 | type: pickerConfig.requestType, 94 | mediaLocation: false, 95 | ), 96 | ); 97 | final PermissionState ps = await permissionCheck( 98 | requestOption: permissionRequestOption, 99 | ); 100 | final AssetPathEntity pathEntity = AssetPathEntity( 101 | id: 'test', 102 | name: 'pathEntity', 103 | ); 104 | final DefaultAssetPickerProvider provider = 105 | DefaultAssetPickerProvider.forTest( 106 | maxAssets: pickerConfig.maxAssets, 107 | pageSize: pickerConfig.pageSize, 108 | pathThumbnailSize: pickerConfig.pathThumbnailSize, 109 | selectedAssets: pickerConfig.selectedAssets, 110 | requestType: pickerConfig.requestType, 111 | sortPathDelegate: pickerConfig.sortPathDelegate, 112 | filterOptions: pickerConfig.filterOptions, 113 | ); 114 | provider 115 | ..currentAssets = [testAssetEntity] 116 | ..currentPath = PathWrapper( 117 | path: pathEntity, 118 | assetCount: 1, 119 | ) 120 | ..hasAssetsToDisplay = true 121 | ..totalAssetsCount = 1; 122 | final Widget picker = AssetPicker( 123 | key: key, 124 | permissionRequestOption: permissionRequestOption, 125 | builder: DefaultAssetPickerBuilderDelegate( 126 | provider: provider, 127 | initialPermission: ps, 128 | gridCount: pickerConfig.gridCount, 129 | pickerTheme: pickerConfig.pickerTheme, 130 | gridThumbnailSize: pickerConfig.gridThumbnailSize, 131 | previewThumbnailSize: pickerConfig.previewThumbnailSize, 132 | specialPickerType: pickerConfig.specialPickerType, 133 | specialItemPosition: pickerConfig.specialItemPosition, 134 | specialItemBuilder: pickerConfig.specialItemBuilder, 135 | loadingIndicatorBuilder: pickerConfig.loadingIndicatorBuilder, 136 | selectPredicate: pickerConfig.selectPredicate, 137 | shouldRevertGrid: pickerConfig.shouldRevertGrid, 138 | limitedPermissionOverlayPredicate: 139 | pickerConfig.limitedPermissionOverlayPredicate, 140 | pathNameBuilder: pickerConfig.pathNameBuilder, 141 | textDelegate: pickerConfig.textDelegate, 142 | themeColor: pickerConfig.themeColor, 143 | locale: Localizations.maybeLocaleOf(context), 144 | shouldAutoplayPreview: pickerConfig.shouldAutoplayPreview, 145 | ), 146 | ); 147 | final List? result = await Navigator.of( 148 | context, 149 | rootNavigator: useRootNavigator, 150 | ).push>( 151 | pageRouteBuilder?.call(picker) ?? 152 | AssetPickerPageRoute>(builder: (_) => picker), 153 | ); 154 | return result; 155 | } 156 | } 157 | 158 | const AssetEntity testAssetEntity = AssetEntity( 159 | id: 'test', 160 | typeInt: 0, 161 | width: 0, 162 | height: 0, 163 | ); 164 | -------------------------------------------------------------------------------- /test/widget_test.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_test/flutter_test.dart'; 7 | import 'package:wechat_assets_picker/wechat_assets_picker.dart'; 8 | 9 | import 'test_utils.dart'; 10 | 11 | void main() { 12 | PhotoManager.withPlugin(TestPhotoManagerPlugin()); 13 | AssetPicker.setPickerDelegate(TestAssetPickerDelegate()); 14 | 15 | group('Confirm button', () { 16 | group('when enabled preview', () { 17 | testWidgets('with multiple assets picking', (WidgetTester tester) async { 18 | await tester.pumpWidget( 19 | defaultPickerTestApp( 20 | onButtonPressed: (BuildContext context) { 21 | AssetPicker.pickAssets( 22 | context, 23 | pickerConfig: const AssetPickerConfig( 24 | maxAssets: 10, 25 | specialPickerType: null, // Explicitly null. 26 | ), 27 | ); 28 | }, 29 | ), 30 | ); 31 | await tester.tap(defaultButtonFinder); 32 | await tester.pumpAndSettle(); 33 | expect( 34 | find.text(const AssetPickerTextDelegate().confirm), 35 | findsOneWidget, 36 | ); 37 | }); 38 | testWidgets('with single asset picking', (WidgetTester tester) async { 39 | await tester.pumpWidget( 40 | defaultPickerTestApp( 41 | onButtonPressed: (BuildContext context) { 42 | AssetPicker.pickAssets( 43 | context, 44 | pickerConfig: const AssetPickerConfig( 45 | maxAssets: 1, 46 | specialPickerType: null, // Explicitly null. 47 | ), 48 | ); 49 | }, 50 | ), 51 | ); 52 | await tester.tap(defaultButtonFinder); 53 | await tester.pumpAndSettle(); 54 | expect( 55 | find.text(const AssetPickerTextDelegate().confirm), 56 | findsOneWidget, 57 | ); 58 | }); 59 | }); 60 | group('when disabled preview', () { 61 | testWidgets('with multiple assets picker', (WidgetTester tester) async { 62 | await tester.pumpWidget( 63 | defaultPickerTestApp( 64 | onButtonPressed: (BuildContext context) { 65 | AssetPicker.pickAssets( 66 | context, 67 | pickerConfig: const AssetPickerConfig( 68 | maxAssets: 2, 69 | specialPickerType: SpecialPickerType.noPreview, 70 | ), 71 | ); 72 | }, 73 | ), 74 | ); 75 | await tester.tap(defaultButtonFinder); 76 | await tester.pumpAndSettle(); 77 | expect( 78 | find.text(const AssetPickerTextDelegate().confirm), 79 | findsOneWidget, 80 | ); 81 | }); 82 | testWidgets('with single asset picker', (WidgetTester tester) async { 83 | await tester.pumpWidget( 84 | defaultPickerTestApp( 85 | onButtonPressed: (BuildContext context) { 86 | AssetPicker.pickAssets( 87 | context, 88 | pickerConfig: const AssetPickerConfig( 89 | maxAssets: 1, 90 | specialPickerType: SpecialPickerType.noPreview, 91 | ), 92 | ); 93 | }, 94 | ), 95 | ); 96 | await tester.tap(defaultButtonFinder); 97 | await tester.pumpAndSettle(); 98 | expect( 99 | find.text(const AssetPickerTextDelegate().confirm), 100 | findsNothing, 101 | ); 102 | }); 103 | }); 104 | }); 105 | } 106 | --------------------------------------------------------------------------------