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/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/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/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'; diff --git a/lib/src/delegates/asset_picker_builder_delegate.dart b/lib/src/delegates/asset_picker_builder_delegate.dart index aa85ec03..121ecefe 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. @@ -736,7 +735,7 @@ abstract class AssetPickerBuilderDelegate { } } -class DefaultAssetPickerBuilderDelegate +class DefaultAssetPickerBuilderDelegate extends AssetPickerBuilderDelegate { DefaultAssetPickerBuilderDelegate({ required this.provider, @@ -769,7 +768,7 @@ class DefaultAssetPickerBuilderDelegate /// [ChangeNotifier] for asset picker. /// 资源选择器状态保持 - final DefaultAssetPickerProvider provider; + final T provider; /// Thumbnail size in the grid. /// 预览时网络的缩略图大小 @@ -877,8 +876,7 @@ class DefaultAssetPickerBuilderDelegate if (selectPredicateResult == false) { return; } - final DefaultAssetPickerProvider provider = - context.read(); + final provider = context.read(); if (selected) { provider.unSelectAsset(asset); return; @@ -1010,7 +1008,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 +1075,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 +1112,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 +1163,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 +1205,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 +1224,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 +1345,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 +1403,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 +1473,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 +1566,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; @@ -1656,13 +1652,13 @@ class DefaultAssetPickerBuilderDelegate ); } - /// It'll pop with [AssetPickerProvider.selectedAssets] + /// It'll pop with [T.selectedAssets] /// when there are any assets were chosen. /// 当有资源已选时,点击按钮将把已选资源通过路由返回。 @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 +1869,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 +1945,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 +2039,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 +2054,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 +2102,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 +2115,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 +2148,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 +2170,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 +2236,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 +2431,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_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/delegates/asset_picker_viewer_builder_delegate.dart b/lib/src/delegates/asset_picker_viewer_builder_delegate.dart index e2c5f1fc..9e992063 100644 --- a/lib/src/delegates/asset_picker_viewer_builder_delegate.dart +++ b/lib/src/delegates/asset_picker_viewer_builder_delegate.dart @@ -28,12 +28,12 @@ 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, required this.currentIndex, - this.selectorProvider, this.provider, this.selectedAssets, this.maxAssets, @@ -45,7 +45,7 @@ abstract class AssetPickerViewerBuilderDelegate { /// [ChangeNotifier] for photo selector viewer. /// 资源预览器的状态保持 - final AssetPickerViewerProvider? provider; + final Provider? provider; /// Assets provided to preview. /// 提供预览的资源 @@ -59,10 +59,6 @@ abstract class AssetPickerViewerBuilderDelegate { /// 已选的资源 final List? selectedAssets; - /// Provider for [AssetPicker]. - /// 资源选择器的状态保持 - final AssetPickerProvider? selectorProvider; - /// Whether the preview sequence is reversed. /// 预览时顺序是否为反向 /// @@ -94,7 +90,7 @@ abstract class AssetPickerViewerBuilderDelegate { /// The [State] for a viewer. /// 预览器的状态实例 - late AssetPickerViewerState viewerState; + late AssetPickerViewerState viewerState; /// [AnimationController] for double tap animation. /// 双击缩放的动画控制器 @@ -164,9 +160,8 @@ abstract class AssetPickerViewerBuilderDelegate { /// Call when viewer is calling [State.initState]. /// 当预览器调用 [State.initState] 时注册 [State]。 @mustCallSuper - void initStateAndTicker( - covariant AssetPickerViewerState state, - TickerProvider v, // TODO(Alex): Remove this in the next major version. + void initState( + covariant AssetPickerViewerState state, ) { initAnimations(state); } @@ -180,9 +175,9 @@ 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); @@ -206,7 +201,9 @@ 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), @@ -264,7 +261,6 @@ abstract class AssetPickerViewerBuilderDelegate { void unSelectAsset(Asset entity) { provider?.unSelectAsset(entity); - selectorProvider?.unSelectAsset(entity); if (!isSelectedPreviewing) { selectedAssets?.remove(entity); } @@ -276,7 +272,6 @@ abstract class AssetPickerViewerBuilderDelegate { return; } provider?.selectAsset(entity); - selectorProvider?.selectAsset(entity); if (!isSelectedPreviewing) { selectedAssets?.add(entity); } @@ -316,12 +311,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. /// 根据资源类型使用不同的构建页 @@ -377,15 +367,17 @@ 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, required super.themeData, - super.selectorProvider, super.provider, super.selectedAssets, + this.selectorProvider, this.previewThumbnailSize, this.specialPickerType, super.maxAssets, @@ -394,9 +386,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 +401,10 @@ class DefaultAssetPickerViewerBuilderDelegate /// 如果类型不为空,则标题将不会显示。 final SpecialPickerType? specialPickerType; + /// Whether the preview should auto play. + /// 预览是否自动播放 + final bool shouldAutoplayPreview; + /// Whether the [SpecialPickerType.wechatMoment] is enabled. /// 当前是否为微信朋友圈选择模式 bool get isWeChatMoment => @@ -422,41 +418,36 @@ class DefaultAssetPickerViewerBuilderDelegate false); @override - Widget assetPageBuilder(BuildContext context, int index) { - final AssetEntity asset = previewAssets.elementAt( + 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, ); - 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: ( - BuildContext c, - AssetPickerViewerProvider? p, - Widget? w, - ) { + child: Consumer( + builder: (context, T? p, child) { final bool isSelected = (p?.currentlySelectedAssets ?? selectedAssets)?.contains(asset) ?? false; @@ -476,14 +467,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) { @@ -573,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), + ], + ), + ), + ], ), ); } @@ -681,10 +699,8 @@ class DefaultAssetPickerViewerBuilderDelegate onTap: () { onTap(asset); }, - child: Selector?, - List?>( - selector: (_, AssetPickerViewerProvider? p) => - p?.currentlySelectedAssets, + child: Selector?>( + selector: (_, T? p) => p?.currentlySelectedAssets, child: item, builder: ( _, @@ -787,86 +803,82 @@ class DefaultAssetPickerViewerBuilderDelegate /// 资源选择器将识别并一同返回。 @override Widget confirmButton(BuildContext context) { - return CNP?>.value( - value: provider, - child: Consumer?>( - builder: (_, AssetPickerViewerProvider? 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; + }(), + ), + ); + }, ); } @@ -923,63 +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, - List>( - 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, + ), + ], + ), + ); + }, + ); + }, ); } @@ -992,7 +1000,7 @@ class DefaultAssetPickerViewerBuilderDelegate : const CustomBouncingScrollPhysics(), controller: pageController, itemCount: previewAssets.length, - itemBuilder: assetPageBuilder, + itemBuilder: assetSemanticsBuilder, onPageChanged: (int index) { currentIndex = index; pageStreamController.add(index); @@ -1003,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), + ], + ], + ), + ), ), ), ), 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.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/lib/src/widget/asset_picker_viewer.dart b/lib/src/widget/asset_picker_viewer.dart index fe30d20e..8edabcfa 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,53 +79,60 @@ 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() { super.initState(); - builder.initStateAndTicker(this, this); + builder.initState(this); } @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..ba92b0e6 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; @@ -118,6 +120,7 @@ class _ImagePageBuilderState extends State { ); if (_isLivePhoto && _livePhotoVideoController != null) { return _LivePhotoWidget( + asset: asset, controller: _livePhotoVideoController!, fit: BoxFit.contain, state: state, @@ -159,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; 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] 且只有一个视频 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(