From 776ab18a4226f50aaefd080559de084bdf3c6e1c Mon Sep 17 00:00:00 2001 From: yujune Date: Fri, 27 Sep 2024 10:45:35 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Support=20multiple=20special=20item?= =?UTF-8?q?s.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README-ZH.md | 3 +- README.md | 3 +- example/lib/constants/picker_method.dart | 233 +++++++++++------- .../pickers/directory_file_asset_picker.dart | 17 +- .../customs/pickers/insta_asset_picker.dart | 1 - example/lib/l10n/app_en.arb | 2 + example/lib/l10n/app_zh.arb | 2 + example/lib/l10n/gen/app_localizations.dart | 12 + .../lib/l10n/gen/app_localizations_en.dart | 7 + .../lib/l10n/gen/app_localizations_zh.dart | 7 + example/lib/pages/multi_assets_page.dart | 1 + example/lib/pages/single_assets_page.dart | 1 + lib/src/constants/config.dart | 23 +- lib/src/constants/enums.dart | 4 - lib/src/constants/typedefs.dart | 8 + .../asset_picker_builder_delegate.dart | 134 +++++----- lib/src/delegates/asset_picker_delegate.dart | 3 +- lib/src/models/special_item.dart | 20 ++ lib/wechat_assets_picker.dart | 2 +- test/test_utils.dart | 3 +- 20 files changed, 302 insertions(+), 184 deletions(-) create mode 100644 lib/src/models/special_item.dart diff --git a/README-ZH.md b/README-ZH.md index fa053f0c..072c6177 100644 --- a/README-ZH.md +++ b/README-ZH.md @@ -295,8 +295,7 @@ final List? result = await AssetPicker.pickAssets( | themeColor | `Color?` | 选择器的主题色 | `Color(0xff00bc56)` | | pickerTheme | `ThemeData?` | 选择器的主题提供,包括查看器 | `null` | | textDelegate | `AssetPickerTextDelegate?` | 选择器的文本代理构建,用于自定义文本 | `AssetPickerTextDelegate()` | -| specialItemPosition | `SpecialItemPosition` | 允许用户在选择器中添加一个自定义item,并指定位置。 | `SpecialPosition.none` | -| specialItemBuilder | `SpecialItemBuilder?` | 自定义item的构造方法 | `null` | +| specialItems | `List` | 自定义item列表 | `const []` | | loadingIndicatorBuilder | `IndicatorBuilder?` | 加载器的实现 | `null` | | selectPredicate | `AssetSelectPredicate` | 判断资源可否被选择 | `null` | | shouldRevertGrid | `bool?` | 判断资源网格是否需要倒序排列 | `null` | diff --git a/README.md b/README.md index 10a4bb1e..242e8757 100644 --- a/README.md +++ b/README.md @@ -304,8 +304,7 @@ Fields in `AssetPickerConfig`: | themeColor | `Color?` | Main theme color for the picker. | `Color(0xff00bc56)` | | pickerTheme | `ThemeData?` | Theme data provider for the picker and the viewer. | `null` | | textDelegate | `AssetPickerTextDelegate?` | Text delegate for the picker, for customize the texts. | `AssetPickerTextDelegate()` | -| specialItemPosition | `SpecialItemPosition` | Allow users set a special item in the picker with several positions. | `SpecialItemPosition.none` | -| specialItemBuilder | `SpecialItemBuilder?` | The widget builder for the special item. | `null` | +| specialItems | `List` | List of special items. | `const []` | | loadingIndicatorBuilder | `IndicatorBuilder?` | Indicates the loading status for the builder. | `null` | | selectPredicate | `AssetSelectPredicate` | Predicate whether an asset can be selected or unselected. | `null` | | shouldRevertGrid | `bool?` | Whether the assets grid should revert. | `null` | diff --git a/example/lib/constants/picker_method.dart b/example/lib/constants/picker_method.dart index 00857410..a9e77197 100644 --- a/example/lib/constants/picker_method.dart +++ b/example/lib/constants/picker_method.dart @@ -135,39 +135,45 @@ class PickMethod { pickerConfig: AssetPickerConfig( maxAssets: maxAssetsCount, selectedAssets: assets, - specialItemPosition: SpecialItemPosition.prepend, - specialItemBuilder: ( - BuildContext context, - AssetPathEntity? path, - int length, - ) { - if (path?.isAll != true) { - return null; - } - return Semantics( - label: textDelegate.sActionUseCameraHint, - button: true, - onTapHint: textDelegate.sActionUseCameraHint, - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () async { - Feedback.forTap(context); - final AssetEntity? result = await _pickFromCamera(context); - if (result != null) { - handleResult(context, result); - } - }, - child: Container( - padding: const EdgeInsets.all(28.0), - color: Theme.of(context).dividerColor, - child: const FittedBox( - fit: BoxFit.fill, - child: Icon(Icons.camera_enhance), + specialItems: [ + SpecialItem( + position: SpecialItemPosition.prepend, + builder: ( + BuildContext context, + AssetPathEntity? path, + int length, + bool isPermissionLimited, + ) { + if (path?.isAll != true) { + return null; + } + return Semantics( + label: textDelegate.sActionUseCameraHint, + button: true, + onTapHint: textDelegate.sActionUseCameraHint, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () async { + Feedback.forTap(context); + final AssetEntity? result = + await _pickFromCamera(context); + if (result != null) { + handleResult(context, result); + } + }, + child: Container( + padding: const EdgeInsets.all(28.0), + color: Theme.of(context).dividerColor, + child: const FittedBox( + fit: BoxFit.fill, + child: Icon(Icons.camera_enhance), + ), + ), ), - ), - ), - ); - }, + ); + }, + ), + ], ), ); }, @@ -186,50 +192,56 @@ class PickMethod { pickerConfig: AssetPickerConfig( maxAssets: maxAssetsCount, selectedAssets: assets, - specialItemPosition: SpecialItemPosition.prepend, - specialItemBuilder: ( - BuildContext context, - AssetPathEntity? path, - int length, - ) { - if (path?.isAll != true) { - return null; - } - return Semantics( - label: textDelegate.sActionUseCameraHint, - button: true, - onTapHint: textDelegate.sActionUseCameraHint, - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () async { - final AssetEntity? result = await _pickFromCamera(context); - if (result == null) { - return; - } - final picker = context.findAncestorWidgetOfExactType< - AssetPicker>()!; - final builder = - picker.builder as DefaultAssetPickerBuilderDelegate; - final p = builder.provider; - await p.switchPath( - PathWrapper( - path: - await p.currentPath!.path.obtainForNewProperties(), + specialItems: [ + SpecialItem( + position: SpecialItemPosition.prepend, + builder: ( + BuildContext context, + AssetPathEntity? path, + int length, + bool isPermissionLimited, + ) { + if (path?.isAll != true) { + return null; + } + return Semantics( + label: textDelegate.sActionUseCameraHint, + button: true, + onTapHint: textDelegate.sActionUseCameraHint, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () async { + final AssetEntity? result = + await _pickFromCamera(context); + if (result == null) { + return; + } + final picker = context.findAncestorWidgetOfExactType< + AssetPicker>()!; + final builder = + picker.builder as DefaultAssetPickerBuilderDelegate; + final p = builder.provider; + await p.switchPath( + PathWrapper( + path: await p.currentPath!.path + .obtainForNewProperties(), + ), + ); + p.selectAsset(result); + }, + child: Container( + padding: const EdgeInsets.all(28.0), + color: Theme.of(context).dividerColor, + child: const FittedBox( + fit: BoxFit.fill, + child: Icon(Icons.camera_enhance), + ), ), - ); - p.selectAsset(result); - }, - child: Container( - padding: const EdgeInsets.all(28.0), - color: Theme.of(context).dividerColor, - child: const FittedBox( - fit: BoxFit.fill, - child: Icon(Icons.camera_enhance), ), - ), - ), - ); - }, + ); + }, + ), + ], ), ); }, @@ -295,16 +307,69 @@ class PickMethod { pickerConfig: AssetPickerConfig( maxAssets: maxAssetsCount, selectedAssets: assets, - specialItemPosition: SpecialItemPosition.prepend, - specialItemBuilder: ( - BuildContext context, - AssetPathEntity? path, - int length, - ) { - return const Center( - child: Text('Custom Widget', textAlign: TextAlign.center), - ); - }, + specialItems: [ + SpecialItem( + position: SpecialItemPosition.prepend, + builder: ( + BuildContext context, + AssetPathEntity? path, + int length, + bool isPermissionLimited, + ) { + return const Center( + child: Text('Custom Widget', textAlign: TextAlign.center), + ); + }, + ), + ], + ), + ); + }, + ); + } + + factory PickMethod.multiSpecialItems( + BuildContext context, + int maxAssetsCount, + ) { + return PickMethod( + icon: '💡', + name: context.l10n.pickMethodMultiSpecialItemsName, + description: context.l10n.pickMethodMultiSpecialItemsDescription, + method: (BuildContext context, List assets) { + return AssetPicker.pickAssets( + context, + pickerConfig: AssetPickerConfig( + maxAssets: maxAssetsCount, + selectedAssets: assets, + specialItems: [ + SpecialItem( + position: SpecialItemPosition.prepend, + builder: ( + BuildContext context, + AssetPathEntity? path, + int length, + bool isPermissionLimited, + ) { + return const Center( + child: Text('Prepand Widget', textAlign: TextAlign.center), + ); + }, + ), + SpecialItem( + position: SpecialItemPosition.append, + builder: ( + BuildContext context, + AssetPathEntity? path, + int length, + bool isPermissionLimited, + ) { + return const Center( + child: Text('Append Widget', textAlign: TextAlign.center), + ); + }, + ), + ], ), ); }, diff --git a/example/lib/customs/pickers/directory_file_asset_picker.dart b/example/lib/customs/pickers/directory_file_asset_picker.dart index 535cb316..d04826bf 100644 --- a/example/lib/customs/pickers/directory_file_asset_picker.dart +++ b/example/lib/customs/pickers/directory_file_asset_picker.dart @@ -584,7 +584,7 @@ class FileAssetPickerBuilder Widget assetsGridBuilder(BuildContext context) { appBarPreferredSize ??= appBar(context).preferredSize; int totalCount = provider.currentAssets.length; - if (specialItemPosition != SpecialItemPosition.none) { + if (specialItems.isNotEmpty) { totalCount += 1; } final int placeholderCount; @@ -701,10 +701,10 @@ class FileAssetPickerBuilder int index, List currentAssets, ) { - final int currentIndex = switch (specialItemPosition) { - SpecialItemPosition.none || SpecialItemPosition.append => index, - SpecialItemPosition.prepend => index - 1, - }; + int currentIndex = index; + + currentIndex = index - prependSpecialItems.length; + final File asset = currentAssets.elementAt(currentIndex); final Widget builder = imageAndVideoItemBuilder( context, @@ -737,12 +737,7 @@ class FileAssetPickerBuilder required List assets, int placeholderCount = 0, }) { - final int length = switch (specialItemPosition) { - SpecialItemPosition.none => assets.length, - SpecialItemPosition.prepend || - SpecialItemPosition.append => - assets.length + 1, - }; + final int length = assets.length + specialItems.length; return length + placeholderCount; } diff --git a/example/lib/customs/pickers/insta_asset_picker.dart b/example/lib/customs/pickers/insta_asset_picker.dart index 879fd87d..c0ed62f3 100644 --- a/example/lib/customs/pickers/insta_asset_picker.dart +++ b/example/lib/customs/pickers/insta_asset_picker.dart @@ -297,7 +297,6 @@ class InstaAssetPickerBuilder extends DefaultAssetPickerBuilderDelegate { super.keepScrollOffset, }) : super( shouldRevertGrid: false, - specialItemPosition: SpecialItemPosition.none, ); /// Save last position of the grid view scroll controller diff --git a/example/lib/l10n/app_en.arb b/example/lib/l10n/app_en.arb index d40489ed..029e46a9 100644 --- a/example/lib/l10n/app_en.arb +++ b/example/lib/l10n/app_en.arb @@ -28,6 +28,8 @@ "pickMethodCustomFilterOptionsDescription": "Add filter options for the picker.", "pickMethodPrependItemName": "Prepend special item", "pickMethodPrependItemDescription": "A special item will prepend to the assets grid.", + "pickMethodMultiSpecialItemsName": "Multiple special items", + "pickMethodMultiSpecialItemsDescription": "Multiple special items will prepend or append to the assets grid", "pickMethodNoPreviewName": "No preview", "pickMethodNoPreviewDescription": "You cannot preview assets during the picking, the behavior is like the WhatsApp/MegaTok pattern.", "pickMethodKeepScrollOffsetName": "Keep scroll offset", diff --git a/example/lib/l10n/app_zh.arb b/example/lib/l10n/app_zh.arb index 79df1bd6..151514b2 100644 --- a/example/lib/l10n/app_zh.arb +++ b/example/lib/l10n/app_zh.arb @@ -28,6 +28,8 @@ "pickMethodCustomFilterOptionsDescription": "为选择器添加自定义过滤条件。", "pickMethodPrependItemName": "往网格前插入 widget", "pickMethodPrependItemDescription": "网格的靠前位置会添加一个自定义的 widget。", + "pickMethodMultiSpecialItemsName": "多个特殊 widget", + "pickMethodMultiSpecialItemsDescription": "网格的靠前或靠后位置会可以多个自定义的 widget。", "pickMethodNoPreviewName": "禁止预览", "pickMethodNoPreviewDescription": "无法预览选择的资源,与 WhatsApp/MegaTok 的行为类似。", "pickMethodKeepScrollOffsetName": "保持滚动位置", diff --git a/example/lib/l10n/gen/app_localizations.dart b/example/lib/l10n/gen/app_localizations.dart index dd848222..914aa33b 100644 --- a/example/lib/l10n/gen/app_localizations.dart +++ b/example/lib/l10n/gen/app_localizations.dart @@ -264,6 +264,18 @@ abstract class AppLocalizations { /// **'A special item will prepend to the assets grid.'** String get pickMethodPrependItemDescription; + /// No description provided for @pickMethodMultiSpecialItemsName. + /// + /// In en, this message translates to: + /// **'Multiple special items'** + String get pickMethodMultiSpecialItemsName; + + /// No description provided for @pickMethodMultiSpecialItemsDescription. + /// + /// In en, this message translates to: + /// **'Multiple special items will prepend or append to the assets grid'** + String get pickMethodMultiSpecialItemsDescription; + /// No description provided for @pickMethodNoPreviewName. /// /// In en, this message translates to: diff --git a/example/lib/l10n/gen/app_localizations_en.dart b/example/lib/l10n/gen/app_localizations_en.dart index b8238fef..1e5474ed 100644 --- a/example/lib/l10n/gen/app_localizations_en.dart +++ b/example/lib/l10n/gen/app_localizations_en.dart @@ -99,6 +99,13 @@ class AppLocalizationsEn extends AppLocalizations { String get pickMethodPrependItemDescription => 'A special item will prepend to the assets grid.'; + @override + String get pickMethodMultiSpecialItemsName => 'Multiple special items'; + + @override + String get pickMethodMultiSpecialItemsDescription => + 'Multiple special items will prepend or append to the assets grid'; + @override String get pickMethodNoPreviewName => 'No preview'; diff --git a/example/lib/l10n/gen/app_localizations_zh.dart b/example/lib/l10n/gen/app_localizations_zh.dart index 0d4f2e66..4bef94cb 100644 --- a/example/lib/l10n/gen/app_localizations_zh.dart +++ b/example/lib/l10n/gen/app_localizations_zh.dart @@ -93,6 +93,13 @@ class AppLocalizationsZh extends AppLocalizations { @override String get pickMethodPrependItemDescription => '网格的靠前位置会添加一个自定义的 widget。'; + @override + String get pickMethodMultiSpecialItemsName => '多个特殊 widget'; + + @override + String get pickMethodMultiSpecialItemsDescription => + '网格的靠前或靠后位置会可以多个自定义的 widget。'; + @override String get pickMethodNoPreviewName => '禁止预览'; diff --git a/example/lib/pages/multi_assets_page.dart b/example/lib/pages/multi_assets_page.dart index 016486da..4d0b30cc 100644 --- a/example/lib/pages/multi_assets_page.dart +++ b/example/lib/pages/multi_assets_page.dart @@ -46,6 +46,7 @@ class _MultiAssetsPageState extends State PickMethod.changeLanguages(context, maxAssetsCount), PickMethod.threeItemsGrid(context, maxAssetsCount), PickMethod.prependItem(context, maxAssetsCount), + PickMethod.multiSpecialItems(context, maxAssetsCount), PickMethod( icon: '🎭', name: context.l10n.pickMethodWeChatMomentName, diff --git a/example/lib/pages/single_assets_page.dart b/example/lib/pages/single_assets_page.dart index 612277dd..ff5a39fb 100644 --- a/example/lib/pages/single_assets_page.dart +++ b/example/lib/pages/single_assets_page.dart @@ -46,6 +46,7 @@ class _SingleAssetPageState extends State PickMethod.changeLanguages(context, maxAssetsCount), PickMethod.threeItemsGrid(context, maxAssetsCount), PickMethod.prependItem(context, maxAssetsCount), + PickMethod.multiSpecialItems(context, maxAssetsCount), PickMethod.customFilterOptions(context, maxAssetsCount), PickMethod.preventGIFPicked(context, maxAssetsCount), PickMethod.noPreview(context, maxAssetsCount), diff --git a/lib/src/constants/config.dart b/lib/src/constants/config.dart index 939f5fa8..b22bfd2d 100644 --- a/lib/src/constants/config.dart +++ b/lib/src/constants/config.dart @@ -8,6 +8,7 @@ import 'package:photo_manager/photo_manager.dart'; import '../constants/typedefs.dart'; import '../delegates/asset_picker_text_delegate.dart'; import '../delegates/sort_path_delegate.dart'; +import '../models/special_item.dart'; import 'constants.dart'; import 'enums.dart'; @@ -29,8 +30,6 @@ class AssetPickerConfig { this.themeColor, this.pickerTheme, this.textDelegate, - this.specialItemPosition = SpecialItemPosition.none, - this.specialItemBuilder, this.loadingIndicatorBuilder, this.selectPredicate, this.shouldRevertGrid, @@ -39,6 +38,7 @@ class AssetPickerConfig { this.assetsChangeCallback, this.assetsChangeRefreshPredicate, this.shouldAutoplayPreview = false, + this.specialItems = const [], }) : assert( pickerTheme == null || themeColor == null, 'pickerTheme and themeColor cannot be set at the same time.', @@ -55,13 +55,6 @@ class AssetPickerConfig { requestType == RequestType.common, 'SpecialPickerType.wechatMoment and requestType ' 'cannot be set at the same time.', - ), - assert( - (specialItemBuilder == null && - identical(specialItemPosition, SpecialItemPosition.none)) || - (specialItemBuilder != null && - !identical(specialItemPosition, SpecialItemPosition.none)), - 'Custom item did not set properly.', ); /// Selected assets. @@ -167,14 +160,6 @@ class AssetPickerConfig { final AssetPickerTextDelegate? textDelegate; - /// Allow users set a special item in the picker with several positions. - /// 允许用户在选择器中添加一个自定义item,并指定位置 - final SpecialItemPosition specialItemPosition; - - /// The widget builder for the the special item. - /// 自定义item的构造方法 - final SpecialItemBuilder? specialItemBuilder; - /// Indicates the loading status for the builder. /// 指示目前加载的状态 final LoadingIndicatorBuilder? loadingIndicatorBuilder; @@ -205,4 +190,8 @@ class AssetPickerConfig { /// Whether the preview should auto play. /// 预览是否自动播放 final bool shouldAutoplayPreview; + + /// List of special items. + /// 自定义item列表 + final List> specialItems; } diff --git a/lib/src/constants/enums.dart b/lib/src/constants/enums.dart index 686c563d..ba3bdff9 100644 --- a/lib/src/constants/enums.dart +++ b/lib/src/constants/enums.dart @@ -30,10 +30,6 @@ enum SpecialPickerType { /// Provide an item slot for custom widget insertion. /// 提供一个自定义位置供特殊item放入资源列表中。 enum SpecialItemPosition { - /// Not insert to the list. - /// 不放入列表 - none, - /// Add as leading of the list. /// 在列表前放入 prepend, diff --git a/lib/src/constants/typedefs.dart b/lib/src/constants/typedefs.dart index e8e5c689..8fc40470 100644 --- a/lib/src/constants/typedefs.dart +++ b/lib/src/constants/typedefs.dart @@ -9,6 +9,8 @@ import 'package:flutter/widgets.dart'; import 'package:photo_manager/photo_manager.dart' show PermissionState; import 'package:provider/provider.dart'; +import './enums.dart'; + /// Mirroring [ChangeNotifierProvider]. typedef CNP = ChangeNotifierProvider; @@ -29,6 +31,7 @@ typedef SpecialItemBuilder = Widget? Function( BuildContext context, Path? path, int length, + bool isPermissionLimited, ); /// {@template wechat_assets_picker.AssetSelectPredicate} @@ -75,3 +78,8 @@ typedef AssetsChangeRefreshPredicate = bool Function( MethodCall call, Path? path, ); + +typedef SpecialItemModel = ({ + SpecialItemPosition position, + Widget item, +}); diff --git a/lib/src/delegates/asset_picker_builder_delegate.dart b/lib/src/delegates/asset_picker_builder_delegate.dart index 96c402c1..a419425f 100644 --- a/lib/src/delegates/asset_picker_builder_delegate.dart +++ b/lib/src/delegates/asset_picker_builder_delegate.dart @@ -21,6 +21,7 @@ import '../constants/typedefs.dart'; import '../delegates/asset_picker_text_delegate.dart'; import '../internals/singleton.dart'; import '../models/path_wrapper.dart'; +import '../models/special_item.dart'; import '../provider/asset_picker_provider.dart'; import '../widget/asset_picker.dart'; import '../widget/asset_picker_app_bar.dart'; @@ -38,8 +39,7 @@ abstract class AssetPickerBuilderDelegate { required this.initialPermission, this.gridCount = 4, this.pickerTheme, - this.specialItemPosition = SpecialItemPosition.none, - this.specialItemBuilder, + this.specialItems = const [], this.loadingIndicatorBuilder, this.selectPredicate, this.shouldRevertGrid, @@ -57,7 +57,12 @@ abstract class AssetPickerBuilderDelegate { ), themeColor = pickerTheme?.colorScheme.secondary ?? themeColor ?? - defaultThemeColorWeChat { + defaultThemeColorWeChat, + prependSpecialItems = specialItems + .where( + (item) => item.position == SpecialItemPosition.prepend, + ) + .toList() { Singleton.textDelegate = textDelegate ?? assetPickerTextDelegateFromLocale(locale); } @@ -84,13 +89,9 @@ abstract class AssetPickerBuilderDelegate { /// 但某些情况下开发者需要亮色或自定义主题。 final ThemeData? pickerTheme; - /// Allow users set a special item in the picker with several positions. - /// 允许用户在选择器中添加一个自定义 item,并指定位置 - final SpecialItemPosition specialItemPosition; - - /// The widget builder for the the special item. - /// 自定义 item 的构造方法 - final SpecialItemBuilder? specialItemBuilder; + /// List of special items. + /// 自定义item列表 + final List> specialItems; /// Indicates the loading status for the builder. /// 指示目前加载的状态 @@ -130,6 +131,10 @@ abstract class AssetPickerBuilderDelegate { final AssetsChangeRefreshPredicate? assetsChangeRefreshPredicate; + /// List of prepend special items. + /// 前置自定义item列表 + final List> prependSpecialItems; + /// [ThemeData] for the picker. /// 选择器使用的主题 ThemeData get theme => pickerTheme ?? AssetPicker.themeData(themeColor); @@ -170,9 +175,7 @@ abstract class AssetPickerBuilderDelegate { /// Whether the delegate should build the special item. /// 是否需要构建自定义 item - bool get shouldBuildSpecialItem => - specialItemPosition != SpecialItemPosition.none && - specialItemBuilder != null; + bool get shouldBuildSpecialItem => specialItems.isNotEmpty; /// Space between assets item widget. /// 资源部件之间的间隔 @@ -743,8 +746,6 @@ class DefaultAssetPickerBuilderDelegate required super.initialPermission, super.gridCount, super.pickerTheme, - super.specialItemPosition, - super.specialItemBuilder, super.loadingIndicatorBuilder, super.selectPredicate, super.shouldRevertGrid, @@ -760,6 +761,7 @@ class DefaultAssetPickerBuilderDelegate this.specialPickerType, this.keepScrollOffset = false, this.shouldAutoplayPreview = false, + super.specialItems = const [], }) { // Add the listener if [keepScrollOffset] is true. if (keepScrollOffset) { @@ -1231,21 +1233,25 @@ class DefaultAssetPickerBuilderDelegate builder: (context, wrapper, _) { // First, we need the count of the assets. int totalCount = wrapper?.assetCount ?? 0; - final Widget? specialItem; - // If user chose a special item's position, add 1 count. - if (specialItemPosition != SpecialItemPosition.none) { - specialItem = specialItemBuilder?.call( - context, - wrapper?.path, - totalCount, - ); - if (specialItem != null) { - totalCount += 1; - } - } else { - specialItem = null; - } - if (totalCount == 0 && specialItem == null) { + + final List specialItemModels = specialItems + .map((item) { + return ( + position: item.position, + item: item.builder?.call( + context, + wrapper?.path, + totalCount, + isPermissionLimited, + ) + ); + }) + .whereType() + .toList(); + + totalCount += specialItemModels.length; + + if (totalCount == 0 && specialItemModels.isEmpty) { return loadingIndicator(context); } // Then we use the [totalCount] to calculate placeholders we need. @@ -1285,7 +1291,7 @@ class DefaultAssetPickerBuilderDelegate context, index, assets, - specialItem: specialItem, + specialItemModels: specialItemModels, ), ), ); @@ -1294,7 +1300,7 @@ class DefaultAssetPickerBuilderDelegate context: context, assets: assets, placeholderCount: placeholderCount, - specialItem: specialItem, + specialItemModels: specialItemModels, ), findChildIndexCallback: (Key? key) { if (key is ValueKey) { @@ -1404,7 +1410,7 @@ class DefaultAssetPickerBuilderDelegate BuildContext context, int index, List currentAssets, { - Widget? specialItem, + List specialItemModels = const [], }) { final DefaultAssetPickerProvider p = context.read(); @@ -1412,21 +1418,30 @@ class DefaultAssetPickerBuilderDelegate final PathWrapper? currentWrapper = p.currentPath; final AssetPathEntity? currentPathEntity = currentWrapper?.path; - if (specialItem != null) { - if ((index == 0 && specialItemPosition == SpecialItemPosition.prepend) || - (index == length && - specialItemPosition == SpecialItemPosition.append)) { - return specialItem; + final prepandSpecialItemModels = specialItemModels.where( + (model) => model.position == SpecialItemPosition.prepend, + ); + final appendSpecialItemModels = specialItemModels.where( + (model) => model.position == SpecialItemPosition.append, + ); + + if (specialItemModels.isNotEmpty) { + if (prepandSpecialItemModels.isNotEmpty) { + if (index < prepandSpecialItemModels.length) { + return specialItemModels[index].item; + } + } + + if (appendSpecialItemModels.isNotEmpty) { + if (index >= length + prepandSpecialItemModels.length) { + return specialItemModels[index - length].item; + } } } - final int currentIndex; - if (specialItem != null && - specialItemPosition == SpecialItemPosition.prepend) { - currentIndex = index - 1; - } else { - currentIndex = index; - } + int currentIndex = index; + + currentIndex = index - prepandSpecialItemModels.length; if (currentPathEntity == null) { return const SizedBox.shrink(); @@ -1461,9 +1476,10 @@ class DefaultAssetPickerBuilderDelegate } int semanticIndex(int index) { - if (specialItemPosition != SpecialItemPosition.prepend) { - return index + 1; + if (prependSpecialItems.isNotEmpty) { + return index - prependSpecialItems.length + 1; } + return index; } @@ -1556,9 +1572,9 @@ class DefaultAssetPickerBuilderDelegate int placeholderCount = 0, }) { int index = assets.indexWhere((AssetEntity e) => e.id == id); - if (specialItemPosition == SpecialItemPosition.prepend) { - index += 1; - } + + index += prependSpecialItems.length; + index += placeholderCount; return index; } @@ -1568,7 +1584,7 @@ class DefaultAssetPickerBuilderDelegate required BuildContext context, required List assets, int placeholderCount = 0, - Widget? specialItem, + List specialItemModels = const [], }) { final PathWrapper? currentWrapper = context .select?>( @@ -1578,19 +1594,21 @@ class DefaultAssetPickerBuilderDelegate final int length = assets.length + placeholderCount; // Return 1 if the [specialItem] build something. - if (currentPathEntity == null && specialItem != null) { - return placeholderCount + 1; + if (currentPathEntity == null && specialItemModels.isNotEmpty) { + return placeholderCount + specialItemModels.length; } // Return actual length if the current path is all. // 如果当前目录是全部内容,则返回实际的内容数量。 - if (currentPathEntity?.isAll != true && specialItem == null) { + if (currentPathEntity?.isAll != true && specialItemModels.isEmpty) { return length; } - return switch (specialItemPosition) { - SpecialItemPosition.none => length, - SpecialItemPosition.prepend || SpecialItemPosition.append => length + 1, - }; + + if (specialItemModels.isEmpty) { + return length; + } + + return length + specialItemModels.length; } @override diff --git a/lib/src/delegates/asset_picker_delegate.dart b/lib/src/delegates/asset_picker_delegate.dart index 3f788b3d..c6943fe4 100644 --- a/lib/src/delegates/asset_picker_delegate.dart +++ b/lib/src/delegates/asset_picker_delegate.dart @@ -105,8 +105,6 @@ class AssetPickerDelegate { gridThumbnailSize: pickerConfig.gridThumbnailSize, previewThumbnailSize: pickerConfig.previewThumbnailSize, specialPickerType: pickerConfig.specialPickerType, - specialItemPosition: pickerConfig.specialItemPosition, - specialItemBuilder: pickerConfig.specialItemBuilder, loadingIndicatorBuilder: pickerConfig.loadingIndicatorBuilder, selectPredicate: pickerConfig.selectPredicate, shouldRevertGrid: pickerConfig.shouldRevertGrid, @@ -119,6 +117,7 @@ class AssetPickerDelegate { themeColor: pickerConfig.themeColor, locale: Localizations.maybeLocaleOf(context), shouldAutoplayPreview: pickerConfig.shouldAutoplayPreview, + specialItems: pickerConfig.specialItems, ), ); final List? result = await Navigator.maybeOf( diff --git a/lib/src/models/special_item.dart b/lib/src/models/special_item.dart new file mode 100644 index 00000000..10b9c169 --- /dev/null +++ b/lib/src/models/special_item.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:wechat_assets_picker/wechat_assets_picker.dart'; + +/// Allow users to set a special item in the picker grid with specified [position]. +/// 允许用户在选择器中添加一个自定义item,并指定其位置。 +@immutable +class SpecialItem { + const SpecialItem({ + required this.builder, + required this.position, + }); + + /// The widget builder for the the special item. + /// 自定义item构建。 + final SpecialItemBuilder? builder; + + /// Define how the item will be positioned. + /// 定义如何摆放item。 + final SpecialItemPosition position; +} diff --git a/lib/wechat_assets_picker.dart b/lib/wechat_assets_picker.dart index ca03acff..dd2afb69 100644 --- a/lib/wechat_assets_picker.dart +++ b/lib/wechat_assets_picker.dart @@ -12,7 +12,6 @@ export 'src/constants/config.dart'; export 'src/constants/constants.dart' hide packageName; export 'src/constants/enums.dart'; export 'src/constants/typedefs.dart'; - export 'src/delegates/asset_picker_builder_delegate.dart'; export 'src/delegates/asset_picker_delegate.dart'; export 'src/delegates/asset_picker_text_delegate.dart'; @@ -20,6 +19,7 @@ export 'src/delegates/asset_picker_viewer_builder_delegate.dart'; export 'src/delegates/sort_path_delegate.dart'; export 'src/models/path_wrapper.dart'; +export 'src/models/special_item.dart'; export 'src/provider/asset_picker_provider.dart'; export 'src/provider/asset_picker_viewer_provider.dart'; diff --git a/test/test_utils.dart b/test/test_utils.dart index c5d0a670..91eb9fdd 100644 --- a/test/test_utils.dart +++ b/test/test_utils.dart @@ -131,8 +131,7 @@ class TestAssetPickerDelegate extends AssetPickerDelegate { gridThumbnailSize: pickerConfig.gridThumbnailSize, previewThumbnailSize: pickerConfig.previewThumbnailSize, specialPickerType: pickerConfig.specialPickerType, - specialItemPosition: pickerConfig.specialItemPosition, - specialItemBuilder: pickerConfig.specialItemBuilder, + specialItems: pickerConfig.specialItems, loadingIndicatorBuilder: pickerConfig.loadingIndicatorBuilder, selectPredicate: pickerConfig.selectPredicate, shouldRevertGrid: pickerConfig.shouldRevertGrid,