From fe84761cc346e3d95cadd7e776a9f645bb6a45da Mon Sep 17 00:00:00 2001 From: Alex Li Date: Wed, 9 Oct 2024 23:43:47 +0800 Subject: [PATCH 01/29] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Abstract=20`assetSem?= =?UTF-8?q?anticsBuilder`=20for=20`AssetPickerViewerBuilderDelegate`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../asset_picker_viewer_builder_delegate.dart | 66 ++++++++++--------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/lib/src/delegates/asset_picker_viewer_builder_delegate.dart b/lib/src/delegates/asset_picker_viewer_builder_delegate.dart index 8f386724..855ef1fd 100644 --- a/lib/src/delegates/asset_picker_viewer_builder_delegate.dart +++ b/lib/src/delegates/asset_picker_viewer_builder_delegate.dart @@ -421,35 +421,10 @@ class DefaultAssetPickerViewerBuilderDelegate (selectedAssets?.any((AssetEntity e) => e.type == AssetType.video) ?? false); - @override - Widget assetPageBuilder(BuildContext context, int index) { - final AssetEntity asset = previewAssets.elementAt( + Widget assetSemanticsBuilder(BuildContext context, int index) { + final asset = previewAssets.elementAt( shouldReversePreview ? previewAssets.length - index - 1 : index, ); - final Widget builder = switch (asset.type) { - AssetType.audio => AudioPageBuilder( - asset: asset, - shouldAutoplayPreview: shouldAutoplayPreview, - ), - AssetType.image => ImagePageBuilder( - asset: asset, - delegate: this, - previewThumbnailSize: previewThumbnailSize, - shouldAutoplayPreview: shouldAutoplayPreview, - ), - AssetType.video => VideoPageBuilder( - asset: asset, - delegate: this, - hasOnlyOneVideoAndMoment: isWeChatMoment && hasVideo, - shouldAutoplayPreview: shouldAutoplayPreview, - ), - AssetType.other => Center( - child: ScaleText( - textDelegate.unSupportedAssetType, - semanticsLabel: semanticsTextDelegate.unSupportedAssetType, - ), - ), - }; return MergeSemantics( child: Consumer?>( builder: ( @@ -476,14 +451,45 @@ class DefaultAssetPickerViewerBuilderDelegate hint: hint, image: asset.type == AssetType.image || asset.type == AssetType.video, - child: w, + child: child, ); }, - child: builder, + child: assetPageBuilder(context, index), ), ); } + @override + Widget assetPageBuilder(BuildContext context, int index) { + final asset = previewAssets.elementAt( + shouldReversePreview ? previewAssets.length - index - 1 : index, + ); + return switch (asset.type) { + AssetType.audio => AudioPageBuilder( + asset: asset, + shouldAutoplayPreview: shouldAutoplayPreview, + ), + AssetType.image => ImagePageBuilder( + asset: asset, + delegate: this, + previewThumbnailSize: previewThumbnailSize, + shouldAutoplayPreview: shouldAutoplayPreview, + ), + AssetType.video => VideoPageBuilder( + asset: asset, + delegate: this, + hasOnlyOneVideoAndMoment: isWeChatMoment && hasVideo, + shouldAutoplayPreview: shouldAutoplayPreview, + ), + AssetType.other => Center( + child: ScaleText( + textDelegate.unSupportedAssetType, + semanticsLabel: semanticsTextDelegate.unSupportedAssetType, + ), + ), + }; + } + /// Preview item widgets for audios. /// 音频的底部预览部件 Widget _audioPreviewItem(AssetEntity asset) { @@ -992,7 +998,7 @@ class DefaultAssetPickerViewerBuilderDelegate : const CustomBouncingScrollPhysics(), controller: pageController, itemCount: previewAssets.length, - itemBuilder: assetPageBuilder, + itemBuilder: assetSemanticsBuilder, onPageChanged: (int index) { currentIndex = index; pageStreamController.add(index); From f65cf3abfbe6809b630649272dac94f42cc47a2c Mon Sep 17 00:00:00 2001 From: Alex Li Date: Wed, 9 Oct 2024 23:45:49 +0800 Subject: [PATCH 02/29] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Move=20`selectorProv?= =?UTF-8?q?ider`=20from=20base=20delegate=20to=20the=20default=20delegate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../asset_picker_viewer_builder_delegate.dart | 50 ++++++++++++------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/lib/src/delegates/asset_picker_viewer_builder_delegate.dart b/lib/src/delegates/asset_picker_viewer_builder_delegate.dart index 855ef1fd..a1b70703 100644 --- a/lib/src/delegates/asset_picker_viewer_builder_delegate.dart +++ b/lib/src/delegates/asset_picker_viewer_builder_delegate.dart @@ -33,7 +33,6 @@ abstract class AssetPickerViewerBuilderDelegate { required this.previewAssets, required this.themeData, required this.currentIndex, - this.selectorProvider, this.provider, this.selectedAssets, this.maxAssets, @@ -59,10 +58,6 @@ abstract class AssetPickerViewerBuilderDelegate { /// 已选的资源 final List? selectedAssets; - /// Provider for [AssetPicker]. - /// 资源选择器的状态保持 - final AssetPickerProvider? selectorProvider; - /// Whether the preview sequence is reversed. /// 预览时顺序是否为反向 /// @@ -264,7 +259,6 @@ abstract class AssetPickerViewerBuilderDelegate { void unSelectAsset(Asset entity) { provider?.unSelectAsset(entity); - selectorProvider?.unSelectAsset(entity); if (!isSelectedPreviewing) { selectedAssets?.remove(entity); } @@ -276,7 +270,6 @@ abstract class AssetPickerViewerBuilderDelegate { return; } provider?.selectAsset(entity); - selectorProvider?.selectAsset(entity); if (!isSelectedPreviewing) { selectedAssets?.add(entity); } @@ -316,12 +309,7 @@ abstract class AssetPickerViewerBuilderDelegate { 'No longer used by the package. ' 'This will be removed in 10.0.0', ) - Future syncSelectedAssetsWhenPop() async { - if (provider?.currentlySelectedAssets != null) { - selectorProvider?.selectedAssets = provider!.currentlySelectedAssets; - } - return true; - } + Future syncSelectedAssetsWhenPop() async => true; /// Split page builder according to type of asset. /// 根据资源类型使用不同的构建页 @@ -383,9 +371,9 @@ class DefaultAssetPickerViewerBuilderDelegate required super.currentIndex, required super.previewAssets, required super.themeData, - super.selectorProvider, super.provider, super.selectedAssets, + this.selectorProvider, this.previewThumbnailSize, this.specialPickerType, super.maxAssets, @@ -394,9 +382,9 @@ class DefaultAssetPickerViewerBuilderDelegate this.shouldAutoplayPreview = false, }); - /// Whether the preview should auto play. - /// 预览是否自动播放 - final bool shouldAutoplayPreview; + /// Provider for [AssetPicker]. + /// 资源选择器的状态保持 + final P? selectorProvider; /// Thumb size for the preview of images in the viewer. /// 预览时图片的缩略图大小 @@ -409,6 +397,10 @@ class DefaultAssetPickerViewerBuilderDelegate /// 如果类型不为空,则标题将不会显示。 final SpecialPickerType? specialPickerType; + /// Whether the preview should auto play. + /// 预览是否自动播放 + final bool shouldAutoplayPreview; + /// Whether the [SpecialPickerType.wechatMoment] is enabled. /// 当前是否为微信朋友圈选择模式 bool get isWeChatMoment => @@ -421,6 +413,30 @@ class DefaultAssetPickerViewerBuilderDelegate (selectedAssets?.any((AssetEntity e) => e.type == AssetType.video) ?? false); + @override + void unSelectAsset(AssetEntity entity) { + super.unSelectAsset(entity); + selectorProvider?.unSelectAsset(entity); + } + + @override + void selectAsset(AssetEntity entity) { + super.selectAsset(entity); + selectedNotifier.value = selectedCount; + } + + @Deprecated( + 'No longer used by the package. ' + 'This will be removed in 10.0.0', + ) + @override + Future syncSelectedAssetsWhenPop() async { + if (provider?.currentlySelectedAssets != null) { + selectorProvider?.selectedAssets = provider!.currentlySelectedAssets; + } + return true; + } + Widget assetSemanticsBuilder(BuildContext context, int index) { final asset = previewAssets.elementAt( shouldReversePreview ? previewAssets.length - index - 1 : index, From 9c5e14fa45d65f3e91918cf906a1f1d4f63fa838 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Wed, 9 Oct 2024 23:46:34 +0800 Subject: [PATCH 03/29] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Mark=20delegates=20a?= =?UTF-8?q?nd=20providers=20as=20generic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pickers/directory_file_asset_picker.dart | 26 ++++-- .../customs/pickers/insta_asset_picker.dart | 25 +++-- .../asset_picker_builder_delegate.dart | 92 +++++++++---------- .../asset_picker_viewer_builder_delegate.dart | 63 +++++++------ .../asset_picker_viewer_provider.dart | 28 +++--- lib/src/widget/asset_picker_viewer.dart | 70 ++++++++------ .../widget/builder/image_page_builder.dart | 4 +- .../widget/builder/video_page_builder.dart | 4 +- 8 files changed, 174 insertions(+), 138 deletions(-) diff --git a/example/lib/customs/pickers/directory_file_asset_picker.dart b/example/lib/customs/pickers/directory_file_asset_picker.dart index 535cb316..e79e44c1 100644 --- a/example/lib/customs/pickers/directory_file_asset_picker.dart +++ b/example/lib/customs/pickers/directory_file_asset_picker.dart @@ -163,7 +163,11 @@ class _DirectoryFileAssetPickerState extends State { return GestureDetector( onTap: isDisplayingDetail ? () async { - final Widget viewer = AssetPickerViewer( + final viewer = AssetPickerViewer< + File, + Directory, + FileAssetPickerViewerProvider, + FileAssetPickerViewerBuilderDelegate>( builder: FileAssetPickerViewerBuilderDelegate( currentIndex: index, previewAssets: fileList, @@ -392,7 +396,11 @@ class FileAssetPickerBuilder Animation animation, Animation secondaryAnimation, ) { - return AssetPickerViewer( + return AssetPickerViewer< + File, + Directory, + FileAssetPickerViewerProvider, + FileAssetPickerViewerBuilderDelegate>( builder: FileAssetPickerViewerBuilderDelegate( currentIndex: index ?? provider.selectedAssets.indexOf(currentAsset), @@ -418,7 +426,8 @@ class FileAssetPickerBuilder List? selectedAssets, FileAssetPickerProvider? selectorProvider, }) async { - final Widget viewer = AssetPickerViewer( + final viewer = AssetPickerViewer( builder: FileAssetPickerViewerBuilderDelegate( currentIndex: index, previewAssets: previewAssets, @@ -1210,17 +1219,18 @@ class FileAssetPickerViewerProvider extends AssetPickerViewerProvider { } class FileAssetPickerViewerBuilderDelegate - extends AssetPickerViewerBuilderDelegate { + extends AssetPickerViewerBuilderDelegate { FileAssetPickerViewerBuilderDelegate({ required super.previewAssets, required super.themeData, required super.currentIndex, super.selectedAssets, - super.selectorProvider, + this.selectorProvider, super.provider, - }) : super( - maxAssets: selectorProvider?.maxAssets, - ); + }) : super(maxAssets: selectorProvider?.maxAssets); + + final FileAssetPickerProvider? selectorProvider; late final PageController _pageController = PageController( initialPage: currentIndex, diff --git a/example/lib/customs/pickers/insta_asset_picker.dart b/example/lib/customs/pickers/insta_asset_picker.dart index 879fd87d..2568237e 100644 --- a/example/lib/customs/pickers/insta_asset_picker.dart +++ b/example/lib/customs/pickers/insta_asset_picker.dart @@ -286,7 +286,8 @@ class _InstaAssetPickerState extends State { } } -class InstaAssetPickerBuilder extends DefaultAssetPickerBuilderDelegate { +class InstaAssetPickerBuilder + extends DefaultAssetPickerBuilderDelegate { InstaAssetPickerBuilder({ required super.provider, required super.initialPermission, @@ -349,7 +350,7 @@ class InstaAssetPickerBuilder extends DefaultAssetPickerBuilderDelegate { /// Initialize [_previewAsset] with [p.selectedAssets] if not empty /// otherwise if the first item of the album Future _initializePreviewAsset( - DefaultAssetPickerProvider p, + T p, bool shouldDisplayAssets, ) async { if (_previewAsset.value != null) { @@ -487,8 +488,8 @@ class InstaAssetPickerBuilder extends DefaultAssetPickerBuilderDelegate { SizedBox( width: MediaQuery.sizeOf(context).width, height: previewHeight(context), - child: Selector>( - selector: (_, DefaultAssetPickerProvider p) => p.selectedAssets, + child: Selector>( + selector: (_, T p) => p.selectedAssets, builder: (_, List selected, __) { if (previewAsset == null && selected.isEmpty) { return loadingIndicator(context); @@ -502,7 +503,11 @@ class InstaAssetPickerBuilder extends DefaultAssetPickerBuilderDelegate { final List assets = selected.isEmpty ? [previewAsset!] : selected; - return AssetPickerViewer( + return AssetPickerViewer< + AssetEntity, + AssetPathEntity, + AssetPickerViewerProvider, + InstaAssetPickerViewerBuilder>( builder: InstaAssetPickerViewerBuilder( currentIndex: effectiveIndex == -1 ? 0 : effectiveIndex, previewAssets: assets, @@ -541,7 +546,7 @@ class InstaAssetPickerBuilder extends DefaultAssetPickerBuilderDelegate { _kPathSelectorRowHeight + MediaQuery.paddingOf(context).top; - return ChangeNotifierProvider.value( + return ChangeNotifierProvider.value( value: provider, builder: (BuildContext context, _) => ValueListenableBuilder( valueListenable: _viewerPosition, @@ -644,8 +649,8 @@ class InstaAssetPickerBuilder extends DefaultAssetPickerBuilderDelegate { Widget _buildListAlbums(BuildContext context) { appBarPreferredSize ??= appBar(context).preferredSize; - return Consumer( - builder: (BuildContext context, DefaultAssetPickerProvider provider, __) { + return Consumer( + builder: (BuildContext context, T provider, __) { if (isAppleOS(context)) { return pathEntityListWidget(context); } @@ -672,8 +677,8 @@ class InstaAssetPickerBuilder extends DefaultAssetPickerBuilderDelegate { Widget _buildGrid(BuildContext context) { appBarPreferredSize ??= appBar(context).preferredSize; - return Consumer( - builder: (BuildContext context, DefaultAssetPickerProvider p, __) { + return Consumer( + builder: (BuildContext context, T p, __) { final bool shouldDisplayAssets = p.hasAssetsToDisplay || shouldBuildSpecialItem; _initializePreviewAsset(p, shouldDisplayAssets); diff --git a/lib/src/delegates/asset_picker_builder_delegate.dart b/lib/src/delegates/asset_picker_builder_delegate.dart index 96c402c1..8c3b93d0 100644 --- a/lib/src/delegates/asset_picker_builder_delegate.dart +++ b/lib/src/delegates/asset_picker_builder_delegate.dart @@ -736,7 +736,7 @@ abstract class AssetPickerBuilderDelegate { } } -class DefaultAssetPickerBuilderDelegate +class DefaultAssetPickerBuilderDelegate extends AssetPickerBuilderDelegate { DefaultAssetPickerBuilderDelegate({ required this.provider, @@ -769,7 +769,7 @@ class DefaultAssetPickerBuilderDelegate /// [ChangeNotifier] for asset picker. /// 资源选择器状态保持 - final DefaultAssetPickerProvider provider; + final T provider; /// Thumbnail size in the grid. /// 预览时网络的缩略图大小 @@ -877,8 +877,7 @@ class DefaultAssetPickerBuilderDelegate if (selectPredicateResult == false) { return; } - final DefaultAssetPickerProvider provider = - context.read(); + final provider = context.read(); if (selected) { provider.unSelectAsset(asset); return; @@ -1010,7 +1009,7 @@ class DefaultAssetPickerBuilderDelegate int? index, AssetEntity currentAsset, ) async { - final p = context.read(); + final p = context.read(); // - When we reached the maximum select count and the asset is not selected, // do nothing. // - When the special type is WeChat Moment, pictures and videos cannot @@ -1077,7 +1076,7 @@ class DefaultAssetPickerBuilderDelegate if (current.isEmpty) { throw StateError('Previewing empty assets is not allowed. $_debugFlow'); } - final List? result = await AssetPickerViewer.pushToViewer( + final result = await AssetPickerViewer.pushToViewer( context, currentIndex: effectiveIndex, previewAssets: current, @@ -1114,8 +1113,8 @@ class DefaultAssetPickerBuilderDelegate Widget androidLayout(BuildContext context) { return AssetPickerAppBarWrapper( appBar: appBar(context), - body: Consumer( - builder: (BuildContext context, DefaultAssetPickerProvider p, _) { + body: Consumer( + builder: (BuildContext context, T p, _) { final bool shouldDisplayAssets = p.hasAssetsToDisplay || shouldBuildSpecialItem; return AnimatedSwitcher( @@ -1165,8 +1164,8 @@ class DefaultAssetPickerBuilderDelegate return Stack( children: [ Positioned.fill( - child: Consumer( - builder: (_, DefaultAssetPickerProvider p, __) { + child: Consumer( + builder: (_, p, __) { final Widget child; final bool shouldDisplayAssets = p.hasAssetsToDisplay || shouldBuildSpecialItem; @@ -1207,8 +1206,8 @@ class DefaultAssetPickerBuilderDelegate @override Widget loadingIndicator(BuildContext context) { - return Selector( - selector: (_, DefaultAssetPickerProvider p) => p.isAssetsEmpty, + return Selector( + selector: (_, T p) => p.isAssetsEmpty, builder: (BuildContext context, bool isAssetsEmpty, Widget? w) { if (loadingIndicatorBuilder != null) { return loadingIndicatorBuilder!(context, isAssetsEmpty); @@ -1226,8 +1225,8 @@ class DefaultAssetPickerBuilderDelegate Widget assetsGridBuilder(BuildContext context) { appBarPreferredSize ??= appBar(context).preferredSize; final bool gridRevert = effectiveShouldRevertGrid(context); - return Selector?>( - selector: (_, DefaultAssetPickerProvider p) => p.currentPath, + return Selector?>( + selector: (_, T p) => p.currentPath, builder: (context, wrapper, _) { // First, we need the count of the assets. int totalCount = wrapper?.assetCount ?? 0; @@ -1347,9 +1346,8 @@ class DefaultAssetPickerBuilderDelegate textDirection: effectiveGridDirection(context), child: ColoredBox( color: theme.canvasColor, - child: Selector>( - selector: (_, DefaultAssetPickerProvider p) => - p.currentAssets, + child: Selector>( + selector: (_, T p) => p.currentAssets, builder: (BuildContext context, List assets, _) { final SliverGap bottomGap = SliverGap.v( context.bottomPadding + bottomSectionHeight, @@ -1406,8 +1404,7 @@ class DefaultAssetPickerBuilderDelegate List currentAssets, { Widget? specialItem, }) { - final DefaultAssetPickerProvider p = - context.read(); + final p = context.read(); final int length = currentAssets.length; final PathWrapper? currentWrapper = p.currentPath; final AssetPathEntity? currentPathEntity = currentWrapper?.path; @@ -1477,8 +1474,8 @@ class DefaultAssetPickerBuilderDelegate return ValueListenableBuilder( valueListenable: isSwitchingPath, builder: (_, bool isSwitchingPath, Widget? child) { - return Consumer( - builder: (_, DefaultAssetPickerProvider p, __) { + return Consumer( + builder: (_, T p, __) { final bool isBanned = (!p.selectedAssets.contains(asset) && p.selectedMaximumAssets) || (isWeChatMoment && @@ -1570,9 +1567,9 @@ class DefaultAssetPickerBuilderDelegate int placeholderCount = 0, Widget? specialItem, }) { - final PathWrapper? currentWrapper = context - .select?>( - (DefaultAssetPickerProvider p) => p.currentPath, + final PathWrapper? currentWrapper = + context.select?>( + (T p) => p.currentPath, ); final AssetPathEntity? currentPathEntity = currentWrapper?.path; final int length = assets.length + placeholderCount; @@ -1661,8 +1658,8 @@ class DefaultAssetPickerBuilderDelegate /// 当有资源已选时,点击按钮将把已选资源通过路由返回。 @override Widget confirmButton(BuildContext context) { - return Consumer( - builder: (_, DefaultAssetPickerProvider p, __) { + return Consumer( + builder: (_, T p, __) { final bool isSelectedNotEmpty = p.isSelectedNotEmpty; final bool shouldAllowConfirm = isSelectedNotEmpty || p.previousSelectedAssets.isNotEmpty; @@ -1873,9 +1870,8 @@ class DefaultAssetPickerBuilderDelegate ), ), Flexible( - child: Selector>>( - selector: (_, DefaultAssetPickerProvider p) => p.paths, + child: Selector>>( + selector: (_, T p) => p.paths, builder: (_, List> paths, __) { final List> filtered = paths .where( @@ -1950,9 +1946,8 @@ class DefaultAssetPickerBuilderDelegate borderRadius: BorderRadius.circular(999), color: theme.focusColor, ), - child: Selector?>( - selector: (_, DefaultAssetPickerProvider p) => p.currentPath, + child: Selector?>( + selector: (_, T p) => p.currentPath, builder: (_, PathWrapper? p, Widget? w) { final AssetPathEntity? path = p?.path; return Row( @@ -2045,8 +2040,8 @@ class DefaultAssetPickerBuilderDelegate if (semanticsCount != null) { labelBuffer.write(': $semanticsCount'); } - return Selector?>( - selector: (_, DefaultAssetPickerProvider p) => p.currentPath, + return Selector?>( + selector: (_, T p) => p.currentPath, builder: (_, PathWrapper? currentWrapper, __) { final bool isSelected = currentWrapper?.path == pathEntity; return Semantics( @@ -2060,7 +2055,7 @@ class DefaultAssetPickerBuilderDelegate splashFactory: InkSplash.splashFactory, onTap: () { Feedback.forTap(context); - context.read().switchPath(wrapper); + context.read().switchPath(wrapper); isSwitchingPath.value = false; gridScrollController.jumpTo(0); }, @@ -2108,8 +2103,8 @@ class DefaultAssetPickerBuilderDelegate @override Widget previewButton(BuildContext context) { - return Consumer( - builder: (_, DefaultAssetPickerProvider p, Widget? child) { + return Consumer( + builder: (_, T p, Widget? child) { return ValueListenableBuilder( valueListenable: isSwitchingPath, builder: (_, bool isSwitchingPath, __) => Semantics( @@ -2121,16 +2116,15 @@ class DefaultAssetPickerBuilderDelegate ), ); }, - child: Consumer( - builder: (context, DefaultAssetPickerProvider p, __) => GestureDetector( + child: Consumer( + builder: (context, T p, __) => GestureDetector( onTap: p.isSelectedNotEmpty ? () { viewAsset(context, null, p.selectedAssets.first); } : null, - child: Selector( - selector: (_, DefaultAssetPickerProvider p) => - p.selectedDescriptions, + child: Selector( + selector: (_, T p) => p.selectedDescriptions, builder: (BuildContext c, __, ___) => Padding( padding: const EdgeInsets.symmetric(vertical: 12), child: ScaleText( @@ -2155,8 +2149,8 @@ class DefaultAssetPickerBuilderDelegate @override Widget itemBannedIndicator(BuildContext context, AssetEntity asset) { - return Consumer( - builder: (_, DefaultAssetPickerProvider p, __) { + return Consumer( + builder: (_, T p, __) { final bool isDisabled = (!p.selectedAssets.contains(asset) && p.selectedMaximumAssets) || (isWeChatMoment && @@ -2177,8 +2171,8 @@ class DefaultAssetPickerBuilderDelegate final double indicatorSize = MediaQuery.sizeOf(context).width / gridCount / 3; final Duration duration = switchingPathDuration * 0.75; - return Selector( - selector: (_, DefaultAssetPickerProvider p) => p.selectedDescriptions, + return Selector( + selector: (_, T p) => p.selectedDescriptions, builder: (BuildContext context, String descriptions, __) { final bool selected = descriptions.contains(asset.toString()); final Widget innerSelector = AnimatedContainer( @@ -2243,8 +2237,8 @@ class DefaultAssetPickerBuilderDelegate viewAsset(context, index, asset); } : null, - child: Consumer( - builder: (_, DefaultAssetPickerProvider p, __) { + child: Consumer( + builder: (_, T p, __) { final int index = p.selectedAssets.indexOf(asset); final bool selected = index != -1; return AnimatedContainer( @@ -2438,7 +2432,7 @@ class DefaultAssetPickerBuilderDelegate data: theme, child: AnnotatedRegion( value: overlayStyle, - child: CNP.value( + child: CNP.value( value: provider, builder: (BuildContext context, _) => Scaffold( backgroundColor: theme.scaffoldBackgroundColor, diff --git a/lib/src/delegates/asset_picker_viewer_builder_delegate.dart b/lib/src/delegates/asset_picker_viewer_builder_delegate.dart index a1b70703..5b319599 100644 --- a/lib/src/delegates/asset_picker_viewer_builder_delegate.dart +++ b/lib/src/delegates/asset_picker_viewer_builder_delegate.dart @@ -28,7 +28,8 @@ import '../widget/builder/fade_image_builder.dart'; import '../widget/builder/image_page_builder.dart'; import '../widget/builder/video_page_builder.dart'; -abstract class AssetPickerViewerBuilderDelegate { +abstract class AssetPickerViewerBuilderDelegate> { AssetPickerViewerBuilderDelegate({ required this.previewAssets, required this.themeData, @@ -44,7 +45,7 @@ abstract class AssetPickerViewerBuilderDelegate { /// [ChangeNotifier] for photo selector viewer. /// 资源预览器的状态保持 - final AssetPickerViewerProvider? provider; + final Provider? provider; /// Assets provided to preview. /// 提供预览的资源 @@ -89,7 +90,8 @@ abstract class AssetPickerViewerBuilderDelegate { /// The [State] for a viewer. /// 预览器的状态实例 - late AssetPickerViewerState viewerState; + late AssetPickerViewerState> viewerState; /// [AnimationController] for double tap animation. /// 双击缩放的动画控制器 @@ -160,7 +162,9 @@ abstract class AssetPickerViewerBuilderDelegate { /// 当预览器调用 [State.initState] 时注册 [State]。 @mustCallSuper void initStateAndTicker( - covariant AssetPickerViewerState state, + covariant AssetPickerViewerState> + state, TickerProvider v, // TODO(Alex): Remove this in the next major version. ) { initAnimations(state); @@ -175,9 +179,15 @@ abstract class AssetPickerViewerBuilderDelegate { /// a new delegate and only calling [State.didUpdateWidget] at the moment. @mustCallSuper void didUpdateViewer( - covariant AssetPickerViewerState state, - covariant AssetPickerViewer oldWidget, - covariant AssetPickerViewer newWidget, + covariant AssetPickerViewerState> + state, + covariant AssetPickerViewer> + oldWidget, + covariant AssetPickerViewer> + newWidget, ) { // Widgets are useless in the default delegate. initAnimations(state); @@ -201,7 +211,11 @@ abstract class AssetPickerViewerBuilderDelegate { /// Initialize animations related to the zooming preview. /// 为缩放预览初始化动画 - void initAnimations(covariant AssetPickerViewerState state) { + void initAnimations( + covariant AssetPickerViewerState> + state, + ) { viewerState = state; doubleTapAnimationController = AnimationController( duration: const Duration(milliseconds: 200), @@ -365,8 +379,10 @@ abstract class AssetPickerViewerBuilderDelegate { Widget build(BuildContext context); } -class DefaultAssetPickerViewerBuilderDelegate - extends AssetPickerViewerBuilderDelegate { +class DefaultAssetPickerViewerBuilderDelegate< + T extends AssetPickerViewerProvider, + P extends DefaultAssetPickerProvider> + extends AssetPickerViewerBuilderDelegate { DefaultAssetPickerViewerBuilderDelegate({ required super.currentIndex, required super.previewAssets, @@ -442,12 +458,8 @@ class DefaultAssetPickerViewerBuilderDelegate shouldReversePreview ? previewAssets.length - index - 1 : index, ); return MergeSemantics( - child: Consumer?>( - builder: ( - BuildContext c, - AssetPickerViewerProvider? p, - Widget? w, - ) { + child: Consumer( + builder: (context, T? p, child) { final bool isSelected = (p?.currentlySelectedAssets ?? selectedAssets)?.contains(asset) ?? false; @@ -595,7 +607,7 @@ class DefaultAssetPickerViewerBuilderDelegate height: context.bottomPadding + bottomDetailHeight, child: child!, ), - child: CNP?>.value( + child: CNP.value( value: provider, child: Column( mainAxisSize: MainAxisSize.min, @@ -703,10 +715,8 @@ class DefaultAssetPickerViewerBuilderDelegate onTap: () { onTap(asset); }, - child: Selector?, - List?>( - selector: (_, AssetPickerViewerProvider? p) => - p?.currentlySelectedAssets, + child: Selector?>( + selector: (_, T? p) => p?.currentlySelectedAssets, child: item, builder: ( _, @@ -809,10 +819,10 @@ class DefaultAssetPickerViewerBuilderDelegate /// 资源选择器将识别并一同返回。 @override Widget confirmButton(BuildContext context) { - return CNP?>.value( + return CNP.value( value: provider, - child: Consumer?>( - builder: (_, AssetPickerViewerProvider? provider, __) { + child: Consumer( + builder: (context, T? provider, __) { assert( isWeChatMoment || provider != null, 'Viewer provider must not be null ' @@ -945,7 +955,7 @@ class DefaultAssetPickerViewerBuilderDelegate @override Widget selectButton(BuildContext context) { - return CNP>.value( + return CNP.value( value: provider!, builder: (_, Widget? w) => StreamBuilder( initialData: currentIndex, @@ -969,8 +979,7 @@ class DefaultAssetPickerViewerBuilderDelegate ); } final asset = previewAssets.elementAt(assetIndex); - return Selector, - List>( + return Selector>( selector: (_, p) => p.currentlySelectedAssets, builder: (context, assets, _) { final bool isSelected = assets.contains(asset); diff --git a/lib/src/provider/asset_picker_viewer_provider.dart b/lib/src/provider/asset_picker_viewer_provider.dart index c8b3c177..62d1d0ad 100644 --- a/lib/src/provider/asset_picker_viewer_provider.dart +++ b/lib/src/provider/asset_picker_viewer_provider.dart @@ -8,14 +8,14 @@ import '../constants/constants.dart'; /// [ChangeNotifier] for assets picker viewer. /// 资源选择查看器的 provider model. -class AssetPickerViewerProvider extends ChangeNotifier { - /// Copy selected assets for editing when constructing. - /// 构造时深拷贝已选择的资源集合,用于后续编辑。 +class AssetPickerViewerProvider extends ChangeNotifier { AssetPickerViewerProvider( - List? assets, { + List? assets, { this.maxAssets = defaultMaxAssetsCount, }) : assert(maxAssets > 0, 'maxAssets must be greater than 0.') { - _currentlySelectedAssets = (assets ?? []).toList(); + // Copy selected assets for editing when constructing. + // 构造时深拷贝已选择的资源集合,用于后续编辑。 + _currentlySelectedAssets = (assets ?? []).toList(); } /// Maximum count for asset selection. @@ -24,11 +24,11 @@ class AssetPickerViewerProvider extends ChangeNotifier { /// Selected assets in the viewer. /// 查看器中已选择的资源 - late List _currentlySelectedAssets; + late List _currentlySelectedAssets; - List get currentlySelectedAssets => _currentlySelectedAssets; + List get currentlySelectedAssets => _currentlySelectedAssets; - set currentlySelectedAssets(List value) { + set currentlySelectedAssets(List value) { if (value == _currentlySelectedAssets) { return; } @@ -41,33 +41,33 @@ class AssetPickerViewerProvider extends ChangeNotifier { /// Select asset. /// 选中资源 - void selectAsset(A item) { + void selectAsset(Asset item) { if (currentlySelectedAssets.length == maxAssets || currentlySelectedAssets.contains(item)) { return; } - final List newList = _currentlySelectedAssets.toList()..add(item); + final List newList = _currentlySelectedAssets.toList()..add(item); currentlySelectedAssets = newList; } /// Un-select asset. /// 取消选中资源 - void unSelectAsset(A item) { + void unSelectAsset(Asset item) { if (currentlySelectedAssets.isEmpty || !currentlySelectedAssets.contains(item)) { return; } - final List newList = _currentlySelectedAssets.toList()..remove(item); + final List newList = _currentlySelectedAssets.toList()..remove(item); currentlySelectedAssets = newList; } @Deprecated('Use selectAsset instead. This will be removed in 10.0.0') - void selectAssetEntity(A entity) { + void selectAssetEntity(Asset entity) { selectAsset(entity); } @Deprecated('Use unSelectAsset instead. This will be removed in 10.0.0') - void unselectAssetEntity(A entity) { + void unselectAssetEntity(Asset entity) { unSelectAsset(entity); } } diff --git a/lib/src/widget/asset_picker_viewer.dart b/lib/src/widget/asset_picker_viewer.dart index fe30d20e..9498a09d 100644 --- a/lib/src/widget/asset_picker_viewer.dart +++ b/lib/src/widget/asset_picker_viewer.dart @@ -13,44 +13,51 @@ import '../constants/typedefs.dart'; import '../delegates/asset_picker_viewer_builder_delegate.dart'; import '../provider/asset_picker_provider.dart'; import '../provider/asset_picker_viewer_provider.dart'; -import 'asset_picker.dart'; -class AssetPickerViewer extends StatefulWidget { +class AssetPickerViewer< + Asset, + Path, + Provider extends AssetPickerViewerProvider, + Delegate extends AssetPickerViewerBuilderDelegate> extends StatefulWidget { const AssetPickerViewer({ super.key, required this.builder, }); - final AssetPickerViewerBuilderDelegate builder; + final Delegate builder; @override - AssetPickerViewerState createState() => - AssetPickerViewerState(); + AssetPickerViewerState createState() => + AssetPickerViewerState(); /// Static method to push with the navigator. /// 跳转至选择预览的静态方法 - static Future?> pushToViewer( + static Future?> + pushToViewer

( BuildContext context, { int currentIndex = 0, required List previewAssets, required ThemeData themeData, - DefaultAssetPickerProvider? selectorProvider, + P? selectorProvider, ThumbnailSize? previewThumbnailSize, List? selectedAssets, SpecialPickerType? specialPickerType, int? maxAssets, bool shouldReversePreview = false, AssetSelectPredicate? selectPredicate, - PermissionRequestOption permissionRequestOption = - const PermissionRequestOption(), bool shouldAutoplayPreview = false, }) async { if (previewAssets.isEmpty) { throw StateError('Previewing empty assets is not allowed.'); } - await AssetPicker.permissionCheck(requestOption: permissionRequestOption); - final Widget viewer = AssetPickerViewer( - builder: DefaultAssetPickerViewerBuilderDelegate( + final viewer = AssetPickerViewer< + AssetEntity, + AssetPathEntity, + AssetPickerViewerProvider, + DefaultAssetPickerViewerBuilderDelegate>( + builder: DefaultAssetPickerViewerBuilderDelegate< + AssetPickerViewerProvider, P>( currentIndex: currentIndex, previewAssets: previewAssets, provider: selectedAssets != null @@ -72,44 +79,49 @@ class AssetPickerViewer extends StatefulWidget { shouldAutoplayPreview: shouldAutoplayPreview, ), ); - final PageRouteBuilder> pageRoute = - PageRouteBuilder>( + final pageRoute = PageRouteBuilder>( pageBuilder: (_, __, ___) => viewer, transitionsBuilder: (_, Animation animation, __, Widget child) { return FadeTransition(opacity: animation, child: child); }, ); - final List? result = + final result = await Navigator.maybeOf(context)?.push>(pageRoute); return result; } /// Call the viewer with provided delegate and provider. /// 通过指定的 [delegate] 调用查看器 - static Future?> pushToViewerWithDelegate( + static Future?> pushToViewerWithDelegate< + A, + P, + Provider extends AssetPickerViewerProvider, + Delegate extends AssetPickerViewerBuilderDelegate>( BuildContext context, { - required AssetPickerViewerBuilderDelegate delegate, - PermissionRequestOption permissionRequestOption = - const PermissionRequestOption(), + required Delegate delegate, }) async { - await AssetPicker.permissionCheck(requestOption: permissionRequestOption); - final Widget viewer = AssetPickerViewer(builder: delegate); - final PageRouteBuilder> pageRoute = PageRouteBuilder>( + final viewer = AssetPickerViewer( + builder: delegate, + ); + final pageRoute = PageRouteBuilder>( pageBuilder: (_, __, ___) => viewer, transitionsBuilder: (_, Animation animation, __, Widget child) { return FadeTransition(opacity: animation, child: child); }, ); - final List? result = - await Navigator.maybeOf(context)?.push>(pageRoute); + final result = await Navigator.maybeOf(context)?.push>(pageRoute); return result; } } -class AssetPickerViewerState - extends State> +class AssetPickerViewerState< + A, + P, + Provider extends AssetPickerViewerProvider, + Delegate extends AssetPickerViewerBuilderDelegate> + extends State> with TickerProviderStateMixin { - AssetPickerViewerBuilderDelegate get builder => widget.builder; + Delegate get builder => widget.builder; @override void initState() { @@ -118,7 +130,9 @@ class AssetPickerViewerState } @override - void didUpdateWidget(covariant AssetPickerViewer oldWidget) { + void didUpdateWidget( + covariant AssetPickerViewer oldWidget, + ) { super.didUpdateWidget(oldWidget); builder.didUpdateViewer(this, oldWidget, widget); } diff --git a/lib/src/widget/builder/image_page_builder.dart b/lib/src/widget/builder/image_page_builder.dart index 8636cbd1..27015d8d 100644 --- a/lib/src/widget/builder/image_page_builder.dart +++ b/lib/src/widget/builder/image_page_builder.dart @@ -16,6 +16,7 @@ import 'package:wechat_picker_library/wechat_picker_library.dart'; import '../../constants/constants.dart'; import '../../delegates/asset_picker_text_delegate.dart'; import '../../delegates/asset_picker_viewer_builder_delegate.dart'; +import '../../provider/asset_picker_viewer_provider.dart'; class ImagePageBuilder extends StatefulWidget { const ImagePageBuilder({ @@ -30,7 +31,8 @@ class ImagePageBuilder extends StatefulWidget { /// 展示的资源 final AssetEntity asset; - final AssetPickerViewerBuilderDelegate delegate; + final AssetPickerViewerBuilderDelegate> delegate; final ThumbnailSize? previewThumbnailSize; diff --git a/lib/src/widget/builder/video_page_builder.dart b/lib/src/widget/builder/video_page_builder.dart index 396105de..0c571d98 100644 --- a/lib/src/widget/builder/video_page_builder.dart +++ b/lib/src/widget/builder/video_page_builder.dart @@ -12,6 +12,7 @@ import 'package:wechat_picker_library/wechat_picker_library.dart'; import '../../constants/constants.dart'; import '../../delegates/asset_picker_viewer_builder_delegate.dart'; import '../../internals/singleton.dart'; +import '../../provider/asset_picker_viewer_provider.dart'; class VideoPageBuilder extends StatefulWidget { const VideoPageBuilder({ @@ -26,7 +27,8 @@ class VideoPageBuilder extends StatefulWidget { /// 展示的资源 final AssetEntity asset; - final AssetPickerViewerBuilderDelegate delegate; + final AssetPickerViewerBuilderDelegate> delegate; /// Only previewing one video and with the [SpecialPickerType.wechatMoment]. /// 是否处于 [SpecialPickerType.wechatMoment] 且只有一个视频 From 4a1366586f63daa83994402cccfb6225f83c41d8 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Thu, 10 Oct 2024 00:05:54 +0800 Subject: [PATCH 04/29] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Correct=20delegate?= =?UTF-8?q?=20type=20reference=20with=20`AssetPicker`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/lib/constants/picker_method.dart | 7 +++-- .../pickers/multi_tabs_assets_picker.dart | 5 +++- .../asset_picker_builder_delegate.dart | 21 +++++++-------- lib/src/delegates/asset_picker_delegate.dart | 18 ++++++++----- lib/src/widget/asset_picker.dart | 26 ++++++++++++------- test/providers_test.dart | 16 ++++++------ test/test_utils.dart | 3 ++- 7 files changed, 55 insertions(+), 41 deletions(-) diff --git a/example/lib/constants/picker_method.dart b/example/lib/constants/picker_method.dart index 00857410..6636452d 100644 --- a/example/lib/constants/picker_method.dart +++ b/example/lib/constants/picker_method.dart @@ -207,10 +207,9 @@ class PickMethod { return; } final picker = context.findAncestorWidgetOfExactType< - AssetPicker>()!; - final builder = - picker.builder as DefaultAssetPickerBuilderDelegate; - final p = builder.provider; + AssetPicker>()!; + final p = picker.builder.provider; await p.switchPath( PathWrapper( path: diff --git a/example/lib/customs/pickers/multi_tabs_assets_picker.dart b/example/lib/customs/pickers/multi_tabs_assets_picker.dart index f60689e6..d15a8e3f 100644 --- a/example/lib/customs/pickers/multi_tabs_assets_picker.dart +++ b/example/lib/customs/pickers/multi_tabs_assets_picker.dart @@ -279,7 +279,10 @@ class MultiTabAssetPickerBuilder extends DefaultAssetPickerBuilderDelegate { late final TabController _tabController; @override - void initState(AssetPickerState state) { + void initState( + AssetPickerState + state, + ) { super.initState(state); _tabController = TabController(length: 3, vsync: state); } diff --git a/lib/src/delegates/asset_picker_builder_delegate.dart b/lib/src/delegates/asset_picker_builder_delegate.dart index 8c3b93d0..43621f63 100644 --- a/lib/src/delegates/asset_picker_builder_delegate.dart +++ b/lib/src/delegates/asset_picker_builder_delegate.dart @@ -121,14 +121,13 @@ abstract class AssetPickerBuilderDelegate { final LimitedPermissionOverlayPredicate? limitedPermissionOverlayPredicate; /// {@macro wechat_assets_picker.PathNameBuilder} - final PathNameBuilder? pathNameBuilder; + final PathNameBuilder? pathNameBuilder; /// {@macro wechat_assets_picker.AssetsChangeCallback} - final AssetsChangeCallback? assetsChangeCallback; + final AssetsChangeCallback? assetsChangeCallback; /// {@macro wechat_assets_picker.AssetsChangeRefreshPredicate} - final AssetsChangeRefreshPredicate? - assetsChangeRefreshPredicate; + final AssetsChangeRefreshPredicate? assetsChangeRefreshPredicate; /// [ThemeData] for the picker. /// 选择器使用的主题 @@ -229,7 +228,11 @@ abstract class AssetPickerBuilderDelegate { /// Keep a `initState` method to sync with [State]. /// 保留一个 `initState` 方法与 [State] 同步。 @mustCallSuper - void initState(AssetPickerState state) {} + void initState( + covariant AssetPickerState> + state, + ) {} /// Keep a `dispose` method to sync with [State]. /// 保留一个 `dispose` 方法与 [State] 同步。 @@ -395,11 +398,7 @@ abstract class AssetPickerBuilderDelegate { /// Loading indicator. /// 加载指示器 /// - /// Subclasses need to implement this due to the generic type limitation, and - /// not all delegates use [AssetPickerProvider]. - /// - /// See also: - /// - [DefaultAssetPickerBuilderDelegate.loadingIndicator] as an example. + /// See [DefaultAssetPickerBuilderDelegate.loadingIndicator] as an example. Widget loadingIndicator(BuildContext context); /// GIF image type indicator. @@ -1653,7 +1652,7 @@ class DefaultAssetPickerBuilderDelegate ); } - /// It'll pop with [AssetPickerProvider.selectedAssets] + /// It'll pop with [T.selectedAssets] /// when there are any assets were chosen. /// 当有资源已选时,点击按钮将把已选资源通过路由返回。 @override diff --git a/lib/src/delegates/asset_picker_delegate.dart b/lib/src/delegates/asset_picker_delegate.dart index 3f788b3d..bee83a1b 100644 --- a/lib/src/delegates/asset_picker_delegate.dart +++ b/lib/src/delegates/asset_picker_delegate.dart @@ -94,7 +94,8 @@ class AssetPickerDelegate { filterOptions: pickerConfig.filterOptions, initializeDelayDuration: route.transitionDuration, ); - final Widget picker = AssetPicker( + final picker = AssetPicker( key: key, permissionRequestOption: permissionRequestOption, builder: DefaultAssetPickerBuilderDelegate( @@ -148,10 +149,13 @@ class AssetPickerDelegate { /// * [AssetPickerBuilderDelegate] for how to customize/override widgets /// during the picking process. /// {@endtemplate} - Future?> pickAssetsWithDelegate>( + Future?> pickAssetsWithDelegate< + Asset, + Path, + PickerProvider extends AssetPickerProvider, + Delegate extends AssetPickerBuilderDelegate>( BuildContext context, { - required AssetPickerBuilderDelegate delegate, + required Delegate delegate, PermissionRequestOption permissionRequestOption = const PermissionRequestOption(), Key? key, @@ -159,15 +163,15 @@ class AssetPickerDelegate { AssetPickerPageRouteBuilder>? pageRouteBuilder, }) async { await permissionCheck(requestOption: permissionRequestOption); - final Widget picker = AssetPicker( + final picker = AssetPicker( key: key, permissionRequestOption: permissionRequestOption, builder: delegate, ); - final List? result = await Navigator.maybeOf( + final result = await Navigator.maybeOf( context, rootNavigator: useRootNavigator, - )?.push>( + )?.push( pageRouteBuilder?.call(picker) ?? AssetPickerPageRoute>(builder: (_) => picker), ); diff --git a/lib/src/widget/asset_picker.dart b/lib/src/widget/asset_picker.dart index ec22a02d..4db70dbd 100644 --- a/lib/src/widget/asset_picker.dart +++ b/lib/src/widget/asset_picker.dart @@ -18,7 +18,9 @@ import 'asset_picker_page_route.dart'; AssetPickerDelegate _pickerDelegate = const AssetPickerDelegate(); -class AssetPicker extends StatefulWidget { +class AssetPicker> + extends StatefulWidget { const AssetPicker({ super.key, required this.permissionRequestOption, @@ -26,7 +28,7 @@ class AssetPicker extends StatefulWidget { }); final PermissionRequestOption permissionRequestOption; - final AssetPickerBuilderDelegate builder; + final Delegate builder; /// Provide another [AssetPickerDelegate] which override with /// custom methods during handling the picking, @@ -66,17 +68,21 @@ class AssetPicker extends StatefulWidget { } /// {@macro wechat_assets_picker.delegates.AssetPickerDelegate.pickAssetsWithDelegate} - static Future?> pickAssetsWithDelegate>( + static Future?> pickAssetsWithDelegate< + Asset, + Path, + PickerProvider extends AssetPickerProvider, + Delegate extends AssetPickerBuilderDelegate>( BuildContext context, { - required AssetPickerBuilderDelegate delegate, + required Delegate delegate, PermissionRequestOption permissionRequestOption = const PermissionRequestOption(), Key? key, AssetPickerPageRouteBuilder>? pageRouteBuilder, bool useRootNavigator = true, }) { - return _pickerDelegate.pickAssetsWithDelegate( + return _pickerDelegate + .pickAssetsWithDelegate( context, key: key, delegate: delegate, @@ -102,11 +108,13 @@ class AssetPicker extends StatefulWidget { } @override - AssetPickerState createState() => - AssetPickerState(); + AssetPickerState createState() => + AssetPickerState(); } -class AssetPickerState extends State> +class AssetPickerState> + extends State> with TickerProviderStateMixin, WidgetsBindingObserver { Completer? permissionStateLock; diff --git a/test/providers_test.dart b/test/providers_test.dart index 33b0eef1..b1228386 100644 --- a/test/providers_test.dart +++ b/test/providers_test.dart @@ -23,15 +23,15 @@ void main() async { ); await tester.tap(defaultButtonFinder); await tester.pumpAndSettle(); - final Finder pickerFinder = find.byType( - AssetPicker, - ); - final AssetPicker picker = tester.widget( - pickerFinder, + final picker = tester.widget< + AssetPicker>( + find.byType( + AssetPicker, + ), ); - final DefaultAssetPickerProvider provider = - (picker.builder as DefaultAssetPickerBuilderDelegate).provider; - expect(provider, isA()); + final provider = picker.builder.provider; await tester.tap(find.widgetWithIcon(IconButton, Icons.close)); await tester.pumpAndSettle(); expect( diff --git a/test/test_utils.dart b/test/test_utils.dart index c5d0a670..cda469b6 100644 --- a/test/test_utils.dart +++ b/test/test_utils.dart @@ -120,7 +120,8 @@ class TestAssetPickerDelegate extends AssetPickerDelegate { ) ..hasAssetsToDisplay = true ..totalAssetsCount = 1; - final Widget picker = AssetPicker( + final picker = AssetPicker( key: key, permissionRequestOption: permissionRequestOption, builder: DefaultAssetPickerBuilderDelegate( From c5c3a1865bb3304dca39a81e71f1fab1822835a0 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Thu, 10 Oct 2024 00:06:12 +0800 Subject: [PATCH 05/29] =?UTF-8?q?=F0=9F=9A=80=20Expose=20item=20builders?= =?UTF-8?q?=20entry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/builder.dart | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 lib/builder.dart diff --git a/lib/builder.dart b/lib/builder.dart new file mode 100644 index 00000000..6b862abe --- /dev/null +++ b/lib/builder.dart @@ -0,0 +1,8 @@ +// Copyright 2019 The FlutterCandies author. All rights reserved. +// Use of this source code is governed by an Apache license that can be found +// in the LICENSE file. + +export 'src/widget/builder/asset_entity_grid_item_builder.dart'; +export 'src/widget/builder/audio_page_builder.dart'; +export 'src/widget/builder/image_page_builder.dart'; +export 'src/widget/builder/video_page_builder.dart'; From 4001e904d7daed6ef8471752cac439e35e1798bf Mon Sep 17 00:00:00 2001 From: Alex Li Date: Thu, 10 Oct 2024 13:21:02 +0800 Subject: [PATCH 06/29] =?UTF-8?q?=F0=9F=90=9B=20Fix=20`AssetPickerViewerSt?= =?UTF-8?q?ate`=20type=20inference=20in=20the=20viewer=20builder=20delegat?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../asset_picker_viewer_builder_delegate.dart | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/lib/src/delegates/asset_picker_viewer_builder_delegate.dart b/lib/src/delegates/asset_picker_viewer_builder_delegate.dart index 5b319599..64205d6c 100644 --- a/lib/src/delegates/asset_picker_viewer_builder_delegate.dart +++ b/lib/src/delegates/asset_picker_viewer_builder_delegate.dart @@ -90,8 +90,7 @@ abstract class AssetPickerViewerBuilderDelegate> viewerState; + late AssetPickerViewerState viewerState; /// [AnimationController] for double tap animation. /// 双击缩放的动画控制器 @@ -162,9 +161,7 @@ abstract class AssetPickerViewerBuilderDelegate> - state, + covariant AssetPickerViewerState state, TickerProvider v, // TODO(Alex): Remove this in the next major version. ) { initAnimations(state); @@ -179,15 +176,9 @@ abstract class AssetPickerViewerBuilderDelegate> - state, - covariant AssetPickerViewer> - oldWidget, - covariant AssetPickerViewer> - newWidget, + covariant AssetPickerViewerState state, + covariant AssetPickerViewer oldWidget, + covariant AssetPickerViewer newWidget, ) { // Widgets are useless in the default delegate. initAnimations(state); @@ -212,9 +203,7 @@ abstract class AssetPickerViewerBuilderDelegate> - state, + covariant AssetPickerViewerState state, ) { viewerState = state; doubleTapAnimationController = AnimationController( From 4e0740dfbff79c5b8086096a4cc1b81258390779 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Thu, 10 Oct 2024 13:22:13 +0800 Subject: [PATCH 07/29] =?UTF-8?q?=F0=9F=94=A5=20Remove=20redundant=20`Tick?= =?UTF-8?q?erProvider`=20of=20viewer=20builder=20delegate=20`initState`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/delegates/asset_picker_viewer_builder_delegate.dart | 3 +-- lib/src/widget/asset_picker_viewer.dart | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/src/delegates/asset_picker_viewer_builder_delegate.dart b/lib/src/delegates/asset_picker_viewer_builder_delegate.dart index 64205d6c..76709d63 100644 --- a/lib/src/delegates/asset_picker_viewer_builder_delegate.dart +++ b/lib/src/delegates/asset_picker_viewer_builder_delegate.dart @@ -160,9 +160,8 @@ abstract class AssetPickerViewerBuilderDelegate Date: Thu, 10 Oct 2024 15:50:06 +0800 Subject: [PATCH 08/29] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Serve=20T=20and=20P?= =?UTF-8?q?=20in=20the=20viewer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../asset_picker_viewer_builder_delegate.dart | 381 +++++++++--------- 1 file changed, 188 insertions(+), 193 deletions(-) diff --git a/lib/src/delegates/asset_picker_viewer_builder_delegate.dart b/lib/src/delegates/asset_picker_viewer_builder_delegate.dart index 76709d63..482c94b2 100644 --- a/lib/src/delegates/asset_picker_viewer_builder_delegate.dart +++ b/lib/src/delegates/asset_picker_viewer_builder_delegate.dart @@ -595,47 +595,43 @@ class DefaultAssetPickerViewerBuilderDelegate< height: context.bottomPadding + bottomDetailHeight, child: child!, ), - child: CNP.value( - value: provider, - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.end, - children: [ - if (provider != null) - ValueListenableBuilder( - valueListenable: selectedNotifier, - builder: (_, int count, __) => Container( - width: count > 0 ? double.maxFinite : 0, - height: bottomPreviewHeight, - color: backgroundColor, - child: ListView.builder( - controller: previewingListController, - scrollDirection: Axis.horizontal, - padding: const EdgeInsets.symmetric(horizontal: 5.0), - physics: const ClampingScrollPhysics(), - itemCount: count, - itemBuilder: bottomDetailItemBuilder, - ), - ), - ), - Container( - height: bottomBarHeight + context.bottomPadding, - padding: const EdgeInsets.symmetric(horizontal: 20.0) - .copyWith(bottom: context.bottomPadding), - decoration: BoxDecoration( - border: Border(top: BorderSide(color: themeData.canvasColor)), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + if (provider != null) + ValueListenableBuilder( + valueListenable: selectedNotifier, + builder: (_, int count, __) => Container( + width: count > 0 ? double.maxFinite : 0, + height: bottomPreviewHeight, color: backgroundColor, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - if (provider != null || isWeChatMoment) - confirmButton(context), - ], + child: ListView.builder( + controller: previewingListController, + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: 5.0), + physics: const ClampingScrollPhysics(), + itemCount: count, + itemBuilder: bottomDetailItemBuilder, + ), ), ), - ], - ), + Container( + height: bottomBarHeight + context.bottomPadding, + padding: const EdgeInsets.symmetric(horizontal: 20.0) + .copyWith(bottom: context.bottomPadding), + decoration: BoxDecoration( + border: Border(top: BorderSide(color: themeData.canvasColor)), + color: backgroundColor, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + if (provider != null || isWeChatMoment) confirmButton(context), + ], + ), + ), + ], ), ); } @@ -807,86 +803,82 @@ class DefaultAssetPickerViewerBuilderDelegate< /// 资源选择器将识别并一同返回。 @override Widget confirmButton(BuildContext context) { - return CNP.value( - value: provider, - child: Consumer( - builder: (context, T? provider, __) { - assert( - isWeChatMoment || provider != null, - 'Viewer provider must not be null ' - 'when the special type is not WeChat moment.', - ); - Future onPressed() async { - if (isWeChatMoment && hasVideo) { - Navigator.maybeOf(context)?.pop([currentAsset]); - return; - } - if (provider!.isSelectedNotEmpty) { - Navigator.maybeOf(context)?.pop(provider.currentlySelectedAssets); - return; - } - if (await onChangingSelected(context, currentAsset, false)) { - Navigator.maybeOf(context)?.pop( - selectedAssets ?? [currentAsset], - ); - } + return Consumer( + builder: (context, T? provider, __) { + assert( + isWeChatMoment || provider != null, + 'Viewer provider must not be null ' + 'when the special type is not WeChat moment.', + ); + Future onPressed() async { + if (isWeChatMoment && hasVideo) { + Navigator.maybeOf(context)?.pop([currentAsset]); + return; + } + if (provider!.isSelectedNotEmpty) { + Navigator.maybeOf(context)?.pop(provider.currentlySelectedAssets); + return; + } + if (await onChangingSelected(context, currentAsset, false)) { + Navigator.maybeOf(context)?.pop( + selectedAssets ?? [currentAsset], + ); } + } - String buildText() { - if (isWeChatMoment && hasVideo) { - return textDelegate.confirm; - } - if (provider!.isSelectedNotEmpty) { - return '${textDelegate.confirm}' - ' (${provider.currentlySelectedAssets.length}' - '/' - '${selectorProvider!.maxAssets})'; - } + String buildText() { + if (isWeChatMoment && hasVideo) { return textDelegate.confirm; } - - final isButtonEnabled = provider == null || - previewAssets.isEmpty || - (selectedAssets?.isNotEmpty ?? false); - return MaterialButton( - minWidth: - (isWeChatMoment && hasVideo) || provider!.isSelectedNotEmpty - ? 48 - : 20, - height: 32, - padding: const EdgeInsets.symmetric(horizontal: 12), - color: themeData.colorScheme.secondary, - disabledColor: themeData.splashColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(3), + if (provider!.isSelectedNotEmpty) { + return '${textDelegate.confirm}' + ' (${provider.currentlySelectedAssets.length}' + '/' + '${selectorProvider!.maxAssets})'; + } + return textDelegate.confirm; + } + + final isButtonEnabled = provider == null || + previewAssets.isEmpty || + (selectedAssets?.isNotEmpty ?? false); + return MaterialButton( + minWidth: (isWeChatMoment && hasVideo) || provider!.isSelectedNotEmpty + ? 48 + : 20, + height: 32, + padding: const EdgeInsets.symmetric(horizontal: 12), + color: themeData.colorScheme.secondary, + disabledColor: themeData.splashColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(3), + ), + onPressed: isButtonEnabled ? onPressed : null, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + child: ScaleText( + buildText(), + style: TextStyle( + color: themeData.textTheme.bodyLarge?.color, + fontSize: 17, + fontWeight: FontWeight.normal, ), - onPressed: isButtonEnabled ? onPressed : null, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - child: ScaleText( - buildText(), - style: TextStyle( - color: themeData.textTheme.bodyLarge?.color, - fontSize: 17, - fontWeight: FontWeight.normal, - ), - overflow: TextOverflow.fade, - softWrap: false, - semanticsLabel: () { - if (isWeChatMoment && hasVideo) { - return semanticsTextDelegate.confirm; - } - if (provider!.isSelectedNotEmpty) { - return '${semanticsTextDelegate.confirm}' - ' (${provider.currentlySelectedAssets.length}' - '/' - '${selectorProvider!.maxAssets})'; - } + overflow: TextOverflow.fade, + softWrap: false, + semanticsLabel: () { + if (isWeChatMoment && hasVideo) { return semanticsTextDelegate.confirm; - }(), - ), - ); - }, - ), + } + if (provider!.isSelectedNotEmpty) { + return '${semanticsTextDelegate.confirm}' + ' (${provider.currentlySelectedAssets.length}' + '/' + '${selectorProvider!.maxAssets})'; + } + return semanticsTextDelegate.confirm; + }(), + ), + ); + }, ); } @@ -943,62 +935,59 @@ class DefaultAssetPickerViewerBuilderDelegate< @override Widget selectButton(BuildContext context) { - return CNP.value( - value: provider!, - builder: (_, Widget? w) => StreamBuilder( - initialData: currentIndex, - stream: pageStreamController.stream, - builder: (_, s) { - final index = s.data!; - final assetIndex = - shouldReversePreview ? previewAssets.length - index - 1 : index; - if (assetIndex < 0) { - throw IndexError.withLength( - assetIndex, - previewAssets.length, - indexable: previewAssets, - name: 'selectButton.assetIndex', - message: 'previewReversed: $shouldReversePreview\n' - 'stream.index: $index\n' - 'selectedAssets.length: ${selectedAssets?.length}\n' - 'previewAssets.length: ${previewAssets.length}\n' - 'currentIndex: $currentIndex\n' - 'maxAssets: $maxAssets', - ); - } - final asset = previewAssets.elementAt(assetIndex); - return Selector>( - selector: (_, p) => p.currentlySelectedAssets, - builder: (context, assets, _) { - final bool isSelected = assets.contains(asset); - return Semantics( - selected: isSelected, - label: semanticsTextDelegate.select, - onTap: () { - onChangingSelected(context, asset, isSelected); - }, - onTapHint: semanticsTextDelegate.select, - excludeSemantics: true, - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - if (isAppleOS(context)) - _appleOSSelectButton(context, isSelected, asset) - else - _androidSelectButton(context, isSelected, asset), - if (!isAppleOS(context)) - ScaleText( - textDelegate.select, - style: const TextStyle(fontSize: 17, height: 1.2), - semanticsLabel: semanticsTextDelegate.select, - ), - ], - ), - ); - }, + return StreamBuilder( + initialData: currentIndex, + stream: pageStreamController.stream, + builder: (_, s) { + final index = s.data!; + final assetIndex = + shouldReversePreview ? previewAssets.length - index - 1 : index; + if (assetIndex < 0) { + throw IndexError.withLength( + assetIndex, + previewAssets.length, + indexable: previewAssets, + name: 'selectButton.assetIndex', + message: 'previewReversed: $shouldReversePreview\n' + 'stream.index: $index\n' + 'selectedAssets.length: ${selectedAssets?.length}\n' + 'previewAssets.length: ${previewAssets.length}\n' + 'currentIndex: $currentIndex\n' + 'maxAssets: $maxAssets', ); - }, - ), + } + final asset = previewAssets.elementAt(assetIndex); + return Selector>( + selector: (_, p) => p.currentlySelectedAssets, + builder: (context, assets, _) { + final bool isSelected = assets.contains(asset); + return Semantics( + selected: isSelected, + label: semanticsTextDelegate.select, + onTap: () { + onChangingSelected(context, asset, isSelected); + }, + onTapHint: semanticsTextDelegate.select, + excludeSemantics: true, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + if (isAppleOS(context)) + _appleOSSelectButton(context, isSelected, asset) + else + _androidSelectButton(context, isSelected, asset), + if (!isAppleOS(context)) + ScaleText( + textDelegate.select, + style: const TextStyle(fontSize: 17, height: 1.2), + semanticsLabel: semanticsTextDelegate.select, + ), + ], + ), + ); + }, + ); + }, ); } @@ -1022,32 +1011,38 @@ class DefaultAssetPickerViewerBuilderDelegate< @override Widget build(BuildContext context) { - return Theme( - data: themeData, - child: AnnotatedRegion( - value: themeData.appBarTheme.systemOverlayStyle ?? - (themeData.effectiveBrightness.isDark - ? SystemUiOverlayStyle.light - : SystemUiOverlayStyle.dark), - child: Scaffold( - resizeToAvoidBottomInset: false, - body: Stack( - children: [ - Positioned.fill(child: _pageViewBuilder(context)), - if (isWeChatMoment && hasVideo) ...[ - momentVideoBackButton(context), - PositionedDirectional( - end: 16, - bottom: context.bottomPadding + 16, - child: confirmButton(context), - ), - ] else ...[ - appBar(context), - if (selectedAssets != null || - (isWeChatMoment && hasVideo && isAppleOS(context))) - bottomDetailBuilder(context), - ], - ], + return CNP.value( + value: provider, + child: CNP.value( + value: selectorProvider, + builder: (context, _) => Theme( + data: themeData, + child: AnnotatedRegion( + value: themeData.appBarTheme.systemOverlayStyle ?? + (themeData.effectiveBrightness.isDark + ? SystemUiOverlayStyle.light + : SystemUiOverlayStyle.dark), + child: Scaffold( + resizeToAvoidBottomInset: false, + body: Stack( + children: [ + Positioned.fill(child: _pageViewBuilder(context)), + if (isWeChatMoment && hasVideo) ...[ + momentVideoBackButton(context), + PositionedDirectional( + end: 16, + bottom: context.bottomPadding + 16, + child: confirmButton(context), + ), + ] else ...[ + appBar(context), + if (selectedAssets != null || + (isWeChatMoment && hasVideo && isAppleOS(context))) + bottomDetailBuilder(context), + ], + ], + ), + ), ), ), ), From 6cc49c8746fcd1d4a0d364a9d12afd054685afb6 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Thu, 10 Oct 2024 18:00:39 +0800 Subject: [PATCH 09/29] =?UTF-8?q?=F0=9F=9A=80=20Pass=20asset=20through=20w?= =?UTF-8?q?idgets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/widget/builder/image_page_builder.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/src/widget/builder/image_page_builder.dart b/lib/src/widget/builder/image_page_builder.dart index 27015d8d..ba92b0e6 100644 --- a/lib/src/widget/builder/image_page_builder.dart +++ b/lib/src/widget/builder/image_page_builder.dart @@ -120,6 +120,7 @@ class _ImagePageBuilderState extends State { ); if (_isLivePhoto && _livePhotoVideoController != null) { return _LivePhotoWidget( + asset: asset, controller: _livePhotoVideoController!, fit: BoxFit.contain, state: state, @@ -161,12 +162,14 @@ class _ImagePageBuilderState extends State { class _LivePhotoWidget extends StatefulWidget { const _LivePhotoWidget({ + required this.asset, required this.controller, required this.state, required this.fit, required this.textDelegate, }); + final AssetEntity asset; final VideoPlayerController controller; final ExtendedImageState state; final BoxFit fit; From 882e631ebe629740cbcb64693ef9b884daf24f37 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Thu, 19 Dec 2024 12:06:30 +0800 Subject: [PATCH 10/29] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Use=20modified=20dat?= =?UTF-8?q?e=20on=20Android?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/provider/asset_picker_provider.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/provider/asset_picker_provider.dart b/lib/src/provider/asset_picker_provider.dart index 16ec896e..59fb26a7 100644 --- a/lib/src/provider/asset_picker_provider.dart +++ b/lib/src/provider/asset_picker_provider.dart @@ -352,7 +352,7 @@ class DefaultAssetPickerProvider )..merge(fog); } else if (fog == null && Platform.isAndroid) { options = AdvancedCustomFilter( - orderBy: [OrderByItem.desc(CustomColumns.android.dateTaken)], + orderBy: [OrderByItem.desc(CustomColumns.android.modifiedDate)], ); } else { options = fog; From cde84bd12b4e59f3be4347de210e19443405eb65 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Fri, 8 Aug 2025 16:40:53 +0800 Subject: [PATCH 11/29] =?UTF-8?q?=F0=9F=9A=80=20Sync=20generic=20fields?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../asset_picker_builder_delegate.dart | 6 ++++- .../asset_picker_viewer_builder_delegate.dart | 22 +------------------ .../asset_picker_viewer_provider.dart | 10 --------- 3 files changed, 6 insertions(+), 32 deletions(-) diff --git a/lib/src/delegates/asset_picker_builder_delegate.dart b/lib/src/delegates/asset_picker_builder_delegate.dart index f02cb5e0..e6eb4c73 100644 --- a/lib/src/delegates/asset_picker_builder_delegate.dart +++ b/lib/src/delegates/asset_picker_builder_delegate.dart @@ -949,7 +949,11 @@ class DefaultAssetPickerBuilderDelegate } @override - void initState(AssetPickerState state) { + void initState( + AssetPickerState + state, + ) { super.initState(state); presentLimitedTapGestureRecognizer = TapGestureRecognizer() ..onTap = PhotoManager.presentLimited; diff --git a/lib/src/delegates/asset_picker_viewer_builder_delegate.dart b/lib/src/delegates/asset_picker_viewer_builder_delegate.dart index dd4d291b..d803d7c7 100644 --- a/lib/src/delegates/asset_picker_viewer_builder_delegate.dart +++ b/lib/src/delegates/asset_picker_viewer_builder_delegate.dart @@ -305,14 +305,6 @@ abstract class AssetPickerViewerBuilderDelegate syncSelectedAssetsWhenPop() async => true; - /// Split page builder according to type of asset. /// 根据资源类型使用不同的构建页 Widget assetPageBuilder(BuildContext context, int index); @@ -333,7 +325,7 @@ abstract class AssetPickerViewerBuilderDelegate syncSelectedAssetsWhenPop() async { - if (provider?.currentlySelectedAssets != null) { - selectorProvider?.selectedAssets = provider!.currentlySelectedAssets; - } - return true; - } - Widget assetSemanticsBuilder(BuildContext context, int index) { final asset = previewAssets.elementAt( shouldReversePreview ? previewAssets.length - index - 1 : index, diff --git a/lib/src/provider/asset_picker_viewer_provider.dart b/lib/src/provider/asset_picker_viewer_provider.dart index 62d1d0ad..c070bfb2 100644 --- a/lib/src/provider/asset_picker_viewer_provider.dart +++ b/lib/src/provider/asset_picker_viewer_provider.dart @@ -60,14 +60,4 @@ class AssetPickerViewerProvider extends ChangeNotifier { final List newList = _currentlySelectedAssets.toList()..remove(item); currentlySelectedAssets = newList; } - - @Deprecated('Use selectAsset instead. This will be removed in 10.0.0') - void selectAssetEntity(Asset entity) { - selectAsset(entity); - } - - @Deprecated('Use unSelectAsset instead. This will be removed in 10.0.0') - void unselectAssetEntity(Asset entity) { - unSelectAsset(entity); - } } From a08f05ce174a919e2bf1322cf130bfe77d243f81 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Fri, 8 Aug 2025 17:45:30 +0800 Subject: [PATCH 12/29] =?UTF-8?q?=F0=9F=94=A5=20Remove=20unnecessary=20cod?= =?UTF-8?q?e=20from=20examples?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pickers/directory_file_asset_picker.dart | 87 +++++-------------- .../customs/pickers/insta_asset_picker.dart | 26 +++--- .../pickers/multi_tabs_assets_picker.dart | 3 +- 3 files changed, 36 insertions(+), 80 deletions(-) diff --git a/example/lib/customs/pickers/directory_file_asset_picker.dart b/example/lib/customs/pickers/directory_file_asset_picker.dart index f9db7aed..2df4113d 100644 --- a/example/lib/customs/pickers/directory_file_asset_picker.dart +++ b/example/lib/customs/pickers/directory_file_asset_picker.dart @@ -163,22 +163,19 @@ class _DirectoryFileAssetPickerState extends State { return GestureDetector( onTap: isDisplayingDetail ? () async { - final viewer = AssetPickerViewer< + final result = await AssetPickerViewer.pushToViewerWithDelegate< File, Directory, FileAssetPickerViewerProvider, FileAssetPickerViewerBuilderDelegate>( - builder: FileAssetPickerViewerBuilderDelegate( + context, + delegate: FileAssetPickerViewerBuilderDelegate( currentIndex: index, previewAssets: fileList, provider: FileAssetPickerViewerProvider(fileList), themeData: AssetPicker.themeData(themeColor), ), ); - final List? result = - await Navigator.maybeOf(context)?.push>( - AssetPickerViewerPageRoute(builder: (context) => viewer), - ); if (result != null && result != fileList) { fileList ..clear() @@ -265,7 +262,8 @@ class _DirectoryFileAssetPickerState extends State { } } -class FileAssetPickerProvider extends AssetPickerProvider { +final class FileAssetPickerProvider + extends AssetPickerProvider { FileAssetPickerProvider({ required List selectedAssets, }) : super(selectedAssets: selectedAssets) { @@ -350,7 +348,7 @@ class FileAssetPickerProvider extends AssetPickerProvider { } } -class FileAssetPickerBuilder +final class FileAssetPickerBuilder extends AssetPickerBuilderDelegate { FileAssetPickerBuilder({ required this.provider, @@ -372,8 +370,13 @@ class FileAssetPickerBuilder int? index, File currentAsset, ) async { - final Widget viewer = AssetPickerViewer( - builder: FileAssetPickerViewerBuilderDelegate( + final result = await AssetPickerViewer.pushToViewerWithDelegate< + File, + Directory, + FileAssetPickerViewerProvider, + FileAssetPickerViewerBuilderDelegate>( + context, + delegate: FileAssetPickerViewerBuilderDelegate( currentIndex: index ?? provider.selectedAssets.indexOf(currentAsset), previewAssets: provider.selectedAssets, provider: FileAssetPickerViewerProvider(provider.selectedAssets), @@ -382,40 +385,11 @@ class FileAssetPickerBuilder selectorProvider: provider, ), ); - final List? result = - await Navigator.maybeOf(context)?.push?>( - AssetPickerViewerPageRoute(builder: (context) => viewer), - ); if (result != null) { Navigator.maybeOf(context)?.maybePop(result); } } - Future?> pushToPicker( - BuildContext context, { - required int index, - required List previewAssets, - List? selectedAssets, - FileAssetPickerProvider? selectorProvider, - }) async { - final viewer = AssetPickerViewer( - builder: FileAssetPickerViewerBuilderDelegate( - currentIndex: index, - previewAssets: previewAssets, - provider: selectedAssets != null - ? FileAssetPickerViewerProvider(selectedAssets) - : null, - themeData: AssetPicker.themeData(themeColor), - selectedAssets: selectedAssets, - selectorProvider: selectorProvider, - ), - ); - return await Navigator.maybeOf(context)?.push?>( - AssetPickerViewerPageRoute(builder: (context) => viewer), - ); - } - @override void selectAsset(BuildContext context, File asset, int index, bool selected) { if (selected) { @@ -980,23 +954,12 @@ class FileAssetPickerBuilder @override Widget previewButton(BuildContext context) { - return Selector( - selector: (_, FileAssetPickerProvider p) => p.isSelectedNotEmpty, - builder: (_, bool isSelectedNotEmpty, __) { + return Consumer( + builder: (_, p, __) { + final isSelectedNotEmpty = p.isSelectedNotEmpty; return GestureDetector( onTap: isSelectedNotEmpty - ? () async { - final List? result = await pushToPicker( - context, - index: 0, - previewAssets: provider.selectedAssets, - selectedAssets: provider.selectedAssets, - selectorProvider: provider, - ); - if (result != null) { - Navigator.maybeOf(context)?.pop(result); - } - } + ? () => viewAsset(context, null, p.selectedAssets.first) : null, child: Padding( padding: const EdgeInsets.symmetric(vertical: 12.0), @@ -1104,15 +1067,8 @@ class FileAssetPickerBuilder selectedAssets.where((File f) => f.path == asset.path).isNotEmpty; return Positioned.fill( child: GestureDetector( - onTap: () async { - final List? result = await pushToPicker( - context, - index: index, - previewAssets: provider.currentAssets, - ); - if (result != null) { - Navigator.maybeOf(context)?.pop(result); - } + onTap: () { + viewAsset(context, index, asset); }, child: AnimatedContainer( duration: switchingPathDuration, @@ -1164,7 +1120,8 @@ class FileAssetPickerBuilder } } -class FileAssetPickerViewerProvider extends AssetPickerViewerProvider { +final class FileAssetPickerViewerProvider + extends AssetPickerViewerProvider { FileAssetPickerViewerProvider(List super.assets); @override @@ -1175,7 +1132,7 @@ class FileAssetPickerViewerProvider extends AssetPickerViewerProvider { } } -class FileAssetPickerViewerBuilderDelegate +final class FileAssetPickerViewerBuilderDelegate extends AssetPickerViewerBuilderDelegate { FileAssetPickerViewerBuilderDelegate({ diff --git a/example/lib/customs/pickers/insta_asset_picker.dart b/example/lib/customs/pickers/insta_asset_picker.dart index 2568237e..d946ed6f 100644 --- a/example/lib/customs/pickers/insta_asset_picker.dart +++ b/example/lib/customs/pickers/insta_asset_picker.dart @@ -286,8 +286,7 @@ class _InstaAssetPickerState extends State { } } -class InstaAssetPickerBuilder - extends DefaultAssetPickerBuilderDelegate { +final class InstaAssetPickerBuilder extends DefaultAssetPickerBuilderDelegate { InstaAssetPickerBuilder({ required super.provider, required super.initialPermission, @@ -350,7 +349,7 @@ class InstaAssetPickerBuilder /// Initialize [_previewAsset] with [p.selectedAssets] if not empty /// otherwise if the first item of the album Future _initializePreviewAsset( - T p, + DefaultAssetPickerProvider p, bool shouldDisplayAssets, ) async { if (_previewAsset.value != null) { @@ -488,9 +487,9 @@ class InstaAssetPickerBuilder SizedBox( width: MediaQuery.sizeOf(context).width, height: previewHeight(context), - child: Selector>( - selector: (_, T p) => p.selectedAssets, - builder: (_, List selected, __) { + child: Selector>( + selector: (_, p) => p.selectedAssets, + builder: (_, selected, __) { if (previewAsset == null && selected.isEmpty) { return loadingIndicator(context); } @@ -500,8 +499,7 @@ class InstaAssetPickerBuilder if (previewAsset != null) { effectiveIndex = selected.indexOf(previewAsset); } - final List assets = - selected.isEmpty ? [previewAsset!] : selected; + final assets = selected.isEmpty ? [previewAsset!] : selected; return AssetPickerViewer< AssetEntity, @@ -546,7 +544,7 @@ class InstaAssetPickerBuilder _kPathSelectorRowHeight + MediaQuery.paddingOf(context).top; - return ChangeNotifierProvider.value( + return ChangeNotifierProvider.value( value: provider, builder: (BuildContext context, _) => ValueListenableBuilder( valueListenable: _viewerPosition, @@ -649,8 +647,8 @@ class InstaAssetPickerBuilder Widget _buildListAlbums(BuildContext context) { appBarPreferredSize ??= appBar(context).preferredSize; - return Consumer( - builder: (BuildContext context, T provider, __) { + return Consumer( + builder: (context, provider, __) { if (isAppleOS(context)) { return pathEntityListWidget(context); } @@ -677,8 +675,8 @@ class InstaAssetPickerBuilder Widget _buildGrid(BuildContext context) { appBarPreferredSize ??= appBar(context).preferredSize; - return Consumer( - builder: (BuildContext context, T p, __) { + return Consumer( + builder: (context, p, __) { final bool shouldDisplayAssets = p.hasAssetsToDisplay || shouldBuildSpecialItem; _initializePreviewAsset(p, shouldDisplayAssets); @@ -772,7 +770,7 @@ class InstaAssetPickerBuilder const SizedBox.shrink(); } -class InstaAssetPickerViewerBuilder +final class InstaAssetPickerViewerBuilder extends DefaultAssetPickerViewerBuilderDelegate { InstaAssetPickerViewerBuilder({ required super.currentIndex, diff --git a/example/lib/customs/pickers/multi_tabs_assets_picker.dart b/example/lib/customs/pickers/multi_tabs_assets_picker.dart index d15a8e3f..3007b539 100644 --- a/example/lib/customs/pickers/multi_tabs_assets_picker.dart +++ b/example/lib/customs/pickers/multi_tabs_assets_picker.dart @@ -260,7 +260,8 @@ class _MultiTabAssetPickerState extends State { } } -class MultiTabAssetPickerBuilder extends DefaultAssetPickerBuilderDelegate { +final class MultiTabAssetPickerBuilder + extends DefaultAssetPickerBuilderDelegate { MultiTabAssetPickerBuilder({ required super.provider, required this.videosProvider, From ecfd35a50e325f55379bd510d61506193617da0c Mon Sep 17 00:00:00 2001 From: Alex Li Date: Fri, 8 Aug 2025 17:45:55 +0800 Subject: [PATCH 13/29] =?UTF-8?q?=F0=9F=9A=9A=20Rename=20generic=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../asset_picker_builder_delegate.dart | 2 +- lib/src/widget/asset_picker_viewer.dart | 30 ++++++++++--------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/lib/src/delegates/asset_picker_builder_delegate.dart b/lib/src/delegates/asset_picker_builder_delegate.dart index e6eb4c73..56c0d2aa 100644 --- a/lib/src/delegates/asset_picker_builder_delegate.dart +++ b/lib/src/delegates/asset_picker_builder_delegate.dart @@ -1836,7 +1836,7 @@ class DefaultAssetPickerBuilderDelegate ); } - /// It'll pop with [T.selectedAssets] + /// It'll pop with provider's `selectedAssets` /// when there are any assets were chosen. /// 当有资源已选时,点击按钮将把已选资源通过路由返回。 @override diff --git a/lib/src/widget/asset_picker_viewer.dart b/lib/src/widget/asset_picker_viewer.dart index e01f24ad..6034b2bd 100644 --- a/lib/src/widget/asset_picker_viewer.dart +++ b/lib/src/widget/asset_picker_viewer.dart @@ -96,36 +96,38 @@ class AssetPickerViewer< /// Call the viewer with provided delegate and provider. /// 通过指定的 [delegate] 调用查看器 - static Future?> pushToViewerWithDelegate< - A, - P, - Provider extends AssetPickerViewerProvider, - Delegate extends AssetPickerViewerBuilderDelegate>( + static Future?> pushToViewerWithDelegate< + Asset, + Path, + Provider extends AssetPickerViewerProvider, + Delegate extends AssetPickerViewerBuilderDelegate>( BuildContext context, { required Delegate delegate, PermissionRequestOption permissionRequestOption = const PermissionRequestOption(), bool useRootNavigator = false, RouteSettings? pageRouteSettings, - AssetPickerViewerPageRouteBuilder>? pageRouteBuilder, + AssetPickerViewerPageRouteBuilder>? pageRouteBuilder, }) async { await AssetPicker.permissionCheck(requestOption: permissionRequestOption); - final viewer = AssetPickerViewer( + final viewer = AssetPickerViewer( builder: delegate, ); final pageRoute = pageRouteBuilder?.call(viewer) ?? AssetPickerViewerPageRoute(builder: (context) => viewer); - final result = await Navigator.maybeOf(context)?.push>(pageRoute); + final result = + await Navigator.maybeOf(context)?.push>(pageRoute); return result; } } class AssetPickerViewerState< - A, - P, - Provider extends AssetPickerViewerProvider, - Delegate extends AssetPickerViewerBuilderDelegate> - extends State> + Asset, + Path, + Provider extends AssetPickerViewerProvider, + Delegate extends AssetPickerViewerBuilderDelegate> + extends State> with TickerProviderStateMixin { Delegate get builder => widget.builder; @@ -137,7 +139,7 @@ class AssetPickerViewerState< @override void didUpdateWidget( - covariant AssetPickerViewer oldWidget, + covariant AssetPickerViewer oldWidget, ) { super.didUpdateWidget(oldWidget); builder.didUpdateViewer(this, oldWidget, widget); From 54b7f07225f0c0c7d63a6a2ac6a518bb652b8884 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Mon, 11 Aug 2025 23:22:12 +0800 Subject: [PATCH 14/29] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Improve=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../asset_picker_builder_delegate.dart | 2 +- .../asset_picker_viewer_builder_delegate.dart | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/src/delegates/asset_picker_builder_delegate.dart b/lib/src/delegates/asset_picker_builder_delegate.dart index 56c0d2aa..5004c203 100644 --- a/lib/src/delegates/asset_picker_builder_delegate.dart +++ b/lib/src/delegates/asset_picker_builder_delegate.dart @@ -950,7 +950,7 @@ class DefaultAssetPickerBuilderDelegate @override void initState( - AssetPickerState state, ) { diff --git a/lib/src/delegates/asset_picker_viewer_builder_delegate.dart b/lib/src/delegates/asset_picker_viewer_builder_delegate.dart index d803d7c7..8fc3816d 100644 --- a/lib/src/delegates/asset_picker_viewer_builder_delegate.dart +++ b/lib/src/delegates/asset_picker_viewer_builder_delegate.dart @@ -259,21 +259,21 @@ abstract class AssetPickerViewerBuilderDelegate selectedNotifier = ValueNotifier(selectedCount); - void unSelectAsset(Asset entity) { - provider?.unSelectAsset(entity); + void unSelectAsset(Asset asset) { + provider?.unSelectAsset(asset); if (!isSelectedPreviewing) { - selectedAssets?.remove(entity); + selectedAssets?.remove(asset); } selectedNotifier.value = selectedCount; } - void selectAsset(Asset entity) { + void selectAsset(Asset asset) { if (maxAssets != null && selectedCount > maxAssets!) { return; } - provider?.selectAsset(entity); + provider?.selectAsset(asset); if (!isSelectedPreviewing) { - selectedAssets?.add(entity); + selectedAssets?.add(asset); } selectedNotifier.value = selectedCount; } @@ -410,14 +410,14 @@ class DefaultAssetPickerViewerBuilderDelegate< false); @override - void unSelectAsset(AssetEntity entity) { - super.unSelectAsset(entity); - selectorProvider?.unSelectAsset(entity); + void unSelectAsset(AssetEntity asset) { + super.unSelectAsset(asset); + selectorProvider?.unSelectAsset(asset); } @override - void selectAsset(AssetEntity entity) { - super.selectAsset(entity); + void selectAsset(AssetEntity asset) { + super.selectAsset(asset); selectedNotifier.value = selectedCount; } From d25705c9c076880a0476ba89979bb23579f473e6 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Mon, 11 Aug 2025 23:24:24 +0800 Subject: [PATCH 15/29] =?UTF-8?q?=F0=9F=93=9D=20Add=20migration=20guide?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- guides/migration_guide.md | 169 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) diff --git a/guides/migration_guide.md b/guides/migration_guide.md index 20edd8a3..fe0afda5 100644 --- a/guides/migration_guide.md +++ b/guides/migration_guide.md @@ -8,6 +8,7 @@ This document gathered all breaking changes and migrations requirement between m ## Breaking changes in versions +- [10.0.0](#1000) - [9.2.0](#920) - [9.1.0](#910) - [9.0.0](#900) @@ -19,6 +20,174 @@ This document gathered all breaking changes and migrations requirement between m - [6.0.0](#600) - [5.0.0](#500) +## 10.0.0 + +### Summary + +> [!NOTE] +> If you didn't extend `AssetPickerBuilderDelegate` or `AssetPickerViewerBuilderDelegate` +> to build delegates on your own, you can stop reading. + +This version introduces more generic types to increase flexibility and customization. +This means that if you have created your own custom delegates or providers, +you will need to update their signatures. +Specifically, `AssetPickerBuilderDelegate` and `AssetPickerViewerBuilderDelegate` +(and their default implementations) now require more generic type arguments. +This allows for greater control over the types of assets, paths, +and providers used within the picker. + +### Details + +#### `AssetPickerBuilderDelegate` and `DefaultAssetPickerBuilderDelegate` + +`AssetPickerBuilderDelegate` and `DefaultAssetPickerBuilderDelegate` are now more generic. + +- `AssetPickerBuilderDelegate`'s `initState` method now takes a generic `AssetPickerState`. +- `DefaultAssetPickerBuilderDelegate` now has a generic type `T` which should extend `DefaultAssetPickerProvider`. + +Before: + +```dart +// In AssetPickerBuilderDelegate +void initState(AssetPickerState state) {} + +// In DefaultAssetPickerBuilderDelegate +class DefaultAssetPickerBuilderDelegate extends AssetPickerBuilderDelegate { + final DefaultAssetPickerProvider provider; + // ... +} +``` + +After: + +```dart +// In AssetPickerBuilderDelegate +void initState( + covariant AssetPickerState> + state, +) {} + +// In DefaultAssetPickerBuilderDelegate +class DefaultAssetPickerBuilderDelegate + extends AssetPickerBuilderDelegate { + final T provider; + // ... +} +``` + +#### `AssetPickerViewerBuilderDelegate` and `DefaultAssetPickerViewerBuilderDelegate` + +Similar to the picker builder delegates, the viewer builder delegates are now more generic. + +- `AssetPickerViewerBuilderDelegate` now has generic types for `Asset`, `Path`, and `Provider`. +- `DefaultAssetPickerViewerBuilderDelegate` is now generic with types `T` (extends `AssetPickerViewerProvider`) and `P` (extends `DefaultAssetPickerProvider`). +- `initStateAndTicker` is renamed to `initState` and its signature has changed. +- `didUpdateViewer`'s signature has changed. + +Before: + +```dart +// In AssetPickerViewerBuilderDelegate +abstract class AssetPickerViewerBuilderDelegate { + void initStateAndTicker( + covariant AssetPickerViewerState state, + TickerProvider v, + ); + + void didUpdateViewer( + covariant AssetPickerViewerState state, + covariant AssetPickerViewer oldWidget, + covariant AssetPickerViewer newWidget, + ); +} + +// In DefaultAssetPickerViewerBuilderDelegate +class DefaultAssetPickerViewerBuilderDelegate + extends AssetPickerViewerBuilderDelegate { + // ... +} +``` + +After: + +```dart +// In AssetPickerViewerBuilderDelegate +abstract class AssetPickerViewerBuilderDelegate> { + void initState( + covariant AssetPickerViewerState state, + ); + + void didUpdateViewer( + covariant AssetPickerViewerState state, + covariant AssetPickerViewer oldWidget, + covariant AssetPickerViewer newWidget, + ); +} + +// In DefaultAssetPickerViewerBuilderDelegate +class DefaultAssetPickerViewerBuilderDelegate< + T extends AssetPickerViewerProvider, + P extends DefaultAssetPickerProvider> + extends AssetPickerViewerBuilderDelegate { + // ... +} +``` + +#### `AssetPicker` and `AssetPickerViewer` + +`AssetPicker`, `AssetPickerViewer` and their states are now generic. +When using `pickAssetsWithDelegate` or `pushToViewerWithDelegate`, +you now need to pass the delegate type. + +Before: + +```dart +// AssetPicker.pickAssetsWithDelegate +static Future?> pickAssetsWithDelegate>( + BuildContext context, { + required AssetPickerBuilderDelegate delegate, +}); + +// AssetPickerViewer.pushToViewerWithDelegate +static Future?> pushToViewerWithDelegate( + BuildContext context, { + required AssetPickerViewerBuilderDelegate delegate, +}); +``` + +After: + +```dart +// AssetPicker.pickAssetsWithDelegate +static Future?> pickAssetsWithDelegate< + Asset, + Path, + PickerProvider extends AssetPickerProvider, + Delegate extends AssetPickerBuilderDelegate>( + BuildContext context, { + required Delegate delegate, +}); + +// AssetPickerViewer.pushToViewerWithDelegate +static Future?> pushToViewerWithDelegate< + Asset, + Path, + Provider extends AssetPickerViewerProvider, + Delegate extends AssetPickerViewerBuilderDelegate>( + BuildContext context, { + required Delegate delegate, +}); +``` + +#### `AssetPickerViewerProvider` + +- The generic type `A` is renamed to `Asset`. +- Deprecated methods `selectAssetEntity` and `unselectAssetEntity` have been removed. + Use `selectAsset` and `unSelectAsset` instead. + ## 9.2.0 > [!NOTE] From 19d2236c30351d0576a633b6d71620462dc7dcd3 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Tue, 12 Aug 2025 22:58:05 +0800 Subject: [PATCH 16/29] =?UTF-8?q?=F0=9F=93=9D=20CHANGELOG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1aa3c5a7..b158adce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ that can be found in the LICENSE file. --> ## Unreleased -*None.* +- Make delegate respect generic types as much as possible. + This is a breaking change for users who use custom delegates and providers. ## 9.6.0 From eb97fb476ac35104bf985e026952ed89a8020746 Mon Sep 17 00:00:00 2001 From: JuNe <56582497+yujune@users.noreply.github.com> Date: Sat, 15 Nov 2025 09:50:33 +0800 Subject: [PATCH 17/29] =?UTF-8?q?=E2=9C=A8=20Support=20Multiple=20Special?= =?UTF-8?q?=20Items=20(#635)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### ✨ What's the context? Current `AssetPickerConfig` only accept single `specialItemPosition` and `specialItemBuilder` which is is not suitable for cases where multiple special items is required. ### 🛠 Changes being made - Added `specialItems` in `AssetPickerConfig` and which accept list of `specialItemPosition` and `specialItemBuilder`. - Added `isPermissionLimited` param to `SpecialItemBuilder` typedef for case where special item is required to remove when `isPermissionLimited` is false. Example as below. ```dart SpecialItem( itemPosition: SpecialItemPosition.append, itemBuilder: ( BuildContext context, AssetPathEntity? path, int length, bool isPermissionLimited, ) { if (!isPermissionLimited) { return null; } return const Center( child: Text('Append Widget', textAlign: TextAlign.center), ); }, ) ``` - Removed unused `SpecialPosition.none` enum. ### ✨ Result --------- Co-authored-by: yujune Co-authored-by: Alex Li --- CHANGELOG.md | 6 + README-ZH.md | 3 +- README.md | 3 +- example/lib/constants/picker_method.dart | 247 +++++++++++------ .../pickers/directory_file_asset_picker.dart | 85 ++++-- .../customs/pickers/insta_asset_picker.dart | 11 +- .../pickers/multi_tabs_assets_picker.dart | 11 +- 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 | 21 +- lib/src/constants/enums.dart | 4 - lib/src/constants/typedefs.dart | 2 +- ...asset_grid_drag_selection_coordinator.dart | 7 + .../asset_picker_builder_delegate.dart | 259 +++++++++--------- lib/src/delegates/asset_picker_delegate.dart | 3 +- lib/src/models/special_item.dart | 45 +++ lib/wechat_assets_picker.dart | 2 +- test/test_utils.dart | 3 +- 23 files changed, 481 insertions(+), 263 deletions(-) create mode 100644 lib/src/models/special_item.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index b158adce..198cf78d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,12 @@ that can be found in the LICENSE file. --> ## Unreleased +**New features** + +- Support multiple append/prepend specials items. + +**Improvements** + - Make delegate respect generic types as much as possible. This is a breaking change for users who use custom delegates and providers. diff --git a/README-ZH.md b/README-ZH.md index c51bbb7a..cfee1ba7 100644 --- a/README-ZH.md +++ b/README-ZH.md @@ -294,8 +294,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 cd8306d9..75c6dfbe 100644 --- a/README.md +++ b/README.md @@ -303,8 +303,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 6636452d..63efbae4 100644 --- a/example/lib/constants/picker_method.dart +++ b/example/lib/constants/picker_method.dart @@ -135,39 +135,44 @@ 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, + PermissionState permissionState, + ) { + 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,49 +191,54 @@ 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 p = picker.builder.provider; - await p.switchPath( - PathWrapper( - path: - await p.currentPath!.path.obtainForNewProperties(), + specialItems: [ + SpecialItem( + position: SpecialItemPosition.prepend, + builder: ( + BuildContext context, + AssetPathEntity? path, + PermissionState permissionState, + ) { + 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 p = picker.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), ), - ), - ), - ); - }, + ); + }, + ), + ], ), ); }, @@ -294,16 +304,87 @@ 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, + PermissionState permissionState, + ) { + 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, + PermissionState permissionState, + ) { + return const Center( + child: Text('Prepand Widget', textAlign: TextAlign.center), + ); + }, + ), + SpecialItem( + position: SpecialItemPosition.append, + builder: ( + BuildContext context, + AssetPathEntity? path, + PermissionState permissionState, + ) { + return const Center( + child: Text('Append Widget', textAlign: TextAlign.center), + ); + }, + ), + //builder which return null will not be shown. + SpecialItem( + position: SpecialItemPosition.append, + builder: ( + BuildContext context, + AssetPathEntity? path, + PermissionState permissionState, + ) { + return null; + }, + ), + SpecialItem( + position: SpecialItemPosition.prepend, + builder: ( + BuildContext context, + AssetPathEntity? path, + PermissionState permissionState, + ) { + return null; + }, + ), + ], ), ); }, diff --git a/example/lib/customs/pickers/directory_file_asset_picker.dart b/example/lib/customs/pickers/directory_file_asset_picker.dart index 2df4113d..43efa803 100644 --- a/example/lib/customs/pickers/directory_file_asset_picker.dart +++ b/example/lib/customs/pickers/directory_file_asset_picker.dart @@ -524,9 +524,27 @@ final class FileAssetPickerBuilder Widget assetsGridBuilder(BuildContext context) { appBarPreferredSize ??= appBar(context).preferredSize; int totalCount = provider.currentAssets.length; - if (specialItemPosition != SpecialItemPosition.none) { - totalCount += 1; - } + + final specialItemsFinalized = specialItems + .map((item) { + final specialItem = item.builder?.call( + context, + provider.currentPath?.path, + permissionNotifier.value, + ); + if (specialItem != null) { + return SpecialItemFinalized( + position: item.position, + item: specialItem, + ); + } + return null; + }) + .whereType() + .toList(); + + totalCount += specialItemsFinalized.length; + final int placeholderCount; if (isAppleOS(context) && totalCount % gridCount != 0) { placeholderCount = gridCount - totalCount % gridCount; @@ -551,13 +569,19 @@ final class FileAssetPickerBuilder } return Directionality( textDirection: Directionality.of(context), - child: assetGridItemBuilder(c, index, assets), + child: assetGridItemBuilder( + context: c, + index: index, + currentAssets: assets, + specialItemsFinalized: specialItemsFinalized, + ), ); }, ), childCount: assetsGridItemCount( context: ctx, assets: assets, + specialItemsFinalized: specialItemsFinalized, placeholderCount: placeholderCount, ), findChildIndexCallback: (Key? key) { @@ -566,6 +590,7 @@ final class FileAssetPickerBuilder id: key.value, assets: assets, placeholderCount: placeholderCount, + specialItemsFinalized: specialItemsFinalized, ); } return null; @@ -636,15 +661,39 @@ final class FileAssetPickerBuilder } @override - Widget assetGridItemBuilder( - BuildContext context, - int index, - List currentAssets, - ) { - final int currentIndex = switch (specialItemPosition) { - SpecialItemPosition.none || SpecialItemPosition.append => index, - SpecialItemPosition.prepend => index - 1, - }; + Widget assetGridItemBuilder({ + required BuildContext context, + required int index, + required List currentAssets, + required List specialItemsFinalized, + }) { + final int length = currentAssets.length; + + final prependItems = []; + final appendItems = []; + for (final item in specialItemsFinalized) { + switch (item.position) { + case SpecialItemPosition.prepend: + prependItems.add(item); + case SpecialItemPosition.append: + appendItems.add(item); + } + } + + if (prependItems.isNotEmpty) { + if (index < prependItems.length) { + return specialItemsFinalized[index].item; + } + } + + if (appendItems.isNotEmpty) { + if (index >= length + prependItems.length) { + return specialItemsFinalized[index - length].item; + } + } + + final currentIndex = index - prependItems.length; + final File asset = currentAssets.elementAt(currentIndex); final Widget builder = imageAndVideoItemBuilder( context, @@ -667,6 +716,7 @@ final class FileAssetPickerBuilder int index, File asset, Widget child, + List specialItemsFinalized, ) { return Semantics(child: child); } @@ -675,14 +725,10 @@ final class FileAssetPickerBuilder int assetsGridItemCount({ required BuildContext context, required List assets, + required List specialItemsFinalized, 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; } @@ -1091,6 +1137,7 @@ final class FileAssetPickerBuilder int findChildIndexBuilder({ required String id, required List assets, + required List specialItemsFinalized, int placeholderCount = 0, }) { return assets.indexWhere((File file) => file.path == id); diff --git a/example/lib/customs/pickers/insta_asset_picker.dart b/example/lib/customs/pickers/insta_asset_picker.dart index d946ed6f..3a02300f 100644 --- a/example/lib/customs/pickers/insta_asset_picker.dart +++ b/example/lib/customs/pickers/insta_asset_picker.dart @@ -297,7 +297,6 @@ final class InstaAssetPickerBuilder extends DefaultAssetPickerBuilderDelegate { super.keepScrollOffset, }) : super( shouldRevertGrid: false, - specialItemPosition: SpecialItemPosition.none, ); /// Save last position of the grid view scroll controller @@ -677,8 +676,14 @@ final class InstaAssetPickerBuilder extends DefaultAssetPickerBuilderDelegate { appBarPreferredSize ??= appBar(context).preferredSize; return Consumer( builder: (context, p, __) { - final bool shouldDisplayAssets = - p.hasAssetsToDisplay || shouldBuildSpecialItem; + final hasAssetsToDisplay = p.hasAssetsToDisplay; + final shouldBuildSpecialItems = assetsGridSpecialItemsFinalized( + context: context, + path: p.currentPath?.path, + ).isNotEmpty; + final shouldDisplayAssets = + hasAssetsToDisplay || shouldBuildSpecialItems; + _initializePreviewAsset(p, shouldDisplayAssets); return AnimatedSwitcher( diff --git a/example/lib/customs/pickers/multi_tabs_assets_picker.dart b/example/lib/customs/pickers/multi_tabs_assets_picker.dart index 3007b539..7cf1e59a 100644 --- a/example/lib/customs/pickers/multi_tabs_assets_picker.dart +++ b/example/lib/customs/pickers/multi_tabs_assets_picker.dart @@ -523,9 +523,14 @@ final class MultiTabAssetPickerBuilder Widget _buildGrid(BuildContext context) { return Consumer( - builder: (BuildContext context, DefaultAssetPickerProvider p, __) { - final bool shouldDisplayAssets = - p.hasAssetsToDisplay || shouldBuildSpecialItem; + builder: (context, p, __) { + final hasAssetsToDisplay = p.hasAssetsToDisplay; + final shouldBuildSpecialItems = assetsGridSpecialItemsFinalized( + context: context, + path: p.currentPath?.path, + ).isNotEmpty; + final shouldDisplayAssets = + hasAssetsToDisplay || shouldBuildSpecialItems; return AnimatedSwitcher( duration: const Duration(milliseconds: 300), child: shouldDisplayAssets 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 9cf300a3..fbdf6b06 100644 --- a/example/lib/l10n/gen/app_localizations.dart +++ b/example/lib/l10n/gen/app_localizations.dart @@ -266,6 +266,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 60672977..2e62e383 100644 --- a/example/lib/l10n/gen/app_localizations_en.dart +++ b/example/lib/l10n/gen/app_localizations_en.dart @@ -103,6 +103,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 fb1e4161..5ae2326c 100644 --- a/example/lib/l10n/gen/app_localizations_zh.dart +++ b/example/lib/l10n/gen/app_localizations_zh.dart @@ -97,6 +97,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 ae35dea4..feb9e87b 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,7 @@ class AssetPickerConfig { this.themeColor, this.pickerTheme, this.textDelegate, - this.specialItemPosition = SpecialItemPosition.none, - this.specialItemBuilder, + this.specialItems = const [], this.loadingIndicatorBuilder, this.selectPredicate, this.shouldRevertGrid, @@ -56,13 +56,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. @@ -168,13 +161,9 @@ 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; + /// List of special items. + /// 自定义 item 列表 + final List> specialItems; /// Indicates the loading status for the builder. /// 指示目前加载的状态 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 631ea449..5e0ff68e 100644 --- a/lib/src/constants/typedefs.dart +++ b/lib/src/constants/typedefs.dart @@ -28,7 +28,7 @@ typedef LoadingIndicatorBuilder = Widget Function( typedef SpecialItemBuilder = Widget? Function( BuildContext context, Path? path, - int length, + PermissionState permissionState, ); /// {@template wechat_assets_picker.AssetSelectPredicate} diff --git a/lib/src/delegates/asset_grid_drag_selection_coordinator.dart b/lib/src/delegates/asset_grid_drag_selection_coordinator.dart index b04f3503..a422a3cf 100644 --- a/lib/src/delegates/asset_grid_drag_selection_coordinator.dart +++ b/lib/src/delegates/asset_grid_drag_selection_coordinator.dart @@ -123,10 +123,16 @@ class AssetGridDragSelectionCoordinator { totalRows * (itemSize + delegate.itemSpacing) <= gridViewport; final reverted = gridRevert && !onlyOneScreen; + final specialItems = delegate.assetsGridSpecialItemsFinalized( + context: context, + path: provider.currentPath?.path, + ); + final double anchor = delegate.assetGridAnchor( context: context, constraints: constraints, pathWrapper: provider.currentPath, + specialItemsFinalized: specialItems, ); final scrolledOffset = delegate.gridScrollController.offset .abs(); // Offset is negative when reverted. @@ -157,6 +163,7 @@ class AssetGridDragSelectionCoordinator { context: context, pathWrapper: provider.currentPath, onlyOneScreen: onlyOneScreen, + specialItemsFinalized: specialItems, ); // Make the index starts with the bottom if the grid is reverted. if (reverted && placeholderCount > 0 && rowIndex > 0 && anchor < 1.0) { diff --git a/lib/src/delegates/asset_picker_builder_delegate.dart b/lib/src/delegates/asset_picker_builder_delegate.dart index 5004c203..6e48074d 100644 --- a/lib/src/delegates/asset_picker_builder_delegate.dart +++ b/lib/src/delegates/asset_picker_builder_delegate.dart @@ -22,6 +22,7 @@ import '../delegates/asset_grid_drag_selection_coordinator.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'; @@ -40,8 +41,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, @@ -89,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. /// 指示目前加载的状态 @@ -176,12 +172,6 @@ abstract class AssetPickerBuilderDelegate { /// 选择器是否为单选模式 bool get isSingleAssetMode; - /// Whether the delegate should build the special item. - /// 是否需要构建自定义 item - bool get shouldBuildSpecialItem => - specialItemPosition != SpecialItemPosition.none && - specialItemBuilder != null; - /// Space between assets item widget. /// 资源部件之间的间隔 double get itemSpacing => 2; @@ -336,6 +326,7 @@ abstract class AssetPickerBuilderDelegate { int? findChildIndexBuilder({ required String id, required List assets, + required List specialItemsFinalized, int placeholderCount = 0, }) => null; @@ -345,31 +336,49 @@ abstract class AssetPickerBuilderDelegate { int assetsGridItemCount({ required BuildContext context, required List assets, + required List specialItemsFinalized, int placeholderCount = 0, }); + List assetsGridSpecialItemsFinalized({ + required BuildContext context, + required Path? path, + }) { + return specialItems + .map((item) { + final specialItem = item.builder?.call( + context, + path, + permissionNotifier.value, + ); + if (specialItem != null) { + return SpecialItemFinalized( + position: item.position, + item: specialItem, + ); + } + return null; + }) + .nonNulls + .toList(); + } + /// Calculates the placeholder count in the assets grid. int assetsGridItemPlaceholderCount({ required BuildContext context, required PathWrapper? pathWrapper, required bool onlyOneScreen, + required List specialItemsFinalized, }) { if (onlyOneScreen) { return 0; } + final bool gridRevert = effectiveShouldRevertGrid(context); int totalCount = pathWrapper?.assetCount ?? 0; - // If user chose a special item's position, add 1 count. - if (specialItemPosition != SpecialItemPosition.none) { - final specialItem = specialItemBuilder?.call( - context, - pathWrapper?.path, - totalCount, - ); - if (specialItem != null) { - totalCount += 1; - } - } + // Add special items' count. + totalCount += specialItemsFinalized.length; + final int result; if (gridRevert && totalCount % gridCount != 0) { // When there are left items that not filled into one row, @@ -379,6 +388,7 @@ abstract class AssetPickerBuilderDelegate { // Otherwise, we don't need placeholders. result = 0; } + return result; } @@ -387,19 +397,12 @@ abstract class AssetPickerBuilderDelegate { required BuildContext context, required BoxConstraints constraints, required PathWrapper? pathWrapper, + required List specialItemsFinalized, }) { int totalCount = pathWrapper?.assetCount ?? 0; - // If user chose a special item's position, add 1 count. - if (specialItemPosition != SpecialItemPosition.none) { - final specialItem = specialItemBuilder?.call( - context, - pathWrapper?.path, - totalCount, - ); - if (specialItem != null) { - totalCount += 1; - } - } + // Add special items' count. + totalCount += specialItemsFinalized.length; + // Here we got a magic calculation. [itemSpacing] needs to be divided by // [gridCount] since every grid item is squeezed by the [itemSpacing], // and it's actual size is reduced with [itemSpacing / gridCount]. @@ -427,11 +430,12 @@ abstract class AssetPickerBuilderDelegate { /// The item builder for the assets' grid. /// 资源列表项的构建 - Widget assetGridItemBuilder( - BuildContext context, - int index, - List currentAssets, - ); + Widget assetGridItemBuilder({ + required BuildContext context, + required int index, + required List currentAssets, + required List specialItemsFinalized, + }); /// The [Semantics] builder for the assets' grid. /// 资源列表项的语义构建 @@ -440,6 +444,7 @@ abstract class AssetPickerBuilderDelegate { int index, Asset asset, Widget child, + List specialItemsFinalized, ); /// The item builder for audio type of asset. @@ -826,8 +831,7 @@ class DefaultAssetPickerBuilderDelegate required super.initialPermission, super.gridCount, super.pickerTheme, - super.specialItemPosition, - super.specialItemBuilder, + super.specialItems = const [], super.loadingIndicatorBuilder, super.selectPredicate, super.shouldRevertGrid, @@ -1227,9 +1231,14 @@ class DefaultAssetPickerBuilderDelegate return AssetPickerAppBarWrapper( appBar: appBar(context), body: Consumer( - builder: (BuildContext context, T p, _) { - final bool shouldDisplayAssets = - p.hasAssetsToDisplay || shouldBuildSpecialItem; + builder: (context, p, _) { + final hasAssetsToDisplay = p.hasAssetsToDisplay; + final shouldBuildSpecialItems = assetsGridSpecialItemsFinalized( + context: context, + path: p.currentPath?.path, + ).isNotEmpty; + final shouldDisplayAssets = + hasAssetsToDisplay || shouldBuildSpecialItems; return AnimatedSwitcher( duration: switchingPathDuration, child: shouldDisplayAssets @@ -1278,10 +1287,15 @@ class DefaultAssetPickerBuilderDelegate children: [ Positioned.fill( child: Consumer( - builder: (_, p, __) { + builder: (context, p, _) { + final hasAssetsToDisplay = p.hasAssetsToDisplay; + final shouldBuildSpecialItems = assetsGridSpecialItemsFinalized( + context: context, + path: p.currentPath?.path, + ).isNotEmpty; + final shouldDisplayAssets = + hasAssetsToDisplay || shouldBuildSpecialItems; final Widget child; - final bool shouldDisplayAssets = - p.hasAssetsToDisplay || shouldBuildSpecialItem; if (shouldDisplayAssets) { child = Stack( children: [ @@ -1344,21 +1358,13 @@ 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 specialItemsFinalized = assetsGridSpecialItemsFinalized( + context: context, + path: wrapper?.path, + ); + totalCount += specialItemsFinalized.length; + + if (totalCount == 0 && specialItemsFinalized.isEmpty) { return loadingIndicator(context); } @@ -1377,6 +1383,7 @@ class DefaultAssetPickerBuilderDelegate context: context, pathWrapper: wrapper, onlyOneScreen: onlyOneScreen, + specialItemsFinalized: specialItemsFinalized, ); return SliverGrid( delegate: SliverChildBuilderDelegate( @@ -1389,10 +1396,10 @@ class DefaultAssetPickerBuilderDelegate } Widget child = assetGridItemBuilder( - context, - index, - assets, - specialItem: specialItem, + context: context, + index: index, + currentAssets: assets, + specialItemsFinalized: specialItemsFinalized, ); // Enables drag-to-select when: @@ -1483,7 +1490,7 @@ class DefaultAssetPickerBuilderDelegate context: context, assets: assets, placeholderCount: placeholderCount, - specialItem: specialItem, + specialItemsFinalized: specialItemsFinalized, ), findChildIndexCallback: (Key? key) { if (key is ValueKey) { @@ -1491,6 +1498,7 @@ class DefaultAssetPickerBuilderDelegate id: key.value, assets: assets, placeholderCount: placeholderCount, + specialItemsFinalized: specialItemsFinalized, ); } return null; @@ -1523,6 +1531,7 @@ class DefaultAssetPickerBuilderDelegate context: context, constraints: constraints, pathWrapper: wrapper, + specialItemsFinalized: specialItemsFinalized, ); final reverted = gridRevert && !onlyOneScreen; @@ -1585,33 +1594,42 @@ class DefaultAssetPickerBuilderDelegate /// 图片和视频类型 /// * 在索引到达倒数第三列的时候加载更多资源。 @override - Widget assetGridItemBuilder( - BuildContext context, - int index, - List currentAssets, { - Widget? specialItem, + Widget assetGridItemBuilder({ + required BuildContext context, + required int index, + required List currentAssets, + required List specialItemsFinalized, }) { final p = context.read(); final int length = currentAssets.length; 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 prependItems = []; + final appendItems = []; + for (final model in specialItemsFinalized) { + switch (model.position) { + case SpecialItemPosition.prepend: + prependItems.add(model); + case SpecialItemPosition.append: + appendItems.add(model); } } - final int currentIndex; - if (specialItem != null && - specialItemPosition == SpecialItemPosition.prepend) { - currentIndex = index - 1; - } else { - currentIndex = index; + if (prependItems.isNotEmpty) { + if (index < prependItems.length) { + return specialItemsFinalized[index].item; + } + } + + if (appendItems.isNotEmpty) { + if (index >= length + prependItems.length) { + return specialItemsFinalized[index - length].item; + } } + final currentIndex = index - prependItems.length; + if (currentPathEntity == null) { return const SizedBox.shrink(); } @@ -1641,14 +1659,23 @@ class DefaultAssetPickerBuilderDelegate itemBannedIndicator(context, asset), ], ); - return assetGridItemSemanticsBuilder(context, index, asset, content); + return assetGridItemSemanticsBuilder( + context, + index, + asset, + content, + specialItemsFinalized, + ); } - int semanticIndex(int index) { - if (specialItemPosition != SpecialItemPosition.prepend) { - return index + 1; - } - return index; + int assetGridItemSemanticIndex( + int index, + List specialItemsFinalized, + ) { + final prependItems = specialItemsFinalized.where( + (model) => model.position == SpecialItemPosition.prepend, + ); + return index - prependItems.length; } @override @@ -1657,6 +1684,7 @@ class DefaultAssetPickerBuilderDelegate int index, AssetEntity asset, Widget child, + List specialItemsFinalized, ) { return ValueListenableBuilder( valueListenable: isSwitchingPath, @@ -1674,7 +1702,7 @@ class DefaultAssetPickerBuilderDelegate final int selectedIndex = p.selectedAssets.indexOf(asset) + 1; final labels = [ '${semanticsTextDelegate.semanticTypeLabel(asset.type)}' - '${semanticIndex(index)}', + '${assetGridItemSemanticIndex(index, specialItemsFinalized)}', asset.createDateTime.toString().replaceAll('.000', ''), if (asset.type == AssetType.audio || asset.type == AssetType.video) @@ -1704,7 +1732,8 @@ class DefaultAssetPickerBuilderDelegate onLongPressHint: semanticsTextDelegate.sActionPreviewHint, selected: isSelected, sortKey: OrdinalSortKey( - semanticIndex(index).toDouble(), + assetGridItemSemanticIndex(index, specialItemsFinalized) + .toDouble(), name: 'GridItem', ), value: selectedIndex > 0 ? '$selectedIndex' : null, @@ -1717,7 +1746,8 @@ class DefaultAssetPickerBuilderDelegate } : null, child: IndexedSemantics( - index: semanticIndex(index), + index: + assetGridItemSemanticIndex(index, specialItemsFinalized), child: child, ), ), @@ -1733,12 +1763,14 @@ class DefaultAssetPickerBuilderDelegate int findChildIndexBuilder({ required String id, required List assets, + required List specialItemsFinalized, int placeholderCount = 0, }) { + final prependItems = specialItemsFinalized.where( + (model) => model.position == SpecialItemPosition.prepend, + ); int index = assets.indexWhere((AssetEntity e) => e.id == id); - if (specialItemPosition == SpecialItemPosition.prepend) { - index += 1; - } + index += prependItems.length; index += placeholderCount; return index; } @@ -1747,30 +1779,10 @@ class DefaultAssetPickerBuilderDelegate int assetsGridItemCount({ required BuildContext context, required List assets, + required List specialItemsFinalized, int placeholderCount = 0, - Widget? specialItem, }) { - final PathWrapper? currentWrapper = - context.select?>( - (T p) => p.currentPath, - ); - final AssetPathEntity? currentPathEntity = currentWrapper?.path; - final int length = assets.length + placeholderCount; - - // Return 1 if the [specialItem] build something. - if (currentPathEntity == null && specialItem != null) { - return placeholderCount + 1; - } - - // Return actual length if the current path is all. - // 如果当前目录是全部内容,则返回实际的内容数量。 - if (currentPathEntity?.isAll != true && specialItem == null) { - return length; - } - return switch (specialItemPosition) { - SpecialItemPosition.none => length, - SpecialItemPosition.prepend || SpecialItemPosition.append => length + 1, - }; + return assets.length + specialItemsFinalized.length + placeholderCount; } @override @@ -2055,11 +2067,8 @@ class DefaultAssetPickerBuilderDelegate child: Selector>>( selector: (_, T p) => p.paths, builder: (_, List> paths, __) { - final List> filtered = paths - .where( - (PathWrapper p) => p.assetCount != 0, - ) - .toList(); + final filtered = + paths.where((p) => p.assetCount != 0).toList(); return ListView.separated( padding: const EdgeInsetsDirectional.only(top: 1), shrinkWrap: true, diff --git a/lib/src/delegates/asset_picker_delegate.dart b/lib/src/delegates/asset_picker_delegate.dart index 45292346..7f41419d 100644 --- a/lib/src/delegates/asset_picker_delegate.dart +++ b/lib/src/delegates/asset_picker_delegate.dart @@ -108,8 +108,7 @@ class 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, diff --git a/lib/src/models/special_item.dart b/lib/src/models/special_item.dart new file mode 100644 index 00000000..fcc1e6e9 --- /dev/null +++ b/lib/src/models/special_item.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; + +import '../constants/enums.dart'; +import '../constants/typedefs.dart'; + +/// Allow users to set special items in the picker grid with [position]. +/// 允许用户在选择器中添加一个自定义item,并指定其位置。 +@immutable +class SpecialItem { + const SpecialItem({ + required this.position, + required this.builder, + }); + + /// Define how the item will be positioned. + /// 定义如何摆放item。 + final SpecialItemPosition position; + + /// The widget builder for the the special item. + /// 自定义item构建。 + final SpecialItemBuilder? builder; + + @override + String toString() { + return 'SpecialItem$Path(position: $position, builder: $builder)'; + } +} + +/// A finalized [SpecialItem] which contains its position and the built widget. +/// 已被构建的 [SpecialItem],包含其位置和 widget 信息。 +@immutable +final class SpecialItemFinalized { + const SpecialItemFinalized({ + required this.position, + required this.item, + }); + + final SpecialItemPosition position; + final Widget item; + + @override + String toString() { + return 'SpecialItemFinalized$Path(position: $position, item: $item)'; + } +} 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 d83611f0..0edc4fe1 100644 --- a/test/test_utils.dart +++ b/test/test_utils.dart @@ -132,8 +132,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, From a3e7cdf95a49c481f53b8cf25dc1820a07142064 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 22 Nov 2025 12:24:47 +0800 Subject: [PATCH 18/29] Add `enableLivePhoto` flag to control Live-Photo functionality (#727) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `enableLivePhoto` flag to `AssetPickerConfig` to control Live Photo functionality ## Summary This PR implements the feature requested in issue #716. It adds a new `enableLivePhoto` bool flag to `AssetPickerConfig` that controls whether Live Photo functionality is enabled throughout the picker. ## Changes Made - [x] Added `enableLivePhoto` bool parameter to AssetPickerConfig (defaults to `true` for backward compatibility) - [x] Passed `enableLivePhoto` from AssetPickerConfig to `DefaultAssetPickerBuilderDelegate` - [x] Added `enableLivePhoto` field to `DefaultAssetPickerBuilderDelegate` - [x] Updated `buildLivePhotoIndicator` call to check the `enableLivePhoto` flag before showing the indicator in the grid - [x] Added `enableLivePhoto` to `DefaultAssetPickerViewerBuilderDelegate` - [x] Updated `ImagePageBuilder` to accept enableLivePhoto parameter - [x] Updated `_isLivePhoto` getter to check both `enableLivePhoto` flag and asset property - [x] Updated `AssetPickerViewer.pushToViewer `to pass `enableLivePhoto` parameter - [x] Added unit tests to verify the new flag works correctly - [x] Added clarifying comment for `_isLivePhoto` getter - [x] Fixed lint error in test_utils.dart (const → final) ## Behavior - **When `enableLivePhoto` is `true` (default)**: All Live-Photo indicators and interactions work as before, maintaining full backward compatibility - **When `enableLivePhoto` is `false`**: - Live Photo indicators are hidden in the grid view - Live Photo video controller is not initialized in preview - No Live Photo widget is rendered in the image viewer ## Implementation Details The flag is propagated through the entire component hierarchy: 1. `AssetPickerConfig` → `AssetPickerDelegate` → `DefaultAssetPickerBuilderDelegate` 2. `DefaultAssetPickerBuilderDelegate` → `AssetPickerViewer.pushToViewer` → `DefaultAssetPickerViewerBuilderDelegate` 3. `DefaultAssetPickerViewerBuilderDelegate` → `ImagePageBuilder` This ensures that all Live-Photo-related functionality is consistently controlled by a single flag, making it easy for users to disable Live Photo features when needed while maintaining full backward compatibility. --------- Co-authored-by: Alex Li Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: AlexV525 <15884415+AlexV525@users.noreply.github.com> --- CHANGELOG.md | 19 +++ README-ZH.md | 1 + README.md | 1 + analysis_options.yaml | 1 + example/ios/Runner.xcodeproj/project.pbxproj | 40 +++--- .../xcshareddata/xcschemes/Runner.xcscheme | 18 +++ example/lib/constants/picker_method.dart | 21 ++++ .../pickers/directory_file_asset_picker.dart | 2 +- .../customs/pickers/insta_asset_picker.dart | 2 +- example/lib/l10n/app_en.arb | 4 +- example/lib/l10n/app_zh.arb | 4 +- example/lib/l10n/gen/app_localizations.dart | 12 ++ .../lib/l10n/gen/app_localizations_en.dart | 7 ++ .../lib/l10n/gen/app_localizations_zh.dart | 6 + example/lib/main.dart | 11 +- example/lib/pages/multi_assets_page.dart | 1 + example/lib/pages/single_assets_page.dart | 1 + example/pubspec.yaml | 4 +- lib/src/constants/config.dart | 11 ++ .../asset_picker_builder_delegate.dart | 18 ++- lib/src/delegates/asset_picker_delegate.dart | 114 +----------------- .../delegates/asset_picker_text_delegate.dart | 9 +- .../asset_picker_viewer_builder_delegate.dart | 7 +- lib/src/provider/asset_picker_provider.dart | 2 +- lib/src/widget/asset_picker.dart | 2 +- lib/src/widget/asset_picker_app_bar.dart | 2 +- lib/src/widget/asset_picker_viewer.dart | 4 +- .../widget/builder/audio_page_builder.dart | 2 +- .../widget/builder/image_page_builder.dart | 9 +- pubspec.yaml | 4 +- test/config_test.dart | 17 +++ test/test_utils.dart | 2 +- 32 files changed, 201 insertions(+), 157 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 198cf78d..5e5d37da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,25 @@ that can be found in the LICENSE file. --> - Make delegate respect generic types as much as possible. This is a breaking change for users who use custom delegates and providers. +## 9.8.0 + +> [!NOTE] +> Be aware of potential minor theme changes since the base theme has taken place in the picker's theme. + +**Improvements** + +- Improve themes by inheriting the base theme rather than standalone constructors. + +**Fixes** + +- Enabling using the package on Flutter 3.35. + +## 9.7.0 + +**Improvements** + +- Allows specifying the fallback text delegate through `assetPickerTextDelegateFromLocale`. + ## 9.6.0 **New features** diff --git a/README-ZH.md b/README-ZH.md index cfee1ba7..f29f92aa 100644 --- a/README-ZH.md +++ b/README-ZH.md @@ -304,6 +304,7 @@ final List? result = await AssetPicker.pickAssets( | assetsChangeRefreshPredicate | `AssetsChangeRefreshPredicate?` | 判断资源变化是否根据 call 和当前选中的路径进行更新 | `null` | | shouldAutoplayPreview | `bool` | 预览是否应自动播放 | `false` | | dragToSelect | `bool` | 是否开启拖拽选择 | `true` | +| enableLivePhoto | `bool` | 是否启用实况图片的功能 | `true` | - 当 `maxAssets` 等于 `1`(即单选模式),搭配 `SpecialPickerType.noPreview` 使用会在用户点选资源换时立刻选中并返回。 diff --git a/README.md b/README.md index 75c6dfbe..94d0ac8a 100644 --- a/README.md +++ b/README.md @@ -313,6 +313,7 @@ Fields in `AssetPickerConfig`: | assetsChangeRefreshPredicate | `AssetsChangeRefreshPredicate?` | Whether assets changing should call refresh with the given call and the current selected path. | `null` | | shouldAutoPlayPreview | `bool` | Whether the preview should auto play. | `false` | | dragToSelect | `bool` | Whether assets selection can be done with drag gestures. | `true` | +| enableLivePhoto | `bool` | Whether to enable Live-Photo functionality in the picker. | `true` | - When `maxAssets` equals to `1` (a.k.a. single picking mode), use `SpecialPickerType.noPreview` will immediately select asset diff --git a/analysis_options.yaml b/analysis_options.yaml index 2377709b..8e4effd3 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -3,6 +3,7 @@ include: package:flutter_lints/flutter.yaml analyzer: errors: deprecated_member_use: ignore + deprecated_member_use_from_same_package: ignore linter: rules: diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 0e0866f5..a29b9171 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 513FA762C8C9C7254396DC7C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65B67F314727E97056D6FB02 /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -54,6 +55,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, 513FA762C8C9C7254396DC7C /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -138,13 +140,15 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 950F336F7D4BCE35DBE7FE4C /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -174,6 +178,9 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; @@ -236,23 +243,6 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 950F336F7D4BCE35DBE7FE4C /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -556,6 +546,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index e3773d42..c3fedb29 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -5,6 +5,24 @@ + + + + + + + + + + assets) { + return AssetPicker.pickAssets( + context, + pickerConfig: AssetPickerConfig( + maxAssets: maxAssetsCount, + selectedAssets: assets, + enableLivePhoto: false, + ), + ); + }, + ); + } + final String icon; final String name; final String description; diff --git a/example/lib/customs/pickers/directory_file_asset_picker.dart b/example/lib/customs/pickers/directory_file_asset_picker.dart index 43efa803..b37404d0 100644 --- a/example/lib/customs/pickers/directory_file_asset_picker.dart +++ b/example/lib/customs/pickers/directory_file_asset_picker.dart @@ -831,7 +831,7 @@ final class FileAssetPickerBuilder borderRadius: isAppleOS(context) ? const BorderRadius.vertical(bottom: Radius.circular(10.0)) : null, - color: theme.colorScheme.background, + color: theme.colorScheme.surface, ), child: w, ), diff --git a/example/lib/customs/pickers/insta_asset_picker.dart b/example/lib/customs/pickers/insta_asset_picker.dart index 3a02300f..919f4313 100644 --- a/example/lib/customs/pickers/insta_asset_picker.dart +++ b/example/lib/customs/pickers/insta_asset_picker.dart @@ -751,7 +751,7 @@ final class InstaAssetPickerBuilder extends DefaultAssetPickerBuilderDelegate { padding: const EdgeInsets.all(4), color: isPreview ? theme.unselectedWidgetColor.withOpacity(.5) - : theme.colorScheme.background.withOpacity(.1), + : theme.colorScheme.surface.withOpacity(.1), child: Align( alignment: AlignmentDirectional.topEnd, child: isSelected && !isSingleAssetMode diff --git a/example/lib/l10n/app_en.arb b/example/lib/l10n/app_en.arb index 029e46a9..d11403ee 100644 --- a/example/lib/l10n/app_en.arb +++ b/example/lib/l10n/app_en.arb @@ -46,6 +46,8 @@ "pickMethodWeChatMomentDescription": "Pick assets with images or only 1 video.", "pickMethodCustomImagePreviewThumbSizeName": "Custom image preview thumb size", "pickMethodCustomImagePreviewThumbSizeDescription": "You can reduce the thumb size to get faster load speed.", + "pickMethodDisableLivePhotoName": "Disable Live-Photo", + "pickMethodDisableLivePhotoDescription": "Not showing Live-Photo's indicator and interactions.", "customPickerNotice": "This page contains customized pickers with different asset types, different UI layouts, or some use case for specific apps. Contribute to add your custom picker are welcomed.\nPickers in this page are located at the lib/customs/pickers folder.", "customPickerCallThePickerButton": "\uD83C\uDF81 Call the Picker", "customPickerDirectoryAndFileName": "Directory+File picker", @@ -57,4 +59,4 @@ "customPickerMultiTabTab3": "Images", "customPickerInstagramLayoutName": "Instagram layout picker", "customPickerInstagramLayoutDescription": "The picker reproduces Instagram layout with preview and scroll animations. It's also published as the package insta_assets_picker." -} \ No newline at end of file +} diff --git a/example/lib/l10n/app_zh.arb b/example/lib/l10n/app_zh.arb index 151514b2..149fb8c2 100644 --- a/example/lib/l10n/app_zh.arb +++ b/example/lib/l10n/app_zh.arb @@ -46,6 +46,8 @@ "pickMethodWeChatMomentDescription": "允许选择图片或仅 1 个视频。", "pickMethodCustomImagePreviewThumbSizeName": "自定义图片预览的缩略图大小", "pickMethodCustomImagePreviewThumbSizeDescription": "通过降低缩略图的质量来获得更快的加载速度。", + "pickMethodDisableLivePhotoName": "禁用实况照片 (Live-Photo)", + "pickMethodDisableLivePhotoDescription": "不显示实况照片的图标和相关交互。", "customPickerNotice": "本页面包含了多种方式、不同界面和特定应用的自定义选择器。欢迎贡献添加你自定义的选择器。\n该页面的所有选择器的代码位于 lib/customs/pickers 目录。", "customPickerCallThePickerButton": "\uD83C\uDF81 开始选择资源", "customPickerDirectoryAndFileName": "Directory+File 选择器", @@ -57,4 +59,4 @@ "customPickerMultiTabTab3": "图片", "customPickerInstagramLayoutName": "Instagram 布局的选择器", "customPickerInstagramLayoutDescription": "该选择器以 Instagram 的布局模式构建,在选择时可以同时预览。其已发布为单独的 package:insta_assets_picker。" -} \ No newline at end of file +} diff --git a/example/lib/l10n/gen/app_localizations.dart b/example/lib/l10n/gen/app_localizations.dart index fbdf6b06..faa6d7b7 100644 --- a/example/lib/l10n/gen/app_localizations.dart +++ b/example/lib/l10n/gen/app_localizations.dart @@ -374,6 +374,18 @@ abstract class AppLocalizations { /// **'You can reduce the thumb size to get faster load speed.'** String get pickMethodCustomImagePreviewThumbSizeDescription; + /// No description provided for @pickMethodDisableLivePhotoName. + /// + /// In en, this message translates to: + /// **'Disable Live-Photo'** + String get pickMethodDisableLivePhotoName; + + /// No description provided for @pickMethodDisableLivePhotoDescription. + /// + /// In en, this message translates to: + /// **'Not showing Live-Photo\'s indicator and interactions.'** + String get pickMethodDisableLivePhotoDescription; + /// No description provided for @customPickerNotice. /// /// 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 2e62e383..95638250 100644 --- a/example/lib/l10n/gen/app_localizations_en.dart +++ b/example/lib/l10n/gen/app_localizations_en.dart @@ -167,6 +167,13 @@ class AppLocalizationsEn extends AppLocalizations { String get pickMethodCustomImagePreviewThumbSizeDescription => 'You can reduce the thumb size to get faster load speed.'; + @override + String get pickMethodDisableLivePhotoName => 'Disable Live-Photo'; + + @override + String get pickMethodDisableLivePhotoDescription => + 'Not showing Live-Photo\'s indicator and interactions.'; + @override String get customPickerNotice => 'This page contains customized pickers with different asset types, different UI layouts, or some use case for specific apps. Contribute to add your custom picker are welcomed.\nPickers in this page are located at the lib/customs/pickers folder.'; diff --git a/example/lib/l10n/gen/app_localizations_zh.dart b/example/lib/l10n/gen/app_localizations_zh.dart index 5ae2326c..c6977db0 100644 --- a/example/lib/l10n/gen/app_localizations_zh.dart +++ b/example/lib/l10n/gen/app_localizations_zh.dart @@ -156,6 +156,12 @@ class AppLocalizationsZh extends AppLocalizations { String get pickMethodCustomImagePreviewThumbSizeDescription => '通过降低缩略图的质量来获得更快的加载速度。'; + @override + String get pickMethodDisableLivePhotoName => '禁用实况照片 (Live-Photo)'; + + @override + String get pickMethodDisableLivePhotoDescription => '不显示实况照片的图标和相关交互。'; + @override String get customPickerNotice => '本页面包含了多种方式、不同界面和特定应用的自定义选择器。欢迎贡献添加你自定义的选择器。\n该页面的所有选择器的代码位于 lib/customs/pickers 目录。'; diff --git a/example/lib/main.dart b/example/lib/main.dart index 8dcc30a8..c76d45f6 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -5,9 +5,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:wechat_assets_picker/wechat_assets_picker.dart'; -import 'package:wechat_assets_picker_demo/l10n/gen/app_localizations.dart'; import 'constants/extensions.dart'; +import 'l10n/gen/app_localizations.dart'; import 'pages/splash_page.dart'; const Color themeColor = Color(0xff00bc56); @@ -28,10 +28,11 @@ class MyApp extends StatelessWidget { const MyApp({super.key}); ThemeData _buildTheme(Brightness brightness) { - return ThemeData( - brightness: brightness, - primarySwatch: themeColor.swatch, - textSelectionTheme: const TextSelectionThemeData(cursorColor: themeColor), + return ThemeData.from( + colorScheme: ColorScheme.fromSwatch( + primarySwatch: themeColor.swatch, + brightness: brightness, + ), ); } diff --git a/example/lib/pages/multi_assets_page.dart b/example/lib/pages/multi_assets_page.dart index 4d0b30cc..0b82e35e 100644 --- a/example/lib/pages/multi_assets_page.dart +++ b/example/lib/pages/multi_assets_page.dart @@ -66,6 +66,7 @@ class _MultiAssetsPageState extends State PickMethod.pathNameBuilder(context, maxAssetsCount), PickMethod.customFilterOptions(context, maxAssetsCount), PickMethod.preventGIFPicked(context, maxAssetsCount), + PickMethod.disableLivePhoto(context, maxAssetsCount), PickMethod.keepScrollOffset( context: context, delegate: () => keepScrollDelegate!, diff --git a/example/lib/pages/single_assets_page.dart b/example/lib/pages/single_assets_page.dart index ff5a39fb..30e30dcd 100644 --- a/example/lib/pages/single_assets_page.dart +++ b/example/lib/pages/single_assets_page.dart @@ -49,6 +49,7 @@ class _SingleAssetPageState extends State PickMethod.multiSpecialItems(context, maxAssetsCount), PickMethod.customFilterOptions(context, maxAssetsCount), PickMethod.preventGIFPicked(context, maxAssetsCount), + PickMethod.disableLivePhoto(context, maxAssetsCount), PickMethod.noPreview(context, maxAssetsCount), PickMethod.customizableTheme(context, maxAssetsCount), PickMethod.pathNameBuilder(context, maxAssetsCount), diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 89c88aab..b264a0b9 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: wechat_assets_picker_demo description: The demo project for the wechat_assets_picker package. -version: 9.6.0+67 +version: 9.8.0+69 publish_to: none environment: @@ -19,7 +19,7 @@ dependencies: extended_image: any intl: any - package_info_plus: '>=6.0.0 <9.0.0' + package_info_plus: '>=6.0.0' path: ^1.8.0 path_provider: ^2.0.15 provider: any diff --git a/lib/src/constants/config.dart b/lib/src/constants/config.dart index feb9e87b..0efb8b75 100644 --- a/lib/src/constants/config.dart +++ b/lib/src/constants/config.dart @@ -40,6 +40,7 @@ class AssetPickerConfig { this.assetsChangeRefreshPredicate, this.shouldAutoplayPreview = false, this.dragToSelect, + this.enableLivePhoto = true, }) : assert( pickerTheme == null || themeColor == null, 'pickerTheme and themeColor cannot be set at the same time.', @@ -207,4 +208,14 @@ class AssetPickerConfig { /// 当 `maxAssets` 为 `1` 时,该功能不可用。 /// {@endtemplate} final bool? dragToSelect; + + /// {@template wechat_assets_picker.constants.AssetPickerConfig.enableLivePhoto} + /// Whether to enable Live-Photo functionality in the picker. + /// 是否启用实况图片的功能 + /// + /// When set to `false`, Live-Photo indicators and interactions will not be + /// displayed throughout the picker. + /// 当设置为 `false` 时,选择器中将不会显示实况图片相关的标识和交互。 + /// {@endtemplate} + final bool enableLivePhoto; } diff --git a/lib/src/delegates/asset_picker_builder_delegate.dart b/lib/src/delegates/asset_picker_builder_delegate.dart index 6e48074d..b3643a1d 100644 --- a/lib/src/delegates/asset_picker_builder_delegate.dart +++ b/lib/src/delegates/asset_picker_builder_delegate.dart @@ -7,7 +7,7 @@ import 'dart:math' as math; import 'dart:ui' as ui; import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide Path; import 'package:flutter/semantics.dart'; import 'package:flutter/services.dart'; import 'package:photo_manager/photo_manager.dart'; @@ -568,7 +568,7 @@ abstract class AssetPickerBuilderDelegate { builder: (_, AssetPickerProvider p, __) { if (!p.selectedAssets.contains(asset) && p.selectedMaximumAssets) { return Container( - color: theme.colorScheme.background.withOpacity(.85), + color: theme.colorScheme.surface.withOpacity(.85), ); } return const SizedBox.shrink(); @@ -851,6 +851,7 @@ class DefaultAssetPickerBuilderDelegate this.keepScrollOffset = false, this.shouldAutoplayPreview = false, this.dragToSelect, + this.enableLivePhoto = true, }) { // Add the listener if [keepScrollOffset] is true. if (keepScrollOffset) { @@ -917,6 +918,9 @@ class DefaultAssetPickerBuilderDelegate /// {@macro wechat_assets_picker.constants.AssetPickerConfig.dragToSelect} final bool? dragToSelect; + /// {@macro wechat_assets_picker.constants.AssetPickerConfig.enableLivePhoto} + final bool enableLivePhoto; + /// [Duration] when triggering path switching. /// 切换路径时的动画时长 Duration get switchingPathDuration => const Duration(milliseconds: 300); @@ -1203,6 +1207,7 @@ class DefaultAssetPickerBuilderDelegate maxAssets: p.maxAssets, shouldReversePreview: revert, shouldAutoplayPreview: shouldAutoplayPreview, + enableLivePhoto: enableLivePhoto, useRootNavigator: viewerUseRootNavigator, pageRouteSettings: viewerPageRouteSettings, pageRouteBuilder: viewerPageRouteBuilder, @@ -1933,7 +1938,8 @@ class DefaultAssetPickerBuilderDelegate ), if (asset.type == AssetType.video) // 如果为视频则显示标识 videoIndicator(context, asset), - if (asset.isLivePhoto) buildLivePhotoIndicator(context, asset), + if (enableLivePhoto && asset.isLivePhoto) + buildLivePhotoIndicator(context, asset), ], ); }, @@ -2018,7 +2024,7 @@ class DefaultAssetPickerBuilderDelegate maxHeight: MediaQuery.sizeOf(context).height * (isAppleOS(context) ? .6 : .8), ), - color: theme.colorScheme.background, + color: theme.colorScheme.surface, child: child, ), ), @@ -2349,7 +2355,7 @@ class DefaultAssetPickerBuilderDelegate p.selectedAssets.isNotEmpty); if (isDisabled) { return Container( - color: theme.colorScheme.background.withOpacity(.85), + color: theme.colorScheme.surface.withOpacity(.85), ); } return const SizedBox.shrink(); @@ -2437,7 +2443,7 @@ class DefaultAssetPickerBuilderDelegate padding: EdgeInsets.all(indicatorSize * .35), color: selected ? theme.colorScheme.primary.withOpacity(.45) - : theme.colorScheme.background.withOpacity(.1), + : theme.colorScheme.surface.withOpacity(.1), child: selected && !isSingleAssetMode ? Align( alignment: AlignmentDirectional.topStart, diff --git a/lib/src/delegates/asset_picker_delegate.dart b/lib/src/delegates/asset_picker_delegate.dart index 7f41419d..c1c403a4 100644 --- a/lib/src/delegates/asset_picker_delegate.dart +++ b/lib/src/delegates/asset_picker_delegate.dart @@ -2,10 +2,11 @@ // Use of this source code is governed by an Apache license that can be found // in the LICENSE file. -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide Path; import 'package:flutter/services.dart'; import 'package:photo_manager/photo_manager.dart'; -import 'package:wechat_picker_library/wechat_picker_library.dart'; +import 'package:wechat_picker_library/wechat_picker_library.dart' + show buildTheme; import '../constants/config.dart'; import '../constants/constants.dart'; @@ -122,6 +123,7 @@ class AssetPickerDelegate { locale: Localizations.maybeLocaleOf(context), shouldAutoplayPreview: pickerConfig.shouldAutoplayPreview, dragToSelect: pickerConfig.dragToSelect, + enableLivePhoto: pickerConfig.enableLivePhoto, ), ); final List? result = await Navigator.maybeOf( @@ -246,112 +248,6 @@ class AssetPickerDelegate { /// 设置 [light] 为 true 时可以获取浅色版本的主题。 /// {@endtemplate} ThemeData themeData(Color? themeColor, {bool light = false}) { - themeColor ??= defaultThemeColorWeChat; - if (light) { - return ThemeData.light().copyWith( - primaryColor: Colors.grey[50], - primaryColorLight: Colors.grey[50], - primaryColorDark: Colors.grey[50], - canvasColor: Colors.grey[100], - scaffoldBackgroundColor: Colors.grey[50], - cardColor: Colors.grey[50], - highlightColor: Colors.transparent, - textSelectionTheme: TextSelectionThemeData( - cursorColor: themeColor, - selectionColor: themeColor.withAlpha(100), - selectionHandleColor: themeColor, - ), - indicatorColor: themeColor, - appBarTheme: AppBarTheme( - backgroundColor: Colors.grey[100], - systemOverlayStyle: const SystemUiOverlayStyle( - statusBarBrightness: Brightness.light, - statusBarIconBrightness: Brightness.dark, - ), - iconTheme: IconThemeData(color: Colors.grey[900]), - elevation: 0, - ), - bottomAppBarTheme: BottomAppBarTheme( - color: Colors.grey[100], - ), - buttonTheme: ButtonThemeData(buttonColor: themeColor), - iconTheme: IconThemeData(color: Colors.grey[900]), - checkboxTheme: CheckboxThemeData( - checkColor: MaterialStateProperty.all(Colors.black), - fillColor: MaterialStateProperty.resolveWith((states) { - if (states.contains(MaterialState.selected)) { - return themeColor; - } - return null; - }), - side: const BorderSide(color: Colors.black), - ), - colorScheme: ColorScheme( - primary: Colors.grey[50]!, - secondary: themeColor, - background: Colors.grey[50]!, - surface: Colors.grey[50]!, - brightness: Brightness.light, - error: const Color(0xffcf6679), - onPrimary: Colors.white, - onSecondary: Colors.grey[100]!, - onSurface: Colors.black, - onBackground: Colors.black, - onError: Colors.white, - ), - ); - } - return ThemeData.dark().copyWith( - primaryColor: Colors.grey[900], - primaryColorLight: Colors.grey[900], - primaryColorDark: Colors.grey[900], - canvasColor: Colors.grey[850], - scaffoldBackgroundColor: Colors.grey[900], - cardColor: Colors.grey[900], - highlightColor: Colors.transparent, - textSelectionTheme: TextSelectionThemeData( - cursorColor: themeColor, - selectionColor: themeColor.withAlpha(100), - selectionHandleColor: themeColor, - ), - indicatorColor: themeColor, - appBarTheme: AppBarTheme( - backgroundColor: Colors.grey[850], - systemOverlayStyle: const SystemUiOverlayStyle( - statusBarBrightness: Brightness.dark, - statusBarIconBrightness: Brightness.light, - ), - iconTheme: const IconThemeData(color: Colors.white), - elevation: 0, - ), - bottomAppBarTheme: BottomAppBarTheme( - color: Colors.grey[850], - ), - buttonTheme: ButtonThemeData(buttonColor: themeColor), - iconTheme: const IconThemeData(color: Colors.white), - checkboxTheme: CheckboxThemeData( - checkColor: MaterialStateProperty.all(Colors.white), - fillColor: MaterialStateProperty.resolveWith((states) { - if (states.contains(MaterialState.selected)) { - return themeColor; - } - return null; - }), - side: const BorderSide(color: Colors.white), - ), - colorScheme: ColorScheme( - primary: Colors.grey[900]!, - secondary: themeColor, - background: Colors.grey[900]!, - surface: Colors.grey[900]!, - brightness: Brightness.dark, - error: const Color(0xffcf6679), - onPrimary: Colors.black, - onSecondary: Colors.grey[850]!, - onSurface: Colors.white, - onBackground: Colors.white, - onError: Colors.black, - ), - ); + return buildTheme(themeColor, light: light); } } diff --git a/lib/src/delegates/asset_picker_text_delegate.dart b/lib/src/delegates/asset_picker_text_delegate.dart index 7f36f8be..ca21fa8e 100644 --- a/lib/src/delegates/asset_picker_text_delegate.dart +++ b/lib/src/delegates/asset_picker_text_delegate.dart @@ -26,9 +26,12 @@ const assetPickerTextDelegates = [ ]; /// Obtain the text delegate from the given locale. -AssetPickerTextDelegate assetPickerTextDelegateFromLocale(Locale? locale) { +AssetPickerTextDelegate assetPickerTextDelegateFromLocale( + Locale? locale, { + AssetPickerTextDelegate fallback = const AssetPickerTextDelegate(), +}) { if (locale == null) { - return const AssetPickerTextDelegate(); + return fallback; } final String languageCode = locale.languageCode; @@ -39,7 +42,7 @@ AssetPickerTextDelegate assetPickerTextDelegateFromLocale(Locale? locale) { (e) => e.languageCode == languageCode, ); if (matchedByLanguage.isEmpty) { - return const AssetPickerTextDelegate(); + return fallback; } final matchedByScript = scriptCode != null diff --git a/lib/src/delegates/asset_picker_viewer_builder_delegate.dart b/lib/src/delegates/asset_picker_viewer_builder_delegate.dart index 8fc3816d..af8b4d3f 100644 --- a/lib/src/delegates/asset_picker_viewer_builder_delegate.dart +++ b/lib/src/delegates/asset_picker_viewer_builder_delegate.dart @@ -6,7 +6,7 @@ import 'dart:async'; import 'dart:math' as math; import 'package:extended_image/extended_image.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide Path; import 'package:flutter/semantics.dart'; import 'package:flutter/services.dart'; import 'package:photo_manager/photo_manager.dart'; @@ -376,12 +376,16 @@ class DefaultAssetPickerViewerBuilderDelegate< super.shouldReversePreview, super.selectPredicate, this.shouldAutoplayPreview = false, + this.enableLivePhoto = true, }); /// Provider for [AssetPicker]. /// 资源选择器的状态保持 final P? selectorProvider; + /// {@macro wechat_assets_picker.constants.AssetPickerConfig.enableLivePhoto} + final bool enableLivePhoto; + /// Thumb size for the preview of images in the viewer. /// 预览时图片的缩略图大小 final ThumbnailSize? previewThumbnailSize; @@ -468,6 +472,7 @@ class DefaultAssetPickerViewerBuilderDelegate< delegate: this, previewThumbnailSize: previewThumbnailSize, shouldAutoplayPreview: shouldAutoplayPreview, + enableLivePhoto: enableLivePhoto, ), AssetType.video => VideoPageBuilder( asset: asset, diff --git a/lib/src/provider/asset_picker_provider.dart b/lib/src/provider/asset_picker_provider.dart index 043e5670..e3dae85e 100644 --- a/lib/src/provider/asset_picker_provider.dart +++ b/lib/src/provider/asset_picker_provider.dart @@ -7,7 +7,7 @@ import 'dart:io'; import 'dart:math' as math; import 'dart:typed_data'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide Path; import 'package:photo_manager/photo_manager.dart'; import 'package:provider/provider.dart'; diff --git a/lib/src/widget/asset_picker.dart b/lib/src/widget/asset_picker.dart index e5176b17..fd7cb03c 100644 --- a/lib/src/widget/asset_picker.dart +++ b/lib/src/widget/asset_picker.dart @@ -5,7 +5,7 @@ import 'dart:async'; import 'dart:io'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide Path; import 'package:flutter/services.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:wechat_picker_library/wechat_picker_library.dart'; diff --git a/lib/src/widget/asset_picker_app_bar.dart b/lib/src/widget/asset_picker_app_bar.dart index 3f1c51b8..a79daea7 100644 --- a/lib/src/widget/asset_picker_app_bar.dart +++ b/lib/src/widget/asset_picker_app_bar.dart @@ -105,7 +105,7 @@ class AssetPickerAppBar extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); - final AppBarTheme appBarTheme = theme.appBarTheme; + final appBarTheme = theme.appBarTheme; final Widget? titleWidget; if (centerTitle) { diff --git a/lib/src/widget/asset_picker_viewer.dart b/lib/src/widget/asset_picker_viewer.dart index 6034b2bd..9800429f 100644 --- a/lib/src/widget/asset_picker_viewer.dart +++ b/lib/src/widget/asset_picker_viewer.dart @@ -4,7 +4,7 @@ import 'dart:async'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide Path; import 'package:photo_manager/photo_manager.dart'; import '../constants/constants.dart'; @@ -49,6 +49,7 @@ class AssetPickerViewer< bool shouldReversePreview = false, AssetSelectPredicate? selectPredicate, bool shouldAutoplayPreview = false, + bool enableLivePhoto = true, bool useRootNavigator = false, RouteSettings? pageRouteSettings, AssetPickerViewerPageRouteBuilder>? pageRouteBuilder, @@ -82,6 +83,7 @@ class AssetPickerViewer< shouldReversePreview: shouldReversePreview, selectPredicate: selectPredicate, shouldAutoplayPreview: shouldAutoplayPreview, + enableLivePhoto: enableLivePhoto, ), ); final result = await Navigator.maybeOf( diff --git a/lib/src/widget/builder/audio_page_builder.dart b/lib/src/widget/builder/audio_page_builder.dart index 5dc43ef1..b8ff3dfd 100644 --- a/lib/src/widget/builder/audio_page_builder.dart +++ b/lib/src/widget/builder/audio_page_builder.dart @@ -201,7 +201,7 @@ class _AudioPageBuilderState extends State { onLongPressHint: Singleton.textDelegate.semanticsTextDelegate.sActionPlayHint, child: ColoredBox( - color: context.theme.colorScheme.background, + color: context.theme.colorScheme.surface, child: isLoaded ? Column( mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/src/widget/builder/image_page_builder.dart b/lib/src/widget/builder/image_page_builder.dart index d596d892..229735f1 100644 --- a/lib/src/widget/builder/image_page_builder.dart +++ b/lib/src/widget/builder/image_page_builder.dart @@ -25,6 +25,7 @@ class ImagePageBuilder extends StatefulWidget { required this.delegate, this.previewThumbnailSize, this.shouldAutoplayPreview = false, + this.enableLivePhoto = true, }); /// Asset currently displayed. @@ -40,6 +41,9 @@ class ImagePageBuilder extends StatefulWidget { /// 预览是否自动播放 final bool shouldAutoplayPreview; + /// {@macro wechat_assets_picker.constants.AssetPickerConfig.enableLivePhoto} + final bool enableLivePhoto; + @override State createState() => _ImagePageBuilderState(); } @@ -50,7 +54,10 @@ class _ImagePageBuilderState extends State { bool get _isOriginal => widget.previewThumbnailSize == null; - bool get _isLivePhoto => widget.asset.isLivePhoto; + /// Whether the asset should be treated as a Live Photo. + /// Checks both the global enableLivePhoto flag and the asset's isLivePhoto + /// property to determine if Live Photo functionality should be active. + bool get _isLivePhoto => widget.enableLivePhoto && widget.asset.isLivePhoto; @override void didUpdateWidget(ImagePageBuilder oldWidget) { diff --git a/pubspec.yaml b/pubspec.yaml index ccff4254..6a9037f9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: wechat_assets_picker -version: 9.6.0 +version: 9.8.0 description: | An image picker (also with videos and audio) for Flutter projects based on WeChat's UI, @@ -22,7 +22,7 @@ dependencies: flutter: sdk: flutter - wechat_picker_library: ^1.0.5 + wechat_picker_library: ^1.0.7 extended_image: '>=8.3.0 <11.0.0' photo_manager: ^3.5.0 diff --git a/test/config_test.dart b/test/config_test.dart index e3fd590e..f3416c10 100644 --- a/test/config_test.dart +++ b/test/config_test.dart @@ -33,4 +33,21 @@ void main() { expect(find.text('testPathNameBuilder'), findsOneWidget); }); }); + + group('enableLivePhoto', () { + test('defaults to true', () { + const config = AssetPickerConfig(); + expect(config.enableLivePhoto, true); + }); + + test('can be set to false', () { + const config = AssetPickerConfig(enableLivePhoto: false); + expect(config.enableLivePhoto, false); + }); + + test('can be set to true explicitly', () { + const config = AssetPickerConfig(enableLivePhoto: true); + expect(config.enableLivePhoto, true); + }); + }); } diff --git a/test/test_utils.dart b/test/test_utils.dart index 0edc4fe1..82d3d279 100644 --- a/test/test_utils.dart +++ b/test/test_utils.dart @@ -159,7 +159,7 @@ class TestAssetPickerDelegate extends AssetPickerDelegate { } } -const AssetEntity testAssetEntity = AssetEntity( +final AssetEntity testAssetEntity = AssetEntity( id: 'test', typeInt: 0, width: 0, From e6e0b35475d8db84086deb2867ea9b095a08f683 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sat, 22 Nov 2025 18:39:49 +0800 Subject: [PATCH 19/29] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Improve=20imports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/constants/config.dart | 2 +- lib/src/constants/constants.dart | 8 ++++---- lib/src/constants/typedefs.dart | 9 +++++---- .../asset_grid_drag_selection_coordinator.dart | 4 ++-- lib/src/delegates/asset_picker_builder_delegate.dart | 11 ++++++----- lib/src/delegates/asset_picker_delegate.dart | 4 ++-- lib/src/delegates/asset_picker_text_delegate.dart | 4 ++-- .../asset_picker_viewer_builder_delegate.dart | 10 +++++----- lib/src/delegates/sort_path_delegate.dart | 2 +- lib/src/internals/singleton.dart | 2 +- lib/src/models/path_wrapper.dart | 8 ++++---- lib/src/models/special_item.dart | 5 +++-- lib/src/provider/asset_picker_provider.dart | 11 +++++------ lib/src/provider/asset_picker_viewer_provider.dart | 2 +- lib/src/widget/asset_picker.dart | 8 ++++---- lib/src/widget/asset_picker_app_bar.dart | 12 +++++++----- pubspec.yaml | 1 + 17 files changed, 54 insertions(+), 49 deletions(-) diff --git a/lib/src/constants/config.dart b/lib/src/constants/config.dart index 0efb8b75..4f73980e 100644 --- a/lib/src/constants/config.dart +++ b/lib/src/constants/config.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by an Apache license that can be found // in the LICENSE file. -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' show Color, ThemeData; import 'package:photo_manager/photo_manager.dart'; import '../constants/typedefs.dart'; diff --git a/lib/src/constants/constants.dart b/lib/src/constants/constants.dart index f81990c0..8af22605 100644 --- a/lib/src/constants/constants.dart +++ b/lib/src/constants/constants.dart @@ -2,12 +2,12 @@ // Use of this source code is governed by an Apache license that can be found // in the LICENSE file. -import 'package:photo_manager/photo_manager.dart'; +import 'package:photo_manager/photo_manager.dart' show ThumbnailSize; -const String packageName = 'wechat_assets_picker'; +const packageName = 'wechat_assets_picker'; const int defaultAssetsPerPage = 80; const int defaultMaxAssetsCount = 9; -const ThumbnailSize defaultAssetGridPreviewSize = ThumbnailSize.square(200); -const ThumbnailSize defaultPathThumbnailSize = ThumbnailSize.square(80); +const defaultAssetGridPreviewSize = ThumbnailSize.square(200); +const defaultPathThumbnailSize = ThumbnailSize.square(80); diff --git a/lib/src/constants/typedefs.dart b/lib/src/constants/typedefs.dart index 5e0ff68e..c07e84b4 100644 --- a/lib/src/constants/typedefs.dart +++ b/lib/src/constants/typedefs.dart @@ -2,12 +2,13 @@ // Use of this source code is governed by an Apache license that can be found // in the LICENSE file. -import 'dart:async'; +import 'dart:async' show FutureOr; -import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; +import 'package:flutter/foundation.dart' show ChangeNotifier; +import 'package:flutter/services.dart' show MethodCall; +import 'package:flutter/widgets.dart' show BuildContext, Widget; import 'package:photo_manager/photo_manager.dart' show PermissionState; -import 'package:provider/provider.dart'; +import 'package:provider/provider.dart' show ChangeNotifierProvider; /// Mirroring [ChangeNotifierProvider]. typedef CNP = ChangeNotifierProvider; diff --git a/lib/src/delegates/asset_grid_drag_selection_coordinator.dart b/lib/src/delegates/asset_grid_drag_selection_coordinator.dart index a422a3cf..4ef83900 100644 --- a/lib/src/delegates/asset_grid_drag_selection_coordinator.dart +++ b/lib/src/delegates/asset_grid_drag_selection_coordinator.dart @@ -2,9 +2,9 @@ // Use of this source code is governed by an Apache license that can be found // in the LICENSE file. -import 'dart:math' as math; +import 'dart:math' as math show max, min; -import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:photo_manager/photo_manager.dart' show AssetEntity; import '../provider/asset_picker_provider.dart'; diff --git a/lib/src/delegates/asset_picker_builder_delegate.dart b/lib/src/delegates/asset_picker_builder_delegate.dart index b3643a1d..831d0e3c 100644 --- a/lib/src/delegates/asset_picker_builder_delegate.dart +++ b/lib/src/delegates/asset_picker_builder_delegate.dart @@ -2,17 +2,18 @@ // Use of this source code is governed by an Apache license that can be found // in the LICENSE file. -import 'dart:async'; +import 'dart:async' show Completer; import 'dart:math' as math; +import 'dart:typed_data' show Uint8List; import 'dart:ui' as ui; -import 'package:flutter/gestures.dart'; +import 'package:flutter/gestures.dart' show TapGestureRecognizer; import 'package:flutter/material.dart' hide Path; -import 'package:flutter/semantics.dart'; -import 'package:flutter/services.dart'; +import 'package:flutter/semantics.dart' show OrdinalSortKey; +import 'package:flutter/services.dart' show MethodCall, SystemUiOverlayStyle; import 'package:photo_manager/photo_manager.dart'; import 'package:photo_manager_image_provider/photo_manager_image_provider.dart'; -import 'package:provider/provider.dart'; +import 'package:provider/provider.dart' show Consumer, ReadContext, Selector; import 'package:wechat_picker_library/wechat_picker_library.dart'; import '../constants/constants.dart'; diff --git a/lib/src/delegates/asset_picker_delegate.dart b/lib/src/delegates/asset_picker_delegate.dart index c1c403a4..aa7f86b9 100644 --- a/lib/src/delegates/asset_picker_delegate.dart +++ b/lib/src/delegates/asset_picker_delegate.dart @@ -3,13 +3,13 @@ // in the LICENSE file. import 'package:flutter/material.dart' hide Path; -import 'package:flutter/services.dart'; +import 'package:flutter/services.dart' show MethodCall; import 'package:photo_manager/photo_manager.dart'; import 'package:wechat_picker_library/wechat_picker_library.dart' show buildTheme; import '../constants/config.dart'; -import '../constants/constants.dart'; +import '../constants/constants.dart' show packageName; import '../provider/asset_picker_provider.dart'; import '../widget/asset_picker.dart'; import '../widget/asset_picker_page_route.dart'; diff --git a/lib/src/delegates/asset_picker_text_delegate.dart b/lib/src/delegates/asset_picker_text_delegate.dart index ca21fa8e..c91eff33 100644 --- a/lib/src/delegates/asset_picker_text_delegate.dart +++ b/lib/src/delegates/asset_picker_text_delegate.dart @@ -3,9 +3,9 @@ // in the LICENSE file. import 'dart:io' show Platform; +import 'dart:ui' show Locale; -import 'package:flutter/foundation.dart'; -import 'package:flutter/rendering.dart'; +import 'package:meta/meta.dart' show nonVirtual; import 'package:photo_manager/photo_manager.dart' show AssetType; /// All text delegates. diff --git a/lib/src/delegates/asset_picker_viewer_builder_delegate.dart b/lib/src/delegates/asset_picker_viewer_builder_delegate.dart index af8b4d3f..d10a7c2c 100644 --- a/lib/src/delegates/asset_picker_viewer_builder_delegate.dart +++ b/lib/src/delegates/asset_picker_viewer_builder_delegate.dart @@ -2,16 +2,16 @@ // Use of this source code is governed by an Apache license that can be found // in the LICENSE file. -import 'dart:async'; -import 'dart:math' as math; +import 'dart:async' show StreamController; +import 'dart:math' as math show max; import 'package:extended_image/extended_image.dart'; import 'package:flutter/material.dart' hide Path; -import 'package:flutter/semantics.dart'; -import 'package:flutter/services.dart'; +import 'package:flutter/semantics.dart' show OrdinalSortKey; +import 'package:flutter/services.dart' show SystemUiOverlayStyle; import 'package:photo_manager/photo_manager.dart'; import 'package:photo_manager_image_provider/photo_manager_image_provider.dart'; -import 'package:provider/provider.dart'; +import 'package:provider/provider.dart' show Consumer, Selector; import 'package:wechat_picker_library/wechat_picker_library.dart'; import '../constants/custom_scroll_physics.dart'; diff --git a/lib/src/delegates/sort_path_delegate.dart b/lib/src/delegates/sort_path_delegate.dart index ba5b3477..aca98265 100644 --- a/lib/src/delegates/sort_path_delegate.dart +++ b/lib/src/delegates/sort_path_delegate.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by an Apache license that can be found // in the LICENSE file. -import 'package:photo_manager/photo_manager.dart'; +import 'package:photo_manager/photo_manager.dart' show AssetPathEntity; import '../models/path_wrapper.dart'; diff --git a/lib/src/internals/singleton.dart b/lib/src/internals/singleton.dart index fbdc9d69..fa329495 100644 --- a/lib/src/internals/singleton.dart +++ b/lib/src/internals/singleton.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by an Apache license that can be found // in the LICENSE file. -import 'package:flutter/widgets.dart'; +import 'package:flutter/widgets.dart' show ScrollPosition; import '../delegates/asset_picker_text_delegate.dart'; import '../delegates/sort_path_delegate.dart'; diff --git a/lib/src/models/path_wrapper.dart b/lib/src/models/path_wrapper.dart index ebe0a925..7be6ec78 100644 --- a/lib/src/models/path_wrapper.dart +++ b/lib/src/models/path_wrapper.dart @@ -2,9 +2,9 @@ // Use of this source code is governed by an Apache license that can be found // in the LICENSE file. -import 'dart:typed_data' as typed_data; +import 'dart:typed_data' show Uint8List; -import 'package:flutter/foundation.dart'; +import 'package:meta/meta.dart' show immutable; /// A wrapper that holds [Path] with it's nullable (late initialize) fields. /// @@ -39,7 +39,7 @@ class PathWrapper { /// See also: /// * [AssetEntity.thumbnailData] API document: /// https://pub.dev/documentation/photo_manager/latest/photo_manager/AssetEntity/thumbnailData.html - final typed_data.Uint8List? thumbnailData; + final Uint8List? thumbnailData; /// Creates a modified copy of the object. /// @@ -47,7 +47,7 @@ class PathWrapper { /// the same value of the current object. PathWrapper copyWith({ int? assetCount, - typed_data.Uint8List? thumbnailData, + Uint8List? thumbnailData, }) { return PathWrapper( path: path, diff --git a/lib/src/models/special_item.dart b/lib/src/models/special_item.dart index fcc1e6e9..9914a4be 100644 --- a/lib/src/models/special_item.dart +++ b/lib/src/models/special_item.dart @@ -1,4 +1,5 @@ -import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart' show Widget; +import 'package:meta/meta.dart' show immutable; import '../constants/enums.dart'; import '../constants/typedefs.dart'; @@ -40,6 +41,6 @@ final class SpecialItemFinalized { @override String toString() { - return 'SpecialItemFinalized$Path(position: $position, item: $item)'; + return 'SpecialItemFinalized(position: $position, item: $item)'; } } diff --git a/lib/src/provider/asset_picker_provider.dart b/lib/src/provider/asset_picker_provider.dart index e3dae85e..de453713 100644 --- a/lib/src/provider/asset_picker_provider.dart +++ b/lib/src/provider/asset_picker_provider.dart @@ -2,14 +2,13 @@ // Use of this source code is governed by an Apache license that can be found // in the LICENSE file. -import 'dart:async'; -import 'dart:io'; -import 'dart:math' as math; -import 'dart:typed_data'; +import 'dart:async' show Completer; +import 'dart:io' as io show Platform; +import 'dart:math' as math show max; +import 'dart:typed_data' show Uint8List; import 'package:flutter/material.dart' hide Path; import 'package:photo_manager/photo_manager.dart'; -import 'package:provider/provider.dart'; import '../constants/constants.dart'; import '../delegates/sort_path_delegate.dart'; @@ -364,7 +363,7 @@ class DefaultAssetPickerProvider createTimeCond: DateTimeCond.def().copyWith(ignore: true), updateTimeCond: DateTimeCond.def().copyWith(ignore: true), )..merge(fog); - } else if (fog == null && Platform.isAndroid) { + } else if (fog == null && io.Platform.isAndroid) { options = AdvancedCustomFilter( orderBy: [OrderByItem.desc(CustomColumns.android.modifiedDate)], ); diff --git a/lib/src/provider/asset_picker_viewer_provider.dart b/lib/src/provider/asset_picker_viewer_provider.dart index c070bfb2..8e00fb51 100644 --- a/lib/src/provider/asset_picker_viewer_provider.dart +++ b/lib/src/provider/asset_picker_viewer_provider.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by an Apache license that can be found // in the LICENSE file. -import 'package:flutter/widgets.dart'; +import 'package:flutter/foundation.dart' show ChangeNotifier; import '../constants/constants.dart'; diff --git a/lib/src/widget/asset_picker.dart b/lib/src/widget/asset_picker.dart index fd7cb03c..d15207a3 100644 --- a/lib/src/widget/asset_picker.dart +++ b/lib/src/widget/asset_picker.dart @@ -2,11 +2,11 @@ // Use of this source code is governed by an Apache license that can be found // in the LICENSE file. -import 'dart:async'; -import 'dart:io'; +import 'dart:async' show Completer; +import 'dart:io' as io show Platform; import 'package:flutter/material.dart' hide Path; -import 'package:flutter/services.dart'; +import 'package:flutter/services.dart' show MethodCall; import 'package:photo_manager/photo_manager.dart'; import 'package:wechat_picker_library/wechat_picker_library.dart'; @@ -139,7 +139,7 @@ class AssetPickerState Brightness.dark, + Brightness.dark => Brightness.light, + }, statusBarBrightness: effectiveBrightness, ); child = AnnotatedRegion( diff --git a/pubspec.yaml b/pubspec.yaml index 6a9037f9..bbede003 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -25,6 +25,7 @@ dependencies: wechat_picker_library: ^1.0.7 extended_image: '>=8.3.0 <11.0.0' + meta: ^1.16.0 photo_manager: ^3.5.0 photo_manager_image_provider: ^2.2.0 provider: ^6.0.5 From 5c257bb4aad79678544ce37e031895dc9752ac81 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sat, 22 Nov 2025 18:46:44 +0800 Subject: [PATCH 20/29] =?UTF-8?q?=E2=AC=87=EF=B8=8F=20Fix=20`package:meta`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index bbede003..90738b1e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -25,7 +25,7 @@ dependencies: wechat_picker_library: ^1.0.7 extended_image: '>=8.3.0 <11.0.0' - meta: ^1.16.0 + meta: ^1.12.0 photo_manager: ^3.5.0 photo_manager_image_provider: ^2.2.0 provider: ^6.0.5 From 57c23350755f60bd302a45ab754aeb126466e7a6 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sat, 22 Nov 2025 18:59:44 +0800 Subject: [PATCH 21/29] =?UTF-8?q?=F0=9F=94=A7=20Update=20example=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/ios/Runner.xcodeproj/project.pbxproj | 18 ++++++ example/l10n.yaml | 1 - example/macos/.gitignore | 1 + .../macos/Runner.xcodeproj/project.pbxproj | 60 +++++++++++++------ .../xcshareddata/xcschemes/Runner.xcscheme | 18 ++++++ 5 files changed, 79 insertions(+), 19 deletions(-) diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index a29b9171..f3e4c54f 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -140,6 +140,7 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 1D5C0E6B95AC1C77343F74BB /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -205,6 +206,23 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 1D5C0E6B95AC1C77343F74BB /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; diff --git a/example/l10n.yaml b/example/l10n.yaml index 0a6804e5..f6387db6 100644 --- a/example/l10n.yaml +++ b/example/l10n.yaml @@ -4,5 +4,4 @@ output-class: AppLocalizations output-dir: lib/l10n/gen output-localization-file: app_localizations.dart template-arb-file: app_en.arb -synthetic-package: false untranslated-messages-file: lib/l10n/gen/app_localizations_untranslated.json diff --git a/example/macos/.gitignore b/example/macos/.gitignore index 746adbb6..87587fb7 100644 --- a/example/macos/.gitignore +++ b/example/macos/.gitignore @@ -5,3 +5,4 @@ # Xcode-related **/dgph **/xcuserdata/ +**/DerivedData/ diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index 21d484a0..81f15b6f 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; 6A1A28F233A58C36E6489F41 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0774F837B8E25B8DC1EAEF89 /* Pods_Runner.framework */; }; + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -69,6 +70,7 @@ 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 67D3AABA709D7751F62A6E47 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 7DF57D4CB2C37AFB79D9C8DE /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; @@ -80,6 +82,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, 6A1A28F233A58C36E6489F41 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -131,6 +134,7 @@ 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, @@ -183,7 +187,7 @@ 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, - 29AC8FA20939B6D1043A3E82 /* [CP] Embed Pods Frameworks */, + 38EBFE9479A716840F91016E /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -191,6 +195,9 @@ 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; + packageProductDependencies = ( + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, + ); productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* Wechat Assets Picker Example.app */; productType = "com.apple.product-type.application"; @@ -231,6 +238,9 @@ Base, ); mainGroup = 33CC10E42044A3C60003C045; + packageReferences = ( + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -254,23 +264,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 29AC8FA20939B6D1043A3E82 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -309,6 +302,23 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + 38EBFE9479A716840F91016E /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; E3F5FF31C2EA33CA437CA11E /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -640,6 +650,20 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { + isa = XCSwiftPackageProductDependency; + productName = FlutterGeneratedPluginSwiftPackage; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 38d43bf5..6307dfc9 100644 --- a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -5,6 +5,24 @@ + + + + + + + + + + Date: Sat, 22 Nov 2025 19:00:28 +0800 Subject: [PATCH 22/29] =?UTF-8?q?=F0=9F=92=9A=20Log=20git=20diff=20before?= =?UTF-8?q?=20dry-run?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/runnable.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/runnable.yml b/.github/workflows/runnable.yml index 54a30901..b4d0838c 100644 --- a/.github/workflows/runnable.yml +++ b/.github/workflows/runnable.yml @@ -48,7 +48,9 @@ jobs: run: flutter test - name: Publish dry-run if: matrix.flutter-version != 'min' - run: dart pub publish --dry-run + run: | + git --no-pager diff + dart pub publish --dry-run - name: Generate docs if: matrix.flutter-version != 'min' run: | From cc582eca0208b78ec04e6aaf23fa28a9e7f7b537 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sat, 22 Nov 2025 19:25:30 +0800 Subject: [PATCH 23/29] =?UTF-8?q?=F0=9F=94=A5=20Remove=20`example/macos/Fl?= =?UTF-8?q?utter/GeneratedPluginRegistrant.swift`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/macos/.gitignore | 1 + .../Flutter/GeneratedPluginRegistrant.swift | 18 ------------------ 2 files changed, 1 insertion(+), 18 deletions(-) delete mode 100644 example/macos/Flutter/GeneratedPluginRegistrant.swift diff --git a/example/macos/.gitignore b/example/macos/.gitignore index 87587fb7..06f360d8 100644 --- a/example/macos/.gitignore +++ b/example/macos/.gitignore @@ -1,6 +1,7 @@ # Flutter-related **/Flutter/ephemeral/ **/Pods/ +Flutter/GeneratedPluginRegistrant.* # Xcode-related **/dgph diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift deleted file mode 100644 index 42579396..00000000 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// Generated file. Do not edit. -// - -import FlutterMacOS -import Foundation - -import package_info_plus -import path_provider_foundation -import photo_manager -import video_player_avfoundation - -func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) - PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) - PhotoManagerPlugin.register(with: registry.registrar(forPlugin: "PhotoManagerPlugin")) - FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) -} From 5c324b2b9243c4729d15d3985ef4ff18fdeff947 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sat, 22 Nov 2025 19:59:17 +0800 Subject: [PATCH 24/29] =?UTF-8?q?=F0=9F=9A=80=20Roll=20Android=20build=20s?= =?UTF-8?q?ystem?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/android/.gitignore | 2 + example/android/app/build.gradle | 88 ------------------- example/android/app/build.gradle.kts | 82 +++++++++++++++++ example/android/build.gradle | 18 ---- example/android/build.gradle.kts | 24 +++++ example/android/gradle.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- example/android/settings.gradle | 26 ------ example/android/settings.gradle.kts | 27 ++++++ 9 files changed, 137 insertions(+), 134 deletions(-) delete mode 100644 example/android/app/build.gradle create mode 100644 example/android/app/build.gradle.kts delete mode 100644 example/android/build.gradle create mode 100644 example/android/build.gradle.kts delete mode 100644 example/android/settings.gradle create mode 100644 example/android/settings.gradle.kts diff --git a/example/android/.gitignore b/example/android/.gitignore index bc2100d8..8d07eca0 100644 --- a/example/android/.gitignore +++ b/example/android/.gitignore @@ -1,5 +1,7 @@ gradle-wrapper.jar /.gradle +/.kotlin +/app/.cxx /captures/ /gradlew /gradlew.bat diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle deleted file mode 100644 index 6a5a0f94..00000000 --- a/example/android/app/build.gradle +++ /dev/null @@ -1,88 +0,0 @@ -plugins { - id "com.android.application" - id "kotlin-android" - id "kotlin-kapt" - // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. - id "dev.flutter.flutter-gradle-plugin" -} - -def flutterVersionCode = null -def flutterVersionName = null -if (flutter.hasProperty('versionCode')) { - flutterVersionCode = flutter.versionCode -} -if (flutter.hasProperty('versionName')) { - flutterVersionName = flutter.versionName -} - -if (flutterVersionCode == null || flutterVersionName == null) { - def localProperties = new Properties() - rootProject.file('local.properties').withReader('UTF-8') { reader -> - localProperties.load(reader) - } - - if (flutterVersionCode == null) { - flutterVersionCode = localProperties.getProperty('flutter.versionCode') - } - if (flutterVersionName == null) { - flutterVersionName = localProperties.getProperty('flutter.versionName') - } -} - -android { - namespace "com.fluttercandies.wechatAssetsPickerExample" - compileSdk flutter.compileSdkVersion - ndkVersion flutter.ndkVersion - - defaultConfig { - applicationId = "com.fluttercandies.wechatAssetsPickerExample" - minSdk 21 - targetSdk flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - - kotlinOptions { - jvmTarget = '17' - } - - compileOptions { - // Sets Java compatibility to Java 17 - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } - - signingConfigs { - forAll { - storeFile file("${rootDir.absolutePath}/key.jks") - storePassword 'picker' - keyAlias 'picker' - keyPassword 'picker' - } - } - - buildTypes { - debug { - signingConfig signingConfigs.forAll - } - profile { - signingConfig signingConfigs.forAll - } - release { - signingConfig signingConfigs.forAll - } - } -} - -flutter { - source '../..' -} - -dependencies { - implementation 'com.github.bumptech.glide:glide:4.15.0' - kapt 'com.github.bumptech.glide:compiler:4.15.0' -} diff --git a/example/android/app/build.gradle.kts b/example/android/app/build.gradle.kts new file mode 100644 index 00000000..c745658f --- /dev/null +++ b/example/android/app/build.gradle.kts @@ -0,0 +1,82 @@ +plugins { + id("com.android.application") + id("kotlin-android") + id("kotlin-kapt") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +val packageName = "com.fluttercandies.wechatAssetsPickerExample" + +android { + namespace = packageName + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + defaultConfig { + applicationId = packageName + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + compileOptions { + // Sets Java compatibility to Java 17 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + sourceSets { + getByName("main") { + java.srcDirs("src/main/kotlin") + } + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } + + packaging { + dex { + useLegacyPackaging = true + } + jniLibs { + useLegacyPackaging = true + } + } + + signingConfigs { + create("forAll") { + storeFile = file("${rootDir.absolutePath}/key.jks") + storePassword = "picker" + keyAlias = "picker" + keyPassword = "picker" + enableV1Signing = true + enableV2Signing = true + enableV3Signing = true + enableV4Signing = true + } + } + + buildTypes { + getByName("debug") { + signingConfig = signingConfigs.getByName("forAll") + } + getByName("profile") { + signingConfig = signingConfigs.getByName("forAll") + } + getByName("release") { + signingConfig = signingConfigs.getByName("forAll") + } + } +} + +flutter { + source = "../.." +} + +dependencies { + implementation("com.github.bumptech.glide:glide:4.15.0") + kapt("com.github.bumptech.glide:compiler:4.15.0") +} diff --git a/example/android/build.gradle b/example/android/build.gradle deleted file mode 100644 index bc157bd1..00000000 --- a/example/android/build.gradle +++ /dev/null @@ -1,18 +0,0 @@ -allprojects { - repositories { - google() - mavenCentral() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -tasks.register("clean", Delete) { - delete rootProject.buildDir -} diff --git a/example/android/build.gradle.kts b/example/android/build.gradle.kts new file mode 100644 index 00000000..dbee657b --- /dev/null +++ b/example/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 25971708..f018a618 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -1,3 +1,3 @@ -org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index e6045a98..20014ed3 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip diff --git a/example/android/settings.gradle b/example/android/settings.gradle deleted file mode 100644 index d4ac21c4..00000000 --- a/example/android/settings.gradle +++ /dev/null @@ -1,26 +0,0 @@ -pluginManagement { - def flutterSdkPath = { - def properties = new Properties() - file("local.properties").withInputStream { properties.load(it) } - def flutterSdkPath = properties.getProperty("flutter.sdk") - assert flutterSdkPath != null, "flutter.sdk not set in local.properties" - return flutterSdkPath - }() - - includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") - - repositories { - google() - mavenCentral() - gradlePluginPortal() - } -} - -plugins { - id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.9.1" apply false - id "com.android.library" version "8.9.1" apply false - id "org.jetbrains.kotlin.android" version "2.1.20" apply false -} - -include ":app" diff --git a/example/android/settings.gradle.kts b/example/android/settings.gradle.kts new file mode 100644 index 00000000..39e191e2 --- /dev/null +++ b/example/android/settings.gradle.kts @@ -0,0 +1,27 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.13.0" apply false + id("org.jetbrains.kotlin.android") version "2.2.0" apply false + id("org.jetbrains.kotlin.kapt") version "2.2.0" apply false +} + +include(":app") From 855cc5c5d0c540dc08b052ec935ffd07bca303e8 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sat, 22 Nov 2025 20:13:53 +0800 Subject: [PATCH 25/29] =?UTF-8?q?=F0=9F=94=A5=20Remove=20SPM=20configs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/ios/Runner.xcodeproj/project.pbxproj | 22 ----------------- .../macos/Runner.xcodeproj/project.pbxproj | 24 ------------------- 2 files changed, 46 deletions(-) diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index f3e4c54f..5cdd67ba 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -11,7 +11,6 @@ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 513FA762C8C9C7254396DC7C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65B67F314727E97056D6FB02 /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -55,7 +54,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, 513FA762C8C9C7254396DC7C /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -147,9 +145,6 @@ dependencies = ( ); name = Runner; - packageProductDependencies = ( - 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, - ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; @@ -179,9 +174,6 @@ Base, ); mainGroup = 97C146E51CF9000F007C117D; - packageReferences = ( - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, - ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; @@ -564,20 +556,6 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ - -/* Begin XCLocalSwiftPackageReference section */ - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { - isa = XCLocalSwiftPackageReference; - relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; - }; -/* End XCLocalSwiftPackageReference section */ - -/* Begin XCSwiftPackageProductDependency section */ - 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { - isa = XCSwiftPackageProductDependency; - productName = FlutterGeneratedPluginSwiftPackage; - }; -/* End XCSwiftPackageProductDependency section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index 81f15b6f..d5d5c78e 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -27,7 +27,6 @@ 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; 6A1A28F233A58C36E6489F41 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0774F837B8E25B8DC1EAEF89 /* Pods_Runner.framework */; }; - 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -70,7 +69,6 @@ 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 67D3AABA709D7751F62A6E47 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 7DF57D4CB2C37AFB79D9C8DE /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; @@ -82,7 +80,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, 6A1A28F233A58C36E6489F41 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -134,7 +131,6 @@ 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( - 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, @@ -195,9 +191,6 @@ 33CC11202044C79F0003C045 /* PBXTargetDependency */, ); name = Runner; - packageProductDependencies = ( - 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, - ); productName = Runner; productReference = 33CC10ED2044A3C60003C045 /* Wechat Assets Picker Example.app */; productType = "com.apple.product-type.application"; @@ -238,9 +231,6 @@ Base, ); mainGroup = 33CC10E42044A3C60003C045; - packageReferences = ( - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, - ); productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -650,20 +640,6 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ - -/* Begin XCLocalSwiftPackageReference section */ - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { - isa = XCLocalSwiftPackageReference; - relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; - }; -/* End XCLocalSwiftPackageReference section */ - -/* Begin XCSwiftPackageProductDependency section */ - 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { - isa = XCSwiftPackageProductDependency; - productName = FlutterGeneratedPluginSwiftPackage; - }; -/* End XCSwiftPackageProductDependency section */ }; rootObject = 33CC10E52044A3C60003C045 /* Project object */; } From 1e79dd2e6e6d42f2e1812bd574376f6f3d8e98f3 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sun, 23 Nov 2025 00:09:53 +0800 Subject: [PATCH 26/29] =?UTF-8?q?=F0=9F=91=B7=20Try=20to=20compatible=20wi?= =?UTF-8?q?th=20versions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/android/app/build.gradle.kts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/example/android/app/build.gradle.kts b/example/android/app/build.gradle.kts index c745658f..9e356757 100644 --- a/example/android/app/build.gradle.kts +++ b/example/android/app/build.gradle.kts @@ -6,6 +6,21 @@ plugins { id("dev.flutter.flutter-gradle-plugin") } +fun Any.safeGetVersionProperty(name: String): Any? { + val klass = this::class.java + runCatching { + return klass.getMethod(name).invoke(this) + } + runCatching { + val getterName = "get" + name.capitalize() + return klass.getMethod(getterName).invoke(this) + } + runCatching { + return klass.getField(name).get(this) + } + return null +} + val packageName = "com.fluttercandies.wechatAssetsPickerExample" android { @@ -17,8 +32,8 @@ android { applicationId = packageName minSdk = flutter.minSdkVersion targetSdk = flutter.targetSdkVersion - versionCode = flutter.versionCode - versionName = flutter.versionName + versionCode = flutter.safeGetVersionProperty("versionCode") as Int? + versionName = flutter.safeGetVersionProperty("versionName") as String? } compileOptions { From 31c13a54c0950ec7803ff5ba8937d7d4444c604d Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sun, 23 Nov 2025 00:50:29 +0800 Subject: [PATCH 27/29] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Bump=20minimum=20Flu?= =?UTF-8?q?tter=20version=20to=203.27?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/runnable.yml | 2 +- README-ZH.md | 14 +++++++------- README.md | 14 +++++++------- example/android/app/build.gradle.kts | 2 +- example/pubspec.yaml | 4 ++-- pubspec.yaml | 4 ++-- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/runnable.yml b/.github/workflows/runnable.yml index b4d0838c..c9999758 100644 --- a/.github/workflows/runnable.yml +++ b/.github/workflows/runnable.yml @@ -13,7 +13,7 @@ on: - main env: - MINIMUM_FLUTTER_VERSION: '3.22.3' + MINIMUM_FLUTTER_VERSION: '3.27.4' jobs: analyze: diff --git a/README-ZH.md b/README-ZH.md index f29f92aa..e7cbb9af 100644 --- a/README-ZH.md +++ b/README-ZH.md @@ -34,13 +34,13 @@ Language: [English](README.md) | 中文 该插件仅保证能与 **stable 渠道的 Flutter SDK** 配合使用。 我们不会为其他渠道的 Flutter SDK 做实时支持。 -| | 3.7 | 3.10 | 3.13 | 3.16 | 3.22 | -|--------|:---:|:----:|:----:|:----:|:----:| -| 9.5.0+ | ❌ | ❌ | ❌ | ❌ | ✅ | -| 8.9.0+ | ❌ | ❌ | ❌ | ✅ | ❌ | -| 8.7.0+ | ❌ | ❌ | ✅ | ❌ | ❌ | -| 8.5.0+ | ❌ | ✅ | ❌ | ❌ | ❌ | -| 8.4.0+ | ✅ | ❌ | ❌ | ❌ | ❌ | +| | 3.10 | 3.13 | 3.16 | 3.22 | 3.27 | +|---------|:----:|:----:|:----:|:----:|:----:| +| 10.0.0+ | ❌ | ❌ | ❌ | ❌ | ✅ | +| 9.5.0+ | ❌ | ❌ | ❌ | ✅ | ✅ | +| 8.9.0+ | ❌ | ❌ | ✅ | ❌ | ❌ | +| 8.7.0+ | ❌ | ✅ | ❌ | ❌ | ❌ | +| 8.5.0+ | ✅ | ❌ | ❌ | ❌ | ❌ | 如果在 `flutter pub get` 时遇到了 `resolve conflict` 失败问题, 请使用 `dependency_overrides` 解决。 diff --git a/README.md b/README.md index 94d0ac8a..a19d73a7 100644 --- a/README.md +++ b/README.md @@ -36,13 +36,13 @@ See the [Migration Guide][] to learn how to migrate between breaking changes. The package only guarantees to be working on **the stable version of Flutter**. We won't update it in real-time to align with other channels of Flutter. -| | 3.7 | 3.10 | 3.13 | 3.16 | 3.22 | -|--------|:---:|:----:|:----:|:----:|:----:| -| 9.5.0+ | ❌ | ❌ | ❌ | ❌ | ✅ | -| 8.9.0+ | ❌ | ❌ | ❌ | ✅ | ❌ | -| 8.7.0+ | ❌ | ❌ | ✅ | ❌ | ❌ | -| 8.5.0+ | ❌ | ✅ | ❌ | ❌ | ❌ | -| 8.4.0+ | ✅ | ❌ | ❌ | ❌ | ❌ | +| | 3.10 | 3.13 | 3.16 | 3.22 | 3.27 | +|---------|:----:|:----:|:----:|:----:|:----:| +| 10.0.0+ | ❌ | ❌ | ❌ | ❌ | ✅ | +| 9.5.0+ | ❌ | ❌ | ❌ | ✅ | ✅ | +| 8.9.0+ | ❌ | ❌ | ✅ | ❌ | ❌ | +| 8.7.0+ | ❌ | ✅ | ❌ | ❌ | ❌ | +| 8.5.0+ | ✅ | ❌ | ❌ | ❌ | ❌ | If you got a `resolve conflict` error when running `flutter pub get`, please use `dependency_overrides` to fix it. diff --git a/example/android/app/build.gradle.kts b/example/android/app/build.gradle.kts index 9e356757..0c87e0b1 100644 --- a/example/android/app/build.gradle.kts +++ b/example/android/app/build.gradle.kts @@ -26,7 +26,7 @@ val packageName = "com.fluttercandies.wechatAssetsPickerExample" android { namespace = packageName compileSdk = flutter.compileSdkVersion - ndkVersion = flutter.ndkVersion + ndkVersion = "27.0.12077973" defaultConfig { applicationId = packageName diff --git a/example/pubspec.yaml b/example/pubspec.yaml index b264a0b9..8c4bfe1e 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -4,8 +4,8 @@ version: 9.8.0+69 publish_to: none environment: - sdk: ^3.4.0 - flutter: '>=3.22.0' + sdk: ^3.6.0 + flutter: '>=3.27.0' dependencies: flutter: diff --git a/pubspec.yaml b/pubspec.yaml index 90738b1e..3dec6e25 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,8 +15,8 @@ repository: https://github.com/fluttercandies/flutter_wechat_assets_picker issue_tracker: https://github.com/fluttercandies/flutter_wechat_assets_picker/issues environment: - sdk: ^3.4.0 - flutter: '>=3.22.0' + sdk: ^3.6.0 + flutter: '>=3.27.0' dependencies: flutter: From 41839bd74591853861c6b6aa0e345b4688236a45 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sun, 23 Nov 2025 01:06:00 +0800 Subject: [PATCH 28/29] =?UTF-8?q?=F0=9F=94=A5=20Remove=20deprecated=20meth?= =?UTF-8?q?ods?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/delegates/asset_picker_builder_delegate.dart | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lib/src/delegates/asset_picker_builder_delegate.dart b/lib/src/delegates/asset_picker_builder_delegate.dart index 831d0e3c..d4fba059 100644 --- a/lib/src/delegates/asset_picker_builder_delegate.dart +++ b/lib/src/delegates/asset_picker_builder_delegate.dart @@ -198,9 +198,6 @@ abstract class AssetPickerBuilderDelegate { /// 权限受限栏的高度 double get permissionLimitedBarHeight => isPermissionLimited ? 75 : 0; - @Deprecated('Use permissionNotifier instead. This will be removed in 10.0.0') - ValueNotifier get permission => permissionNotifier; - /// Notifier for the current [PermissionState]. /// 当前 [PermissionState] 的监听 late final permissionNotifier = ValueNotifier( @@ -714,12 +711,6 @@ abstract class AssetPickerBuilderDelegate { ); } - /// The overlay when the permission is limited on iOS. - @Deprecated('Use permissionOverlay instead. This will be removed in 10.0.0') - Widget iOSPermissionOverlay(BuildContext context) { - return permissionOverlay(context); - } - /// The overlay when the permission is limited. Widget permissionOverlay(BuildContext context) { final Size size = MediaQuery.sizeOf(context); From f1536c33617c0a0ff726fd1c13f74cd5b4409136 Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sun, 23 Nov 2025 01:09:56 +0800 Subject: [PATCH 29/29] =?UTF-8?q?=F0=9F=94=96=2010.0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 13 ++++++++++--- example/pubspec.yaml | 2 +- pubspec.yaml | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c6f8400..b9d3783c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,14 +9,21 @@ that can be found in the LICENSE file. --> ## Unreleased -**New features** +*None*. -- Support multiple append/prepend specials items. +## 10.0.0 -**Improvements** +**Breaking changes** +- Migrate to Flutter 3.27 and drop support for previous Flutter versions. - Make delegate respect generic types as much as possible. This is a breaking change for users who use custom delegates and providers. + See the migration guide for more details. +- Remove deprecated methods. + +**New features** + +- Support multiple append/prepend specials items. ## 9.8.0 diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 8c4bfe1e..f832eaec 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: wechat_assets_picker_demo description: The demo project for the wechat_assets_picker package. -version: 9.8.0+69 +version: 10.0.0+70 publish_to: none environment: diff --git a/pubspec.yaml b/pubspec.yaml index 3dec6e25..a78be860 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: wechat_assets_picker -version: 9.8.0 +version: 10.0.0 description: | An image picker (also with videos and audio) for Flutter projects based on WeChat's UI,