├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ ├── config.yml
│ ├── flutter_bug_report.yml
│ ├── flutter_discussions.yml
│ ├── flutter_how_to_use.yml
│ └── flutter_new_feature.yml
└── workflows
│ ├── build-example-apk.yml
│ ├── check-compatibility.yml
│ ├── issue-triage.yml
│ ├── issues.yml
│ ├── publish.yml
│ ├── remove-issue-label.yml
│ └── runnable.yml
├── .gitignore
├── .markdownlint.jsonc
├── CHANGELOG.md
├── CODEOWNERS
├── LICENSE
├── MIGRATION_GUIDE.md
├── README-ZH.md
├── README.md
├── analysis_options.yaml
├── android
├── .gitignore
├── build.gradle
├── settings.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── kotlin
│ └── com
│ └── fluttercandies
│ └── photo_manager
│ ├── PhotoManagerPlugin.kt
│ ├── constant
│ ├── AssetType.kt
│ └── Methods.kt
│ ├── core
│ ├── PhotoManager.kt
│ ├── PhotoManagerDeleteManager.kt
│ ├── PhotoManagerNotifyChannel.kt
│ ├── PhotoManagerPlugin.kt
│ ├── cache
│ │ └── ScopedCache.kt
│ ├── entity
│ │ ├── AssetEntity.kt
│ │ ├── AssetPathEntity.kt
│ │ ├── PermissionResult.kt
│ │ ├── ThumbLoadOption.kt
│ │ └── filter
│ │ │ ├── CommonFilterOption.kt
│ │ │ ├── CustomOption.kt
│ │ │ └── FilterOption.kt
│ └── utils
│ │ ├── AndroidQDBUtils.kt
│ │ ├── CommonExt.kt
│ │ ├── ConvertUtils.kt
│ │ ├── DBUtils.kt
│ │ ├── IDBUtils.kt
│ │ ├── MediaStoreUtils.kt
│ │ ├── RequestTypeUtils.kt
│ │ └── VideoUtils.kt
│ ├── permission
│ ├── PermissionDelegate.kt
│ ├── PermissionsListener.kt
│ ├── PermissionsUtils.kt
│ └── impl
│ │ ├── PermissionDelegate19.kt
│ │ ├── PermissionDelegate23.kt
│ │ ├── PermissionDelegate29.kt
│ │ ├── PermissionDelegate33.kt
│ │ └── PermissionDelegate34.kt
│ ├── thumb
│ └── ThumbnailUtil.kt
│ └── util
│ ├── LogUtils.kt
│ └── ResultHandler.kt
├── copy_on_publish.sh
├── example
├── .gitignore
├── README.md
├── analysis_options.yaml
├── android
│ ├── .gitignore
│ ├── app
│ │ ├── build.gradle
│ │ └── src
│ │ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── fluttercandies
│ │ │ │ └── photo_manager_example
│ │ │ │ ├── ExampleAppGlideModule.kt
│ │ │ │ └── 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
│ ├── photo-manager-keystore
│ └── settings.gradle
├── custom.dart
├── devtools_options.yaml
├── 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
│ │ └── 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
├── lib
│ ├── main.dart
│ ├── model
│ │ └── photo_provider.dart
│ ├── page
│ │ ├── change_notify_page.dart
│ │ ├── copy_to_another_gallery_example.dart
│ │ ├── custom_filter
│ │ │ ├── advance_filter_page.dart
│ │ │ ├── custom_filter_sql_gif_image.dart
│ │ │ ├── custom_filter_sql_page.dart
│ │ │ ├── filter_assets_page.dart
│ │ │ ├── image_list.dart
│ │ │ ├── include_hidden_test_page.dart
│ │ │ ├── order_by_action.dart
│ │ │ ├── path_list.dart
│ │ │ ├── path_page.dart
│ │ │ └── where_action.dart
│ │ ├── custom_filter_example_page.dart
│ │ ├── detail_page.dart
│ │ ├── developer
│ │ │ ├── android
│ │ │ │ └── column_names_page.dart
│ │ │ ├── create_entity_by_id.dart
│ │ │ ├── custom_filter_page.dart
│ │ │ ├── dev_title_page.dart
│ │ │ ├── develop_index_page.dart
│ │ │ ├── ios
│ │ │ │ ├── create_folder_example.dart
│ │ │ │ └── edit_asset.dart
│ │ │ ├── issues_page
│ │ │ │ ├── issue_1025.dart
│ │ │ │ ├── issue_1031.dart
│ │ │ │ ├── issue_1051.dart
│ │ │ │ ├── issue_1053.dart
│ │ │ │ ├── issue_1152.dart
│ │ │ │ ├── issue_1271_demo.dart
│ │ │ │ ├── issue_734.dart
│ │ │ │ ├── issue_918.dart
│ │ │ │ ├── issue_962.dart
│ │ │ │ ├── issue_979.dart
│ │ │ │ ├── issue_988.dart
│ │ │ │ └── issue_index_page.dart
│ │ │ ├── permission_state_page.dart
│ │ │ ├── remove_all_android_not_exists_example.dart
│ │ │ └── verbose_log_page.dart
│ │ ├── filter_option_page.dart
│ │ ├── gallery_list_page.dart
│ │ ├── home_page.dart
│ │ ├── image_list_page.dart
│ │ ├── index_page.dart
│ │ ├── move_to_another_gallery_example.dart
│ │ ├── save_image_example.dart
│ │ └── sub_gallery_page.dart
│ ├── util
│ │ ├── asset_utils.dart
│ │ ├── common_util.dart
│ │ ├── log.dart
│ │ └── log_export.dart
│ └── widget
│ │ ├── change_notifier_builder.dart
│ │ ├── dialog
│ │ ├── duration_picker.dart
│ │ └── list_dialog.dart
│ │ ├── gallery_item_widget.dart
│ │ ├── image_item_widget.dart
│ │ ├── live_photos_widget.dart
│ │ ├── loading_widget.dart
│ │ ├── nav_button.dart
│ │ ├── nav_column.dart
│ │ ├── theme_button.dart
│ │ └── video_widget.dart
├── macos
│ ├── .gitignore
│ ├── Flutter
│ │ ├── Flutter-Debug.xcconfig
│ │ └── Flutter-Release.xcconfig
│ ├── 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
├── example_ohos
├── .gitignore
├── README.md
├── analysis_options.yaml
├── custom.dart
├── lib
│ └── main.dart
├── ohos
│ ├── .gitignore
│ ├── AppScope
│ │ ├── app.json5
│ │ └── resources
│ │ │ └── base
│ │ │ ├── element
│ │ │ └── string.json
│ │ │ └── media
│ │ │ └── app_icon.png
│ ├── build-profile.json5
│ ├── entry
│ │ ├── .gitignore
│ │ ├── build-profile.json5
│ │ ├── hvigorfile.ts
│ │ ├── oh-package.json5
│ │ └── src
│ │ │ └── main
│ │ │ ├── ets
│ │ │ ├── entryability
│ │ │ │ └── EntryAbility.ets
│ │ │ ├── pages
│ │ │ │ └── Index.ets
│ │ │ └── plugins
│ │ │ │ └── GeneratedPluginRegistrant.ets
│ │ │ ├── module.json5
│ │ │ └── resources
│ │ │ ├── base
│ │ │ ├── element
│ │ │ │ ├── color.json
│ │ │ │ └── string.json
│ │ │ ├── media
│ │ │ │ └── icon.png
│ │ │ └── profile
│ │ │ │ └── main_pages.json
│ │ │ ├── en_US
│ │ │ └── element
│ │ │ │ └── string.json
│ │ │ └── zh_CN
│ │ │ └── element
│ │ │ └── string.json
│ ├── hvigor
│ │ ├── hvigor-config.json5
│ │ └── hvigor-wrapper.js
│ ├── hvigorfile.ts
│ ├── hvigorw
│ ├── hvigorw.bat
│ └── oh-package.json5
└── pubspec.yaml
├── flow_chart
├── advance_custom_filter.dio
└── advance_custom_filter.png
├── ios
├── .gitignore
├── Classes
│ ├── PMConverter.h
│ ├── PMConverter.m
│ ├── PMImport.h
│ ├── PMNotificationManager.h
│ ├── PMNotificationManager.m
│ ├── PMPlugin.h
│ ├── PMPlugin.m
│ ├── PMProgressHandler.h
│ ├── PMProgressHandler.m
│ ├── PhotoManagerPlugin.h
│ ├── PhotoManagerPlugin.m
│ ├── ResultHandler.h
│ ├── ResultHandler.m
│ └── core
│ │ ├── AssetEntity.h
│ │ ├── AssetEntity.m
│ │ ├── NSString+PM_COMMON.h
│ │ ├── NSString+PM_COMMON.m
│ │ ├── PHAsset+PM_COMMON.h
│ │ ├── PHAsset+PM_COMMON.m
│ │ ├── PHAssetCollection+PM_COMMON.h
│ │ ├── PHAssetCollection+PM_COMMON.m
│ │ ├── PHAssetResource+PM_COMMON.h
│ │ ├── PHAssetResource+PM_COMMON.m
│ │ ├── PMAssetPathEntity.h
│ │ ├── PMAssetPathEntity.m
│ │ ├── PMBaseFilter.h
│ │ ├── PMCacheContainer.h
│ │ ├── PMCacheContainer.m
│ │ ├── PMConvertProtocol.h
│ │ ├── PMConvertUtils.h
│ │ ├── PMConvertUtils.m
│ │ ├── PMFileHelper.h
│ │ ├── PMFileHelper.m
│ │ ├── PMFilterOption.h
│ │ ├── PMFilterOption.m
│ │ ├── PMFolderUtils.h
│ │ ├── PMFolderUtils.m
│ │ ├── PMImageUtil.h
│ │ ├── PMImageUtil.m
│ │ ├── PMLogUtils.h
│ │ ├── PMLogUtils.m
│ │ ├── PMMD5Utils.h
│ │ ├── PMMD5Utils.m
│ │ ├── PMManager.h
│ │ ├── PMManager.m
│ │ ├── PMPathFilterOption.h
│ │ ├── PMPathFilterOption.m
│ │ ├── PMProgressHandlerProtocol.h
│ │ ├── PMRequestTypeUtils.h
│ │ ├── PMRequestTypeUtils.m
│ │ ├── PMResultHandler.h
│ │ ├── PMThumbLoadOption.h
│ │ ├── PMThumbLoadOption.m
│ │ ├── Reply.h
│ │ └── Reply.m
├── Resources
│ └── PrivacyInfo.xcprivacy
└── photo_manager.podspec
├── lib
├── photo_manager.dart
├── platform_utils.dart
└── src
│ ├── filter
│ ├── base_filter.dart
│ ├── classical
│ │ ├── filter_option_group.dart
│ │ └── filter_options.dart
│ ├── custom
│ │ ├── advance.dart
│ │ ├── custom_columns.dart
│ │ ├── custom_filter.dart
│ │ └── order_by_item.dart
│ └── path_filter.dart
│ ├── internal
│ ├── constants.dart
│ ├── editor.dart
│ ├── enums.dart
│ ├── extensions.dart
│ ├── map_interface.dart
│ ├── plugin.dart
│ └── progress_handler.dart
│ ├── managers
│ ├── caching_manager.dart
│ ├── notify_manager.dart
│ └── photo_manager.dart
│ ├── types
│ ├── entity.dart
│ ├── thumbnail.dart
│ └── types.dart
│ └── utils
│ ├── column_utils.dart
│ └── convert_utils.dart
├── log
├── README.md
├── android
│ ├── android-28-delete.log
│ ├── android-28-info.log
│ ├── android-28-insert.log
│ ├── android-30.info.log
│ ├── android-q-delete.log
│ ├── android-q-insert.log
│ ├── androidq-info.log
│ └── sql-28.log
└── iOS
│ ├── ios-create.log
│ └── ios-delete.log
├── macos
├── ohos
├── .gitignore
├── build-profile.json5
├── hvigorfile.ts
├── index.ets
├── oh-package.json5
└── src
│ └── main
│ ├── ets
│ └── components
│ │ └── plugin
│ │ ├── PhotoManagerNotifyPlugin.ets
│ │ ├── PhotoManagerPlugin.ets
│ │ ├── filter
│ │ └── FilterOption.ets
│ │ ├── handlers
│ │ ├── AlbumHandler.ets
│ │ ├── HandlerBase.ets
│ │ ├── PermissionHandler.ets
│ │ ├── PhotoAssetHandler.ets
│ │ └── SaveHandler.ets
│ │ ├── types
│ │ └── RequestType.ets
│ │ └── utils
│ │ ├── EnumUtils.ets
│ │ └── MapUtils.ets
│ └── module.json5
├── pubspec.yaml
├── scripts
└── check_license.dart
└── test
├── internal
└── extensions_test.dart
└── photo_manager_test.dart
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Force batch scripts to always use CRLF line endings so that if a repo is accessed
2 | # in Windows via a file share from Linux, the scripts will work.
3 | *.{cmd,[cC][mM][dD]} text eol=crlf
4 | *.{bat,[bB][aA][tT]} text eol=crlf
5 |
6 | # Force bash scripts to always use LF line endings so that if a repo is accessed
7 | # in Unix via a file share from Windows, the scripts will work.
8 | *.sh text eol=lf
9 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/flutter_bug_report.yml:
--------------------------------------------------------------------------------
1 | name: Flutter plugin bug report
2 | description: bug report
3 | title: "[Bug report] "
4 | body:
5 | - type: input
6 | id: version
7 | attributes:
8 | label: Version
9 | description: Version of the plugin
10 | placeholder: ex. 2.5.2
11 | validations:
12 | required: true
13 | - type: dropdown
14 | id: platforms
15 | attributes:
16 | label: Platforms
17 | description: Check all that apply
18 | multiple: true
19 | options:
20 | - dart
21 | - Android
22 | - iOS
23 | - macOS
24 | validations:
25 | required: true
26 | - type: input
27 | id: device-model
28 | attributes:
29 | label: Device Model
30 | description: "Device Model (ex. iPhone 12(iOS 14), Galaxy S21(Android 11))"
31 | placeholder: ex. iPhone 12 (iOS 14)
32 | validations:
33 | required: true
34 | - type: textarea
35 | id: flutter-doctor
36 | attributes:
37 | label: flutter info
38 | description: "flutter doctor -v in your terminal"
39 | placeholder: ex. flutter doctor -v
40 | render: sh
41 | validations:
42 | required: true
43 | - type: textarea
44 | id: what-happened
45 | attributes:
46 | label: "How to reproduce?"
47 | description: "How to reproduce the problem?"
48 | placeholder: "ex. I found a bug when I call the PhotoManager.getAssetPathList method."
49 | validations:
50 | required: true
51 | - type: textarea
52 | id: log
53 | attributes:
54 | label: Logs
55 | description: "Error log"
56 | placeholder: ex. flutter run --verbose
57 | render: sh
58 | validations:
59 | required: false
60 | - type: textarea
61 | id: example-code
62 | attributes:
63 | label: "Example code (optional)"
64 | description: "If you can, please provide a minimal example to reproduce the problem."
65 | placeholder: "ex. PhotoManager.editor.saveImageWithId(id, quality: 100)"
66 | render: Dart
67 | validations:
68 | required: false
69 | - type: input
70 | id: contact
71 | attributes:
72 | label: Contact
73 | description: Your contace (no requird)
74 | placeholder: ex. email@example.com / twitter / github
75 | validations:
76 | required: false
77 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/flutter_discussions.yml:
--------------------------------------------------------------------------------
1 | name: Discussions
2 | description: Discussions
3 | title: "[Discussions] "
4 | body:
5 | - type: markdown
6 | attributes:
7 | value: |
8 | First check whether the project has Discussions enabled.
9 | If so, it is recommended to use Discussions to create discussions.
10 | The discussions is here:
11 | [Discussions](https://github.com/FlutterCandies/flutter_photo_manager/discussions)
12 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/flutter_how_to_use.yml:
--------------------------------------------------------------------------------
1 | name: How to use
2 | description: How to use the Flutter plugin
3 | title: "[How to use] "
4 | body:
5 | - type: markdown
6 | attributes:
7 | value: |
8 | Thanks for taking the time to fill out this issue!
9 | You need read document before you ask question.
10 | If you can, search the issue list
11 | or [StackOverflow](https://stackoverflow.com/questions/tagged/flutter)
12 | to see if someone has asked the same question.
13 | - type: dropdown
14 | id: platforms
15 | attributes:
16 | label: Platforms
17 | description: Check all that apply
18 | multiple: true
19 | options:
20 | - dart
21 | - Android
22 | - iOS
23 | - macOS
24 | validations:
25 | required: true
26 | - type: textarea
27 | id: description
28 | attributes:
29 | label: Description
30 | description: "Describe the feature you want to be added"
31 | placeholder: "ex. I want to add a new method to get the thumbnail of the asset."
32 | validations:
33 | required: true
34 | - type: textarea
35 | id: my-code
36 | attributes:
37 | label: My code
38 | description: "I want to know how to use the method."
39 | placeholder: |
40 | 'ex. I want to get the thumbnail of the asset to display it in the list.'
41 | validations:
42 | required: false
43 | - type: textarea
44 | id: try-do-it
45 | attributes:
46 | label: Try do it
47 | description: "I have tried to do it, but I don't know how to do it."
48 | placeholder: |
49 | 'ex. I want to get the thumbnail of the asset to display it in the list.'
50 | validations:
51 | required: false
52 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/flutter_new_feature.yml:
--------------------------------------------------------------------------------
1 | name: "Flutter new feature request"
2 | description: "Request a new feature for the Flutter plugin"
3 | title: "[Feature request] "
4 | body:
5 | - type: markdown
6 | attributes:
7 | value: |
8 | Thanks for taking the time to fill out this feature request!
9 | Before submitting, make sure you read the document and search the issue list.
10 | If you are not sure, you can run the example code
11 | first to understand the example that may be your requirement.
12 | - type: dropdown
13 | id: platforms
14 | attributes:
15 | label: Platforms
16 | description: Check all that apply
17 | multiple: true
18 | options:
19 | - dart
20 | - Android
21 | - iOS
22 | - macOS
23 | validations:
24 | required: true
25 | - type: textarea
26 | id: description
27 | attributes:
28 | label: Description
29 | description: "Describe the feature you want to be added"
30 | placeholder: "ex. I want to add a new method to get the thumbnail of the asset."
31 | validations:
32 | required: true
33 | - type: textarea
34 | id: why
35 | attributes:
36 | label: Why
37 | description: "Why do you want this feature to be added? What's the use case?"
38 | placeholder: |
39 | ex. I want to get the thumbnail of the asset to display it in the list.
40 | validations:
41 | required: false
42 | - type: markdown
43 | attributes:
44 | value: |
45 | Add as many or as many reasons as possible,
46 | which will give priority to feature request
47 |
--------------------------------------------------------------------------------
/.github/workflows/build-example-apk.yml:
--------------------------------------------------------------------------------
1 | name: Build example apk
2 |
3 | concurrency:
4 | group: ${{ github.workflow }}-${{ github.ref }}
5 | cancel-in-progress: true
6 |
7 | on:
8 | release:
9 | types:
10 | - created
11 |
12 | permissions:
13 | contents: write
14 |
15 | env:
16 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
17 |
18 | jobs:
19 | build-apk:
20 | runs-on: ubuntu-latest
21 | steps:
22 | - uses: actions/checkout@v4
23 | - uses: actions/setup-java@v4
24 | with:
25 | distribution: 'zulu'
26 | java-version: '17'
27 | - uses: flutter-actions/setup-flutter@v4
28 | with:
29 | channel: 'stable'
30 | cache: true
31 | - run: flutter doctor -v
32 | name: Flutter info
33 | - run: flutter build apk --release
34 | name: Build apk
35 | working-directory: ${{ github.workspace }}/example
36 | - name: Upload apk to release ${{ github.event.release.tag_name }}
37 | run: |
38 | gh release upload ${{ github.event.release.tag_name }} build/app/outputs/flutter-apk/*.apk
39 | echo "Show apk download url: "
40 | gh release view ${{ github.event.release.tag_name }} --json assets --jq '.assets.[].url'
41 | working-directory: ${{ github.workspace }}/example
42 |
--------------------------------------------------------------------------------
/.github/workflows/issue-triage.yml:
--------------------------------------------------------------------------------
1 | # For more information, see
2 | # https://github.com/fluttercandies/triage_bot_for_flutter_photo_manager
3 | name: Issue Triage Bot
4 |
5 | # Run when an issue is created.
6 | on:
7 | issues:
8 | types:
9 | - opened
10 |
11 | # All permissions not specified are set to 'none'.
12 | permissions:
13 | issues: write
14 |
15 | jobs:
16 | triage-issues:
17 | runs-on: ubuntu-latest
18 | steps:
19 | - uses: actions/checkout@v4
20 | with:
21 | repository: fluttercandies/triage_bot_for_flutter_photo_manager
22 | - uses: dart-lang/setup-dart@v1
23 | - run: dart pub get
24 | working-directory: pkgs/issue_triage_bot
25 |
26 | # Delay 2 minutes to allow a grace period between opening or transferring
27 | # an issue and assigning a label.
28 | - name: sleep 2m
29 | run: sleep 120
30 |
31 | - name: triage issue
32 | working-directory: pkgs/issue_triage_bot
33 | env:
34 | ISSUE_URL: ${{ github.event.issue.html_url }}
35 | GITHUB_TOKEN: ${{ secrets.TRIAGE_BOT_TOKEN }}
36 | GEMINI_API_KEY: ${{ secrets.TRIAGE_BOT_API_KEY }}
37 | run: dart bin/triage.dart $ISSUE_URL --release
38 |
--------------------------------------------------------------------------------
/.github/workflows/issues.yml:
--------------------------------------------------------------------------------
1 | name: Auto add label to new issue
2 | on:
3 | issues:
4 | types:
5 | - opened
6 |
7 | jobs:
8 | add-label:
9 | runs-on: ubuntu-latest
10 | permissions:
11 | issues: write
12 | steps:
13 | - uses: actions/github-script@v3
14 | name: Add label to new issue
15 | with:
16 | github-token: ${{ secrets.GITHUB_TOKEN }}
17 | script: |
18 | github.issues.addLabels({
19 | issue_number: context.issue.number,
20 | owner: context.repo.owner,
21 | repo: context.repo.repo,
22 | labels: ['await triage']
23 | })
24 |
--------------------------------------------------------------------------------
/.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
13 | uses: actions/checkout@v4
14 | - name: Copy on publish
15 | run: ./copy_on_publish.sh
16 | - name: Publish
17 | uses: sakebook/actions-flutter-pub-publisher@v1.3.1
18 | with:
19 | credential: ${{ secrets.CREDENTIAL_JSON }}
20 | flutter_package: true
21 | skip_test: true
22 | dry_run: false
23 |
--------------------------------------------------------------------------------
/.github/workflows/remove-issue-label.yml:
--------------------------------------------------------------------------------
1 | name: Remove label when issue is closed
2 | on:
3 | issues:
4 | types:
5 | - closed
6 |
7 | jobs:
8 | add-label:
9 | runs-on: ubuntu-latest
10 | permissions:
11 | issues: write
12 | env:
13 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
14 | steps:
15 | - name: remove label
16 | run:
17 | gh issue edit ${{ github.event.issue.number }} --remove-label 'await triage'
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .dart_tool/
3 |
4 | .packages
5 | .pub/
6 | pubspec.lock
7 |
8 | build/
9 | .idea/
10 | .vscode/
11 | android/.classpath
12 | android/.project
13 |
14 | fvm
15 | doc/
16 | .env
17 | *.iml
18 | .metadata
19 |
--------------------------------------------------------------------------------
/.markdownlint.jsonc:
--------------------------------------------------------------------------------
1 | {
2 | "MD033": false,
3 | "MD013": false,
4 | "MD004": false,
5 | "MD024": false
6 | }
--------------------------------------------------------------------------------
/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @CaiJingLong @AlexV525
2 | ohos/ @zmtzawqlp
3 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:flutter_lints/flutter.yaml
2 |
3 | analyzer:
4 | errors:
5 | always_declare_return_types: error
6 | always_put_control_body_on_new_line: warning
7 | avoid_renaming_method_parameters: error
8 | avoid_void_async: error
9 | camel_case_types: error
10 | constant_identifier_names: error
11 | deprecated_member_use_from_same_package: ignore
12 | non_constant_identifier_names: error
13 | prefer_single_quotes: warning
14 | require_trailing_commas: warning
15 | invalid_annotation_target: false
16 |
17 | linter:
18 | rules:
19 | always_declare_return_types: true
20 | always_put_control_body_on_new_line: true
21 | avoid_print: true
22 | avoid_unnecessary_containers: true
23 | avoid_void_async: true
24 | curly_braces_in_flow_control_structures: true
25 | directives_ordering: true
26 | prefer_const_constructors: true
27 | prefer_const_constructors_in_immutables: false
28 | prefer_final_fields: true
29 | prefer_final_in_for_each: true
30 | prefer_final_locals: true
31 | prefer_single_quotes: true
32 | require_trailing_commas: true
33 | sort_child_properties_last: true
34 | sort_constructors_first: true
35 | sort_unnamed_constructors_first: true
36 | unnecessary_await_in_return: true
37 | unnecessary_late: true
38 | unnecessary_parenthesis: true
39 | use_build_context_synchronously: false
40 | void_checks: true
41 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | /gradlew
10 | /gradlew.bat
11 | .classpath
12 | .project
13 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | File pubspec = new File(project.projectDir.parentFile, 'pubspec.yaml')
2 | String yaml = pubspec.text
3 | java.util.regex.Matcher versionMatcher = java.util.regex.Pattern.compile("^version:\\s*['|\"]?([^\\n|'|\"]*)['|\"]?\$", java.util.regex.Pattern.MULTILINE).matcher(yaml)
4 | versionMatcher.find()
5 | String library_version = versionMatcher.group(1).replaceAll("\\+", "-")
6 |
7 | group 'com.fluttercandies.photo_manager'
8 | version library_version
9 |
10 | buildscript {
11 | ext.kotlin_version = '1.9.23'
12 | repositories {
13 | google()
14 | mavenCentral()
15 | }
16 |
17 | dependencies {
18 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
19 | }
20 | }
21 |
22 | rootProject.allprojects {
23 | repositories {
24 | google()
25 | mavenCentral()
26 | }
27 | }
28 |
29 | apply plugin: 'com.android.library'
30 | apply plugin: 'kotlin-android'
31 |
32 | android {
33 | if (project.android.hasProperty('namespace')) {
34 | namespace 'com.flutterandies.photo_manager'
35 | }
36 |
37 | compileSdkVersion 34
38 |
39 | defaultConfig {
40 | minSdkVersion 16
41 | }
42 |
43 | compileOptions {
44 | sourceCompatibility JavaVersion.VERSION_17
45 | targetCompatibility JavaVersion.VERSION_17
46 | }
47 |
48 | kotlinOptions {
49 | jvmTarget = '17'
50 | }
51 |
52 | sourceSets {
53 | main.java.srcDirs += 'src/main/kotlin'
54 | }
55 | }
56 |
57 | dependencies {
58 | implementation 'com.github.bumptech.glide:glide:4.15.1'
59 | implementation 'androidx.exifinterface:exifinterface:1.3.7'
60 | }
61 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'photo_manager'
2 |
3 | pluginManagement {
4 | repositories {
5 | gradlePluginPortal()
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/com/fluttercandies/photo_manager/constant/AssetType.kt:
--------------------------------------------------------------------------------
1 | package com.fluttercandies.photo_manager.constant
2 |
3 | enum class AssetType { Image, Video, Audio }
4 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/com/fluttercandies/photo_manager/core/entity/AssetEntity.kt:
--------------------------------------------------------------------------------
1 | package com.fluttercandies.photo_manager.core.entity
2 |
3 | import android.net.Uri
4 | import com.fluttercandies.photo_manager.core.utils.IDBUtils.Companion.isAboveAndroidQ
5 | import com.fluttercandies.photo_manager.core.utils.MediaStoreUtils
6 | import java.io.File
7 |
8 | data class AssetEntity(
9 | val id: Long,
10 | val path: String,
11 | val duration: Long,
12 | val createDt: Long,
13 | val width: Int,
14 | val height: Int,
15 | val type: Int,
16 | val displayName: String,
17 | val modifiedDate: Long,
18 | val orientation: Int,
19 | val lat: Double? = null,
20 | val lng: Double? = null,
21 | val androidQRelativePath: String? = null,
22 | val mimeType: String? = null
23 | ) {
24 | fun getUri(): Uri = MediaStoreUtils.getUri(
25 | id,
26 | MediaStoreUtils.convertTypeToMediaType(type)
27 | )
28 |
29 | val relativePath: String? = if (isAboveAndroidQ) {
30 | androidQRelativePath
31 | } else {
32 | File(path).parent
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/com/fluttercandies/photo_manager/core/entity/AssetPathEntity.kt:
--------------------------------------------------------------------------------
1 | package com.fluttercandies.photo_manager.core.entity
2 |
3 | data class AssetPathEntity(
4 | val id: String,
5 | val name: String,
6 | var assetCount: Int,
7 | val typeInt: Int,
8 | var isAll: Boolean = false,
9 | var modifiedDate: Long? = null
10 | )
11 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/com/fluttercandies/photo_manager/core/entity/PermissionResult.kt:
--------------------------------------------------------------------------------
1 | package com.fluttercandies.photo_manager.core.entity
2 |
3 | enum class PermissionResult(val value: Int) {
4 | NotDetermined(0),
5 | Denied(2),
6 | Authorized(3),
7 | Limited(4),
8 | }
9 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/com/fluttercandies/photo_manager/core/entity/ThumbLoadOption.kt:
--------------------------------------------------------------------------------
1 | package com.fluttercandies.photo_manager.core.entity
2 |
3 | import android.graphics.Bitmap
4 |
5 | data class ThumbLoadOption(
6 | val width: Int,
7 | val height: Int,
8 | val format: Bitmap.CompressFormat,
9 | val quality: Int,
10 | val frame: Long,
11 | ) {
12 | companion object Factory {
13 | fun fromMap(map: Map<*, *>): ThumbLoadOption {
14 | val width = map["width"] as Int
15 | val height = map["height"] as Int
16 | val format = map["format"] as Int
17 | val quality = map["quality"] as Int
18 | val frame = (map["frame"] as Int).toLong()
19 |
20 | val compressFormat = if (format == 0) {
21 | Bitmap.CompressFormat.JPEG
22 | } else {
23 | Bitmap.CompressFormat.PNG
24 | }
25 |
26 | return ThumbLoadOption(width, height, compressFormat, quality, frame)
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/com/fluttercandies/photo_manager/core/entity/filter/CustomOption.kt:
--------------------------------------------------------------------------------
1 | package com.fluttercandies.photo_manager.core.entity.filter
2 |
3 | import com.fluttercandies.photo_manager.core.utils.RequestTypeUtils
4 |
5 | class CustomOption(private val map: Map<*, *>) : FilterOption() {
6 |
7 | override val containsPathModified: Boolean = map["containsPathModified"] as Boolean
8 |
9 | override fun orderByCondString(): String? {
10 | val list = map["orderBy"] as? List<*>
11 | if (list.isNullOrEmpty()) {
12 | return null
13 | }
14 | return list.joinToString(",") {
15 | val map = it as Map<*, *>
16 | val column = map["column"] as String
17 | val isAsc = map["isAsc"] as Boolean
18 | "$column ${if (isAsc) "ASC" else "DESC"}"
19 | }
20 | }
21 |
22 | override fun makeWhere(requestType: Int, args: ArrayList, needAnd: Boolean): String {
23 | val where = map["where"] as String
24 |
25 | val typeWhere = RequestTypeUtils.toWhere(requestType)
26 |
27 | if (where.trim().isEmpty()) {
28 | if (needAnd) {
29 | return "AND $typeWhere"
30 | }
31 |
32 | return typeWhere
33 | }
34 |
35 | if (needAnd && where.trim().isNotEmpty()) {
36 | return "AND ( $where )"
37 | }
38 | return "( $where )"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/com/fluttercandies/photo_manager/core/entity/filter/FilterOption.kt:
--------------------------------------------------------------------------------
1 | package com.fluttercandies.photo_manager.core.entity.filter
2 |
3 | abstract class FilterOption {
4 | abstract val containsPathModified: Boolean
5 |
6 | abstract fun orderByCondString(): String?
7 |
8 | abstract fun makeWhere(
9 | requestType: Int,
10 | args: ArrayList,
11 | needAnd: Boolean = true
12 | ): String
13 | }
14 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/com/fluttercandies/photo_manager/core/utils/CommonExt.kt:
--------------------------------------------------------------------------------
1 | package com.fluttercandies.photo_manager.core.utils
2 |
3 | import androidx.exifinterface.media.ExifInterface
4 | import java.io.File
5 | import java.io.InputStream
6 |
7 | /**
8 | * Create the directory if it's not exist.
9 | */
10 | fun String.checkDirs() {
11 | val targetFile = File(this)
12 | if (!targetFile.parentFile!!.exists()) {
13 | targetFile.parentFile!!.mkdirs()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/com/fluttercandies/photo_manager/core/utils/MediaStoreUtils.kt:
--------------------------------------------------------------------------------
1 | package com.fluttercandies.photo_manager.core.utils
2 |
3 | import android.content.ContentUris
4 | import android.net.Uri
5 | import android.provider.MediaStore
6 |
7 | object MediaStoreUtils {
8 | fun getInsertUri(mediaType: Int): Uri {
9 | return when (mediaType) {
10 | MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
11 | MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
12 | MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
13 | else -> IDBUtils.allUri
14 | }
15 | }
16 |
17 | fun getUri(id: Long, mediaType: Int): Uri {
18 | return ContentUris.withAppendedId(getInsertUri(mediaType), id)
19 | }
20 |
21 | fun convertTypeToMediaType(type: Int): Int {
22 | return when (type) {
23 | 1 -> MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE
24 | 2 -> MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO
25 | 3 -> MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO
26 | else -> 0
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/com/fluttercandies/photo_manager/core/utils/RequestTypeUtils.kt:
--------------------------------------------------------------------------------
1 | package com.fluttercandies.photo_manager.core.utils
2 |
3 | import android.provider.MediaStore
4 |
5 | object RequestTypeUtils {
6 | private const val typeImage = 1
7 | private const val typeVideo = 1.shl(1)
8 | private const val typeAudio = 1.shl(2)
9 |
10 | fun containsImage(type: Int): Boolean = checkType(type, typeImage)
11 |
12 | fun containsVideo(type: Int): Boolean = checkType(type, typeVideo)
13 |
14 | fun containsAudio(type: Int): Boolean = checkType(type, typeAudio)
15 |
16 | private fun checkType(type: Int, targetType: Int): Boolean {
17 | return type and targetType == targetType
18 | }
19 |
20 | fun toWhere(requestType: Int): String {
21 | val typeInt = arrayListOf()
22 | if (containsImage(requestType)) {
23 | typeInt.add(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE)
24 | }
25 | if (containsAudio(requestType)) {
26 | typeInt.add(MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO)
27 | }
28 | if (containsVideo(requestType)) {
29 | typeInt.add(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO)
30 | }
31 |
32 | val where = typeInt.joinToString(" OR ") {
33 | "${MediaStore.Files.FileColumns.MEDIA_TYPE} = $it"
34 | }
35 |
36 | return "( $where )"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/com/fluttercandies/photo_manager/core/utils/VideoUtils.kt:
--------------------------------------------------------------------------------
1 | package com.fluttercandies.photo_manager.core.utils
2 |
3 | import android.media.MediaPlayer
4 |
5 | object VideoUtils {
6 | data class VideoInfo(var width: Int?, var height: Int?, var duration: Int?)
7 |
8 | fun getPropertiesUseMediaPlayer(path: String): VideoInfo {
9 | val mediaPlayer = MediaPlayer()
10 | mediaPlayer.setDataSource(path)
11 | mediaPlayer.setOnErrorListener { _, _, _ -> true }
12 | try {
13 | mediaPlayer.prepare()
14 | } catch (e: Throwable) {
15 | mediaPlayer.release()
16 | return VideoInfo(null, null, null)
17 | }
18 | mediaPlayer.videoHeight
19 | val info = VideoInfo(mediaPlayer.videoWidth, mediaPlayer.videoHeight, mediaPlayer.duration)
20 |
21 | mediaPlayer.stop()
22 | mediaPlayer.release()
23 |
24 | return info
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/com/fluttercandies/photo_manager/permission/PermissionsListener.kt:
--------------------------------------------------------------------------------
1 | package com.fluttercandies.photo_manager.permission
2 |
3 | interface PermissionsListener {
4 | fun onGranted(needPermissions: MutableList)
5 |
6 | fun onDenied(
7 | deniedPermissions: MutableList,
8 | grantedPermissions: MutableList,
9 | needPermissions: MutableList
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/com/fluttercandies/photo_manager/permission/impl/PermissionDelegate19.kt:
--------------------------------------------------------------------------------
1 | package com.fluttercandies.photo_manager.permission.impl
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import com.fluttercandies.photo_manager.core.entity.PermissionResult
6 | import com.fluttercandies.photo_manager.permission.PermissionDelegate
7 | import com.fluttercandies.photo_manager.permission.PermissionsUtils
8 |
9 | class PermissionDelegate19 : PermissionDelegate() {
10 | override fun requestPermission(
11 | permissionsUtils: PermissionsUtils,
12 | context: Context,
13 | requestType: Int,
14 | mediaLocation: Boolean
15 | ) {
16 | permissionsUtils.permissionsListener?.onGranted(mutableListOf())
17 | }
18 |
19 | override fun havePermissions(context: Context, requestType: Int): Boolean {
20 | return true
21 | }
22 |
23 | override fun haveMediaLocation(context: Context): Boolean {
24 | return true
25 | }
26 |
27 | override fun getAuthValue(
28 | context: Application, requestType: Int, mediaLocation: Boolean
29 | ): PermissionResult {
30 | return PermissionResult.Authorized
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/com/fluttercandies/photo_manager/permission/impl/PermissionDelegate23.kt:
--------------------------------------------------------------------------------
1 | package com.fluttercandies.photo_manager.permission.impl
2 |
3 | import android.Manifest
4 | import android.app.Application
5 | import android.content.Context
6 | import androidx.annotation.RequiresApi
7 | import com.fluttercandies.photo_manager.core.entity.PermissionResult
8 | import com.fluttercandies.photo_manager.permission.PermissionDelegate
9 | import com.fluttercandies.photo_manager.permission.PermissionsUtils
10 |
11 | @RequiresApi(23)
12 | class PermissionDelegate23 : PermissionDelegate() {
13 | companion object {
14 | private const val readPermission = Manifest.permission.READ_EXTERNAL_STORAGE
15 | private const val writePermission = Manifest.permission.WRITE_EXTERNAL_STORAGE
16 | }
17 |
18 | override fun requestPermission(
19 | permissionsUtils: PermissionsUtils,
20 | context: Context,
21 | requestType: Int,
22 | mediaLocation: Boolean
23 | ) {
24 | val permissions = mutableListOf(readPermission, writePermission)
25 |
26 | if (havePermissions(context, requestType)) {
27 | permissionsUtils.permissionsListener?.onGranted(permissions)
28 | } else {
29 | requestPermission(permissionsUtils, permissions)
30 | }
31 | }
32 |
33 | override fun havePermissions(context: Context, requestType: Int): Boolean {
34 | val requireWritePermission = havePermissionInManifest(context, writePermission)
35 | val validWritePermission =
36 | !requireWritePermission || havePermission(context, writePermission)
37 | return havePermission(context, readPermission) && validWritePermission
38 | }
39 |
40 | override fun haveMediaLocation(context: Context): Boolean {
41 | return true
42 | }
43 |
44 | override fun getAuthValue(
45 | context: Application,
46 | requestType: Int,
47 | mediaLocation: Boolean
48 | ): PermissionResult {
49 | return if (havePermissions(context, requestType)) {
50 | PermissionResult.Authorized
51 | } else {
52 | PermissionResult.Denied
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/com/fluttercandies/photo_manager/permission/impl/PermissionDelegate29.kt:
--------------------------------------------------------------------------------
1 | package com.fluttercandies.photo_manager.permission.impl
2 |
3 | import android.Manifest
4 | import android.app.Application
5 | import android.content.Context
6 | import android.os.Build
7 | import androidx.annotation.RequiresApi
8 | import com.fluttercandies.photo_manager.core.entity.PermissionResult
9 | import com.fluttercandies.photo_manager.permission.PermissionDelegate
10 | import com.fluttercandies.photo_manager.permission.PermissionsUtils
11 |
12 | @RequiresApi(Build.VERSION_CODES.Q)
13 | class PermissionDelegate29 : PermissionDelegate() {
14 |
15 | companion object {
16 | private const val readPermission = Manifest.permission.READ_EXTERNAL_STORAGE
17 | private const val mediaLocationPermission = Manifest.permission.ACCESS_MEDIA_LOCATION
18 | }
19 |
20 | override fun requestPermission(
21 | permissionsUtils: PermissionsUtils,
22 | context: Context,
23 | requestType: Int,
24 | mediaLocation: Boolean
25 | ) {
26 | val permissions = mutableListOf(readPermission)
27 |
28 | if (mediaLocation) {
29 | permissions.add(mediaLocationPermission)
30 | }
31 |
32 | if (havePermissions(context, *permissions.toTypedArray())) {
33 | permissionsUtils.permissionsListener?.onGranted(permissions)
34 | } else {
35 | requestPermission(permissionsUtils, permissions)
36 | }
37 | }
38 |
39 | override fun havePermissions(context: Context, requestType: Int): Boolean {
40 | return havePermission(context, readPermission)
41 | }
42 |
43 | override fun haveMediaLocation(context: Context): Boolean {
44 | return havePermission(context, mediaLocationPermission)
45 | }
46 |
47 | override fun getAuthValue(
48 | context: Application,
49 | requestType: Int,
50 | mediaLocation: Boolean
51 | ): PermissionResult {
52 | return if (havePermissions(context, readPermission)) {
53 | PermissionResult.Authorized
54 | } else {
55 | PermissionResult.Denied
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/com/fluttercandies/photo_manager/util/ResultHandler.kt:
--------------------------------------------------------------------------------
1 | package com.fluttercandies.photo_manager.util
2 |
3 | import android.os.Handler
4 | import android.os.Looper
5 | import io.flutter.plugin.common.MethodCall
6 | import io.flutter.plugin.common.MethodChannel
7 |
8 | class ResultHandler(var result: MethodChannel.Result, val call: MethodCall) {
9 | init {
10 | handler.hasMessages(0) // just do it to init handler
11 | }
12 |
13 | companion object {
14 | @JvmField
15 | val handler = Handler(Looper.getMainLooper())
16 | }
17 |
18 | private var isReplied = false
19 |
20 | fun reply(any: Any?) {
21 | if (isReplied) {
22 | return
23 | }
24 | isReplied = true
25 | val result = this.result
26 | handler.post {
27 | try {
28 | result.success(any)
29 | } catch (e: IllegalStateException) {
30 | // Do nothing
31 | }
32 | }
33 | }
34 |
35 | fun replyError(code: String, message: String? = null, obj: Any? = null) {
36 | if (isReplied) {
37 | return
38 | }
39 | isReplied = true
40 | val result = this.result
41 | handler.post {
42 | result.error(code, message, obj)
43 | }
44 | }
45 |
46 | fun notImplemented() {
47 | if (isReplied) {
48 | return
49 | }
50 | isReplied = true
51 | val result = this.result
52 | handler.post {
53 | result.notImplemented()
54 | }
55 | }
56 |
57 | fun isReplied(): Boolean {
58 | return isReplied
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/copy_on_publish.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | rm macos
4 | cp -r ios macos
5 | git rm --cached macos
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | **/Podfile.lock
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | **/doc/api/
26 | **/ios/Flutter/.last_build_id
27 | .dart_tool/
28 | .flutter-plugins
29 | .flutter-plugins-dependencies
30 | .packages
31 | .pub-cache/
32 | .pub/
33 | /build/
34 | /pubspec.lock
35 |
36 | # Web related
37 | lib/generated_plugin_registrant.dart
38 |
39 | # Symbolication related
40 | app.*.symbols
41 |
42 | # Obfuscation related
43 | app.*.map.json
44 | .metadata
45 |
46 | local.properties
47 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # photo_manager_example
2 |
3 | Demonstrates how to use the photo_manager plugin.
4 |
5 | ## Getting Started
6 |
7 | For help getting started with Flutter, view our online
8 | [documentation](https://flutter.io/).
9 |
--------------------------------------------------------------------------------
/example/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: ../analysis_options.yaml
2 |
3 | linter:
4 | rules:
5 | avoid_print: false
6 | dangling_library_doc_comments: false
7 | use_build_context_synchronously: false
8 |
--------------------------------------------------------------------------------
/example/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 | .kotlin
9 |
10 | # Remember to never publicly share your keystore.
11 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
12 | key.properties
13 | **/*.keystore
14 | **/*.jks
15 |
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.android.application"
3 | id "kotlin-android"
4 | id "dev.flutter.flutter-gradle-plugin"
5 | id 'org.jetbrains.kotlin.kapt'
6 | }
7 |
8 | def localProperties = new Properties()
9 | def localPropertiesFile = rootProject.file('local.properties')
10 | if (localPropertiesFile.exists()) {
11 | localPropertiesFile.withReader('UTF-8') { reader ->
12 | localProperties.load(reader)
13 | }
14 | }
15 |
16 | android {
17 | namespace 'com.fluttercandies.photo_manager_example'
18 |
19 | compileSdk flutter.compileSdkVersion
20 | ndkVersion = "27.0.12077973"
21 |
22 | kotlinOptions {
23 | jvmTarget = '17'
24 | }
25 |
26 | compileOptions {
27 | sourceCompatibility JavaVersion.VERSION_17
28 | targetCompatibility JavaVersion.VERSION_17
29 | }
30 |
31 | sourceSets {
32 | main.java.srcDirs += 'src/main/kotlin'
33 | }
34 |
35 | defaultConfig {
36 | applicationId "com.fluttercandies.photo_manager_example"
37 | minSdkVersion 21
38 | targetSdk = flutter.targetSdkVersion
39 | versionCode = flutter.versionCode
40 | versionName = flutter.versionName
41 | }
42 |
43 | signingConfigs {
44 | forAll {
45 | storeFile file("${rootDir.absolutePath}/photo-manager-keystore")
46 | storePassword '123456'
47 | keyAlias 'key0'
48 | keyPassword '123456'
49 | }
50 | }
51 |
52 | buildTypes {
53 | debug {
54 | signingConfig signingConfigs.forAll
55 | }
56 | profile {
57 | signingConfig signingConfigs.forAll
58 | }
59 | release {
60 | signingConfig signingConfigs.forAll
61 | }
62 | }
63 | }
64 |
65 | flutter {
66 | source '../..'
67 | }
68 |
69 | dependencies {
70 | implementation 'com.github.bumptech.glide:glide:4.15.1'
71 | kapt 'com.github.bumptech.glide:compiler:4.15.1'
72 | }
73 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
19 |
27 |
31 |
35 |
36 |
37 |
38 |
39 |
40 |
42 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/example/android/app/src/main/kotlin/com/fluttercandies/photo_manager_example/ExampleAppGlideModule.kt:
--------------------------------------------------------------------------------
1 | package com.fluttercandies.photo_manager_example
2 |
3 | import com.bumptech.glide.module.AppGlideModule
4 |
5 | @com.bumptech.glide.annotation.GlideModule
6 | class ExampleAppGlideModule : AppGlideModule()
--------------------------------------------------------------------------------
/example/android/app/src/main/kotlin/com/fluttercandies/photo_manager_example/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.fluttercandies.photo_manager_example
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity : FlutterActivity()
--------------------------------------------------------------------------------
/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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 | kotlin.code.style=official
5 |
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/example/android/photo-manager-keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/flutter_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/example/android/photo-manager-keystore
--------------------------------------------------------------------------------
/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 | id "org.jetbrains.kotlin.kapt" version "2.1.20" apply false
25 | }
26 |
27 | include ":app"
28 |
--------------------------------------------------------------------------------
/example/custom.dart:
--------------------------------------------------------------------------------
1 | import 'package:photo_manager/photo_manager.dart';
2 |
3 | void main(List args) {
4 | final filter = AdvancedCustomFilter().addWhereCondition(
5 | WhereConditionGroup()
6 | .andGroup(
7 | WhereConditionGroup()
8 | .andText('width > 1000')
9 | .andText('height > 1000'),
10 | )
11 | .orGroup(
12 | WhereConditionGroup().andText('width < 500').andText('height < 500'),
13 | ),
14 | );
15 |
16 | PhotoManager.getAssetPathList(filterOption: filter).then((value) {
17 | print(value);
18 | });
19 | }
20 |
--------------------------------------------------------------------------------
/example/devtools_options.yaml:
--------------------------------------------------------------------------------
1 | extensions:
2 |
--------------------------------------------------------------------------------
/example/ios/.gitignore:
--------------------------------------------------------------------------------
1 | *.mode1v3
2 | *.mode2v3
3 | *.moved-aside
4 | *.pbxuser
5 | *.perspectivev3
6 | **/*sync/
7 | .sconsign.dblite
8 | .tags*
9 | **/.vagrant/
10 | **/DerivedData/
11 | Icon?
12 | **/Flutter/ephemeral/
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/app.flx
23 | Flutter/app.zip
24 | Flutter/flutter_assets/
25 | Flutter/flutter_export_environment.sh
26 | ServiceDefinitions.json
27 | Runner/GeneratedPluginRegistrant.*
28 |
29 | # Exceptions to above rules.
30 | !default.mode1v3
31 | !default.mode2v3
32 | !default.pbxuser
33 | !default.perspectivev3
34 | .build/
--------------------------------------------------------------------------------
/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.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/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/flutter_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | photo_manager Example
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | com.fluttercandies.photo_manager_example
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(FLUTTER_BUILD_NUMBER)
25 | LSRequiresIPhoneOS
26 |
27 | NSAppTransportSecurity
28 |
29 | NSAllowsArbitraryLoads
30 |
31 |
32 | NSBonjourServices
33 |
34 | _dartobservatory._tcp
35 |
36 | NSLocalNetworkUsageDescription
37 | Requesting local network for debugging processes
38 | NSPhotoLibraryUsageDescription
39 | You must agree with the photo album to see your pictures
40 | NSPhotoLibraryAddUsageDescription
41 | You must agree the additions permission to save assets.
42 | UILaunchStoryboardName
43 | LaunchScreen
44 | UIMainStoryboardFile
45 | Main
46 | UISupportedInterfaceOrientations
47 |
48 | UIInterfaceOrientationPortrait
49 | UIInterfaceOrientationLandscapeLeft
50 | UIInterfaceOrientationLandscapeRight
51 |
52 | UISupportedInterfaceOrientations~ipad
53 |
54 | UIInterfaceOrientationPortrait
55 | UIInterfaceOrientationPortraitUpsideDown
56 | UIInterfaceOrientationLandscapeLeft
57 | UIInterfaceOrientationLandscapeRight
58 |
59 | UIViewControllerBasedStatusBarAppearance
60 |
61 | CADisableMinimumFrameDurationOnPhone
62 |
63 | UIApplicationSupportsIndirectInputEvents
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/example/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/example/lib/page/custom_filter/advance_filter_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:photo_manager/photo_manager.dart';
3 |
4 | import 'order_by_action.dart';
5 | import 'where_action.dart';
6 |
7 | class AdvancedCustomFilterPage extends StatefulWidget {
8 | const AdvancedCustomFilterPage({
9 | super.key,
10 | required this.builder,
11 | });
12 |
13 | final Widget Function(BuildContext context, CustomFilter filter) builder;
14 |
15 | @override
16 | State createState() =>
17 | _AdvancedCustomFilterPageState();
18 | }
19 |
20 | class _AdvancedCustomFilterPageState extends State {
21 | List _where = [];
22 | List _orderBy = [
23 | OrderByItem.named(
24 | column: CustomColumns.base.createDate,
25 | isAsc: false,
26 | ),
27 | ];
28 |
29 | late CustomFilter filter;
30 |
31 | @override
32 | void initState() {
33 | super.initState();
34 | filter = _createFilter();
35 | }
36 |
37 | AdvancedCustomFilter _createFilter() {
38 | final filter = AdvancedCustomFilter(
39 | orderBy: _orderBy,
40 | where: _where,
41 | );
42 | return filter;
43 | }
44 |
45 | @override
46 | Widget build(BuildContext context) {
47 | return Scaffold(
48 | appBar: AppBar(
49 | title: const Text('Advanced Custom Filter Example'),
50 | actions: [
51 | WhereAction(
52 | items: _where,
53 | onChanged: (value) {
54 | if (!mounted) {
55 | return;
56 | }
57 | setState(() {
58 | _where = value;
59 | filter = _createFilter();
60 | });
61 | },
62 | ),
63 | OrderByAction(
64 | items: _orderBy,
65 | onChanged: (values) {
66 | if (!mounted) {
67 | return;
68 | }
69 | setState(() {
70 | _orderBy = values;
71 | filter = _createFilter();
72 | });
73 | },
74 | ),
75 | ],
76 | ),
77 | body: Column(
78 | children: [
79 | Expanded(
80 | child: widget.builder(context, filter),
81 | ),
82 | ],
83 | ),
84 | );
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/example/lib/page/custom_filter/filter_assets_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:photo_manager/photo_manager.dart';
3 | import 'package:photo_manager_example/page/custom_filter/image_list.dart';
4 |
5 | class FilterAssetsContent extends StatelessWidget {
6 | const FilterAssetsContent({
7 | super.key,
8 | required this.filter,
9 | });
10 | final CustomFilter filter;
11 |
12 | Future> getAssets() async {
13 | final count = await PhotoManager.getAssetCount(filterOption: filter);
14 | if (count == 0) {
15 | return [];
16 | }
17 | final list = await PhotoManager.getAssetListRange(
18 | start: 0,
19 | end: count,
20 | filterOption: filter,
21 | );
22 | return list;
23 | }
24 |
25 | @override
26 | Widget build(BuildContext context) {
27 | return FutureBuilder>(
28 | future: getAssets(),
29 | builder:
30 | (BuildContext context, AsyncSnapshot> snapshot) {
31 | if (snapshot.hasData) {
32 | return ImageList(list: snapshot.data!);
33 | }
34 | return const SizedBox();
35 | },
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/example/lib/page/custom_filter/image_list.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:photo_manager/photo_manager.dart';
3 | import 'package:photo_manager_example/widget/image_item_widget.dart';
4 |
5 | class ImageList extends StatelessWidget {
6 | const ImageList({super.key, required this.list});
7 |
8 | final List list;
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | return GridView.builder(
13 | gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
14 | crossAxisCount: 5,
15 | mainAxisSpacing: 2,
16 | crossAxisSpacing: 2,
17 | ),
18 | itemBuilder: (context, index) {
19 | final entity = list[index];
20 | return ImageItemWidget(
21 | entity: entity,
22 | option: ThumbnailOption.ios(
23 | size: const ThumbnailSize.square(500),
24 | ),
25 | );
26 | },
27 | itemCount: list.length,
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/example/lib/page/custom_filter/include_hidden_test_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:photo_manager/photo_manager.dart';
3 |
4 | class IncludeHiddenTestPage extends StatefulWidget {
5 | const IncludeHiddenTestPage({super.key});
6 |
7 | @override
8 | State createState() => _IncludeHiddenTestPageState();
9 | }
10 |
11 | class _IncludeHiddenTestPageState extends State {
12 | final List _logs = [];
13 |
14 | Future _test() async {
15 | final option = CustomFilter.sql(where: '');
16 | final count = await PhotoManager.getAssetCount(filterOption: option);
17 |
18 | setState(() {
19 | _logs.add('Not include hidden count: $count');
20 | });
21 |
22 | final option2 = CustomFilter.sql(where: '');
23 | option2.includeHiddenAssets = true;
24 | final count2 = await PhotoManager.getAssetCount(filterOption: option2);
25 |
26 | setState(() {
27 | _logs.add('Include hidden count: $count2');
28 | });
29 | }
30 |
31 | @override
32 | Widget build(BuildContext context) {
33 | return Scaffold(
34 | appBar: AppBar(
35 | title: const Text('Include Hidden Test'),
36 | ),
37 | body: Column(
38 | children: [
39 | ElevatedButton(
40 | onPressed: _test,
41 | child: const Text('Test the count of hidden and not hidden'),
42 | ),
43 | Expanded(
44 | child: ListView(
45 | children: [
46 | ..._logs.reversed.map((e) => ListTile(title: Text(e))),
47 | ],
48 | ),
49 | ),
50 | ],
51 | ),
52 | );
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/example/lib/page/custom_filter/path_list.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:oktoast/oktoast.dart';
3 | import 'package:photo_manager/photo_manager.dart';
4 | import 'package:photo_manager_example/page/custom_filter/path_page.dart';
5 |
6 | class FilterPathList extends StatelessWidget {
7 | const FilterPathList({super.key, required this.filter});
8 |
9 | final CustomFilter filter;
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | return FutureBuilder>(
14 | future: Future(() async {
15 | final ps = await PhotoManager.requestPermissionExtend();
16 | if (!ps.hasAccess) {
17 | throw StateError('No access');
18 | }
19 | return PhotoManager.getAssetPathList(filterOption: filter);
20 | }),
21 | builder: (
22 | BuildContext context,
23 | AsyncSnapshot> snapshot,
24 | ) {
25 | if (snapshot.hasData) {
26 | return PathList(list: snapshot.data!);
27 | }
28 | return const SizedBox();
29 | },
30 | );
31 | }
32 | }
33 |
34 | class PathList extends StatelessWidget {
35 | const PathList({
36 | super.key,
37 | required this.list,
38 | });
39 |
40 | final List list;
41 |
42 | @override
43 | Widget build(BuildContext context) {
44 | return ListView.builder(
45 | itemBuilder: (BuildContext context, int index) {
46 | final AssetPathEntity path = list[index];
47 | return ListTile(
48 | title: Text(path.name),
49 | subtitle: Text(path.id),
50 | trailing: FutureBuilder(
51 | future: path.assetCountAsync,
52 | builder: (BuildContext context, AsyncSnapshot snapshot) {
53 | if (snapshot.hasData) {
54 | return Text(snapshot.data.toString());
55 | }
56 | return const SizedBox();
57 | },
58 | ),
59 | onTap: () {
60 | path.assetCountAsync.then((value) {
61 | showToast(
62 | 'Asset count: $value',
63 | position: ToastPosition.bottom,
64 | );
65 | });
66 | Navigator.push(
67 | context,
68 | MaterialPageRoute(
69 | builder: (_) => PathPage(path: path),
70 | ),
71 | );
72 | },
73 | );
74 | },
75 | itemCount: list.length,
76 | );
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/example/lib/page/custom_filter/path_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:photo_manager/photo_manager.dart';
3 | import 'package:photo_manager_example/page/custom_filter/image_list.dart';
4 |
5 | class PathPage extends StatefulWidget {
6 | const PathPage({super.key, required this.path});
7 | final AssetPathEntity path;
8 |
9 | @override
10 | State createState() => _PathPageState();
11 | }
12 |
13 | class _PathPageState extends State {
14 | @override
15 | Widget build(BuildContext context) {
16 | return Scaffold(
17 | appBar: AppBar(
18 | title: Text(widget.path.name),
19 | ),
20 | body: GalleryWidget(
21 | path: widget.path,
22 | ),
23 | );
24 | }
25 | }
26 |
27 | class GalleryWidget extends StatefulWidget {
28 | const GalleryWidget({super.key, required this.path});
29 |
30 | final AssetPathEntity path;
31 |
32 | @override
33 | State createState() => _GalleryWidgetState();
34 | }
35 |
36 | class _GalleryWidgetState extends State {
37 | List _list = [];
38 |
39 | @override
40 | void initState() {
41 | super.initState();
42 | _refresh();
43 | }
44 |
45 | Future _refresh() async {
46 | final count = await widget.path.assetCountAsync;
47 | if (count == 0) {
48 | return;
49 | }
50 | final list = await widget.path.getAssetListRange(start: 0, end: count);
51 | setState(() {
52 | if (mounted) {
53 | _list = list;
54 | }
55 | });
56 | }
57 |
58 | @override
59 | Widget build(BuildContext context) {
60 | return ImageList(list: _list);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/example/lib/page/developer/android/column_names_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/services.dart';
3 | import 'package:photo_manager/photo_manager.dart';
4 |
5 | class ColumnNamesPage extends StatefulWidget {
6 | const ColumnNamesPage({super.key});
7 |
8 | @override
9 | State createState() => _ColumnNamesPageState();
10 | }
11 |
12 | class _ColumnNamesPageState extends State {
13 | List _columns = [];
14 |
15 | Future _refresh() async {
16 | final columns = await PhotoManager.plugin.androidColumns();
17 | print('columns: $columns');
18 | columns.sort();
19 | setState(() {
20 | _columns = columns;
21 | });
22 | }
23 |
24 | @override
25 | void initState() {
26 | _refresh();
27 | super.initState();
28 | }
29 |
30 | @override
31 | Widget build(BuildContext context) {
32 | return Scaffold(
33 | appBar: AppBar(
34 | title: const Text('Column Names'),
35 | actions: [
36 | IconButton(
37 | icon: const Icon(Icons.refresh),
38 | onPressed: _refresh,
39 | ),
40 | ],
41 | ),
42 | body: _buildBody(),
43 | );
44 | }
45 |
46 | Widget _buildBody() {
47 | final list = _columns;
48 | return ListView.builder(
49 | itemBuilder: (context, index) {
50 | final column = list[index];
51 | return ListTile(
52 | title: Text(column),
53 | subtitle: const Text('click to copy'),
54 | onTap: () {
55 | Clipboard.setData(ClipboardData(text: column));
56 | },
57 | );
58 | },
59 | itemCount: list.length,
60 | );
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/example/lib/page/developer/create_entity_by_id.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:oktoast/oktoast.dart';
3 | import 'package:photo_manager/photo_manager.dart';
4 |
5 | import '../detail_page.dart';
6 |
7 | class CreateEntityById extends StatefulWidget {
8 | const CreateEntityById({super.key});
9 |
10 | @override
11 | State createState() => _CreateEntityByIdState();
12 | }
13 |
14 | class _CreateEntityByIdState extends State {
15 | TextEditingController controller = TextEditingController();
16 |
17 | @override
18 | void initState() {
19 | super.initState();
20 | controller.text = '1016711';
21 | }
22 |
23 | @override
24 | Widget build(BuildContext context) {
25 | return Scaffold(
26 | appBar: AppBar(
27 | title: const Text('Create AssetEntity by id'),
28 | ),
29 | body: Padding(
30 | padding: const EdgeInsets.all(8.0),
31 | child: Column(
32 | children: [
33 | TextField(
34 | controller: controller,
35 | decoration: const InputDecoration(
36 | hintText: 'input asset id',
37 | ),
38 | ),
39 | ElevatedButton(
40 | onPressed: createAssetEntityAndShow,
41 | child: const Text('Create assetEntity'),
42 | ),
43 | ],
44 | ),
45 | ),
46 | );
47 | }
48 |
49 | Future createAssetEntityAndShow() async {
50 | final String id = controller.text.trim();
51 | final AssetEntity? asset = await AssetEntity.fromId(id);
52 | if (asset == null) {
53 | showToast('Cannot create asset by $id');
54 | return;
55 | }
56 |
57 | if (!mounted) {
58 | return;
59 | }
60 | return Navigator.push(
61 | context,
62 | MaterialPageRoute(
63 | builder: (BuildContext c) => DetailPage(entity: asset),
64 | ),
65 | );
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/example/lib/page/developer/dev_title_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:photo_manager/photo_manager.dart';
3 |
4 | import '../../util/log.dart';
5 |
6 | class DevelopingExample extends StatefulWidget {
7 | const DevelopingExample({super.key});
8 |
9 | @override
10 | State createState() => _DevelopingExampleState();
11 | }
12 |
13 | class _DevelopingExampleState extends State {
14 | @override
15 | Widget build(BuildContext context) {
16 | return Scaffold(
17 | appBar: AppBar(),
18 | body: Container(
19 | padding: const EdgeInsets.all(8.0),
20 | alignment: Alignment.topCenter,
21 | child: ElevatedButton(
22 | child: const Text('Test title speed'),
23 | onPressed: () async {
24 | final DateTime start = DateTime.now();
25 | final PermissionState result =
26 | await PhotoManager.requestPermissionExtend();
27 | if (result.isAuth) {
28 | final List imageList = [];
29 | final List list =
30 | await PhotoManager.getAssetPathList(
31 | type: RequestType.image,
32 | );
33 | for (final AssetPathEntity path in list) {
34 | imageList.addAll(
35 | await path.getAssetListRange(start: 0, end: 1),
36 | );
37 | }
38 | if (imageList.isNotEmpty) {
39 | imageList.shuffle();
40 | }
41 | }
42 | final Duration diff = DateTime.now().difference(start);
43 | Log.d(diff);
44 | },
45 | ),
46 | ),
47 | );
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/example/lib/page/developer/issues_page/issue_1025.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:photo_manager/photo_manager.dart';
3 |
4 | import 'issue_index_page.dart';
5 |
6 | class Issue1025Page extends StatefulWidget {
7 | const Issue1025Page({super.key});
8 |
9 | @override
10 | State createState() => _Issue1025PageState();
11 | }
12 |
13 | class _Issue1025PageState extends State
14 | with IssueBase {
15 | String log = '';
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return buildScaffold(
20 | [
21 | buildButton('Test for ignore permission', _testForIgnorePermission),
22 | Expanded(child: Text(log)),
23 | ],
24 | );
25 | }
26 |
27 | @override
28 | int get issueNumber => 1025;
29 |
30 | Future _testForIgnorePermission() async {
31 | await PhotoManager.setIgnorePermissionCheck(true);
32 | setState(() {
33 | log = 'setIgnorePermissionCheck(true) success' '\n$log';
34 | });
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/example/lib/page/developer/issues_page/issue_1031.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/cupertino.dart';
2 | import 'package:photo_manager/photo_manager.dart';
3 |
4 | import 'issue_index_page.dart';
5 |
6 | class Issue1031Page extends StatefulWidget {
7 | const Issue1031Page({super.key});
8 |
9 | @override
10 | State createState() => _Issue1031PageState();
11 | }
12 |
13 | class _Issue1031PageState extends State
14 | with IssueBase {
15 | String log = '';
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return buildScaffold(
20 | [
21 | buildButton('Test for permission', _testForIgnorePermission),
22 | Expanded(child: Text(log)),
23 | ],
24 | );
25 | }
26 |
27 | @override
28 | int get issueNumber => 1031;
29 |
30 | Future _testForIgnorePermission() async {
31 | final permission = await PhotoManager.requestPermissionExtend();
32 | log = 'permission: $permission' '\n$log';
33 | log = 'isAuth: ${permission.isAuth}' '\n$log';
34 | setState(() {});
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/example/lib/page/developer/issues_page/issue_1051.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:photo_manager/photo_manager.dart';
3 | import 'package:photo_manager_example/page/developer/issues_page/issue_index_page.dart';
4 |
5 | class Issus1051 extends StatefulWidget {
6 | const Issus1051({super.key});
7 |
8 | @override
9 | State createState() => _Issus1051State();
10 | }
11 |
12 | class _Issus1051State extends State with IssueBase {
13 | Future _test() async {
14 | final status = await PhotoManager.requestPermissionExtend();
15 | if (status.hasAccess) {
16 | await PhotoManager.presentLimited();
17 | print('present limited');
18 | }
19 |
20 | print('status: $status');
21 | }
22 |
23 | @override
24 | Widget build(BuildContext context) {
25 | return buildScaffold([
26 | buildButton(
27 | 'Test presentLimited',
28 | _test,
29 | ),
30 | ]);
31 | }
32 |
33 | @override
34 | int get issueNumber => 1051;
35 |
36 | @override
37 | List? get supportPlatforms => [
38 | TargetPlatform.android,
39 | ];
40 | }
41 |
--------------------------------------------------------------------------------
/example/lib/page/developer/issues_page/issue_1053.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:oktoast/oktoast.dart';
3 | import 'package:photo_manager/photo_manager.dart';
4 |
5 | import 'issue_index_page.dart';
6 |
7 | class Issus1053 extends StatefulWidget {
8 | const Issus1053({super.key});
9 |
10 | @override
11 | State createState() => _Issus1053State();
12 | }
13 |
14 | class _Issus1053State extends State with IssueBase {
15 | Future _requestPermission() async {
16 | if (checkStatus.isEmpty) {
17 | showToast('Please select at least one type');
18 | return;
19 | }
20 |
21 | final requestType = RequestType.fromTypes(checkStatus);
22 |
23 | final PermissionState status = await PhotoManager.requestPermissionExtend(
24 | requestOption: PermissionRequestOption(
25 | androidPermission: AndroidPermission(
26 | type: requestType,
27 | mediaLocation: false,
28 | ),
29 | ),
30 | );
31 | addLog('status: $status');
32 | }
33 |
34 | @override
35 | List? get supportPlatforms => [
36 | TargetPlatform.android,
37 | ];
38 |
39 | List checkStatus = [];
40 |
41 | @override
42 | Widget build(BuildContext context) {
43 | return buildScaffold([
44 | for (final item in RequestType.values) _buildCheck(item),
45 | buildButton(
46 | 'Test requestPermissionExtend',
47 | _requestPermission,
48 | ),
49 | buildLogWidget(),
50 | ]);
51 | }
52 |
53 | @override
54 | int get issueNumber => 1053;
55 |
56 | Widget _buildCheck(RequestType item) {
57 | String title = '';
58 | switch (item.value) {
59 | case 1:
60 | title = 'image';
61 | break;
62 | case 2:
63 | title = 'video';
64 | break;
65 | case 4:
66 | title = 'audio';
67 | break;
68 | }
69 |
70 | return CheckboxListTile(
71 | title: Text(title),
72 | value: checkStatus.contains(item),
73 | onChanged: (checked) {
74 | if (checked == true) {
75 | checkStatus.add(item);
76 | } else {
77 | checkStatus.remove(item);
78 | }
79 | setState(() {});
80 | },
81 | );
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/example/lib/page/developer/issues_page/issue_1152.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:oktoast/oktoast.dart';
3 | import 'package:photo_manager/photo_manager.dart';
4 |
5 | import 'issue_index_page.dart';
6 |
7 | class Issus1152 extends StatefulWidget {
8 | const Issus1152({super.key});
9 |
10 | @override
11 | State createState() => _Issus1152State();
12 | }
13 |
14 | class _Issus1152State extends State with IssueBase {
15 | @override
16 | List? get supportPlatforms => [
17 | TargetPlatform.android,
18 | ];
19 |
20 | List checkStatus = [];
21 |
22 | @override
23 | void initState() {
24 | super.initState();
25 | PhotoManager.setLog(true);
26 | }
27 |
28 | @override
29 | Widget build(BuildContext context) {
30 | return buildScaffold([
31 | buildButton(
32 | 'Test API 28 get images',
33 | reproduce,
34 | ),
35 | buildLogWidget(),
36 | ]);
37 | }
38 |
39 | @override
40 | int get issueNumber => 1152;
41 |
42 | Future reproduce() async {
43 | final pathList = await PhotoManager.getAssetPathList();
44 | final noAllPathList = pathList.where((element) => !element.isAll);
45 | if (noAllPathList.isEmpty) {
46 | showToast('No path found');
47 | return;
48 | }
49 |
50 | final path = noAllPathList.first;
51 |
52 | // final name = path.name;
53 | // final count = await path.assetCountAsync;
54 | // addLog('assetCount of "$name": $count');
55 |
56 | // final list = await path.getAssetListPaged(page: 0, size: 50);
57 | // addLog('list.length: ${list.length}');
58 |
59 | await fetchNewProperties(path);
60 | }
61 |
62 | Future fetchNewProperties(AssetPathEntity path) async {
63 | try {
64 | await path.obtainForNewProperties();
65 | } catch (e) {
66 | addLog('error: $e');
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/example/lib/page/developer/issues_page/issue_734.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:photo_manager/photo_manager.dart';
3 |
4 | import '../../../util/log.dart';
5 | import 'issue_index_page.dart';
6 |
7 | class Issue734Page extends StatefulWidget {
8 | const Issue734Page({super.key});
9 |
10 | @override
11 | State createState() => _Issue734PageState();
12 | }
13 |
14 | class _Issue734PageState extends State
15 | with IssueBase {
16 | @override
17 | int get issueNumber => 734;
18 |
19 | @override
20 | Widget build(BuildContext context) {
21 | return buildScaffold(
22 | [
23 | buildButton('Reproduct', _reproduct),
24 | ],
25 | );
26 | }
27 |
28 | Future _reproduct() async {
29 | const FilterOption opt = FilterOption(
30 | needTitle: true,
31 | sizeConstraint: SizeConstraint(ignoreSize: true),
32 | );
33 |
34 | final FilterOptionGroup group = FilterOptionGroup(
35 | imageOption: opt,
36 | audioOption: opt,
37 | videoOption: opt,
38 | );
39 |
40 | final List pathList =
41 | await PhotoManager.getAssetPathList(filterOption: group);
42 |
43 | if (pathList.isEmpty) {
44 | Log.d('The path list is empty');
45 | return;
46 | }
47 |
48 | final AssetPathEntity recent = pathList[0];
49 | final int assetCount = await recent.assetCountAsync;
50 | Log.d('recent.assetCount: $assetCount');
51 |
52 | for (int i = 0; i < assetCount; i++) {
53 | final List pageAssetList = await recent.getAssetListPaged(
54 | page: i,
55 | size: 1,
56 | );
57 |
58 | try {
59 | Log.d(' page($i, 1) list asset count: ${pageAssetList.length}');
60 | Log.d(' page($i, 1) list asset[0] id: ${pageAssetList[0].id}');
61 | } catch (e) {
62 | Log.d(e);
63 | }
64 |
65 | final List rangeList =
66 | await recent.getAssetListRange(start: i, end: i + 1);
67 | try {
68 | Log.d('range($i, ${i + 1}) list asset count: ${rangeList.length}');
69 | Log.d('range($i, ${i + 1}) list asset[0] id: ${rangeList[0].id}');
70 | } catch (e) {
71 | Log.d(e);
72 | }
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/example/lib/page/developer/issues_page/issue_918.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:photo_manager/photo_manager.dart';
3 | import 'issue_index_page.dart';
4 |
5 | class Issue918Page extends StatefulWidget {
6 | const Issue918Page({super.key});
7 |
8 | @override
9 | State createState() => _Issue918PageState();
10 | }
11 |
12 | class _Issue918PageState extends State
13 | with IssueBase {
14 | @override
15 | int get issueNumber => 918;
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return buildScaffold(
20 | [
21 | buildButton('Reproduct', _reproduct),
22 | ],
23 | );
24 | }
25 |
26 | Future _reproduct() async {
27 | final assetList = await PhotoManager.getAssetListRange(
28 | start: 0,
29 | end: 10,
30 | filterOption: FilterOptionGroup(
31 | imageOption: const FilterOption(needTitle: true),
32 | videoOption: const FilterOption(needTitle: true),
33 | orders: [
34 | const OrderOption(type: OrderOptionType.createDate),
35 | ],
36 | ),
37 | );
38 |
39 | for (final asset in assetList) {
40 | print(asset.title);
41 | print(asset.createDateTime);
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/example/lib/page/developer/issues_page/issue_962.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:photo_manager/photo_manager.dart';
3 |
4 | import 'issue_index_page.dart';
5 |
6 | class Issue962 extends StatefulWidget {
7 | const Issue962({super.key});
8 |
9 | @override
10 | State createState() => _Issue962State();
11 | }
12 |
13 | class _Issue962State extends State with IssueBase {
14 | int start = 0;
15 | int end = 5;
16 |
17 | ValueNotifier logNotifier = ValueNotifier('');
18 |
19 | Future onChange() async {
20 | if (start >= end) {
21 | logNotifier.value = 'start must less than end';
22 | return;
23 | }
24 |
25 | final assetList = await PhotoManager.getAssetListRange(
26 | start: start,
27 | end: end,
28 | filterOption: FilterOptionGroup(
29 | imageOption: const FilterOption(needTitle: true),
30 | videoOption: const FilterOption(needTitle: true),
31 | orders: [
32 | const OrderOption(type: OrderOptionType.createDate),
33 | ],
34 | ),
35 | );
36 |
37 | logNotifier.value = 'The assetList count: ${assetList.length}';
38 | }
39 |
40 | final node = FocusScopeNode();
41 |
42 | @override
43 | void dispose() {
44 | node.dispose();
45 | super.dispose();
46 | }
47 |
48 | @override
49 | Widget build(BuildContext context) {
50 | return buildScaffold([
51 | TextFormField(
52 | initialValue: start.toString(),
53 | decoration: const InputDecoration(
54 | labelText: 'start',
55 | ),
56 | onChanged: (value) {
57 | start = int.tryParse(value) ?? 0;
58 | onChange();
59 | },
60 | ),
61 | TextFormField(
62 | initialValue: end.toString(),
63 | decoration: const InputDecoration(
64 | labelText: 'end',
65 | ),
66 | onChanged: (value) {
67 | end = int.tryParse(value) ?? 0;
68 | onChange();
69 | },
70 | ),
71 | buildButton('Reproduct issue 962', onChange),
72 | AnimatedBuilder(
73 | animation: logNotifier,
74 | builder: (context, w) {
75 | return Text(
76 | logNotifier.value,
77 | style: const TextStyle(
78 | fontSize: 12,
79 | ),
80 | );
81 | },
82 | ),
83 | ]);
84 | }
85 |
86 | @override
87 | int get issueNumber => 962;
88 | }
89 |
--------------------------------------------------------------------------------
/example/lib/page/developer/issues_page/issue_979.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:photo_manager/photo_manager.dart';
3 | import 'package:photo_manager_example/page/developer/issues_page/issue_index_page.dart';
4 | import 'package:photo_manager_example/util/asset_utils.dart';
5 |
6 | class Issue979 extends StatefulWidget {
7 | const Issue979({super.key});
8 |
9 | @override
10 | State createState() => _Issue979State();
11 | }
12 |
13 | class _Issue979State extends State with IssueBase {
14 | @override
15 | Widget build(BuildContext context) {
16 | return buildScaffold([
17 | buildButton('Save image and read asset', _saveAndRead),
18 | buildLogWidget(),
19 | ]);
20 | }
21 |
22 | @override
23 | int get issueNumber => 979;
24 |
25 | Future _saveAndRead() async {
26 | final auth = await PhotoManager.requestPermissionExtend(
27 | requestOption: const PermissionRequestOption(
28 | iosAccessLevel: IosAccessLevel.addOnly,
29 | ),
30 | );
31 | if (!auth.hasAccess) {
32 | addLog('request permission fail, $auth');
33 | return;
34 | }
35 |
36 | addLog('request permission success, wait download file...');
37 |
38 | try {
39 | final file = await AssetsUtils.downloadJpeg();
40 | final name = file.path.split('/').last;
41 | final asset = await PhotoManager.editor.saveImageWithPath(
42 | file.path,
43 | title: name,
44 | );
45 | final id = asset.id;
46 | final width = asset.width;
47 | final height = asset.height;
48 | addLog('The save id = $id, width = $width, height = $height');
49 | } catch (e) {
50 | addLog('Save error : $e');
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/example/lib/page/developer/remove_all_android_not_exists_example.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:photo_manager/photo_manager.dart';
3 |
4 | class RemoveAndroidNotExistsExample extends StatefulWidget {
5 | const RemoveAndroidNotExistsExample({super.key});
6 |
7 | @override
8 | State createState() =>
9 | _RemoveAndroidNotExistsExampleState();
10 | }
11 |
12 | class _RemoveAndroidNotExistsExampleState
13 | extends State {
14 | @override
15 | Widget build(BuildContext context) {
16 | return Scaffold(
17 | appBar: AppBar(
18 | title: const Text('Remove android not exists assets.'),
19 | ),
20 | body: Padding(
21 | padding: const EdgeInsets.all(8.0),
22 | child: Column(
23 | crossAxisAlignment: CrossAxisAlignment.stretch,
24 | children: [
25 | ElevatedButton(
26 | child: const Text('Click and see android logcat log.'),
27 | onPressed: () {
28 | PhotoManager.editor.android.removeAllNoExistsAsset();
29 | },
30 | ),
31 | ],
32 | ),
33 | ),
34 | );
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/example/lib/page/gallery_list_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:photo_manager/photo_manager.dart';
3 | import 'package:provider/provider.dart';
4 |
5 | import '../model/photo_provider.dart';
6 | import '../widget/gallery_item_widget.dart';
7 |
8 | class GalleryListPage extends StatefulWidget {
9 | const GalleryListPage({super.key});
10 |
11 | @override
12 | State createState() => _GalleryListPageState();
13 | }
14 |
15 | class _GalleryListPageState extends State {
16 | PhotoProvider get provider => context.watch();
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return Scaffold(
21 | appBar: AppBar(
22 | title: const Text('Gallery list'),
23 | ),
24 | body: Scrollbar(
25 | child: ListView.builder(
26 | itemBuilder: _buildItem,
27 | itemCount: provider.list.length,
28 | ),
29 | ),
30 | );
31 | }
32 |
33 | Widget _buildItem(BuildContext context, int index) {
34 | final AssetPathEntity item = provider.list[index];
35 | return GalleryItemWidget(
36 | path: item,
37 | setState: setState,
38 | );
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/example/lib/page/index_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:photo_manager_example/page/custom_filter_example_page.dart';
3 | import 'package:photo_manager_example/widget/nav_button.dart';
4 |
5 | import 'change_notify_page.dart';
6 | import 'developer/develop_index_page.dart';
7 | import 'home_page.dart';
8 | import 'save_image_example.dart';
9 |
10 | class IndexPage extends StatefulWidget {
11 | const IndexPage({super.key});
12 |
13 | @override
14 | State createState() => _IndexPageState();
15 | }
16 |
17 | class _IndexPageState extends State {
18 | @override
19 | Widget build(BuildContext context) {
20 | return Scaffold(
21 | appBar: AppBar(
22 | title: const Text('Example for photo manager.'),
23 | ),
24 | body: ListView(
25 | padding: const EdgeInsets.all(8.0),
26 | children: [
27 | routePage('Gallery list', const NewHomePage()),
28 | routePage('Custom filter example', const CustomFilterExamplePage()),
29 | routePage('Save media example', const SaveMediaExample()),
30 | routePage('Change notify example', const ChangeNotifyExample()),
31 | routePage('For Developer page', const DeveloperIndexPage()),
32 | ],
33 | ),
34 | );
35 | }
36 |
37 | Widget routePage(String title, Widget page) {
38 | return NavButton(title: title, page: page);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/example/lib/page/sub_gallery_page.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:photo_manager/photo_manager.dart';
3 |
4 | import '../widget/gallery_item_widget.dart';
5 |
6 | class SubFolderPage extends StatefulWidget {
7 | const SubFolderPage({
8 | super.key,
9 | required this.pathList,
10 | required this.title,
11 | });
12 |
13 | final List pathList;
14 | final String title;
15 |
16 | @override
17 | State createState() => _SubFolderPageState();
18 | }
19 |
20 | class _SubFolderPageState extends State {
21 | @override
22 | Widget build(BuildContext context) {
23 | return Scaffold(
24 | appBar: AppBar(
25 | title: Text(widget.title),
26 | ),
27 | body: ListView.builder(
28 | itemBuilder: _buildItem,
29 | itemCount: widget.pathList.length,
30 | ),
31 | );
32 | }
33 |
34 | Widget _buildItem(BuildContext context, int index) {
35 | final AssetPathEntity item = widget.pathList[index];
36 | return GalleryItemWidget(
37 | path: item,
38 | setState: setState,
39 | );
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/example/lib/util/asset_utils.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:http/http.dart';
4 | import 'package:path_provider/path_provider.dart';
5 |
6 | class AssetsUtils {
7 | AssetsUtils._();
8 |
9 | static const String jpegUrl =
10 | 'https://gitlab.com/CaiJingLong/ExampleAsset/-/raw/main/IMG_1096.jpeg?ref_type=heads';
11 |
12 | static Future downloadJpeg() async {
13 | final cacheDir = await getTemporaryDirectory();
14 |
15 | final dtMs = DateTime.now().millisecondsSinceEpoch;
16 | final file = File('${cacheDir.path}/$dtMs.jpg');
17 |
18 | final bytes = await get(Uri.parse(jpegUrl));
19 | await file.writeAsBytes(bytes.bodyBytes);
20 | return file;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/example/lib/util/log_export.dart:
--------------------------------------------------------------------------------
1 | import 'package:path_provider/path_provider.dart';
2 |
3 | /// Because the photo_manager plugin does not depend on the path_provider plugin,
4 | /// we need to create a new class in the example to get the log file path.
5 | class PMVerboseLogUtil {
6 | PMVerboseLogUtil();
7 |
8 | static final shared = PMVerboseLogUtil();
9 |
10 | static String? _logDirPath;
11 |
12 | String _logFilePath = '';
13 |
14 | /// Get the log file path.
15 | ///
16 | /// Use in the `PhotoManager.setLog` method.
17 | Future getLogFilePath() async {
18 | if (_logFilePath.isNotEmpty) {
19 | return _logFilePath;
20 | }
21 |
22 | if (_logDirPath == null) {
23 | final cacheDir = await getApplicationCacheDirectory();
24 | _logDirPath = cacheDir.path;
25 | }
26 |
27 | final timeStr = DateTime.now().toIso8601String();
28 | _logFilePath = '$_logDirPath/pmlog-$timeStr.txt';
29 |
30 | return _logFilePath;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/example/lib/widget/change_notifier_builder.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | typedef ChangeNotifierWidgetBuilder = Widget Function(
4 | BuildContext context,
5 | T value,
6 | );
7 |
8 | class ChangeNotifierBuilder extends StatefulWidget {
9 | const ChangeNotifierBuilder({
10 | super.key,
11 | required this.builder,
12 | required this.value,
13 | });
14 |
15 | final ChangeNotifierWidgetBuilder builder;
16 | final T value;
17 |
18 | @override
19 | State> createState() =>
20 | _ChangeNotifierBuilderState();
21 | }
22 |
23 | class _ChangeNotifierBuilderState
24 | extends State> {
25 | @override
26 | void initState() {
27 | super.initState();
28 | widget.value.addListener(_onChange);
29 | }
30 |
31 | void _onChange() {
32 | if (mounted) {
33 | setState(() {});
34 | }
35 | }
36 |
37 | @override
38 | void dispose() {
39 | widget.value.removeListener(_onChange);
40 | super.dispose();
41 | }
42 |
43 | @override
44 | Widget build(BuildContext context) {
45 | return widget.builder(context, widget.value);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/example/lib/widget/dialog/list_dialog.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class ListDialog extends StatefulWidget {
4 | const ListDialog({
5 | super.key,
6 | required this.children,
7 | this.padding = EdgeInsets.zero,
8 | });
9 |
10 | final List children;
11 | final EdgeInsetsGeometry padding;
12 |
13 | @override
14 | State createState() => _ListDialogState();
15 | }
16 |
17 | class _ListDialogState extends State {
18 | @override
19 | Widget build(BuildContext context) {
20 | return Center(
21 | child: ListView(
22 | padding: widget.padding,
23 | shrinkWrap: true,
24 | children: widget.children,
25 | ),
26 | );
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/example/lib/widget/loading_widget.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:flutter/cupertino.dart';
4 | import 'package:flutter/material.dart';
5 |
6 | final Center loadWidget = Center(
7 | child: SizedBox.fromSize(
8 | size: const Size.square(30),
9 | child: (Platform.isIOS || Platform.isMacOS)
10 | ? const CupertinoActivityIndicator()
11 | : const CircularProgressIndicator(),
12 | ),
13 | );
14 |
--------------------------------------------------------------------------------
/example/lib/widget/nav_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class NavButton extends StatelessWidget {
4 | const NavButton({
5 | super.key,
6 | required this.title,
7 | required this.page,
8 | });
9 |
10 | final String title;
11 | final Widget page;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return CustomButton(
16 | onPressed: () => Navigator.push(
17 | context,
18 | MaterialPageRoute(builder: (_) => page),
19 | ),
20 | title: title,
21 | );
22 | }
23 | }
24 |
25 | class CustomButton extends StatelessWidget {
26 | const CustomButton({
27 | super.key,
28 | required this.title,
29 | required this.onPressed,
30 | });
31 |
32 | final String title;
33 | final VoidCallback onPressed;
34 |
35 | @override
36 | Widget build(BuildContext context) {
37 | return Container(
38 | padding: const EdgeInsets.all(8.0),
39 | height: 64,
40 | child: ElevatedButton(
41 | onPressed: onPressed,
42 | child: Text(title),
43 | ),
44 | );
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/example/lib/widget/nav_column.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:photo_manager_example/widget/theme_button.dart';
3 |
4 | String _defaultBuilder(Widget w) {
5 | return w.toStringShort();
6 | }
7 |
8 | class NavColumn extends StatelessWidget {
9 | const NavColumn({
10 | super.key,
11 | required this.children,
12 | this.titleBuilder = _defaultBuilder,
13 | });
14 |
15 | final List children;
16 | final String Function(Widget w) titleBuilder;
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return Padding(
21 | padding: const EdgeInsets.all(8.0),
22 | child: SingleChildScrollView(
23 | child: Center(
24 | child: Column(
25 | children: [
26 | for (final Widget item in children) buildItem(context, item),
27 | ],
28 | ),
29 | ),
30 | ),
31 | );
32 | }
33 |
34 | Widget buildItem(BuildContext context, Widget item) {
35 | return ThemeButton(
36 | text: titleBuilder(item),
37 | onPressed: () {
38 | Navigator.push(
39 | context,
40 | MaterialPageRoute(builder: (_) => item),
41 | );
42 | },
43 | );
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/example/lib/widget/theme_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class ThemeButton extends StatelessWidget {
4 | const ThemeButton({
5 | super.key,
6 | required this.text,
7 | required this.onPressed,
8 | });
9 |
10 | final String text;
11 | final VoidCallback onPressed;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return Container(
16 | height: 44,
17 | width: double.infinity,
18 | margin: const EdgeInsets.only(bottom: 5, top: 5),
19 | child: ElevatedButton(
20 | onPressed: onPressed,
21 | child: Text(text),
22 | ),
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/example/macos/.gitignore:
--------------------------------------------------------------------------------
1 | # Flutter-related
2 | **/Flutter/ephemeral/
3 | **/Pods/
4 |
5 | # Xcode-related
6 | **/xcuserdata/
7 |
8 | Flutter/GeneratedPluginRegistrant.swift
--------------------------------------------------------------------------------
/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/Podfile:
--------------------------------------------------------------------------------
1 | platform :osx, '10.15'
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.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 |
--------------------------------------------------------------------------------
/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/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 = photo_manager Example
9 |
10 | // The application's bundle identifier
11 | PRODUCT_BUNDLE_IDENTIFIER = com.fluttercandies.photo-manager-example
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.assets.movies.read-write
8 |
9 | com.apple.security.assets.music.read-write
10 |
11 | com.apple.security.assets.pictures.read-write
12 |
13 | com.apple.security.cs.allow-jit
14 |
15 | com.apple.security.network.server
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/example/macos/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSPhotoLibraryUsageDescription
6 | I will use photo library
7 | NSPhotoLibraryAddUsageDescription
8 | You must agree the additions permission to save assets.
9 | CFBundleDevelopmentRegion
10 | $(DEVELOPMENT_LANGUAGE)
11 | CFBundleExecutable
12 | $(EXECUTABLE_NAME)
13 | CFBundleIconFile
14 |
15 | CFBundleIdentifier
16 | $(PRODUCT_BUNDLE_IDENTIFIER)
17 | CFBundleInfoDictionaryVersion
18 | 6.0
19 | CFBundleName
20 | $(PRODUCT_NAME)
21 | CFBundlePackageType
22 | APPL
23 | CFBundleShortVersionString
24 | $(FLUTTER_BUILD_NAME)
25 | CFBundleVersion
26 | $(FLUTTER_BUILD_NUMBER)
27 | LSMinimumSystemVersion
28 | $(MACOSX_DEPLOYMENT_TARGET)
29 | NSHumanReadableCopyright
30 | $(PRODUCT_COPYRIGHT)
31 | NSMainNibFile
32 | MainMenu
33 | NSPrincipalClass
34 | NSApplication
35 |
36 |
37 |
--------------------------------------------------------------------------------
/example/macos/Runner/MainFlutterWindow.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | class MainFlutterWindow: NSWindow {
5 | override func awakeFromNib() {
6 | let flutterViewController = FlutterViewController.init()
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.assets.movies.read-write
8 |
9 | com.apple.security.assets.music.read-write
10 |
11 | com.apple.security.assets.pictures.read-write
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: photo_manager_example
2 | description: Demonstrates how to use the photo_manager plugin.
3 | publish_to: none
4 |
5 | environment:
6 | sdk: ">=2.19.0"
7 | flutter: ">=3.7.0"
8 |
9 | dependencies:
10 | flutter:
11 | sdk: flutter
12 |
13 | photo_manager: # override
14 | photo_manager_image_provider: ^2.1.1
15 |
16 | http: ">=0.13.6 <2.0.0"
17 | oktoast: ^3.1.0
18 | path_provider: ^2.0.5
19 | provider: ^6.0.0
20 | url_launcher: ^6.1.10
21 | video_player: ^2.1.14
22 | image: ^4.5.4
23 |
24 | dev_dependencies:
25 | flutter_lints: any
26 | flutter_test:
27 | sdk: flutter
28 | test: any
29 |
30 | flutter:
31 | uses-material-design: true
32 |
33 | dependency_overrides:
34 | photo_manager:
35 | path: ../
36 |
--------------------------------------------------------------------------------
/example_ohos/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | **/Podfile.lock
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | **/doc/api/
26 | **/ios/Flutter/.last_build_id
27 | .dart_tool/
28 | .flutter-plugins
29 | .flutter-plugins-dependencies
30 | .packages
31 | .pub-cache/
32 | .pub/
33 | /build/
34 |
35 | # Web related
36 | lib/generated_plugin_registrant.dart
37 |
38 | # Symbolication related
39 | app.*.symbols
40 |
41 | # Obfuscation related
42 | app.*.map.json
43 | .metadata
--------------------------------------------------------------------------------
/example_ohos/README.md:
--------------------------------------------------------------------------------
1 | # photo_manager_example
2 |
3 | Demonstrates how to use the photo_manager plugin on OpenHarmony.
4 |
5 | ## Getting Started
6 |
7 | For help getting started with Flutter, view our online
8 | [documentation](https://flutter.io/).
9 |
--------------------------------------------------------------------------------
/example_ohos/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: ../analysis_options.yaml
2 |
3 | linter:
4 | rules:
5 | avoid_print: false
6 | dangling_library_doc_comments: false
7 | use_build_context_synchronously: false
8 |
--------------------------------------------------------------------------------
/example_ohos/custom.dart:
--------------------------------------------------------------------------------
1 | import 'package:photo_manager/photo_manager.dart';
2 |
3 | void main(List args) {
4 | final filter = AdvancedCustomFilter().addWhereCondition(
5 | WhereConditionGroup()
6 | .andGroup(
7 | WhereConditionGroup()
8 | .andText('width > 1000')
9 | .andText('height > 1000'),
10 | )
11 | .orGroup(
12 | WhereConditionGroup().andText('width < 500').andText('height < 500'),
13 | ),
14 | );
15 |
16 | PhotoManager.getAssetPathList(filterOption: filter).then((value) {
17 | print(value);
18 | });
19 | }
20 |
--------------------------------------------------------------------------------
/example_ohos/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:photo_manager_example/main.dart' as example;
2 |
3 | void main() {
4 | example.main();
5 | }
6 |
--------------------------------------------------------------------------------
/example_ohos/ohos/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /oh_modules
3 | /local.properties
4 | /.idea
5 | **/build
6 | /.hvigor
7 | .cxx
8 | /.clangd
9 | /.clang-format
10 | /.clang-tidy
11 | **/.test
12 | *.har
13 | **/BuildProfile.ets
14 | **/oh-package-lock.json5
15 |
16 | **/src/main/resources/rawfile/flutter_assets/
17 | **/libs/arm64-v8a/libapp.so
18 | **/libs/arm64-v8a/libflutter.so
19 | **/libs/arm64-v8a/libvmservice_snapshot.so
20 |
--------------------------------------------------------------------------------
/example_ohos/ohos/AppScope/app.json5:
--------------------------------------------------------------------------------
1 | {
2 | "app": {
3 | "bundleName": "com.fluttercandies.example",
4 | "vendor": "example",
5 | "versionCode": 1000000,
6 | "versionName": "1.0.0",
7 | "icon": "$media:app_icon",
8 | "label": "$string:app_name"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/example_ohos/ohos/AppScope/resources/base/element/string.json:
--------------------------------------------------------------------------------
1 | {
2 | "string": [
3 | {
4 | "name": "app_name",
5 | "value": "example"
6 | }
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/example_ohos/ohos/AppScope/resources/base/media/app_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/flutter_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/example_ohos/ohos/AppScope/resources/base/media/app_icon.png
--------------------------------------------------------------------------------
/example_ohos/ohos/build-profile.json5:
--------------------------------------------------------------------------------
1 | {
2 | "app": {
3 | "signingConfigs": [],
4 | "products": [
5 | {
6 | "name": "default",
7 | "signingConfig": "default",
8 | "compileSdkVersion": "4.1.0(11)",
9 | "compatibleSdkVersion": "4.1.0(11)",
10 | "runtimeOS": "HarmonyOS"
11 | }
12 | ]
13 | },
14 | "modules": [
15 | {
16 | "name": "entry",
17 | "srcPath": "./entry",
18 | "targets": [
19 | {
20 | "name": "default",
21 | "applyToProducts": [
22 | "default"
23 | ]
24 | }
25 | ]
26 | }
27 | ]
28 | }
--------------------------------------------------------------------------------
/example_ohos/ohos/entry/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | /node_modules
3 | /oh_modules
4 | /.preview
5 | /build
6 | /.cxx
7 | /.test
8 | /har
9 | /libs
10 | oh-package-lock.json5
--------------------------------------------------------------------------------
/example_ohos/ohos/entry/build-profile.json5:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 |
16 | {
17 | "apiType": 'stageMode',
18 | "buildOption": {
19 | },
20 | "targets": [
21 | {
22 | "name": "default",
23 | "runtimeOS": "HarmonyOS"
24 | },
25 | {
26 | "name": "ohosTest",
27 | }
28 | ]
29 | }
--------------------------------------------------------------------------------
/example_ohos/ohos/entry/hvigorfile.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 |
16 | // Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently.
17 | export { hapTasks } from '@ohos/hvigor-ohos-plugin';
18 |
--------------------------------------------------------------------------------
/example_ohos/ohos/entry/oh-package.json5:
--------------------------------------------------------------------------------
1 | {
2 | "name": "entry",
3 | "version": "1.0.0",
4 | "description": "Please describe the basic information.",
5 | "main": "",
6 | "author": "",
7 | "license": "",
8 | "dependencies": {
9 | "path_provider_ohos": "file:../har/path_provider_ohos.har",
10 | "url_launcher_ohos": "file:../har/url_launcher_ohos.har",
11 | "video_player_ohos": "file:../har/video_player_ohos.har",
12 | "photo_manager": "file:../har/photo_manager.har"
13 | }
14 | }
--------------------------------------------------------------------------------
/example_ohos/ohos/entry/src/main/ets/entryability/EntryAbility.ets:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 |
16 | import { FlutterAbility, FlutterEngine } from '@ohos/flutter_ohos';
17 | import { GeneratedPluginRegistrant } from '../plugins/GeneratedPluginRegistrant';
18 |
19 | export default class EntryAbility extends FlutterAbility {
20 | configureFlutterEngine(flutterEngine: FlutterEngine) {
21 | super.configureFlutterEngine(flutterEngine)
22 | GeneratedPluginRegistrant.registerWith(flutterEngine)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/example_ohos/ohos/entry/src/main/ets/pages/Index.ets:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 |
16 | import common from '@ohos.app.ability.common';
17 | import { FlutterPage } from '@ohos/flutter_ohos'
18 |
19 | let storage = LocalStorage.getShared()
20 | const EVENT_BACK_PRESS = 'EVENT_BACK_PRESS'
21 |
22 | @Entry(storage)
23 | @Component
24 | struct Index {
25 | private context = getContext(this) as common.UIAbilityContext
26 | @LocalStorageLink('viewId') viewId: string = "";
27 |
28 | build() {
29 | Column() {
30 | FlutterPage({ viewId: this.viewId })
31 | }
32 | }
33 |
34 | onBackPress(): boolean {
35 | this.context.eventHub.emit(EVENT_BACK_PRESS)
36 | return true
37 | }
38 | }
--------------------------------------------------------------------------------
/example_ohos/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets:
--------------------------------------------------------------------------------
1 | import { FlutterEngine, Log } from '@ohos/flutter_ohos';
2 | import PathProviderPlugin from 'path_provider_ohos';
3 | import PhotoManagerPlugin from 'photo_manager';
4 | import UrlLauncherPlugin from 'url_launcher_ohos';
5 | import VideoPlayerPlugin from 'video_player_ohos';
6 |
7 | /**
8 | * Generated file. Do not edit.
9 | * This file is generated by the Flutter tool based on the
10 | * plugins that support the Ohos platform.
11 | */
12 |
13 | const TAG = "GeneratedPluginRegistrant";
14 |
15 | export class GeneratedPluginRegistrant {
16 |
17 | static registerWith(flutterEngine: FlutterEngine) {
18 | try {
19 | flutterEngine.getPlugins()?.add(new PathProviderPlugin());
20 | flutterEngine.getPlugins()?.add(new PhotoManagerPlugin());
21 | flutterEngine.getPlugins()?.add(new UrlLauncherPlugin());
22 | flutterEngine.getPlugins()?.add(new VideoPlayerPlugin());
23 | } catch (e) {
24 | Log.e(
25 | TAG,
26 | "Tried to register plugins with FlutterEngine ("
27 | + flutterEngine
28 | + ") failed.");
29 | Log.e(TAG, "Received exception while registering", e);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/example_ohos/ohos/entry/src/main/module.json5:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | {
16 | "module": {
17 | "name": "entry",
18 | "type": "entry",
19 | "description": "$string:module_desc",
20 | "mainElement": "EntryAbility",
21 | "deviceTypes": [
22 | "phone"
23 | ],
24 | "deliveryWithInstall": true,
25 | "installationFree": false,
26 | "pages": "$profile:main_pages",
27 | "abilities": [
28 | {
29 | "name": "EntryAbility",
30 | "srcEntry": "./ets/entryability/EntryAbility.ets",
31 | "description": "$string:EntryAbility_desc",
32 | "icon": "$media:icon",
33 | "label": "$string:EntryAbility_label",
34 | "startWindowIcon": "$media:icon",
35 | "startWindowBackground": "$color:start_window_background",
36 | "exported": true,
37 | "skills": [
38 | {
39 | "entities": [
40 | "entity.system.home"
41 | ],
42 | "actions": [
43 | "action.system.home"
44 | ]
45 | }
46 | ]
47 | }
48 | ],
49 | "requestPermissions": [
50 | {"name" : "ohos.permission.INTERNET"},
51 | {
52 | "name": "ohos.permission.READ_IMAGEVIDEO",
53 | "reason": "$string:EntryAbility_label",
54 | "usedScene": {
55 | "abilities": [
56 | "EntryAbility"
57 | ],
58 | "when": "inuse"
59 | }
60 | },
61 | {
62 | "name": "ohos.permission.WRITE_IMAGEVIDEO",
63 | "reason": "$string:EntryAbility_label",
64 | "usedScene": {
65 | "abilities": [
66 | "EntryAbility"
67 | ],
68 | "when": "inuse"
69 | }
70 | }
71 | ]
72 | }
73 | }
--------------------------------------------------------------------------------
/example_ohos/ohos/entry/src/main/resources/base/element/color.json:
--------------------------------------------------------------------------------
1 | {
2 | "color": [
3 | {
4 | "name": "start_window_background",
5 | "value": "#FFFFFF"
6 | }
7 | ]
8 | }
--------------------------------------------------------------------------------
/example_ohos/ohos/entry/src/main/resources/base/element/string.json:
--------------------------------------------------------------------------------
1 | {
2 | "string": [
3 | {
4 | "name": "module_desc",
5 | "value": "module description"
6 | },
7 | {
8 | "name": "EntryAbility_desc",
9 | "value": "description"
10 | },
11 | {
12 | "name": "EntryAbility_label",
13 | "value": "example"
14 | }
15 | ]
16 | }
--------------------------------------------------------------------------------
/example_ohos/ohos/entry/src/main/resources/base/media/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/flutter_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/example_ohos/ohos/entry/src/main/resources/base/media/icon.png
--------------------------------------------------------------------------------
/example_ohos/ohos/entry/src/main/resources/base/profile/main_pages.json:
--------------------------------------------------------------------------------
1 | {
2 | "src": [
3 | "pages/Index"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/example_ohos/ohos/entry/src/main/resources/en_US/element/string.json:
--------------------------------------------------------------------------------
1 | {
2 | "string": [
3 | {
4 | "name": "module_desc",
5 | "value": "module description"
6 | },
7 | {
8 | "name": "EntryAbility_desc",
9 | "value": "description"
10 | },
11 | {
12 | "name": "EntryAbility_label",
13 | "value": "example"
14 | }
15 | ]
16 | }
--------------------------------------------------------------------------------
/example_ohos/ohos/entry/src/main/resources/zh_CN/element/string.json:
--------------------------------------------------------------------------------
1 | {
2 | "string": [
3 | {
4 | "name": "module_desc",
5 | "value": "模块描述"
6 | },
7 | {
8 | "name": "EntryAbility_desc",
9 | "value": "description"
10 | },
11 | {
12 | "name": "EntryAbility_label",
13 | "value": "example"
14 | }
15 | ]
16 | }
--------------------------------------------------------------------------------
/example_ohos/ohos/hvigor/hvigor-config.json5:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 |
16 | {
17 | "hvigorVersion": "4.1.2",
18 | "dependencies": {
19 | "@ohos/hvigor-ohos-plugin": "4.1.2"
20 | }
21 | }
--------------------------------------------------------------------------------
/example_ohos/ohos/hvigorfile.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 |
16 | import { appTasks } from '@ohos/hvigor-ohos-plugin';
17 |
18 | export default {
19 | system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
20 | plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
21 | }
--------------------------------------------------------------------------------
/example_ohos/ohos/hvigorw:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd.
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 |
16 | # ----------------------------------------------------------------------------
17 | # Hvigor startup script, version 1.0.0
18 | #
19 | # Required ENV vars:
20 | # ------------------
21 | # NODE_HOME - location of a Node home dir
22 | # or
23 | # Add /usr/local/nodejs/bin to the PATH environment variable
24 | # ----------------------------------------------------------------------------
25 |
26 | HVIGOR_APP_HOME=$(dirname $(readlink -f $0))
27 | HVIGOR_WRAPPER_SCRIPT=${HVIGOR_APP_HOME}/hvigor/hvigor-wrapper.js
28 | warn() {
29 | echo ""
30 | echo -e "\033[1;33m`date '+[%Y-%m-%d %H:%M:%S]'`$@\033[0m"
31 | }
32 |
33 | error() {
34 | echo ""
35 | echo -e "\033[1;31m`date '+[%Y-%m-%d %H:%M:%S]'`$@\033[0m"
36 | }
37 |
38 | fail() {
39 | error "$@"
40 | exit 1
41 | }
42 |
43 | # Determine node to start hvigor wrapper script
44 | if [ -n "${NODE_HOME}" ];then
45 | EXECUTABLE_NODE="${NODE_HOME}/bin/node"
46 | if [ ! -x "$EXECUTABLE_NODE" ];then
47 | fail "ERROR: NODE_HOME is set to an invalid directory,check $NODE_HOME\n\nPlease set NODE_HOME in your environment to the location where your nodejs installed"
48 | fi
49 | else
50 | EXECUTABLE_NODE="node"
51 | which ${EXECUTABLE_NODE} > /dev/null 2>&1 || fail "ERROR: NODE_HOME is not set and not 'node' command found in your path"
52 | fi
53 |
54 | # Check hvigor wrapper script
55 | if [ ! -r "$HVIGOR_WRAPPER_SCRIPT" ];then
56 | fail "ERROR: Couldn't find hvigor/hvigor-wrapper.js in ${HVIGOR_APP_HOME}"
57 | fi
58 |
59 | # start hvigor-wrapper script
60 | exec "${EXECUTABLE_NODE}" \
61 | "${HVIGOR_WRAPPER_SCRIPT}" "$@"
62 |
--------------------------------------------------------------------------------
/example_ohos/ohos/hvigorw.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Hvigor startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
17 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
18 |
19 | set WRAPPER_MODULE_PATH=%APP_HOME%\hvigor\hvigor-wrapper.js
20 | set NODE_EXE=node.exe
21 |
22 | goto start
23 |
24 | :start
25 | @rem Find node.exe
26 | if defined NODE_HOME goto findNodeFromNodeHome
27 |
28 | %NODE_EXE% --version >NUL 2>&1
29 | if "%ERRORLEVEL%" == "0" goto execute
30 |
31 | echo.
32 | echo ERROR: NODE_HOME is not set and no 'node' command could be found in your PATH.
33 | echo.
34 | echo Please set the NODE_HOME variable in your environment to match the
35 | echo location of your NodeJs installation.
36 |
37 | goto fail
38 |
39 | :findNodeFromNodeHome
40 | set NODE_HOME=%NODE_HOME:"=%
41 | set NODE_EXE_PATH=%NODE_HOME%/%NODE_EXE%
42 |
43 | if exist "%NODE_EXE_PATH%" goto execute
44 | echo.
45 | echo ERROR: NODE_HOME is not set and no 'node' command could be found in your PATH.
46 | echo.
47 | echo Please set the NODE_HOME variable in your environment to match the
48 | echo location of your NodeJs installation.
49 |
50 | goto fail
51 |
52 | :execute
53 | @rem Execute hvigor
54 | "%NODE_EXE%" %WRAPPER_MODULE_PATH% %*
55 |
56 | if "%ERRORLEVEL%" == "0" goto hvigorwEnd
57 |
58 | :fail
59 | exit /b 1
60 |
61 | :hvigorwEnd
62 | if "%OS%" == "Windows_NT" endlocal
63 |
64 | :end
65 |
--------------------------------------------------------------------------------
/example_ohos/ohos/oh-package.json5:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example_ohos",
3 | "version": "1.0.0",
4 | "description": "Please describe the basic information.",
5 | "main": "",
6 | "author": "",
7 | "license": "",
8 | "dependencies": {
9 | "@ohos/flutter_ohos": "file:./har/flutter.har"
10 | },
11 | "devDependencies": {
12 | "@ohos/hypium": "1.0.6"
13 | },
14 | "overrides": {
15 | "@ohos/flutter_ohos": "file:./har/flutter.har"
16 | }
17 | }
--------------------------------------------------------------------------------
/example_ohos/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: photo_manager_example_ohos
2 | description: Demonstrates how to use the photo_manager plugin.
3 | publish_to: none
4 |
5 | environment:
6 | sdk: ">=2.19.0"
7 | flutter: ">=3.7.0"
8 |
9 | dependencies:
10 | flutter:
11 | sdk: flutter
12 |
13 | photo_manager: # override
14 | photo_manager_example:
15 | path: ../example
16 |
17 | photo_manager_image_provider: # override
18 | video_player: # override
19 | url_launcher: # override
20 | path_provider: # override
21 |
22 | dev_dependencies:
23 | flutter_lints: any
24 | flutter_test:
25 | sdk: flutter
26 | test: any
27 |
28 | flutter:
29 | uses-material-design: true
30 |
31 | dependency_overrides:
32 | photo_manager:
33 | path: ../
34 | photo_manager_image_provider: ^1.1.1
35 |
36 | path_provider:
37 | git:
38 | url: https://gitee.com/openharmony-sig/flutter_packages
39 | path: packages/path_provider/path_provider
40 | url_launcher:
41 | git:
42 | url: https://gitee.com/openharmony-sig/flutter_packages
43 | path: packages/url_launcher/url_launcher
44 | video_player:
45 | git:
46 | url: https://gitee.com/openharmony-sig/flutter_packages
47 | path: packages/video_player/video_player
48 |
--------------------------------------------------------------------------------
/flow_chart/advance_custom_filter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fluttercandies/flutter_photo_manager/e59d65ad598eb9d0f4925afd595e4226fc1006ae/flow_chart/advance_custom_filter.png
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | # OS X
2 | .DS_Store
3 |
4 | # Xcode
5 | build/
6 | *.pbxuser
7 | !default.pbxuser
8 | *.mode1v3
9 | !default.mode1v3
10 | *.mode2v3
11 | !default.mode2v3
12 | *.perspectivev3
13 | !default.perspectivev3
14 | xcuserdata/
15 | *.xccheckout
16 | profile
17 | *.moved-aside
18 | DerivedData
19 | *.hmap
20 | *.ipa
21 |
22 | # Bundler
23 | .bundle
24 |
25 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
26 | # Carthage/Checkouts
27 |
28 | Carthage/Build
29 |
30 | # We recommend against adding the Pods directory to your .gitignore. However
31 | # you should judge for yourself, the pros and cons are mentioned at:
32 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
33 | #
34 | # Note: if you ignore the Pods directory, make sure to uncomment
35 | # `pod install` in .travis.yml
36 | #
37 | # Pods/
38 |
--------------------------------------------------------------------------------
/ios/Classes/PMConverter.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import "PMConvertProtocol.h"
3 |
4 | @interface PMConverter : NSObject
5 |
6 | @end
7 |
--------------------------------------------------------------------------------
/ios/Classes/PMConverter.m:
--------------------------------------------------------------------------------
1 | #import "PMConverter.h"
2 | #import "PMImport.h"
3 |
4 | @implementation PMConverter {
5 |
6 | }
7 |
8 | - (id)convertData:(NSData *)data {
9 | return [FlutterStandardTypedData typedDataWithBytes:data];
10 | }
11 |
12 | @end
13 |
--------------------------------------------------------------------------------
/ios/Classes/PMImport.h:
--------------------------------------------------------------------------------
1 | //
2 | // PMImport.h
3 | // Pods
4 | //
5 |
6 | #ifndef PMImport_h
7 | #define PMImport_h
8 |
9 | #if TARGET_OS_OSX
10 | #import
11 | #elif TARGET_OS_IOS
12 | #import
13 | #endif
14 |
15 | #endif /* PMImport_h */
16 |
--------------------------------------------------------------------------------
/ios/Classes/PMNotificationManager.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import "PMImport.h"
3 |
4 | @protocol FlutterPluginRegistrar;
5 |
6 | @interface PMNotificationManager : NSObject
7 | @property(nonatomic, strong) NSObject *registrar;
8 |
9 | - (instancetype)initWithRegistrar:(NSObject *)registrar;
10 |
11 | - (void)startNotify;
12 |
13 | - (void)stopNotify;
14 |
15 | + (instancetype)managerWithRegistrar:(NSObject *)registrar;
16 |
17 | - (BOOL)isNotifying;
18 |
19 | - (void)detach;
20 |
21 | @end
22 |
--------------------------------------------------------------------------------
/ios/Classes/PMPlugin.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import "PMImport.h"
3 | #import
4 |
5 | @class PMManager;
6 | @class PMNotificationManager;
7 |
8 | @interface PMPlugin : NSObject
9 | @property(nonatomic, strong) PMManager *manager;
10 | @property(nonatomic, strong) PMNotificationManager *notificationManager;
11 | - (void)registerPlugin:(NSObject *)registrar;
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/ios/Classes/PMProgressHandler.h:
--------------------------------------------------------------------------------
1 | //
2 | // PMProgressHandler.h
3 | // path_provider
4 | //
5 |
6 | #import
7 | #import "PMProgressHandlerProtocol.h"
8 | #import "PMImport.h"
9 |
10 | NS_ASSUME_NONNULL_BEGIN
11 |
12 | @interface PMProgressHandler : NSObject
13 |
14 | @property(nonatomic, assign) int channelIndex;
15 |
16 | - (void)notify:(double)progress state:(PMProgressState)state;
17 |
18 | - (void)register:(NSObject *)registrar channelIndex:(int)index;
19 |
20 | @end
21 |
22 | NS_ASSUME_NONNULL_END
23 |
--------------------------------------------------------------------------------
/ios/Classes/PMProgressHandler.m:
--------------------------------------------------------------------------------
1 | //
2 | // PMProgressHandler.m
3 | // path_provider
4 | //
5 |
6 | #import "PMProgressHandler.h"
7 |
8 | @implementation PMProgressHandler {
9 | FlutterMethodChannel *channel;
10 | }
11 |
12 | - (void)notify:(double)progress state:(PMProgressState)state {
13 | int s = state;
14 | NSDictionary *dict = @{
15 | @"state": @(s),
16 | @"progress": @(progress),
17 | };
18 |
19 | if (!channel) {
20 | return;
21 | }
22 |
23 | // Use the main thread to invoke the method.
24 | dispatch_async(dispatch_get_main_queue(), ^{
25 | @try {
26 | FlutterMethodChannel *channel = self->channel;
27 | if (!channel) {
28 | return;
29 | }
30 | // Try to invoke the channel in the thread, regardless if it's available.
31 | [channel invokeMethod:@"notifyProgress" arguments:dict];
32 | } @catch (NSException *exception) {
33 | // Do nothing when it throws.
34 | } @finally {
35 | if (state == PMProgressStateSuccess || state == PMProgressStateFailed) {
36 | [self->channel setMethodCallHandler:nil];
37 | self->channel = nil;
38 | }
39 | }
40 | });
41 | }
42 |
43 | - (void)register:(NSObject *)registrar channelIndex:(int)index {
44 | NSString *name = [NSString stringWithFormat:@"com.fluttercandies/photo_manager/progress/%d", index];
45 | channel = [FlutterMethodChannel methodChannelWithName:name binaryMessenger:registrar.messenger];
46 | }
47 |
48 | @end
49 |
--------------------------------------------------------------------------------
/ios/Classes/PhotoManagerPlugin.h:
--------------------------------------------------------------------------------
1 | #import "PMImport.h"
2 |
3 | @interface PhotoManagerPlugin : NSObject
4 | @property(nonatomic, strong) NSObject *registrar;
5 | @end
6 |
--------------------------------------------------------------------------------
/ios/Classes/PhotoManagerPlugin.m:
--------------------------------------------------------------------------------
1 | #import "PhotoManagerPlugin.h"
2 | #import "PMPlugin.h"
3 |
4 | @implementation PhotoManagerPlugin {
5 | }
6 | + (void)registerWithRegistrar:(NSObject *)registrar {
7 | PMPlugin *plugin = [PMPlugin new];
8 | [plugin registerPlugin:registrar];
9 | [registrar addApplicationDelegate:plugin];
10 | }
11 |
12 | @end
13 |
--------------------------------------------------------------------------------
/ios/Classes/ResultHandler.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import "PMImport.h"
3 | #import "PMResultHandler.h"
4 |
5 | @interface ResultHandler : NSObject
6 |
7 | @property (nonatomic, strong) FlutterMethodCall* call;
8 | @property(nonatomic, strong) FlutterResult result;
9 |
10 | - (instancetype)initWithResult:(FlutterResult)result;
11 |
12 | - (instancetype)initWithCall:(FlutterMethodCall *)call result:(FlutterResult)result;
13 |
14 | + (instancetype)handlerWithCall:(FlutterMethodCall *)call result:(FlutterResult)result;
15 |
16 |
17 | @end
18 |
--------------------------------------------------------------------------------
/ios/Classes/core/AssetEntity.h:
--------------------------------------------------------------------------------
1 | //
2 | // AssetEntity.h
3 | // photo_manager
4 | //
5 |
6 | #import
7 | #import
8 |
9 | @interface AssetEntity : NSObject
10 |
11 | @property(nonatomic,strong) PHAsset *asset;
12 | @property(nonatomic, assign) BOOL isCloud;
13 |
14 | @end
15 |
--------------------------------------------------------------------------------
/ios/Classes/core/AssetEntity.m:
--------------------------------------------------------------------------------
1 | //
2 | // AssetEntity.m
3 | // photo_manager
4 | //
5 |
6 | #import "AssetEntity.h"
7 |
8 | @implementation AssetEntity
9 |
10 | @end
11 |
--------------------------------------------------------------------------------
/ios/Classes/core/NSString+PM_COMMON.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @interface NSString (PM_COMMON)
4 |
5 | - (BOOL)isEmpty;
6 |
7 | @end
8 |
--------------------------------------------------------------------------------
/ios/Classes/core/NSString+PM_COMMON.m:
--------------------------------------------------------------------------------
1 | #import "NSString+PM_COMMON.h"
2 |
3 | @implementation NSString (PM_COMMON)
4 |
5 | - (BOOL)isEmpty {
6 | if (self.length == 0) {
7 | return YES;
8 | }
9 | if ([self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]].length == 0) {
10 | return YES;
11 | }
12 | return NO;
13 | }
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/ios/Classes/core/PHAsset+PM_COMMON.h:
--------------------------------------------------------------------------------
1 | //
2 | // PHAsset+PHAsset_checkType.h
3 | // photo_manager
4 | //
5 |
6 | #import
7 | #import "PMLogUtils.h"
8 |
9 | NS_ASSUME_NONNULL_BEGIN
10 |
11 | @interface PHAsset (PM_COMMON)
12 |
13 | - (bool)isImage;
14 | - (bool)isVideo;
15 | - (bool)isAudio;
16 | - (bool)isImageOrVideo;
17 | - (bool)isLivePhoto;
18 |
19 | - (NSString*)title;
20 |
21 | - (NSString *)filenameWithOptions:(int)subtype isOrigin:(BOOL)isOrigin fileType:(AVFileType)fileType;
22 |
23 | /**
24 | Get the MIME type for this asset from UTI (`PHAssetResource.uniformTypeIdentifier`), such as `image/jpeg`, `image/heic`, `video/quicktime`, etc.
25 |
26 | @note For Live Photos, this returns a type representing its image file.
27 | @return The MIME type of this asset if available, otherwise `nil`.
28 | */
29 | - (nullable NSString*)mimeType;
30 | - (PHAssetResource *)getCurrentResource;
31 | - (void)requestCurrentResourceData:(void (^)(NSData *_Nullable result))block;
32 | - (PHAssetResource *)getLivePhotosResource;
33 |
34 | @end
35 |
36 | NS_ASSUME_NONNULL_END
37 |
--------------------------------------------------------------------------------
/ios/Classes/core/PHAssetCollection+PM_COMMON.h:
--------------------------------------------------------------------------------
1 | //
2 | // PHAssetCollection+PHAssetCollection_obtainAssetCount.h
3 | // photo_manager
4 | //
5 | // Created by Alex on 2022/2/24.
6 | //
7 |
8 | #import
9 |
10 | @interface PHAssetCollection (PM_COMMON)
11 |
12 | - (NSUInteger)obtainAssetCount:(PHFetchOptions *)options;
13 |
14 | @end
15 |
--------------------------------------------------------------------------------
/ios/Classes/core/PHAssetCollection+PM_COMMON.m:
--------------------------------------------------------------------------------
1 | //
2 | // PHAssetCollection+PHAssetCollection_obtainAssetCount.m
3 | // photo_manager
4 | //
5 | // Created by Alex on 2022/2/24.
6 | //
7 |
8 | #import "PHAssetCollection+PM_COMMON.h"
9 |
10 | @implementation PHAssetCollection (PM_COMMON)
11 |
12 | - (NSUInteger)obtainAssetCount:(PHFetchOptions *)options {
13 | PHFetchResult *fetchResult = [PHAsset fetchAssetsInAssetCollection:self options:options];
14 | NSUInteger count = fetchResult.count;
15 | return count;
16 | }
17 |
18 | @end
19 |
--------------------------------------------------------------------------------
/ios/Classes/core/PHAssetResource+PM_COMMON.h:
--------------------------------------------------------------------------------
1 | //
2 | // PHAssetResource+PHAssetResource_checkType.h
3 | // photo_manager
4 | //
5 |
6 | #import
7 |
8 | @interface PHAssetResource (PM_COMMON)
9 |
10 | - (bool)isImage;
11 | - (bool)isVideo;
12 | - (bool)isAudio;
13 | - (bool)isImageOrVideo;
14 | - (bool)isValid;
15 |
16 | @end
17 |
--------------------------------------------------------------------------------
/ios/Classes/core/PHAssetResource+PM_COMMON.m:
--------------------------------------------------------------------------------
1 | //
2 | // PHAssetResource+PHAssetResource_checkType.m
3 | // photo_manager
4 | //
5 |
6 | #import "PHAssetResource+PM_COMMON.h"
7 |
8 | @implementation PHAssetResource (PM_COMMON)
9 |
10 | - (bool)isImage {
11 | return [self type] == PHAssetResourceTypePhoto
12 | || [self type] == PHAssetResourceTypeAlternatePhoto
13 | || [self type] == PHAssetResourceTypeFullSizePhoto
14 | || [self type] == PHAssetResourceTypeAdjustmentBasePhoto;
15 | }
16 |
17 | - (bool)isVideo {
18 | BOOL predicate = [self type] == PHAssetResourceTypeVideo || PHAssetResourceTypeFullSizeVideo;
19 | if (@available(iOS 9.1, *)) {
20 | predicate = (predicate || [self type] == PHAssetResourceTypePairedVideo);
21 | }
22 | if (@available(iOS 10.0, *)) {
23 | predicate = (predicate || [self type] == PHAssetResourceTypeFullSizePairedVideo);
24 | predicate = (predicate || [self type] == PHAssetResourceTypeAdjustmentBasePairedVideo);
25 | }
26 | if (@available(iOS 13.0, *)) {
27 | predicate = (predicate || [self type] == PHAssetResourceTypeAdjustmentBaseVideo);
28 | }
29 | return predicate;
30 | }
31 |
32 | - (bool)isAudio {
33 | return [self type] == PHAssetResourceTypeAudio;
34 | }
35 |
36 | - (bool)isImageOrVideo {
37 | return [self isVideo] || [self isImage];
38 | }
39 |
40 | - (bool)isValid {
41 | bool isResource = self.type != PHAssetResourceTypeAdjustmentData;
42 |
43 | #if __IPHONE_17_0
44 | if (@available(iOS 17.0, *)) {
45 | isResource = isResource && self.type != PHAssetResourceTypePhotoProxy;
46 | }
47 | #endif
48 | return isResource;
49 | }
50 |
51 | @end
52 |
--------------------------------------------------------------------------------
/ios/Classes/core/PMAssetPathEntity.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | #define PM_TYPE_ALBUM 1
4 | #define PM_TYPE_FOLDER 2
5 | @class PHAsset;
6 | @class PHAssetCollection;
7 |
8 | @interface PMAssetPathEntity : NSObject
9 |
10 | @property(nonatomic, copy) NSString *id;
11 | @property(nonatomic, copy) NSString *name;
12 | @property(nonatomic, assign) BOOL isAll;
13 | @property(nonatomic, assign) int type;
14 | @property(nonatomic, assign) NSUInteger assetCount;
15 | @property(nonatomic, assign) long modifiedDate;
16 | @property(nonatomic, strong) PHAssetCollection *collection;
17 |
18 | + (instancetype)entityWithId:(NSString *)id name:(NSString *)name assetCollection:(PHAssetCollection*)collection;
19 |
20 | @end
21 |
22 | @interface PMAssetEntity : NSObject
23 |
24 | @property(nonatomic, copy) NSString *id;
25 | @property(nonatomic, assign) long createDt;
26 | @property(nonatomic, assign) NSUInteger width;
27 | @property(nonatomic, assign) NSUInteger height;
28 | @property(nonatomic, assign) long duration;
29 | @property(nonatomic, assign) int type;
30 | @property(nonatomic, strong) PHAsset *phAsset;
31 | @property(nonatomic, assign) long modifiedDt;
32 | @property(nonatomic, assign) double lat;
33 | @property(nonatomic, assign) double lng;
34 | @property(nonatomic, copy) NSString *title;
35 | @property(nonatomic, assign) NSUInteger subtype;
36 | @property(nonatomic, assign) BOOL favorite;
37 | @property(nonatomic, assign) BOOL isLocallyAvailable;
38 |
39 | - (instancetype)initWithId:(NSString *)id
40 | createDt:(long)createDt
41 | width:(NSUInteger)width
42 | height:(NSUInteger)height
43 | duration:(long)duration
44 | type:(int)type;
45 |
46 | + (instancetype)entityWithId:(NSString *)id
47 | createDt:(long)createDt
48 | width:(NSUInteger)width
49 | height:(NSUInteger)height
50 | duration:(long)duration
51 | type:(int)type;
52 |
53 | @end
54 |
--------------------------------------------------------------------------------
/ios/Classes/core/PMAssetPathEntity.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import "PMAssetPathEntity.h"
3 |
4 | @implementation PMAssetPathEntity {}
5 |
6 | - (instancetype)init {
7 | self = [super init];
8 | if (self) {
9 | self.type = PM_TYPE_ALBUM;
10 | self.assetCount = NSIntegerMax;
11 | }
12 | return self;
13 | }
14 |
15 | - (instancetype)initWithId:(NSString *)id name:(NSString *)name assetCollection:(PHAssetCollection*)collection {
16 | self = [super init];
17 | if (self) {
18 | self.id = id;
19 | self.name = name;
20 | self.type = PM_TYPE_ALBUM;
21 | self.assetCount = NSIntegerMax;
22 | self.collection = collection;
23 | }
24 | return self;
25 | }
26 |
27 | + (instancetype)entityWithId:(NSString *)id name:(NSString *)name assetCollection:(PHAssetCollection *)collection {
28 | return [[self alloc] initWithId:id name:name assetCollection:collection];
29 | }
30 |
31 | @end
32 |
33 | @implementation PMAssetEntity {}
34 |
35 | - (instancetype)initWithId:(NSString *)id createDt:(long)createDt width:(NSUInteger)width height:(NSUInteger)height
36 | duration:(long)duration type:(int)type {
37 | self = [super init];
38 | if (self) {
39 | self.id = id;
40 | self.createDt = createDt;
41 | self.width = width;
42 | self.height = height;
43 | self.duration = duration;
44 | self.type = type;
45 | }
46 | return self;
47 | }
48 |
49 | + (instancetype)entityWithId:(NSString *)id createDt:(long)createDt width:(NSUInteger)width height:(NSUInteger)height
50 | duration:(long)duration type:(int)type {
51 | return [[self alloc] initWithId:id createDt:createDt width:width height:height duration:duration type:type];
52 | }
53 |
54 |
55 | @end
56 |
--------------------------------------------------------------------------------
/ios/Classes/core/PMBaseFilter.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by jinglong cai on 2023/2/9.
3 | //
4 |
5 | #import
6 |
7 | @protocol PMBaseFilter
8 |
9 | - (PHFetchOptions *)getFetchOptions:(int)type;
10 |
11 | - (BOOL) containsModified;
12 |
13 | - (BOOL) needTitle;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/ios/Classes/core/PMCacheContainer.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @class PMAssetEntity;
4 | @class AVPlayerItem;
5 |
6 | @interface PMCacheContainer : NSObject
7 |
8 | - (void)putAssetEntity:(PMAssetEntity *)entity;
9 |
10 | - (PMAssetEntity *)getAssetEntity:(NSString *)id;
11 |
12 | - (void)clearCache;
13 |
14 | @end
15 |
--------------------------------------------------------------------------------
/ios/Classes/core/PMCacheContainer.m:
--------------------------------------------------------------------------------
1 | #import "PMCacheContainer.h"
2 | #import "PMAssetPathEntity.h"
3 | #import
4 |
5 | @implementation PMCacheContainer {
6 | NSMutableDictionary *map;
7 | }
8 |
9 | - (instancetype)init {
10 | self = [super init];
11 | if (self) {
12 | map = [NSMutableDictionary new];
13 | }
14 |
15 | return self;
16 | }
17 |
18 | - (void)putAssetEntity:(PMAssetEntity *)entity {
19 | @synchronized (map) {
20 | self->map[entity.id] = entity;
21 | }
22 | }
23 |
24 | - (PMAssetEntity *)getAssetEntity:(NSString *)id {
25 | return map[id];
26 | }
27 |
28 | - (void)clearCache {
29 | @synchronized (map) {
30 | [self->map removeAllObjects];
31 | }
32 | }
33 |
34 | @end
35 |
--------------------------------------------------------------------------------
/ios/Classes/core/PMConvertProtocol.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @protocol PMConvertProtocol
4 |
5 | - (id)convertData:(NSData *)data;
6 |
7 | @end
--------------------------------------------------------------------------------
/ios/Classes/core/PMConvertUtils.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 | #import "PMBaseFilter.h"
4 |
5 | @class PMAssetPathEntity;
6 | @class PMAssetEntity;
7 | @class PMFilterOption;
8 | @class PMFilterOptionGroup;
9 |
10 | @interface PMConvertUtils : NSObject
11 |
12 | + (NSDictionary *)convertPathToMap:(NSArray *)array;
13 |
14 | + (NSDictionary *)convertAssetToMap:(NSArray *)array
15 | optionGroup:(NSObject *)optionGroup;
16 |
17 | + (NSDictionary *)convertPHAssetToMap:(PHAsset *)asset
18 | needTitle:(BOOL)needTitle;
19 |
20 | + (NSDictionary *)convertPMAssetToMap:(PMAssetEntity *)asset
21 | needTitle:(BOOL)needTitle;
22 |
23 | + (PMFilterOption *)convertMapToPMFilterOption:(NSDictionary *)map;
24 |
25 | + (NSObject *)convertMapToOptionContainer:(NSDictionary *)map;
26 |
27 | + (AVFileType)convertNumberToAVFileType:(int)number;
28 |
29 | + (NSString *)convertAVFileTypeToExtension:(AVFileType)fileType;
30 | @end
31 |
--------------------------------------------------------------------------------
/ios/Classes/core/PMFileHelper.h:
--------------------------------------------------------------------------------
1 | //
2 | // PMFileHelper.h
3 | // photo_manager
4 | //
5 |
6 | #import
7 |
8 | NS_ASSUME_NONNULL_BEGIN
9 |
10 | /// Contains access file methods
11 | @interface PMFileHelper : NSObject
12 |
13 | +(void)deleteFile:(NSString *)path isDirectory:(BOOL)isDirectory error:(NSError *)error;
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/ios/Classes/core/PMFileHelper.m:
--------------------------------------------------------------------------------
1 | //
2 | // PMFileHelper.m
3 | // photo_manager
4 | //
5 |
6 | #import "PMFileHelper.h"
7 |
8 | @implementation PMFileHelper
9 |
10 | + (void)deleteFile:(NSString *)path isDirectory:(BOOL)isDirectory error:(NSError *)error {
11 | NSFileManager *fileManager = NSFileManager.defaultManager;
12 | BOOL exists = [fileManager fileExistsAtPath:path isDirectory:&isDirectory];
13 | if (exists) {
14 | [fileManager removeItemAtPath:path error:&error];
15 | }
16 | }
17 |
18 | @end
19 |
--------------------------------------------------------------------------------
/ios/Classes/core/PMFilterOption.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import "PMBaseFilter.h"
3 |
4 | @interface PMDateOption : NSObject
5 |
6 | @property(nonatomic, strong) NSDate *min;
7 | @property(nonatomic, strong) NSDate *max;
8 | @property(nonatomic, assign) BOOL ignore;
9 |
10 | - (NSString *)dateCond:(NSString *)key;
11 |
12 | - (NSArray *)dateArgs;
13 |
14 | @end
15 |
16 | typedef struct PMSizeConstraint {
17 |
18 | unsigned int minWidth;
19 | unsigned int maxWidth;
20 | unsigned int minHeight;
21 | unsigned int maxHeight;
22 | BOOL ignoreSize;
23 |
24 | } PMSizeConstraint;
25 |
26 | typedef struct PMDurationConstraint {
27 |
28 | double minDuration;
29 | double maxDuration;
30 | BOOL allowNullable;
31 |
32 | } PMDurationConstraint;
33 |
34 | @interface PMFilterOption : NSObject
35 |
36 | @property(nonatomic, assign) BOOL needTitle;
37 | @property(nonatomic, assign) PMSizeConstraint sizeConstraint;
38 | @property(nonatomic, assign) PMDurationConstraint durationConstraint;
39 |
40 | - (NSString *)sizeCond;
41 |
42 | - (NSArray *)sizeArgs;
43 |
44 | - (NSString *)durationCond;
45 |
46 | - (NSArray *)durationArgs;
47 |
48 | @end
49 |
50 |
51 | @interface PMFilterOptionGroup : NSObject
52 |
53 | @property(nonatomic, strong) PMFilterOption *imageOption;
54 | @property(nonatomic, strong) PMFilterOption *videoOption;
55 | @property(nonatomic, strong) PMFilterOption *audioOption;
56 | @property(nonatomic, strong) PMDateOption *dateOption;
57 | @property(nonatomic, strong) PMDateOption *updateOption;
58 | @property(nonatomic, assign) BOOL containsLivePhotos;
59 | @property(nonatomic, assign) BOOL onlyLivePhotos;
60 | @property(nonatomic, assign) BOOL containsModified;
61 | @property(nonatomic, assign) BOOL includeHiddenAssets;
62 | @property(nonatomic, strong) NSArray *sortArray;
63 |
64 | - (NSArray *)sortCond;
65 |
66 | - (void)injectSortArray:(NSArray *)array;
67 | @end
68 |
69 |
70 | @interface PMCustomFilterOption : NSObject
71 | @property (nonatomic, strong) NSDictionary *params;
72 | @end
73 |
--------------------------------------------------------------------------------
/ios/Classes/core/PMFolderUtils.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface PMFolderUtils : NSObject
5 |
6 | + (NSArray *)getRootFolderWithOptions:(PHFetchOptions *)options;
7 |
8 | + (BOOL)isRecentCollection:(NSString *)id1;
9 |
10 | + (NSArray *)getSubCollectionWithCollection:(PHCollectionList *)collection
11 | options:(PHFetchOptions *)options;
12 |
13 | +(void)debugInfo:(PHCollection*)collection ;
14 |
15 | @end
--------------------------------------------------------------------------------
/ios/Classes/core/PMFolderUtils.m:
--------------------------------------------------------------------------------
1 | #import "PMFolderUtils.h"
2 | #import "PMLogUtils.h"
3 |
4 | @implementation PMFolderUtils {
5 |
6 | }
7 |
8 | + (NSArray *)getRootFolderWithOptions:(PHFetchOptions *)options {
9 | PHFetchResult *result = [PHCollection fetchTopLevelUserCollectionsWithOptions:options];
10 |
11 | NSMutableArray *array = [NSMutableArray new];
12 |
13 | for (PHCollection *item in result) {
14 | if ([item isMemberOfClass:PHCollectionList.class]) {
15 | [array addObject:item];
16 | }
17 | }
18 |
19 | return array;
20 | }
21 |
22 | + (BOOL)isRecentCollection:(NSString *)id {
23 | PHFetchResult *result = [PHAssetCollection
24 | fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum
25 | subtype:PHAssetCollectionSubtypeAlbumRegular
26 | options:nil];
27 |
28 | if (result && result.count) {
29 | for (PHAssetCollection *collection in result) {
30 | if (collection.assetCollectionSubtype == PHAssetCollectionSubtypeSmartAlbumUserLibrary) {
31 | return [id isEqualToString:collection.localIdentifier];
32 | }
33 | }
34 | }
35 |
36 | return NO;
37 | }
38 |
39 | + (NSArray *)getSubCollectionWithCollection:(PHCollectionList *)collection
40 | options:(PHFetchOptions *)options {
41 |
42 | PHFetchResult *result = [PHCollection fetchCollectionsInCollectionList:collection options:nil];
43 |
44 | NSMutableArray *array = [NSMutableArray new];
45 |
46 | for (PHCollection *item in result) {
47 | if ([item isMemberOfClass:PHCollectionList.class] || [item isMemberOfClass:PHAssetCollection.class]) {
48 | [array addObject:item];
49 | }
50 | }
51 |
52 | return array;
53 | }
54 |
55 | + (void)debugInfo:(PHCollection *)collection {
56 | NSString *title = collection.localizedTitle;
57 | if ([collection isMemberOfClass:PHCollectionList.class]) {
58 | [PMLogUtils.sharedInstance info: [NSString stringWithFormat:@"title = %@, type: %@", title, @"文件夹"]];
59 | } else {
60 | [PMLogUtils.sharedInstance info: [NSString stringWithFormat:@"title = %@, type: %@", title, @"相簿"]];
61 | }
62 | }
63 |
64 |
65 | @end
66 |
--------------------------------------------------------------------------------
/ios/Classes/core/PMImageUtil.h:
--------------------------------------------------------------------------------
1 | //
2 | // PMImageUtil.h
3 | // path_provider_macos
4 | //
5 |
6 | #import
7 | #import "PMThumbLoadOption.h"
8 |
9 | #if TARGET_OS_IOS
10 | typedef UIImage PMImage;
11 | #endif
12 |
13 | #if TARGET_OS_OSX
14 | typedef NSImage PMImage;
15 | #endif
16 |
17 | NS_ASSUME_NONNULL_BEGIN
18 |
19 | @interface PMImageUtil : NSObject
20 |
21 | + (NSData *)convertToData:(PMImage *)image formatType:(PMThumbFormatType)type quality:(float)quality;
22 |
23 | @end
24 |
25 | NS_ASSUME_NONNULL_END
26 |
--------------------------------------------------------------------------------
/ios/Classes/core/PMImageUtil.m:
--------------------------------------------------------------------------------
1 | //
2 | // PMImageUtil.m
3 | // path_provider_macos
4 | //
5 |
6 | #import "PMImageUtil.h"
7 |
8 | @implementation PMImageUtil
9 |
10 | + (NSData *)convertToData:(PMImage *)image formatType:(PMThumbFormatType)type quality:(float)quality {
11 |
12 | #if TARGET_OS_OSX
13 |
14 | NSData *imageData = [image TIFFRepresentation];
15 | NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:imageData];
16 | NSData *resultData;
17 | if (type == PMThumbFormatTypePNG) {
18 | resultData = [imageRep representationUsingType:NSBitmapImageFileTypePNG properties:@{}];
19 | } else {
20 | resultData = [imageRep representationUsingType:NSBitmapImageFileTypeJPEG properties:@{
21 | NSImageCompressionFactor: @(quality)
22 | }];
23 | }
24 |
25 | return resultData;
26 |
27 | #endif
28 |
29 | #if TARGET_OS_IOS
30 | NSData *resultData;
31 | if (type == PMThumbFormatTypePNG) {
32 | resultData = UIImagePNGRepresentation(image);
33 | } else {
34 | resultData = UIImageJPEGRepresentation(image, quality);
35 | }
36 |
37 | return resultData;
38 |
39 | #endif
40 | }
41 |
42 |
43 | @end
44 |
--------------------------------------------------------------------------------
/ios/Classes/core/PMLogUtils.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @interface PMLogUtils : NSObject
4 |
5 | @property(nonatomic, assign) BOOL isLog;
6 |
7 | + (instancetype)sharedInstance;
8 |
9 | - (void)info:(NSString *)info;
10 | - (void)debug:(NSString *)info;
11 |
12 | @end
--------------------------------------------------------------------------------
/ios/Classes/core/PMLogUtils.m:
--------------------------------------------------------------------------------
1 | #import "PMLogUtils.h"
2 |
3 | @implementation PMLogUtils {
4 |
5 | }
6 | + (instancetype)sharedInstance {
7 | static PMLogUtils *_sharedInstance = nil;
8 | static dispatch_once_t onceToken;
9 | dispatch_once(&onceToken, ^{
10 | _sharedInstance = [[self alloc] init];
11 | _sharedInstance.isLog = NO;
12 | });
13 |
14 | return _sharedInstance;
15 | }
16 |
17 | - (void)info:(NSString *)info {
18 | if (!self.isLog) {
19 | return;
20 | }
21 | NSLog(@"PhotoManager info: %@", info);
22 | }
23 |
24 | - (void)debug:(NSString *)info {
25 | if (!self.isLog) {
26 | return;
27 | }
28 | NSLog(@"PhotoManager debug: %@", info);
29 | }
30 |
31 | @end
32 |
--------------------------------------------------------------------------------
/ios/Classes/core/PMMD5Utils.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | #define PMFileHashDefaultChunkSizeForReadingData 1024*8 // 8K
5 |
6 | @interface PMMD5Utils : NSObject
7 |
8 | // 计算 NSData 的 MD5
9 | + (NSString *)getMD5FromData:(NSData *)data;
10 |
11 | // 计算字符串的 MD5
12 | + (NSString *)getMD5FromString:(NSString *)string;
13 |
14 | // 计算文件的 MD5
15 | + (NSString *)getMD5FromPath:(NSString *)path;
16 |
17 | @end
18 |
--------------------------------------------------------------------------------
/ios/Classes/core/PMPathFilterOption.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by jinglong cai on 2023/7/18.
3 | //
4 |
5 | #import
6 | #import
7 |
8 | @interface PMPathFilterOption : NSObject
9 |
10 | + (instancetype)optionWithDict:(NSDictionary *)dict;
11 |
12 | @property(nonatomic, strong) NSArray *type;
13 | @property(nonatomic, assign) NSArray *subType;
14 |
15 | @end
--------------------------------------------------------------------------------
/ios/Classes/core/PMPathFilterOption.m:
--------------------------------------------------------------------------------
1 | //
2 | // Created by jinglong cai on 2023/7/18.
3 | //
4 |
5 | #import "PMPathFilterOption.h"
6 |
7 |
8 | @implementation PMPathFilterOption {
9 |
10 | }
11 | + (instancetype)optionWithDict:(NSDictionary *)dict {
12 | PMPathFilterOption *option = [PMPathFilterOption new];
13 | NSDictionary *darwinDict = dict[@"darwin"];
14 |
15 | option.type = darwinDict[@"type"];;
16 | option.subType = darwinDict[@"subType"];;
17 |
18 | return option;
19 | }
20 |
21 | @end
--------------------------------------------------------------------------------
/ios/Classes/core/PMProgressHandlerProtocol.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | typedef enum PMProgressState{
4 | PMProgressStatePrepare = 0,
5 | PMProgressStateLoading = 1,
6 | PMProgressStateSuccess = 2,
7 | PMProgressStateFailed = 3,
8 | } PMProgressState;
9 |
10 |
11 | @protocol PMProgressHandlerProtocol
12 |
13 | - (void)notify:(double)progress state:(PMProgressState)state;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/ios/Classes/core/PMRequestTypeUtils.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @interface PMRequestTypeUtils : NSObject
4 |
5 | + (BOOL)containsImage:(int)type;
6 |
7 | + (BOOL)containsVideo:(int)type;
8 |
9 | + (BOOL)containsAudio:(int)type;
10 |
11 | @end
12 |
--------------------------------------------------------------------------------
/ios/Classes/core/PMRequestTypeUtils.m:
--------------------------------------------------------------------------------
1 | #import "PMRequestTypeUtils.h"
2 |
3 | #define PM_TYPE_IMAGE 1
4 | #define PM_TYPE_VIDEO 1<<1
5 | #define PM_TYPE_AUDIO 1<<2
6 |
7 | @implementation PMRequestTypeUtils {
8 |
9 | }
10 |
11 | + (BOOL)checkContainsType:(int)type targetType:(int)targetType {
12 | return (type & targetType) == targetType;
13 | }
14 |
15 | + (BOOL)containsImage:(int)type {
16 | return [self checkContainsType:type targetType:PM_TYPE_IMAGE];
17 | }
18 |
19 | + (BOOL)containsVideo:(int)type {
20 | return [self checkContainsType:type targetType:PM_TYPE_VIDEO];
21 | }
22 |
23 | + (BOOL)containsAudio:(int)type {
24 | return [self checkContainsType:type targetType:PM_TYPE_AUDIO];
25 | }
26 |
27 | @end
28 |
--------------------------------------------------------------------------------
/ios/Classes/core/PMResultHandler.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @protocol PMResultHandler
4 |
5 | - (void)replyError:(NSObject *)value;
6 |
7 | - (void)reply:(id)obj;
8 |
9 | - (void)notImplemented;
10 |
11 | - (BOOL)isReplied;
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/ios/Classes/core/PMThumbLoadOption.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | typedef enum PMThumbFormatType {
5 | PMThumbFormatTypeJPEG,
6 | PMThumbFormatTypePNG,
7 | } PMThumbFormatType;
8 |
9 | @interface PMThumbLoadOption : NSObject
10 |
11 | @property(nonatomic, assign) int width;
12 | @property(nonatomic, assign) int height;
13 | @property(nonatomic, assign) PMThumbFormatType format;
14 | @property(nonatomic, assign) float quality;
15 |
16 | #pragma mark only iOS
17 | @property(nonatomic, assign) PHImageContentMode contentMode;
18 | @property(nonatomic, assign) PHImageRequestOptionsDeliveryMode deliveryMode;
19 | @property(nonatomic, assign) PHImageRequestOptionsResizeMode resizeMode;
20 |
21 | + (instancetype)optionDict:(NSDictionary *)dict;
22 |
23 | -(CGSize)makeSize;
24 |
25 | @end
26 |
--------------------------------------------------------------------------------
/ios/Classes/core/Reply.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @interface Reply : NSObject
4 |
5 | @property (nonatomic, assign) BOOL isReply;
6 |
7 | - (instancetype)initWithIsReply:(BOOL)isReply;
8 |
9 | + (instancetype)replyWithIsReply:(BOOL)isReply;
10 |
11 |
12 | @end
13 |
--------------------------------------------------------------------------------
/ios/Classes/core/Reply.m:
--------------------------------------------------------------------------------
1 | #import "Reply.h"
2 |
3 | @implementation Reply {
4 |
5 | }
6 |
7 | - (instancetype)initWithIsReply:(BOOL)isReply {
8 | self = [super init];
9 | if (self) {
10 | self.isReply = isReply;
11 | }
12 |
13 | return self;
14 | }
15 |
16 | + (instancetype)replyWithIsReply:(BOOL)isReply {
17 | return [[self alloc] initWithIsReply:isReply];
18 | }
19 |
20 | @end
21 |
--------------------------------------------------------------------------------
/ios/Resources/PrivacyInfo.xcprivacy:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSPrivacyCollectedDataTypes
6 |
7 |
8 | NSPrivacyCollectedDataType
9 | NSPrivacyCollectedDataTypePhotosorVideos
10 | NSPrivacyCollectedDataTypeLinked
11 |
12 | NSPrivacyCollectedDataTypeTracking
13 |
14 | NSPrivacyCollectedDataTypePurposes
15 |
16 | NSPrivacyCollectedDataTypePurposeProductPersonalization
17 | NSPrivacyCollectedDataTypePurposeOther
18 |
19 |
20 |
21 | NSPrivacyTrackingDomains
22 |
23 | NSPrivacyAccessedAPITypes
24 |
25 |
26 | NSPrivacyAccessedAPIType
27 | NSPrivacyAccessedAPICategoryFileTimestamp
28 | NSPrivacyAccessedAPITypeReasons
29 |
30 | C617.1
31 |
32 |
33 |
34 | NSPrivacyTracking
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/ios/photo_manager.podspec:
--------------------------------------------------------------------------------
1 | pubspec = YAML.load_file(File.join('..', 'pubspec.yaml'))
2 | library_version = pubspec['version'].gsub('+', '-')
3 |
4 | Pod::Spec.new do |s|
5 | s.name = 'photo_manager'
6 | s.version = library_version
7 | s.summary = 'Photo management APIs for Flutter.'
8 | s.description = <<-DESC
9 | A Flutter plugin that provides assets abstraction management APIs.
10 | DESC
11 | s.homepage = 'https://github.com/fluttercandies/flutter_photo_manager'
12 | s.license = { :file => '../LICENSE' }
13 | s.author = { 'CaiJingLong' => 'cjl_spy@163.com' }
14 | s.source = { :path => '.' }
15 | s.source_files = 'Classes/**/*'
16 | s.public_header_files = 'Classes/**/*.h','Classes/**/**/*.h'
17 | s.osx.dependency 'FlutterMacOS'
18 | s.ios.dependency 'Flutter'
19 |
20 | s.ios.framework = 'Photos'
21 | s.ios.framework = 'PhotosUI'
22 |
23 | s.osx.framework = 'Photos'
24 | s.osx.framework = 'PhotosUI'
25 |
26 | s.ios.deployment_target = '9.0'
27 | s.osx.deployment_target = '10.15'
28 |
29 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
30 | s.swift_version = '5.0'
31 |
32 | s.resource_bundles = {'photo_manager_privacy' => ['Resources/PrivacyInfo.xcprivacy']}
33 | end
34 |
--------------------------------------------------------------------------------
/lib/photo_manager.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2018 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 | /// The main library that contains all functions integrating with photo library.
6 | ///
7 | /// To use, import `package:photo_manager/photo_manager.dart`.
8 | library photo_manager;
9 |
10 | export 'src/filter/base_filter.dart';
11 | export 'src/filter/classical/filter_option_group.dart';
12 | export 'src/filter/classical/filter_options.dart';
13 | export 'src/filter/custom/advance.dart';
14 | export 'src/filter/custom/custom_columns.dart';
15 | export 'src/filter/custom/custom_filter.dart';
16 | export 'src/filter/custom/order_by_item.dart';
17 | export 'src/filter/path_filter.dart';
18 |
19 | export 'src/internal/enums.dart';
20 | export 'src/internal/extensions.dart' show PermissionStateExt;
21 | export 'src/internal/plugin.dart' show PhotoManagerPlugin;
22 | export 'src/internal/progress_handler.dart';
23 |
24 | export 'src/managers/caching_manager.dart';
25 | export 'src/managers/notify_manager.dart';
26 | export 'src/managers/photo_manager.dart';
27 |
28 | export 'src/types/entity.dart';
29 | export 'src/types/thumbnail.dart';
30 | export 'src/types/types.dart';
31 |
32 | export 'src/utils/column_utils.dart';
33 |
--------------------------------------------------------------------------------
/lib/platform_utils.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2024 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 | library photo_manager_platform_utils;
5 |
6 | import 'dart:io';
7 |
8 | /// An utility to match platforms. It adds a general support for
9 | /// OpenHarmony OS.
10 | class PlatformUtils {
11 | const PlatformUtils._();
12 |
13 | /// Whether the operating system is a version of
14 | /// [ohos](https://en.wikipedia.org/wiki/OpenHarmony).
15 | static final isOhos = Platform.operatingSystem == 'ohos';
16 | }
17 |
--------------------------------------------------------------------------------
/lib/src/filter/custom/order_by_item.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2018 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 | /// {@template PM.order_by_item}
6 | ///
7 | /// The order by item.
8 | ///
9 | /// Example:
10 | /// ```dart
11 | /// OrderByItem(CustomColumns.base.width, true);
12 | /// ```
13 | ///
14 | /// See also:
15 | /// - [CustomFilter]
16 | /// - [CustomColumns.base]
17 | /// - [CustomColumns.android]
18 | /// - [CustomColumns.darwin]
19 | /// - [CustomColumns.platformValues]
20 | ///
21 | /// {@endtemplate}
22 | class OrderByItem {
23 | /// {@macro PM.order_by_item}
24 | const OrderByItem(this.column, this.isAsc);
25 |
26 | /// {@macro PM.order_by_item}
27 | const OrderByItem.desc(this.column) : isAsc = false;
28 |
29 | /// {@macro PM.order_by_item}
30 | const OrderByItem.asc(this.column) : isAsc = true;
31 |
32 | /// {@macro PM.order_by_item}
33 | const OrderByItem.named({
34 | required this.column,
35 | this.isAsc = true,
36 | });
37 |
38 | /// The column name.
39 | final String column;
40 |
41 | /// The order type.
42 | final bool isAsc;
43 |
44 | /// Convert to the map.
45 | Map toMap() {
46 | return {
47 | 'column': column,
48 | 'isAsc': isAsc,
49 | };
50 | }
51 |
52 | @override
53 | String toString() {
54 | return 'OrderByItem{column: $column, isAsc: $isAsc}';
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/lib/src/internal/extensions.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2018 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 'enums.dart';
6 |
7 | /// Provides extension methods for [PermissionState] values.
8 | extension PermissionStateExt on PermissionState {
9 | /// Returns `true` if the permission has been granted; otherwise, `false`.
10 | bool get isAuth {
11 | return this == PermissionState.authorized;
12 | }
13 |
14 | /// Returns `true` if the permission grants partial or full access to assets; otherwise, `false`.
15 | bool get hasAccess {
16 | return this == PermissionState.authorized ||
17 | this == PermissionState.limited;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lib/src/internal/map_interface.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2018 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 | /// Contains an abstract method toMap to indicate that it can be converted into a Map object
6 | mixin IMapMixin {
7 | /// Convert current object to a map.
8 | ///
9 | /// Usually for transfer to MethodChannel.
10 | Map toMap();
11 | }
12 |
--------------------------------------------------------------------------------
/lib/src/managers/caching_manager.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2018 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 '../internal/constants.dart';
6 | import '../internal/plugin.dart';
7 | import '../types/entity.dart';
8 | import '../types/thumbnail.dart';
9 |
10 | /// The cache manager that helps to create/remove caches with specified assets.
11 | class PhotoCachingManager {
12 | factory PhotoCachingManager() => instance;
13 |
14 | PhotoCachingManager._();
15 |
16 | static final PhotoCachingManager instance = PhotoCachingManager._();
17 |
18 | static const ThumbnailOption _defaultOption = ThumbnailOption(
19 | size: ThumbnailSize.square(PMConstants.vDefaultThumbnailSize),
20 | );
21 |
22 | /// Request caching for assets.
23 | /// The method does not supported on OpenHarmony.
24 | Future requestCacheAssets({
25 | required List assets,
26 | ThumbnailOption option = _defaultOption,
27 | }) {
28 | assert(assets.isNotEmpty);
29 | return plugin.requestCacheAssetsThumbnail(
30 | assets.map((AssetEntity e) => e.id).toList(),
31 | option,
32 | );
33 | }
34 |
35 | /// Request caching for assets' ID.
36 | /// The method does not supported on OpenHarmony.
37 | Future requestCacheAssetsWithIds({
38 | required List assetIds,
39 | ThumbnailOption option = _defaultOption,
40 | }) {
41 | assert(assetIds.isNotEmpty);
42 | return plugin.requestCacheAssetsThumbnail(assetIds, option);
43 | }
44 |
45 | /// Cancel all cache request.
46 | /// The method does not supported on OpenHarmony.
47 | Future cancelCacheRequest() => plugin.cancelCacheRequests();
48 | }
49 |
--------------------------------------------------------------------------------
/lib/src/utils/column_utils.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2018 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 | /// Utility functions for working with the [CustomColumns] class.
8 | class ColumnUtils {
9 | const ColumnUtils._internal();
10 |
11 | /// An instance of the [ColumnUtils] class.
12 | static const instance = ColumnUtils._internal();
13 |
14 | /// Converts a [DateTime] object to the format used by [MediaStore][] on Android or [NSPredicate][] on iOS/macOS.
15 | ///
16 | /// [MediaStore]: https://developer.android.com/reference/android/provider/MediaStore
17 | /// [NSPredicate]: https://developer.apple.com/documentation/foundation/nspredicate
18 | String convertDateTimeToSql(DateTime date, {bool isSeconds = true}) {
19 | final unix = date.millisecondsSinceEpoch;
20 |
21 | if (Platform.isAndroid) {
22 | return isSeconds ? (unix ~/ 1000).toString() : unix.toString();
23 | } else if (Platform.isIOS || Platform.isMacOS) {
24 | // The NSDate epoch starts at 2001-01-01T00:00:00Z, so we subtract this from the Unix timestamp in seconds.
25 | final secondsFrom2001 = (unix / 1000) - 978307200;
26 | final dateStr = secondsFrom2001.toStringAsFixed(6);
27 | return 'CAST($dateStr, "NSDate")';
28 | } else {
29 | throw UnsupportedError('Unsupported platform with date');
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/log/README.md:
--------------------------------------------------------------------------------
1 | 一些帮助开发者开发的日志
2 |
3 | 安卓日志来源: https://gitee.com/kikt/media_store_simple_utils 运行的项目
4 |
--------------------------------------------------------------------------------
/log/android/android-28-delete.log:
--------------------------------------------------------------------------------
1 | 2020-03-22 15:48:37.069 8286-8286/top.kikt.handledeletemedia I/My-Tag: 发生变化 = content://media/
2 | 2020-03-22 15:48:37.069 8286-8286/top.kikt.handledeletemedia I/My-Tag: uri 中不包含id, 应该是删除
--------------------------------------------------------------------------------
/log/android/android-q-delete.log:
--------------------------------------------------------------------------------
1 | 2020-03-21 15:24:04.870 17771-17771/top.kikt.handledeletemedia I/My-Tag: 发生变化 = content://media/external/images/media
2 | 2020-03-21 15:24:04.871 17771-17771/top.kikt.handledeletemedia I/My-Tag: uri 为总uri content://media/external/images/media, 应该是删除
--------------------------------------------------------------------------------
/log/android/androidq-info.log:
--------------------------------------------------------------------------------
1 | 2020-03-22 16:01:17.043 11581-11581/top.kikt.handledeletemedia I/My-Tag: instance_id : null
2 | duration : 784
3 | orientation : null
4 | format : 12297
5 | height : null
6 | is_drm : 0
7 | bucket_display_name : Music
8 | owner_package_name : null
9 | parent : 902112
10 | volume_name : external_primary
11 | date_modified : 1579009250
12 | date_expires : null
13 | _display_name : 50c01294df9d48902639978235bcb56c6ecacc2342c54b61a91bacabae37c58d.mp3
14 | datetaken : null
15 | mime_type : audio/mpeg
16 | _id : 902113
17 | _data : /storage/emulated/0/Music/50c01294df9d48902639978235bcb56c6ecacc2342c54b61a91bacabae37c58d.mp3
18 | _hash : null
19 | _size : 3421
20 | title : 50c01294df9d48902639978235bcb56c6ecacc2342c54b61a91bacabae37c58d
21 | width : null
22 | is_trashed : 0
23 | group_id : -300030646
24 | document_id : null
25 | is_download : 0
26 | is_pending : 0
27 | date_added : 1579009250
28 | primary_directory : Music
29 | secondary_directory : null
30 | original_document_id : null
31 | bucket_id : 82896267
32 | media_type : 2
33 | relative_path : Music/
--------------------------------------------------------------------------------
/log/android/sql-28.log:
--------------------------------------------------------------------------------
1 | bucket_id IS NOT NULL AND ( ( media_type = ? AND duration >=? AND duration <=? ) ) AND datetaken <= ? ) GROUP BY (bucket_id
--------------------------------------------------------------------------------
/log/iOS/ios-create.log:
--------------------------------------------------------------------------------
1 | 2020-03-21 15:37:38.493108+0800 Runner[90808:13081766] flutter: Observatory listening on http://127.0.0.1:55083/75DfBE07g2M=/
2 | 2020-03-21 15:37:41.312862+0800 Runner[90808:13081731] [core] "Error returned from daemon: Error Domain=com.apple.accounts Code=7 "(null)""
3 | 2020-03-21 15:37:42.999059+0800 Runner[90808:13081729] create asset : id = AA3ED26E-539B-4CE4-922D-B6AB8045A600/L0/001
4 | 2020-03-21 15:37:43.017767+0800 Runner[90808:13081731] changed = (
5 | )
6 | 2020-03-21 15:37:43.018169+0800 Runner[90808:13081731] inserted = (
7 | " AA3ED26E-539B-4CE4-922D-B6AB8045A600/L0/001 mediaType=1/0, sourceType=1, (440x1325), creationDate=2020-03-21 07:37:42 +0000, location=0, hidden=0, favorite=0, adjusted=0 "
8 | )
9 | 2020-03-21 15:37:43.018342+0800 Runner[90808:13081731] removed = (
10 | )
11 | 2020-03-21 15:37:43.018990+0800 Runner[90808:13081752] flutter: AssetEntity{id:AA3ED26E-539B-4CE4-922D-B6AB8045A600/L0/001}
12 | 2020-03-21 15:37:43.019402+0800 Runner[90808:13081731] on change result = {
13 | create = (
14 | {
15 | id = "AA3ED26E-539B-4CE4-922D-B6AB8045A600/L0/001";
16 | }
17 | );
18 | delete = (
19 | );
20 | update = (
21 | );
22 | }
23 | 2020-03-21 15:37:43.025146+0800 Runner[90808:13081752] flutter: {delete: [], create: [{id: AA3ED26E-539B-4CE4-922D-B6AB8045A600/L0/001}], update: []}
24 | 2020-03-21 15:37:43.026938+0800 Runner[90808:13081752] flutter: {delete: [], create: [{id: AA3ED26E-539B-4CE4-922D-B6AB8045A600/L0/001}], update: []}
25 | 2020-03-21 15:37:43.340886+0800 Runner[90808:13081728] changed = (null)
26 | 2020-03-21 15:37:43.341043+0800 Runner[90808:13081728] inserted = (null)
27 | 2020-03-21 15:37:43.341174+0800 Runner[90808:13081728] removed = (null)
28 | 2020-03-21 15:37:43.341329+0800 Runner[90808:13081728] on change result = {
29 | create = (
30 | );
31 | delete = (
32 | );
33 | update = (
34 | );
35 | }
36 | 2020-03-21 15:37:43.342040+0800 Runner[90808:13081752] flutter: {delete: [], create: [], update: []}
37 | 2020-03-21 15:37:43.342530+0800 Runner[90808:13081752] flutter: {delete: [], create: [], update: []}
--------------------------------------------------------------------------------
/macos:
--------------------------------------------------------------------------------
1 | ios
--------------------------------------------------------------------------------
/ohos/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /oh_modules
3 | /.preview
4 | /.idea
5 | /build
6 | /.cxx
7 | /.test
8 | /BuildProfile.ets
9 | /oh-package-lock.json5
10 | local.properties
--------------------------------------------------------------------------------
/ohos/build-profile.json5:
--------------------------------------------------------------------------------
1 | {
2 | "apiType": "stageMode",
3 | "buildOption": {
4 | },
5 | "targets": [
6 | {
7 | "name": "default"
8 | }
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/ohos/hvigorfile.ts:
--------------------------------------------------------------------------------
1 | // Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently.
2 | export { harTasks } from '@ohos/hvigor-ohos-plugin';
--------------------------------------------------------------------------------
/ohos/index.ets:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 |
16 | import PhotoManagerPlugin from './src/main/ets/components/plugin/PhotoManagerPlugin';
17 | export default PhotoManagerPlugin;
18 |
--------------------------------------------------------------------------------
/ohos/oh-package.json5:
--------------------------------------------------------------------------------
1 | {
2 | "name": "photo_manager",
3 | "version": "1.0.0",
4 | "description": "Please describe the basic information.",
5 | "main": "index.ets",
6 | "author": "",
7 | "license": "Apache-2.0",
8 | "dependencies": {
9 | "@ohos/flutter_ohos": "file:./har/flutter.har"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/ohos/src/main/ets/components/plugin/handlers/HandlerBase.ets:
--------------------------------------------------------------------------------
1 | import { common } from '@kit.AbilityKit';
2 | import { MethodCall } from '@ohos/flutter_ohos';
3 | import { MethodResult } from '@ohos/flutter_ohos/src/main/ets/plugin/common/MethodChannel';
4 | import PhotoManagerPlugin from '../PhotoManagerPlugin';
5 |
6 | export class HandlerBase {
7 | get uiContext(): common.UIAbilityContext | null {
8 | return PhotoManagerPlugin.uiContext;
9 | }
10 |
11 | get context(): common.Context | null {
12 | return PhotoManagerPlugin.uiContext;
13 | }
14 |
15 | get showLog(): boolean {
16 | return PhotoManagerPlugin.showLog;
17 | }
18 |
19 | async tryAgain(call: MethodCall, result: MethodResult, doWork: () => Promise, numberOfRetries: number = 1): Promise {
20 | if (numberOfRetries < 0) {
21 | return false;
22 | }
23 | try {
24 | return await doWork();
25 | }
26 | catch (e) {
27 | if (this.showLog) {
28 | console.error(`${call.method} failed, code is ${e.code}, message is ${e.message}`);
29 | }
30 | if (numberOfRetries - 1 > 0) {
31 | return this.tryAgain(call, result, doWork, numberOfRetries - 1);
32 | }
33 | else {
34 | result.error(`${e.code}`, call.method, `${e.message}`);
35 | return true;
36 | }
37 | }
38 | }
39 | }
40 |
41 |
42 | export interface MethodCallHandlerBase {
43 | onMethodCall(call: MethodCall, result: MethodResult): Promise;
44 | }
--------------------------------------------------------------------------------
/ohos/src/main/ets/components/plugin/types/RequestType.ets:
--------------------------------------------------------------------------------
1 |
2 | export class RequestType {
3 | constructor(value: number) {
4 | this.value = value;
5 | }
6 | value: number;
7 | static readonly _imageValue = 1;
8 | static readonly _videoValue = 1 << 1;
9 | static readonly _audioValue = 1 << 2;
10 | static readonly all = new RequestType(
11 | RequestType._imageValue | RequestType._videoValue | RequestType._audioValue
12 | );
13 | static readonly common = new RequestType(
14 | RequestType._imageValue | RequestType._videoValue
15 | );
16 | static readonly image = new RequestType(RequestType._imageValue);
17 | static readonly video = new RequestType(RequestType._videoValue);
18 | static readonly audio = new RequestType(RequestType._audioValue);
19 |
20 | containsImage(): boolean {
21 | return (this.value & RequestType._imageValue) === RequestType._imageValue;
22 | }
23 |
24 | containsVideo(): boolean {
25 | return (this.value & RequestType._videoValue) === RequestType._videoValue;
26 | }
27 |
28 | containsAudio(): boolean {
29 | return (this.value & RequestType._audioValue) === RequestType._audioValue;
30 | }
31 |
32 | containsType(type: RequestType): boolean {
33 | return (this.value & type.value) === type.value;
34 | }
35 |
36 | add(type: RequestType): RequestType {
37 | return new RequestType(this.value | type.value);
38 | }
39 |
40 | subtract(type: RequestType): RequestType {
41 | return new RequestType(this.value ^ type.value);
42 | }
43 |
44 | shiftRight(bit: number): RequestType {
45 | return new RequestType(this.value >> bit);
46 | }
47 |
48 | shiftLeft(bit: number): RequestType {
49 | return new RequestType(this.value << bit);
50 | }
51 |
52 | static values: RequestType[] = [RequestType.image, RequestType.video, RequestType.audio];
53 |
54 | static fromTypes(types: RequestType[]): RequestType {
55 | let result = new RequestType(0);
56 | for (const type of types) {
57 | result = result.add(type);
58 | }
59 | return result;
60 | }
61 |
62 | equals(other: ESObject): boolean {
63 | return other instanceof RequestType && this.value === other.value;
64 | }
65 |
66 | hashCode(): number {
67 | return this.value;
68 | }
69 |
70 | toString(): string {
71 | return `${this.constructor.name}(${this.value})`;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/ohos/src/main/ets/components/plugin/utils/EnumUtils.ets:
--------------------------------------------------------------------------------
1 | export class EnumUtils {
2 | static getName(enumType: ESObject, value: ESObject,): string {
3 | let keys = Object.keys(enumType)
4 | .filter(x => (enumType[x] === value));
5 | let typeName: string = keys.length > 0 ? keys[0] : '';
6 | return typeName;
7 | }
8 | }
--------------------------------------------------------------------------------
/ohos/src/main/ets/components/plugin/utils/MapUtils.ets:
--------------------------------------------------------------------------------
1 |
2 |
3 | export class MapUtils {
4 | static get(map: Map, key: string, defaultValue: T): T {
5 | return map.has(key) ? map.get(key) as T : defaultValue;
6 | }
7 | }
--------------------------------------------------------------------------------
/ohos/src/main/module.json5:
--------------------------------------------------------------------------------
1 | {
2 | "module": {
3 | "name": "photo_manager",
4 | "type": "har",
5 | "deviceTypes": [
6 | "default",
7 | "tablet"
8 | ]
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: photo_manager
2 | description: A Flutter plugin that provides album assets abstraction management APIs on Android, iOS, macOS, and OpenHarmony.
3 | repository: https://github.com/fluttercandies/flutter_photo_manager
4 | version: 3.7.1
5 |
6 | environment:
7 | sdk: ">=2.13.0 <4.0.0"
8 | flutter: ">=2.2.0"
9 |
10 | dependencies:
11 | flutter:
12 | sdk: flutter
13 |
14 | dev_dependencies:
15 | flutter_lints: any
16 | flutter_test:
17 | sdk: flutter
18 |
19 | flutter:
20 | plugin:
21 | platforms:
22 | android:
23 | package: com.fluttercandies.photo_manager
24 | pluginClass: PhotoManagerPlugin
25 | ios:
26 | pluginClass: PhotoManagerPlugin
27 | macos:
28 | pluginClass: PhotoManagerPlugin
29 | ohos:
30 | pluginClass: PhotoManagerPlugin
31 |
--------------------------------------------------------------------------------
/scripts/check_license.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2018 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_for_file: avoid_print
6 |
7 | import 'dart:io';
8 |
9 | const _license =
10 | '''// Copyright 2018 The FlutterCandies author. All rights reserved.
11 | // Use of this source code is governed by an Apache license that can be found
12 | // in the LICENSE file.''';
13 |
14 | void main(List args) {
15 | final dir = Directory(args[0]);
16 |
17 | final dartFiles = dir
18 | .listSync(recursive: true)
19 | .whereType()
20 | .where((file) => file.path.endsWith('.dart'));
21 |
22 | final unlicensedFiles = [];
23 |
24 | for (final file in dartFiles) {
25 | final content = file.readAsStringSync();
26 | final lines = content.split('\n');
27 | final checkLines = lines.sublist(0, 3);
28 |
29 | if (checkLines.join('\n') != _license) {
30 | unlicensedFiles.add(file);
31 | }
32 | }
33 |
34 | if (unlicensedFiles.isNotEmpty) {
35 | print('The following files are missing the license header:');
36 | for (final file in unlicensedFiles) {
37 | print(file.path);
38 | }
39 | exit(1);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/test/internal/extensions_test.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2018 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_test/flutter_test.dart';
6 | import 'package:photo_manager/photo_manager.dart';
7 |
8 | void main() {
9 | test('Permission extensions equality test', () async {
10 | const PermissionState permission = PermissionState.limited;
11 | expect(
12 | permission.isAuth == (permission == PermissionState.authorized),
13 | equals(true),
14 | );
15 | expect(
16 | permission.hasAccess ==
17 | (permission == PermissionState.authorized ||
18 | permission == PermissionState.limited),
19 | equals(true),
20 | );
21 | });
22 | }
23 |
--------------------------------------------------------------------------------
/test/photo_manager_test.dart:
--------------------------------------------------------------------------------
1 | // Copyright 2018 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_for_file: use_named_constants
6 | import 'package:flutter_test/flutter_test.dart';
7 | import 'package:photo_manager/photo_manager.dart';
8 |
9 | class _TestPlugin extends PhotoManagerPlugin {
10 | @override
11 | Future requestPermissionExtend(_) {
12 | return Future.value(PermissionState.notDetermined);
13 | }
14 | }
15 |
16 | void main() {
17 | test('RequestType equality test', () {
18 | expect(RequestType.image == const RequestType(1), equals(true));
19 | expect(RequestType.video == const RequestType(2), equals(true));
20 | expect(RequestType.audio == const RequestType(4), equals(true));
21 | expect(RequestType.common == const RequestType(3), equals(true));
22 | expect(RequestType.all == const RequestType(7), equals(true));
23 | });
24 |
25 | test('Construct custom plugin', () async {
26 | final _TestPlugin testPlugin = _TestPlugin();
27 | PhotoManager.withPlugin(testPlugin);
28 | final PermissionState permission =
29 | await PhotoManager.requestPermissionExtend();
30 | expect(permission == PermissionState.notDetermined, equals(true));
31 | });
32 | }
33 |
--------------------------------------------------------------------------------