diff --git a/CHANGELOG.md b/CHANGELOG.md index f8fe8b62..e0ad0166 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,10 @@ that can be found in the LICENSE file. --> ## Unreleased -*None.* +**Fixes** + +- Fix picker and viewer selection syncing when selecting the last allowed asset. +- Fix Live Photo visibility callbacks updating video state after the widget is disposed. ## 10.1.2 diff --git a/lib/src/delegates/asset_picker_viewer_builder_delegate.dart b/lib/src/delegates/asset_picker_viewer_builder_delegate.dart index 103f6eb9..ba7969b3 100644 --- a/lib/src/delegates/asset_picker_viewer_builder_delegate.dart +++ b/lib/src/delegates/asset_picker_viewer_builder_delegate.dart @@ -251,7 +251,9 @@ abstract class AssetPickerViewerBuilderDelegate - selectedAssets != null && selectedAssets!.length == maxAssets; + selectedAssets != null && + maxAssets != null && + selectedAssets!.length >= maxAssets!; /// Construct a notifier to notify /// whether if a new asset is selected or unselected. @@ -268,11 +270,11 @@ abstract class AssetPickerViewerBuilderDelegate maxAssets!) { + if (selectedMaximumAssets && !(selectedAssets?.contains(asset) ?? false)) { return; } provider?.selectAsset(asset); - if (!isSelectedPreviewing) { + if (!isSelectedPreviewing && !(selectedAssets?.contains(asset) ?? false)) { selectedAssets?.add(asset); } selectedNotifier.value = selectedCount; diff --git a/lib/src/provider/asset_picker_provider.dart b/lib/src/provider/asset_picker_provider.dart index f7fa83e1..67d88e63 100644 --- a/lib/src/provider/asset_picker_provider.dart +++ b/lib/src/provider/asset_picker_provider.dart @@ -234,17 +234,17 @@ abstract class AssetPickerProvider extends ChangeNotifier { bool get isSelectedNotEmpty => selectedAssets.isNotEmpty; /// 是否已经选择了最大数量的资源 - bool get selectedMaximumAssets => selectedAssets.length == maxAssets; + bool get selectedMaximumAssets => selectedAssets.length >= maxAssets; /// Select asset. /// 选中资源 void selectAsset(Asset item) { - if (selectedAssets.length == maxAssets) { + if (selectedAssets.contains(item)) { + notifyListeners(); return; } - if (selectedAssets.contains(item)) { - notifyListeners(); + if (selectedAssets.length >= maxAssets) { return; } diff --git a/lib/src/provider/asset_picker_viewer_provider.dart b/lib/src/provider/asset_picker_viewer_provider.dart index 8e00fb51..976c1c32 100644 --- a/lib/src/provider/asset_picker_viewer_provider.dart +++ b/lib/src/provider/asset_picker_viewer_provider.dart @@ -42,7 +42,7 @@ class AssetPickerViewerProvider extends ChangeNotifier { /// Select asset. /// 选中资源 void selectAsset(Asset item) { - if (currentlySelectedAssets.length == maxAssets || + if (currentlySelectedAssets.length >= maxAssets || currentlySelectedAssets.contains(item)) { return; } diff --git a/lib/src/widget/builder/image_page_builder.dart b/lib/src/widget/builder/image_page_builder.dart index 229735f1..2efd36da 100644 --- a/lib/src/widget/builder/image_page_builder.dart +++ b/lib/src/widget/builder/image_page_builder.dart @@ -217,6 +217,9 @@ class _LivePhotoWidgetState extends State<_LivePhotoWidget> { } void _handleScrollNotification(ScrollNotification notification) { + if (!mounted) { + return; + } if (notification is ScrollStartNotification) { _scrolling = true; } else if (notification is ScrollEndNotification) { @@ -225,6 +228,9 @@ class _LivePhotoWidgetState extends State<_LivePhotoWidget> { } void _onVisibilityChanged(VisibilityInfo info) { + if (!mounted) { + return; + } final fraction = info.visibleFraction; if (fraction == 1 && !_showVideo.value && !_scrolling) { _showVideoAndPlay(); @@ -234,15 +240,21 @@ class _LivePhotoWidgetState extends State<_LivePhotoWidget> { } Future _notify() async { - if (_controller.value.position >= _controller.value.duration) { - await _controller.pause(); - await _controller.seekTo(Duration.zero); + if (!mounted || _controller.value.position < _controller.value.duration) { + return; + } + await _controller.pause(); + if (!mounted) { + return; + } + await _controller.seekTo(Duration.zero); + if (mounted) { _showVideo.value = false; } } Future _showVideoAndPlay() async { - if (_controller.value.isPlaying) { + if (!mounted || _controller.value.isPlaying) { return; } HapticFeedback.lightImpact(); @@ -251,9 +263,18 @@ class _LivePhotoWidgetState extends State<_LivePhotoWidget> { } Future _hideVideoAndStop() async { + if (!mounted) { + return; + } _showVideo.value = false; await Future.delayed(kThemeChangeDuration); + if (!mounted) { + return; + } await _controller.pause(); + if (!mounted) { + return; + } await _controller.seekTo(Duration.zero); } diff --git a/test/delegates_test.dart b/test/delegates_test.dart index 6ae1386a..f33dbc1f 100644 --- a/test/delegates_test.dart +++ b/test/delegates_test.dart @@ -2,10 +2,12 @@ // Use of this source code is governed by an Apache license that can be found // in the LICENSE file. -import 'package:flutter/rendering.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:wechat_assets_picker/wechat_assets_picker.dart'; +import 'test_utils.dart'; + void main() { group(AssetPickerTextDelegate, () { test('returns the default when available', () { @@ -60,4 +62,83 @@ void main() { (PathWrapper e) => e.path.name == 'Screenshots', ); }); + + group(AssetPickerViewerBuilderDelegate, () { + test('notifies the picker provider when selecting the last allowed asset', + () { + final DefaultAssetPickerProvider selectorProvider = + DefaultAssetPickerProvider.forTest(maxAssets: 1); + addTearDown(selectorProvider.dispose); + int notificationCount = 0; + selectorProvider.addListener(() { + notificationCount++; + }); + final AssetPickerViewerProvider viewerProvider = + AssetPickerViewerProvider( + selectorProvider.selectedAssets, + maxAssets: selectorProvider.maxAssets, + ); + addTearDown(viewerProvider.dispose); + final DefaultAssetPickerViewerBuilderDelegate< + AssetPickerViewerProvider, + DefaultAssetPickerProvider> delegate = + DefaultAssetPickerViewerBuilderDelegate< + AssetPickerViewerProvider, + DefaultAssetPickerProvider>( + currentIndex: 0, + previewAssets: [testAssetEntity], + themeData: ThemeData(), + provider: viewerProvider, + selectedAssets: selectorProvider.selectedAssets, + selectorProvider: selectorProvider, + maxAssets: selectorProvider.maxAssets, + ); + + delegate.selectAsset(testAssetEntity); + + expect(selectorProvider.selectedAssets, [testAssetEntity]); + expect( + viewerProvider.currentlySelectedAssets, + [testAssetEntity], + ); + expect(notificationCount, 1); + }); + + test('does not add unselected assets beyond maxAssets', () { + final AssetEntity extraAsset = AssetEntity( + id: 'test-extra', + typeInt: 0, + width: 0, + height: 0, + ); + final List selectedAssets = [testAssetEntity]; + final AssetPickerViewerProvider viewerProvider = + AssetPickerViewerProvider( + selectedAssets, + maxAssets: 1, + ); + addTearDown(viewerProvider.dispose); + final DefaultAssetPickerViewerBuilderDelegate< + AssetPickerViewerProvider, + DefaultAssetPickerProvider> delegate = + DefaultAssetPickerViewerBuilderDelegate< + AssetPickerViewerProvider, + DefaultAssetPickerProvider>( + currentIndex: 0, + previewAssets: [testAssetEntity, extraAsset], + themeData: ThemeData(), + provider: viewerProvider, + selectedAssets: selectedAssets, + maxAssets: 1, + ); + + delegate.selectAsset(extraAsset); + + expect(selectedAssets, [testAssetEntity]); + expect( + viewerProvider.currentlySelectedAssets, + [testAssetEntity], + ); + }); + }); }