diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8c359adab..0fb3f7929 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
+## [v1.11.3] - 2024-06-17
+
+### Added
+
+- handle `MediaStore.ACTION_REVIEW` intent
+
## [v1.11.2] - 2024-06-11
### Added
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index e0a82ddb2..67ad18af7 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -142,6 +142,7 @@
+
@@ -161,6 +162,7 @@
+
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt
index 5c4903840..96dd14303 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt
@@ -10,6 +10,7 @@ import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
+import android.provider.MediaStore
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.content.pm.ShortcutInfoCompat
@@ -299,6 +300,7 @@ open class MainActivity : FlutterFragmentActivity() {
Intent.ACTION_VIEW,
Intent.ACTION_SEND,
+ MediaStore.ACTION_REVIEW,
"com.android.camera.action.REVIEW",
"com.android.camera.action.SPLIT_SCREEN_REVIEW" -> {
(intent.data ?: intent.getParcelableExtraCompat(Intent.EXTRA_STREAM))?.let { uri ->
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt
index a96f84e90..5ac512f91 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt
@@ -340,7 +340,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
}
ioScope.launch {
- provider.fetchSingle(context, uri, mimeType, object : ImageProvider.ImageOpCallback {
+ provider.fetchSingle(context, uri, mimeType, false, object : ImageProvider.ImageOpCallback {
override fun onSuccess(fields: FieldMap) {
resultFields.putAll(fields)
result.success(resultFields)
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchObjectHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchObjectHandler.kt
index 941928831..be5696a03 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchObjectHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchObjectHandler.kt
@@ -29,6 +29,7 @@ class MediaFetchObjectHandler(private val context: Context) : MethodCallHandler
private fun getEntry(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument("mimeType") // MIME type is optional
val uri = call.argument("uri")?.let { Uri.parse(it) }
+ val allowUnsized = call.argument("allowUnsized") ?: false
if (uri == null) {
result.error("getEntry-args", "missing arguments", null)
return
@@ -40,7 +41,7 @@ class MediaFetchObjectHandler(private val context: Context) : MethodCallHandler
return
}
- provider.fetchSingle(context, uri, mimeType, object : ImageOpCallback {
+ provider.fetchSingle(context, uri, mimeType, allowUnsized, object : ImageOpCallback {
override fun onSuccess(fields: FieldMap) = result.success(fields)
override fun onFailure(throwable: Throwable) = result.error("getEntry-failure", "failed to get entry for uri=$uri mimeType=$mimeType", throwable.message)
})
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/FileImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/FileImageProvider.kt
index cfa0c34e8..bc45c1c2c 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/FileImageProvider.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/FileImageProvider.kt
@@ -12,7 +12,7 @@ import deckers.thibault.aves.utils.LogUtils
import java.io.File
internal class FileImageProvider : ImageProvider() {
- override fun fetchSingle(context: Context, uri: Uri, sourceMimeType: String?, callback: ImageOpCallback) {
+ override fun fetchSingle(context: Context, uri: Uri, sourceMimeType: String?, allowUnsized: Boolean, callback: ImageOpCallback) {
var mimeType = sourceMimeType
if (mimeType == null) {
@@ -54,7 +54,7 @@ internal class FileImageProvider : ImageProvider() {
}
entry.fillPreCatalogMetadata(context)
- if (entry.isSized || entry.isSvg || entry.isVideo) {
+ if (allowUnsized || entry.isSized || entry.isSvg || entry.isVideo) {
callback.onSuccess(entry.toMap())
} else {
callback.onFailure(Exception("entry has no size"))
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt
index d406f12fd..be8dc5270 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt
@@ -70,7 +70,7 @@ import java.util.TimeZone
import kotlin.math.absoluteValue
abstract class ImageProvider {
- open fun fetchSingle(context: Context, uri: Uri, sourceMimeType: String?, callback: ImageOpCallback) {
+ open fun fetchSingle(context: Context, uri: Uri, sourceMimeType: String?, allowUnsized: Boolean, callback: ImageOpCallback) {
callback.onFailure(UnsupportedOperationException("`fetchSingle` is not supported by this image provider"))
}
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt
index 87c166c28..faa55eef7 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt
@@ -89,7 +89,7 @@ class MediaStoreImageProvider : ImageProvider() {
// the provided URI can point to the wrong media collection,
// e.g. a GIF image with the URI `content://media/external/video/media/[ID]`
// so the effective entry URI may not match the provided URI
- override fun fetchSingle(context: Context, uri: Uri, sourceMimeType: String?, callback: ImageOpCallback) {
+ override fun fetchSingle(context: Context, uri: Uri, sourceMimeType: String?, allowUnsized: Boolean, callback: ImageOpCallback) {
var found = false
val fetched = arrayListOf()
val id = uri.tryParseId()
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/UnknownContentProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/UnknownContentProvider.kt
index 8fc4e6db0..ffcc2400f 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/UnknownContentProvider.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/UnknownContentProvider.kt
@@ -17,7 +17,7 @@ open class UnknownContentProvider : ImageProvider() {
open val reliableProviderMimeType: Boolean
get() = false
- override fun fetchSingle(context: Context, uri: Uri, sourceMimeType: String?, callback: ImageOpCallback) {
+ override fun fetchSingle(context: Context, uri: Uri, sourceMimeType: String?, allowUnsized: Boolean, callback: ImageOpCallback) {
var mimeType = sourceMimeType
if (sourceMimeType == null || !reliableProviderMimeType) {
// source MIME type may be incorrect, so we get a second opinion if possible
@@ -71,7 +71,7 @@ open class UnknownContentProvider : ImageProvider() {
}
val entry = SourceEntry(fields).fillPreCatalogMetadata(context)
- if (entry.isSized || entry.isSvg || entry.isVideo) {
+ if (allowUnsized || entry.isSized || entry.isSvg || entry.isVideo) {
callback.onSuccess(entry.toMap())
} else {
callback.onFailure(Exception("entry has no size"))
diff --git a/android/build.gradle b/android/build.gradle
index 87bd1337b..90a76ffec 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -1,6 +1,6 @@
buildscript {
ext {
- agp_version = '8.4.1' // same as `settings.ext.agp_version` in `/android/settings.gradle`
+ agp_version = '8.5.0' // same as `settings.ext.agp_version` in `/android/settings.gradle`
glide_version = '4.16.0'
// AppGallery Connect plugin versions: https://developer.huawei.com/consumer/en/doc/development/AppGallery-connect-Guides/agc-sdk-changenotes-0000001058732550
huawei_agconnect_version = '1.9.1.300'
diff --git a/android/settings.gradle b/android/settings.gradle
index 293bc6423..5ad529b31 100644
--- a/android/settings.gradle
+++ b/android/settings.gradle
@@ -10,7 +10,7 @@ pluginManagement {
settings.ext.kotlin_version = '1.9.24'
settings.ext.ksp_version = "$kotlin_version-1.0.20"
- settings.ext.agp_version = '8.4.1'
+ settings.ext.agp_version = '8.5.0'
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
diff --git a/fastlane/metadata/android/en-US/changelogs/122.txt b/fastlane/metadata/android/en-US/changelogs/122.txt
new file mode 100644
index 000000000..dfb8b7c93
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/122.txt
@@ -0,0 +1,3 @@
+In v1.11.3:
+- show selected albums together in Collection
+Full changelog available on GitHub
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/12201.txt b/fastlane/metadata/android/en-US/changelogs/12201.txt
new file mode 100644
index 000000000..dfb8b7c93
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/12201.txt
@@ -0,0 +1,3 @@
+In v1.11.3:
+- show selected albums together in Collection
+Full changelog available on GitHub
\ No newline at end of file
diff --git a/lib/model/source/media_store_source.dart b/lib/model/source/media_store_source.dart
index 1b1e4c318..d616dbe98 100644
--- a/lib/model/source/media_store_source.dart
+++ b/lib/model/source/media_store_source.dart
@@ -360,7 +360,7 @@ class MediaStoreSource extends CollectionSource {
existingDirectories.add(existingDirectory);
}
} else {
- final sourceEntry = await mediaFetchService.getEntry(uri, null);
+ final sourceEntry = await mediaFetchService.getEntry(uri, null, allowUnsized: true);
if (sourceEntry != null) {
newEntries.add(sourceEntry.copyWith(
id: metadataDb.nextId,
diff --git a/lib/model/source/trash.dart b/lib/model/source/trash.dart
index 0249da2d4..98393b0b1 100644
--- a/lib/model/source/trash.dart
+++ b/lib/model/source/trash.dart
@@ -67,7 +67,7 @@ mixin TrashMixin on SourceBase {
await metadataDb.updateTrash(id, entry.trashDetails);
} else {
// there is no matching entry
- final sourceEntry = await mediaFetchService.getEntry(uri, null);
+ final sourceEntry = await mediaFetchService.getEntry(uri, null, allowUnsized: true);
if (sourceEntry != null) {
final id = metadataDb.nextId;
sourceEntry.id = id;
diff --git a/lib/model/vaults/vaults.dart b/lib/model/vaults/vaults.dart
index 90a442dab..1491a9ec0 100644
--- a/lib/model/vaults/vaults.dart
+++ b/lib/model/vaults/vaults.dart
@@ -166,7 +166,7 @@ class Vaults extends ChangeNotifier {
debugPrint('Recovering ${untrackedPaths.length} untracked vault items');
await Future.forEach(untrackedPaths, (untrackedPath) async {
final uri = Uri.file(untrackedPath).toString();
- final sourceEntry = await mediaFetchService.getEntry(uri, null);
+ final sourceEntry = await mediaFetchService.getEntry(uri, null, allowUnsized: true);
if (sourceEntry != null) {
sourceEntry.id = metadataDb.nextId;
sourceEntry.origin = EntryOrigins.vault;
diff --git a/lib/services/media/media_fetch_service.dart b/lib/services/media/media_fetch_service.dart
index e249024d2..756e1d58e 100644
--- a/lib/services/media/media_fetch_service.dart
+++ b/lib/services/media/media_fetch_service.dart
@@ -13,7 +13,7 @@ import 'package:flutter/services.dart';
import 'package:streams_channel/streams_channel.dart';
abstract class MediaFetchService {
- Future getEntry(String uri, String? mimeType);
+ Future getEntry(String uri, String? mimeType, {bool allowUnsized = false});
Future getSvg(
String uri,
@@ -75,11 +75,12 @@ class PlatformMediaFetchService implements MediaFetchService {
static const double _thumbnailDefaultSize = 64.0;
@override
- Future getEntry(String uri, String? mimeType) async {
+ Future getEntry(String uri, String? mimeType, {bool allowUnsized = false}) async {
try {
final result = await _platformObject.invokeMethod('getEntry', {
'uri': uri,
'mimeType': mimeType,
+ 'allowUnsized': allowUnsized,
}) as Map;
return AvesEntry.fromMap(result);
} on PlatformException catch (e, stack) {
diff --git a/lib/widgets/about/translators.dart b/lib/widgets/about/translators.dart
index 2e582a454..37e4641bf 100644
--- a/lib/widgets/about/translators.dart
+++ b/lib/widgets/about/translators.dart
@@ -51,6 +51,7 @@ class _RandomTextSpanHighlighter extends StatefulWidget {
class _RandomTextSpanHighlighterState extends State<_RandomTextSpanHighlighter> with SingleTickerProviderStateMixin {
late final AnimationController _controller;
+ late final CurvedAnimation _animation;
late final Animation _animatedStyle;
late final TextStyle _baseStyle;
int _highlightedIndex = 0;
@@ -90,14 +91,16 @@ class _RandomTextSpanHighlighterState extends State<_RandomTextSpanHighlighter>
}
})
..repeat(reverse: true);
- _animatedStyle = ShadowedTextStyleTween(begin: _baseStyle, end: highlightStyle).animate(CurvedAnimation(
+ _animation = CurvedAnimation(
parent: _controller,
curve: Curves.easeInOutCubic,
- ));
+ );
+ _animatedStyle = ShadowedTextStyleTween(begin: _baseStyle, end: highlightStyle).animate(_animation);
}
@override
void dispose() {
+ _animation.dispose();
_controller.dispose();
super.dispose();
}
diff --git a/lib/widgets/collection/collection_grid.dart b/lib/widgets/collection/collection_grid.dart
index 08832097a..d4d54012f 100644
--- a/lib/widgets/collection/collection_grid.dart
+++ b/lib/widgets/collection/collection_grid.dart
@@ -113,11 +113,13 @@ class _CollectionGridContent extends StatefulWidget {
class _CollectionGridContentState extends State<_CollectionGridContent> {
final ValueNotifier _focusedItemNotifier = ValueNotifier(null);
final ValueNotifier _isScrollingNotifier = ValueNotifier(false);
+ final ValueNotifier _selectingAppModeNotifier = ValueNotifier(AppMode.pickFilteredMediaInternal);
@override
void dispose() {
_focusedItemNotifier.dispose();
_isScrollingNotifier.dispose();
+ _selectingAppModeNotifier.dispose();
super.dispose();
}
@@ -252,7 +254,7 @@ class _CollectionGridContentState extends State<_CollectionGridContent> {
if (selection.isSelecting) {
child = MultiProvider(
providers: [
- ListenableProvider>.value(value: ValueNotifier(AppMode.pickFilteredMediaInternal)),
+ ListenableProvider>.value(value: _selectingAppModeNotifier),
ChangeNotifierProvider>.value(value: selection),
],
child: child,
diff --git a/lib/widgets/common/action_controls/quick_choosers/common/button.dart b/lib/widgets/common/action_controls/quick_choosers/common/button.dart
index b4d409d1f..5925099b7 100644
--- a/lib/widgets/common/action_controls/quick_choosers/common/button.dart
+++ b/lib/widgets/common/action_controls/quick_choosers/common/button.dart
@@ -22,7 +22,7 @@ abstract class ChooserQuickButton extends StatefulWidget {
abstract class ChooserQuickButtonState, U> extends State with SingleTickerProviderStateMixin {
AnimationController? _animationController;
- Animation? _animation;
+ CurvedAnimation? _animation;
OverlayEntry? _chooserOverlayEntry;
final ValueNotifier _chooserValueNotifier = ValueNotifier(null);
final StreamController _moveUpdateStreamController = StreamController.broadcast();
@@ -47,6 +47,7 @@ abstract class ChooserQuickButtonState, U> exten
@override
void dispose() {
+ _animation?.dispose();
_animationController?.dispose();
_clearChooserOverlayEntry();
_chooserValueNotifier.dispose();
diff --git a/lib/widgets/common/action_mixins/feedback.dart b/lib/widgets/common/action_mixins/feedback.dart
index ad348f983..ef145603f 100644
--- a/lib/widgets/common/action_mixins/feedback.dart
+++ b/lib/widgets/common/action_mixins/feedback.dart
@@ -178,7 +178,7 @@ class ReportOverlay extends StatefulWidget {
class _ReportOverlayState extends State> with SingleTickerProviderStateMixin {
final processed = {};
late AnimationController _animationController;
- late Animation _animation;
+ late CurvedAnimation _animation;
Stream get opStream => widget.opStream;
@@ -212,6 +212,7 @@ class _ReportOverlayState extends State> with SingleTickerPr
@override
void dispose() {
+ _animation.dispose();
_animationController.dispose();
super.dispose();
}
@@ -317,6 +318,7 @@ class _FeedbackMessage extends StatefulWidget {
class _FeedbackMessageState extends State<_FeedbackMessage> with SingleTickerProviderStateMixin {
AnimationController? _animationController;
+ CurvedAnimation? _animation;
Animation? _remainingDurationMillis;
int? _totalDurationMillis;
@@ -333,19 +335,21 @@ class _FeedbackMessageState extends State<_FeedbackMessage> with SingleTickerPro
duration: effectiveDuration,
vsync: this,
);
+ _animation = CurvedAnimation(
+ parent: _animationController!,
+ curve: Curves.linear,
+ );
_remainingDurationMillis = IntTween(
begin: effectiveDuration.inMilliseconds,
end: 0,
- ).animate(CurvedAnimation(
- parent: _animationController!,
- curve: Curves.linear,
- ));
+ ).animate(_animation!);
_animationController!.forward();
}
}
@override
void dispose() {
+ _animation?.dispose();
_animationController?.dispose();
super.dispose();
}
diff --git a/lib/widgets/common/action_mixins/overlay_snack_bar.dart b/lib/widgets/common/action_mixins/overlay_snack_bar.dart
index 97a71a97c..ed2d5a710 100644
--- a/lib/widgets/common/action_mixins/overlay_snack_bar.dart
+++ b/lib/widgets/common/action_mixins/overlay_snack_bar.dart
@@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
// adapted from Flutter `SnackBar` in `/material/snack_bar.dart`
-// As of Flutter v3.0.1, `SnackBar` is not customizable enough to add margin
+// As of Flutter v3.23.0, `SnackBar` is not customizable enough to add margin
// and ignore pointers in that area, so we use an overlay entry instead.
// This overlay entry is not under a `Scaffold` (which is expected by `SnackBar`
// and `SnackBarAction`), and is not dismissed the same way.
@@ -73,10 +73,17 @@ class OverlaySnackBar extends StatefulWidget {
class _OverlaySnackBarState extends State {
bool _wasVisible = false;
+ CurvedAnimation? _heightAnimation;
+ CurvedAnimation? _fadeInAnimation;
+ CurvedAnimation? _fadeInM3Animation;
+ CurvedAnimation? _fadeOutAnimation;
+ CurvedAnimation? _heightM3Animation;
+
@override
void initState() {
super.initState();
widget.animation!.addStatusListener(_onAnimationStatusChanged);
+ _setAnimations();
}
@override
@@ -85,26 +92,55 @@ class _OverlaySnackBarState extends State {
if (widget.animation != oldWidget.animation) {
oldWidget.animation!.removeStatusListener(_onAnimationStatusChanged);
widget.animation!.addStatusListener(_onAnimationStatusChanged);
+ _disposeAnimations();
+ _setAnimations();
}
}
+ void _setAnimations() {
+ assert(widget.animation != null);
+ _heightAnimation = CurvedAnimation(parent: widget.animation!, curve: _snackBarHeightCurve);
+ _fadeInAnimation = CurvedAnimation(parent: widget.animation!, curve: _snackBarFadeInCurve);
+ _fadeInM3Animation = CurvedAnimation(parent: widget.animation!, curve: _snackBarM3FadeInCurve);
+ _fadeOutAnimation = CurvedAnimation(
+ parent: widget.animation!,
+ curve: _snackBarFadeOutCurve,
+ reverseCurve: const Threshold(0.0),
+ );
+ // Material 3 Animation has a height animation on entry, but a direct fade out on exit.
+ _heightM3Animation = CurvedAnimation(
+ parent: widget.animation!,
+ curve: _snackBarM3HeightCurve,
+ reverseCurve: const Threshold(0.0),
+ );
+ }
+
+ void _disposeAnimations() {
+ _heightAnimation?.dispose();
+ _fadeInAnimation?.dispose();
+ _fadeInM3Animation?.dispose();
+ _fadeOutAnimation?.dispose();
+ _heightM3Animation?.dispose();
+ _heightAnimation = null;
+ _fadeInAnimation = null;
+ _fadeInM3Animation = null;
+ _fadeOutAnimation = null;
+ _heightM3Animation = null;
+ }
+
@override
void dispose() {
widget.animation!.removeStatusListener(_onAnimationStatusChanged);
+ _disposeAnimations();
super.dispose();
}
void _onAnimationStatusChanged(AnimationStatus animationStatus) {
- switch (animationStatus) {
- case AnimationStatus.dismissed:
- case AnimationStatus.forward:
- case AnimationStatus.reverse:
- break;
- case AnimationStatus.completed:
- if (widget.onVisible != null && !_wasVisible) {
- widget.onVisible!();
- }
- _wasVisible = true;
+ if (animationStatus == AnimationStatus.completed) {
+ if (widget.onVisible != null && !_wasVisible) {
+ widget.onVisible!();
+ }
+ _wasVisible = true;
}
}
@@ -173,28 +209,13 @@ class _OverlaySnackBarState extends State {
final double iconHorizontalMargin = (widget.padding?.resolve(TextDirection.ltr).right ?? horizontalPadding) / 12.0;
- final CurvedAnimation heightAnimation = CurvedAnimation(parent: widget.animation!, curve: _snackBarHeightCurve);
- final CurvedAnimation fadeInAnimation = CurvedAnimation(parent: widget.animation!, curve: _snackBarFadeInCurve);
- final CurvedAnimation fadeInM3Animation = CurvedAnimation(parent: widget.animation!, curve: _snackBarM3FadeInCurve);
-
- final CurvedAnimation fadeOutAnimation = CurvedAnimation(
- parent: widget.animation!,
- curve: _snackBarFadeOutCurve,
- reverseCurve: const Threshold(0.0),
- );
- // Material 3 Animation has a height animation on entry, but a direct fade out on exit.
- final CurvedAnimation heightM3Animation = CurvedAnimation(
- parent: widget.animation!,
- curve: _snackBarM3HeightCurve,
- reverseCurve: const Threshold(0.0),
- );
-
final IconButton? iconButton = showCloseIcon
? IconButton(
icon: const Icon(Icons.close),
iconSize: 24.0,
color: widget.closeIconColor ?? snackBarTheme.closeIconColor ?? defaults.closeIconColor,
onPressed: () => ScaffoldMessenger.of(context).hideCurrentSnackBar(reason: SnackBarClosedReason.dismiss),
+ tooltip: MaterialLocalizations.of(context).closeButtonTooltip,
)
: null;
@@ -253,7 +274,7 @@ class _OverlaySnackBarState extends State {
child: accessibleNavigation || theme.useMaterial3
? snackBar
: FadeTransition(
- opacity: fadeOutAnimation,
+ opacity: _fadeOutAnimation!,
child: snackBar,
),
),
@@ -288,7 +309,7 @@ class _OverlaySnackBarState extends State {
key: const Key('dismissible'),
direction: widget.dismissDirection,
resizeDuration: null,
- behavior: widget.hitTestBehavior ?? (widget.margin != null ? HitTestBehavior.deferToChild : HitTestBehavior.opaque),
+ behavior: widget.hitTestBehavior ?? (widget.margin != null || snackBarTheme.insetPadding != null ? HitTestBehavior.deferToChild : HitTestBehavior.opaque),
onDismissed: (direction) => widget.onDismiss(),
child: snackBar,
),
@@ -299,19 +320,19 @@ class _OverlaySnackBarState extends State {
snackBarTransition = snackBar;
} else if (isFloatingSnackBar && !theme.useMaterial3) {
snackBarTransition = FadeTransition(
- opacity: fadeInAnimation,
+ opacity: _fadeInAnimation!,
child: snackBar,
);
// Is Material 3 Floating Snack Bar.
} else if (isFloatingSnackBar && theme.useMaterial3) {
snackBarTransition = FadeTransition(
- opacity: fadeInM3Animation,
- child: AnimatedBuilder(
- animation: heightM3Animation,
- builder: (context, child) {
+ opacity: _fadeInM3Animation!,
+ child: ValueListenableBuilder(
+ valueListenable: _heightM3Animation!,
+ builder: (context, value, child) {
return Align(
- alignment: AlignmentDirectional.bottomStart,
- heightFactor: heightM3Animation.value,
+ alignment: Alignment.bottomLeft,
+ heightFactor: value,
child: child,
);
},
@@ -319,12 +340,12 @@ class _OverlaySnackBarState extends State {
),
);
} else {
- snackBarTransition = AnimatedBuilder(
- animation: heightAnimation,
- builder: (context, child) {
+ snackBarTransition = ValueListenableBuilder(
+ valueListenable: _heightAnimation!,
+ builder: (context, value, child) {
return Align(
alignment: AlignmentDirectional.topStart,
- heightFactor: heightAnimation.value,
+ heightFactor: value,
child: child,
);
},
diff --git a/lib/widgets/common/basic/draggable_scrollbar/scrollbar.dart b/lib/widgets/common/basic/draggable_scrollbar/scrollbar.dart
index 40f91e8bc..eb42528bb 100644
--- a/lib/widgets/common/basic/draggable_scrollbar/scrollbar.dart
+++ b/lib/widgets/common/basic/draggable_scrollbar/scrollbar.dart
@@ -121,9 +121,9 @@ class _DraggableScrollbarState extends State with TickerProv
late Offset _longPressLastGlobalPosition;
late AnimationController _thumbAnimationController;
- late Animation _thumbAnimation;
+ late CurvedAnimation _thumbAnimation;
late AnimationController _labelAnimationController;
- late Animation _labelAnimation;
+ late CurvedAnimation _labelAnimation;
Timer? _fadeoutTimer;
Map? _percentCrumbs;
final Map _viewportCrumbs = {};
@@ -167,7 +167,9 @@ class _DraggableScrollbarState extends State with TickerProv
@override
void dispose() {
+ _thumbAnimation.dispose();
_thumbAnimationController.dispose();
+ _labelAnimation.dispose();
_labelAnimationController.dispose();
_fadeoutTimer?.cancel();
super.dispose();
diff --git a/lib/widgets/common/basic/text/animated_diff.dart b/lib/widgets/common/basic/text/animated_diff.dart
index a76726e25..fe37d428c 100644
--- a/lib/widgets/common/basic/text/animated_diff.dart
+++ b/lib/widgets/common/basic/text/animated_diff.dart
@@ -26,7 +26,7 @@ class AnimatedDiffText extends StatefulWidget {
class _AnimatedDiffTextState extends State with SingleTickerProviderStateMixin {
late final AnimationController _controller;
- late final Animation _animation;
+ late final CurvedAnimation _animation;
final List<_TextDiff> _diffs = [];
TextStyle get _textStyle {
@@ -66,6 +66,7 @@ class _AnimatedDiffTextState extends State with SingleTickerPr
@override
void dispose() {
+ _animation.dispose();
_controller.dispose();
super.dispose();
}
diff --git a/lib/widgets/common/basic/text/change_highlight.dart b/lib/widgets/common/basic/text/change_highlight.dart
index eaebecf12..cea4737a2 100644
--- a/lib/widgets/common/basic/text/change_highlight.dart
+++ b/lib/widgets/common/basic/text/change_highlight.dart
@@ -22,6 +22,7 @@ class ChangeHighlightText extends StatefulWidget {
class _ChangeHighlightTextState extends State with SingleTickerProviderStateMixin {
late final AnimationController _controller;
+ late final CurvedAnimation _animation;
late final Animation _style;
@override
@@ -33,10 +34,11 @@ class _ChangeHighlightTextState extends State with SingleTi
)
..value = 1
..addListener(() => setState(() {}));
- _style = ShadowedTextStyleTween(begin: widget.changedStyle, end: widget.style).animate(CurvedAnimation(
+ _animation = CurvedAnimation(
parent: _controller,
curve: widget.curve,
- ));
+ );
+ _style = ShadowedTextStyleTween(begin: widget.changedStyle, end: widget.style).animate(_animation);
}
@override
@@ -51,6 +53,7 @@ class _ChangeHighlightTextState extends State with SingleTi
@override
void dispose() {
+ _animation.dispose();
_controller.dispose();
super.dispose();
}
diff --git a/lib/widgets/common/fx/sweeper.dart b/lib/widgets/common/fx/sweeper.dart
index fc218fa3b..6ed6bdec8 100644
--- a/lib/widgets/common/fx/sweeper.dart
+++ b/lib/widgets/common/fx/sweeper.dart
@@ -31,6 +31,7 @@ class Sweeper extends StatefulWidget {
class _SweeperState extends State with SingleTickerProviderStateMixin {
late AnimationController _angleAnimationController;
+ late CurvedAnimation _angleAnimation;
late Animation _angle;
bool _isAppearing = false;
@@ -46,13 +47,14 @@ class _SweeperState extends State with SingleTickerProviderStateMixin {
final startAngle = widget.startAngle;
final sweepAngle = widget.sweepAngle;
final centerSweep = widget.centerSweep;
+ _angleAnimation = CurvedAnimation(
+ parent: _angleAnimationController,
+ curve: widget.curve,
+ );
_angle = Tween(
begin: startAngle - sweepAngle * (centerSweep ? .5 : 0),
end: startAngle + pi * 2 - sweepAngle * (centerSweep ? .5 : 1),
- ).animate(CurvedAnimation(
- parent: _angleAnimationController,
- curve: widget.curve,
- ));
+ ).animate(_angleAnimation);
_angleAnimationController.addStatusListener(_onAnimationStatusChanged);
_registerWidget(widget);
}
@@ -66,7 +68,7 @@ class _SweeperState extends State with SingleTickerProviderStateMixin {
@override
void dispose() {
- _angleAnimationController.removeStatusListener(_onAnimationStatusChanged);
+ _angleAnimation.dispose();
_angleAnimationController.dispose();
_unregisterWidget(widget);
super.dispose();
diff --git a/lib/widgets/common/map/leaflet/map.dart b/lib/widgets/common/map/leaflet/map.dart
index 901ffe79e..84d87be05 100644
--- a/lib/widgets/common/map/leaflet/map.dart
+++ b/lib/widgets/common/map/leaflet/map.dart
@@ -286,9 +286,8 @@ class _EntryLeafletMapState extends State> with TickerProv
final animation = CurvedAnimation(parent: controller, curve: Curves.fastOutSlowIn);
controller.addListener(() => animate(animation));
animation.addStatusListener((status) {
- if (status == AnimationStatus.completed) {
- controller.dispose();
- } else if (status == AnimationStatus.dismissed) {
+ if (status == AnimationStatus.completed || status == AnimationStatus.dismissed) {
+ animation.dispose();
controller.dispose();
}
});
diff --git a/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart b/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart
index 634341c88..ae7d7cc7e 100644
--- a/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart
+++ b/lib/widgets/dialogs/pick_dialogs/album_pick_page.dart
@@ -64,6 +64,7 @@ class _AlbumPickPage extends StatefulWidget {
class _AlbumPickPageState extends State<_AlbumPickPage> {
final ValueNotifier _appBarHeightNotifier = ValueNotifier(0);
+ final ValueNotifier _appModeNotifier = ValueNotifier(AppMode.pickFilterInternal);
CollectionSource get source => widget.source;
@@ -93,13 +94,14 @@ class _AlbumPickPageState extends State<_AlbumPickPage> {
@override
void dispose() {
_appBarHeightNotifier.dispose();
+ _appModeNotifier.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ListenableProvider>.value(
- value: ValueNotifier(AppMode.pickFilterInternal),
+ value: _appModeNotifier,
child: Selector(
selector: (context, s) => (s.albumGroupFactor, s.albumSortFactor),
builder: (context, s, child) {
diff --git a/lib/widgets/dialogs/pick_dialogs/item_pick_page.dart b/lib/widgets/dialogs/pick_dialogs/item_pick_page.dart
index 490f5edb7..49fe4266a 100644
--- a/lib/widgets/dialogs/pick_dialogs/item_pick_page.dart
+++ b/lib/widgets/dialogs/pick_dialogs/item_pick_page.dart
@@ -29,20 +29,23 @@ class ItemPickPage extends StatefulWidget {
}
class _ItemPickPageState extends State {
+ final ValueNotifier _appModeNotifier = ValueNotifier(AppMode.initialization);
+
CollectionLens get collection => widget.collection;
@override
void dispose() {
collection.dispose();
+ _appModeNotifier.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final liveFilter = collection.filters.firstWhereOrNull((v) => v is QueryFilter && v.live) as QueryFilter?;
- final mode = widget.canRemoveFilters ? AppMode.pickUnfilteredMediaInternal : AppMode.pickFilteredMediaInternal;
+ _appModeNotifier.value = widget.canRemoveFilters ? AppMode.pickUnfilteredMediaInternal : AppMode.pickFilteredMediaInternal;
return ListenableProvider>.value(
- value: ValueNotifier(mode),
+ value: _appModeNotifier,
child: AvesScaffold(
body: SelectionProvider(
child: QueryProvider(
diff --git a/lib/widgets/editor/transform/cropper.dart b/lib/widgets/editor/transform/cropper.dart
index 86447f735..c3abc7338 100644
--- a/lib/widgets/editor/transform/cropper.dart
+++ b/lib/widgets/editor/transform/cropper.dart
@@ -43,7 +43,7 @@ class _CropperState extends State with SingleTickerProviderStateMixin {
final ValueNotifier _outlineNotifier = ValueNotifier(Rect.zero);
final ValueNotifier _gridDivisionNotifier = ValueNotifier(0);
late AnimationController _gridAnimationController;
- late Animation _gridOpacity;
+ late CurvedAnimation _gridOpacity;
static const double minDimension = Cropper.handleDimension;
static const int panResizeGridDivision = 3;
@@ -87,6 +87,7 @@ class _CropperState extends State with SingleTickerProviderStateMixin {
_viewportSizeNotifier.dispose();
_outlineNotifier.dispose();
_gridDivisionNotifier.dispose();
+ _gridOpacity.dispose();
_gridAnimationController.dispose();
_unregisterWidget(widget);
super.dispose();
diff --git a/lib/widgets/map/map_page.dart b/lib/widgets/map/map_page.dart
index 7d44c479a..528db5eb7 100644
--- a/lib/widgets/map/map_page.dart
+++ b/lib/widgets/map/map_page.dart
@@ -103,7 +103,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
final ValueNotifier _overlayOpacityNotifier = ValueNotifier(1);
final ValueNotifier _overlayVisible = ValueNotifier(true);
late AnimationController _overlayAnimationController;
- late Animation _overlayScale, _scrollerSize;
+ late CurvedAnimation _overlayScale, _scrollerSize;
CoordinateFilter? _regionFilter;
CollectionLens? get regionCollection => _regionCollectionNotifier.value;
@@ -170,6 +170,8 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
_dotEntryNotifier.dispose();
_overlayOpacityNotifier.dispose();
_overlayVisible.dispose();
+ _overlayScale.dispose();
+ _scrollerSize.dispose();
_overlayAnimationController.dispose();
// provided collection should be a new instance specifically created
diff --git a/lib/widgets/navigation/nav_bar/floating.dart b/lib/widgets/navigation/nav_bar/floating.dart
index 2c4f27ad7..63ffb53a4 100644
--- a/lib/widgets/navigation/nav_bar/floating.dart
+++ b/lib/widgets/navigation/nav_bar/floating.dart
@@ -25,7 +25,8 @@ class FloatingNavBar extends StatefulWidget {
class _FloatingNavBarState extends State with SingleTickerProviderStateMixin {
final List _subscriptions = [];
late AnimationController _controller;
- late Animation _offsetAnimation;
+ late CurvedAnimation _animation;
+ late Animation _offset;
double? _lastOffset;
bool _isDragging = false;
@@ -36,13 +37,14 @@ class _FloatingNavBarState extends State with SingleTickerProvid
duration: const Duration(milliseconds: 200),
vsync: this,
);
- _offsetAnimation = Tween(
- begin: const Offset(0, 0),
- end: const Offset(0, 1),
- ).animate(CurvedAnimation(
+ _animation = CurvedAnimation(
parent: _controller,
curve: Curves.linear,
- ))
+ );
+ _offset = Tween(
+ begin: const Offset(0, 0),
+ end: const Offset(0, 1),
+ ).animate(_animation)
..addListener(() {
if (!mounted) return;
setState(() {});
@@ -63,6 +65,8 @@ class _FloatingNavBarState extends State with SingleTickerProvid
@override
void dispose() {
_unregisterWidget(widget);
+ _animation.dispose();
+ _controller.dispose();
super.dispose();
}
@@ -82,7 +86,7 @@ class _FloatingNavBarState extends State with SingleTickerProvid
@override
Widget build(BuildContext context) {
return SlideTransition(
- position: _offsetAnimation,
+ position: _offset,
child: widget.child,
);
}
diff --git a/lib/widgets/viewer/controls/controller.dart b/lib/widgets/viewer/controls/controller.dart
index 0ee13ddbd..54bb798f7 100644
--- a/lib/widgets/viewer/controls/controller.dart
+++ b/lib/widgets/viewer/controls/controller.dart
@@ -22,7 +22,7 @@ class ViewerController with CastMixin {
late final ValueNotifier _autopilotNotifier;
Timer? _playTimer;
final StreamController _streamController = StreamController.broadcast();
- final Map _autopilotAnimationControllers = {};
+ final Map _autopilotAnimators = {};
ScaleLevel? _autopilotInitialScale;
Stream get _events => _streamController.stream;
@@ -94,9 +94,12 @@ class ViewerController with CastMixin {
void _stopPlayTimer() => _playTimer?.cancel();
- void _clearAutopilotAnimations() => _autopilotAnimationControllers.keys.toSet().forEach((v) => stopAutopilotAnimation(vsync: v));
+ void _clearAutopilotAnimations() => _autopilotAnimators.keys.toSet().forEach((v) => stopAutopilotAnimation(vsync: v));
- void stopAutopilotAnimation({required TickerProvider vsync}) => _autopilotAnimationControllers.remove(vsync)?.dispose();
+ void stopAutopilotAnimation({required TickerProvider vsync}) {
+ final animationController = _autopilotAnimators.remove(vsync);
+ return animationController?.dispose();
+ }
void startAutopilotAnimation({
required TickerProvider vsync,
@@ -113,16 +116,37 @@ class ViewerController with CastMixin {
duration: autopilotInterval,
vsync: vsync,
);
- animationController.addListener(() => onUpdate.call(
- scaleLevel: ScaleLevel(
- ref: scaleLevelRef,
- factor: scaleFactorTween.evaluate(CurvedAnimation(
- parent: animationController,
- curve: Curves.linear,
- )),
- ),
- ));
- _autopilotAnimationControllers[vsync] = animationController;
- Future.delayed(ADurations.viewerHorizontalPageAnimation).then((_) => _autopilotAnimationControllers[vsync]?.forward());
+ final animation = CurvedAnimation(
+ parent: animationController,
+ curve: Curves.linear,
+ );
+ animationController.addListener(() {
+ return onUpdate.call(
+ scaleLevel: ScaleLevel(
+ ref: scaleLevelRef,
+ factor: scaleFactorTween.evaluate(animation),
+ ),
+ );
+ });
+ _autopilotAnimators[vsync] = _AutopilotAnimators(
+ controller: animationController,
+ animation: animation,
+ );
+ Future.delayed(ADurations.viewerHorizontalPageAnimation).then((_) => _autopilotAnimators[vsync]?.controller.forward());
+ }
+}
+
+class _AutopilotAnimators {
+ final AnimationController controller;
+ final CurvedAnimation animation;
+
+ _AutopilotAnimators({
+ required this.controller,
+ required this.animation,
+ });
+
+ void dispose() {
+ animation.dispose();
+ controller.dispose();
}
}
diff --git a/lib/widgets/viewer/entry_viewer_stack.dart b/lib/widgets/viewer/entry_viewer_stack.dart
index afa511a45..16188cf7d 100644
--- a/lib/widgets/viewer/entry_viewer_stack.dart
+++ b/lib/widgets/viewer/entry_viewer_stack.dart
@@ -73,7 +73,7 @@ class _EntryViewerStackState extends State with EntryViewContr
final ValueNotifier _viewLocked = ValueNotifier(false);
final ValueNotifier _overlayExpandedNotifier = ValueNotifier(false);
late AnimationController _verticalPageAnimationController, _overlayAnimationController;
- late Animation _overlayButtonScale, _overlayVideoControlScale, _overlayOpacity;
+ late CurvedAnimation _overlayButtonScale, _overlayVideoControlScale, _overlayOpacity, _overlayTopOffsetAnimation;
late Animation _overlayTopOffset;
EdgeInsets? _frozenViewInsets, _frozenViewPadding;
late VideoActionDelegate _videoActionDelegate;
@@ -158,10 +158,11 @@ class _EntryViewerStackState extends State with EntryViewContr
parent: _overlayAnimationController,
curve: Curves.easeOutQuad,
);
- _overlayTopOffset = Tween(begin: const Offset(0, -1), end: const Offset(0, 0)).animate(CurvedAnimation(
+ _overlayTopOffsetAnimation = CurvedAnimation(
parent: _overlayAnimationController,
curve: Curves.easeOutQuad,
- ));
+ );
+ _overlayTopOffset = Tween(begin: const Offset(0, -1), end: const Offset(0, 0)).animate(_overlayTopOffsetAnimation);
_overlayVisible.value = settings.showOverlayOnOpening && !viewerController.autopilot;
_overlayVisible.addListener(_onOverlayVisibleChanged);
_viewLocked.addListener(_onViewLockedChanged);
@@ -188,6 +189,10 @@ class _EntryViewerStackState extends State with EntryViewContr
cleanEntryControllers(entryNotifier.value);
_videoActionDelegate.dispose();
_verticalPageAnimationController.dispose();
+ _overlayButtonScale.dispose();
+ _overlayVideoControlScale.dispose();
+ _overlayOpacity.dispose();
+ _overlayTopOffsetAnimation.dispose();
_overlayAnimationController.dispose();
_overlayVisible.dispose();
_viewLocked.dispose();
diff --git a/lib/widgets/viewer/info/info_page.dart b/lib/widgets/viewer/info/info_page.dart
index 95bfd9642..85cb274c7 100644
--- a/lib/widgets/viewer/info/info_page.dart
+++ b/lib/widgets/viewer/info/info_page.dart
@@ -286,5 +286,8 @@ class _InfoPageContentState extends State<_InfoPageContent> {
});
}
- void _onFilter(CollectionFilter filter) => FilterSelectedNotification(filter).dispatch(context);
+ void _onFilter(CollectionFilter filter) {
+ if (!mounted) return;
+ FilterSelectedNotification(filter).dispatch(context);
+ }
}
diff --git a/lib/widgets/viewer/overlay/bottom.dart b/lib/widgets/viewer/overlay/bottom.dart
index 728c530d5..9a6024f17 100644
--- a/lib/widgets/viewer/overlay/bottom.dart
+++ b/lib/widgets/viewer/overlay/bottom.dart
@@ -133,7 +133,7 @@ class _BottomOverlayContent extends StatefulWidget {
class _BottomOverlayContentState extends State<_BottomOverlayContent> {
final FocusScopeNode _buttonRowFocusScopeNode = FocusScopeNode();
- late Animation _buttonScale, _thumbnailOpacity;
+ late CurvedAnimation _buttonScale, _thumbnailOpacity;
@override
void initState() {
@@ -170,7 +170,8 @@ class _BottomOverlayContentState extends State<_BottomOverlayContent> {
}
void _unregisterWidget(_BottomOverlayContent widget) {
- // nothing
+ _buttonScale.dispose();
+ _thumbnailOpacity.dispose();
}
@override
diff --git a/lib/widgets/viewer/overlay/histogram.dart b/lib/widgets/viewer/overlay/histogram.dart
index 62a4e9ca8..60ec190c3 100644
--- a/lib/widgets/viewer/overlay/histogram.dart
+++ b/lib/widgets/viewer/overlay/histogram.dart
@@ -53,8 +53,10 @@ class _ImageHistogramState extends State {
void _registerWidget(ImageHistogram widget) {
_imageStream = imageProvider.resolve(ImageConfiguration.empty);
- _imageListener = ImageStreamListener((image, synchronousCall) {
- _updateLevels(image);
+ _imageListener = ImageStreamListener((image, synchronousCall) async {
+ // implementer is responsible for disposing the provided `ImageInfo`
+ await _updateLevels(image);
+ image.dispose();
});
_imageStream?.addListener(_imageListener);
}
diff --git a/lib/widgets/viewer/overlay/locked.dart b/lib/widgets/viewer/overlay/locked.dart
index 7c730708a..ed4afe749 100644
--- a/lib/widgets/viewer/overlay/locked.dart
+++ b/lib/widgets/viewer/overlay/locked.dart
@@ -25,7 +25,7 @@ class ViewerLockedOverlay extends StatefulWidget {
}
class _ViewerLockedOverlayState extends State {
- late Animation _buttonScale;
+ late CurvedAnimation _buttonScale;
@override
void initState() {
@@ -55,7 +55,7 @@ class _ViewerLockedOverlayState extends State {
}
void _unregisterWidget(ViewerLockedOverlay widget) {
- // nothing
+ _buttonScale.dispose();
}
@override
diff --git a/lib/widgets/viewer/overlay/slideshow_buttons.dart b/lib/widgets/viewer/overlay/slideshow_buttons.dart
index 5b84cc031..a34af9a6c 100644
--- a/lib/widgets/viewer/overlay/slideshow_buttons.dart
+++ b/lib/widgets/viewer/overlay/slideshow_buttons.dart
@@ -25,7 +25,7 @@ class SlideshowButtons extends StatefulWidget {
class _SlideshowButtonsState extends State {
final FocusScopeNode _buttonRowFocusScopeNode = FocusScopeNode();
- late Animation _buttonScale;
+ late CurvedAnimation _buttonScale;
static const List _actions = [
SlideshowAction.resume,
@@ -65,7 +65,7 @@ class _SlideshowButtonsState extends State {
}
void _unregisterWidget(SlideshowButtons widget) {
- // nothing
+ _buttonScale.dispose();
}
@override
diff --git a/lib/widgets/viewer/overlay/video/progress_bar.dart b/lib/widgets/viewer/overlay/video/progress_bar.dart
index 0a9144c9c..310b363a2 100644
--- a/lib/widgets/viewer/overlay/video/progress_bar.dart
+++ b/lib/widgets/viewer/overlay/video/progress_bar.dart
@@ -222,6 +222,6 @@ class _VideoProgressBarState extends State {
double? _progressToDx(double progress) {
final box = _getProgressBarRenderBox();
- return box == null ? null : progress * box.size.width;
+ return box != null && box.hasSize ? progress * box.size.width : null;
}
}
diff --git a/lib/widgets/viewer/slideshow_page.dart b/lib/widgets/viewer/slideshow_page.dart
index 12331b58a..a67ca879d 100644
--- a/lib/widgets/viewer/slideshow_page.dart
+++ b/lib/widgets/viewer/slideshow_page.dart
@@ -35,6 +35,7 @@ class SlideshowPage extends StatefulWidget {
}
class _SlideshowPageState extends State {
+ final ValueNotifier _appModeNotifier = ValueNotifier(AppMode.slideshow);
late ViewerController _viewerController;
late CollectionLens _slideshowCollection;
AvesEntry? _initialEntry;
@@ -51,6 +52,7 @@ class _SlideshowPageState extends State {
@override
void dispose() {
+ _appModeNotifier.dispose();
_disposeViewerController();
super.dispose();
}
@@ -59,7 +61,7 @@ class _SlideshowPageState extends State {
Widget build(BuildContext context) {
final initialEntry = _initialEntry;
return ListenableProvider>.value(
- value: ValueNotifier(AppMode.slideshow),
+ value: _appModeNotifier,
child: AvesScaffold(
body: initialEntry == null
? EmptyContent(
diff --git a/lib/widgets/viewer/visual/raster.dart b/lib/widgets/viewer/visual/raster.dart
index 7e38eed88..b4c015b6c 100644
--- a/lib/widgets/viewer/visual/raster.dart
+++ b/lib/widgets/viewer/visual/raster.dart
@@ -41,6 +41,7 @@ class _RasterImageViewState extends State {
ImageStream? _fullImageStream;
late ImageStreamListener _fullImageListener;
final ValueNotifier _fullImageLoaded = ValueNotifier(false);
+ ImageInfo? _fullImageInfo;
AvesEntry get entry => widget.entry;
@@ -101,10 +102,13 @@ class _RasterImageViewState extends State {
void _unregisterFullImage() {
_fullImageStream?.removeListener(_fullImageListener);
_fullImageStream = null;
+ _fullImageInfo?.dispose();
}
void _onFullImageCompleted(ImageInfo image, bool synchronousCall) {
+ // implementer is responsible for disposing the provided `ImageInfo`
_unregisterFullImage();
+ _fullImageInfo = image;
_fullImageLoaded.value = true;
FullImageLoadedNotification(entry, fullImageProvider).dispatch(context);
}
diff --git a/lib/widgets/viewer/visual/vector.dart b/lib/widgets/viewer/visual/vector.dart
index b76cbb5ab..95ced38ed 100644
--- a/lib/widgets/viewer/visual/vector.dart
+++ b/lib/widgets/viewer/visual/vector.dart
@@ -37,6 +37,7 @@ class _VectorImageViewState extends State {
ImageStream? _fullImageStream;
late ImageStreamListener _fullImageListener;
final ValueNotifier _fullImageLoaded = ValueNotifier(false);
+ ImageInfo? _fullImageInfo;
AvesEntry get entry => widget.entry;
@@ -91,10 +92,13 @@ class _VectorImageViewState extends State {
void _unregisterFullImage() {
_fullImageStream?.removeListener(_fullImageListener);
_fullImageStream = null;
+ _fullImageInfo?.dispose();
}
void _onFullImageCompleted(ImageInfo image, bool synchronousCall) {
+ // implementer is responsible for disposing the provided `ImageInfo`
_unregisterFullImage();
+ _fullImageInfo = image;
_fullImageLoaded.value = true;
}
diff --git a/lib/widgets/wallpaper_page.dart b/lib/widgets/wallpaper_page.dart
index 56b51a35b..10763ee5a 100644
--- a/lib/widgets/wallpaper_page.dart
+++ b/lib/widgets/wallpaper_page.dart
@@ -71,7 +71,7 @@ class EntryEditor extends StatefulWidget {
class _EntryEditorState extends State with EntryViewControllerMixin, SingleTickerProviderStateMixin {
final ValueNotifier _overlayVisible = ValueNotifier(true);
late AnimationController _overlayAnimationController;
- late Animation _overlayVideoControlScale;
+ late CurvedAnimation _overlayVideoControlScale;
EdgeInsets? _frozenViewInsets, _frozenViewPadding;
late VideoActionDelegate _videoActionDelegate;
late final ViewerController _viewerController;
@@ -120,6 +120,7 @@ class _EntryEditorState extends State with EntryViewControllerMixin
void dispose() {
cleanEntryControllers(entry);
_overlayVisible.dispose();
+ _overlayVideoControlScale.dispose();
_overlayAnimationController.dispose();
_videoActionDelegate.dispose();
_viewerController.dispose();
diff --git a/plugins/aves_screen_state/android/build.gradle b/plugins/aves_screen_state/android/build.gradle
index 941d165b2..2a7c98004 100644
--- a/plugins/aves_screen_state/android/build.gradle
+++ b/plugins/aves_screen_state/android/build.gradle
@@ -4,7 +4,7 @@ version '1.0-SNAPSHOT'
buildscript {
ext {
kotlin_version = '1.9.24'
- agp_version = '8.4.1'
+ agp_version = '8.5.0'
}
repositories {
diff --git a/plugins/aves_video/lib/src/controller.dart b/plugins/aves_video/lib/src/controller.dart
index 7babf040f..8fac10917 100644
--- a/plugins/aves_video/lib/src/controller.dart
+++ b/plugins/aves_video/lib/src/controller.dart
@@ -47,6 +47,7 @@ abstract class AvesVideoController with ABRepeatMixin {
if (kFlutterMemoryAllocationsEnabled) {
FlutterMemoryAllocations.instance.dispatchObjectDisposed(object: this);
}
+ abRepeatNotifier.dispose();
_entry.visualChangeNotifier.removeListener(onVisualChanged);
await _savePlaybackState();
}
diff --git a/pubspec.yaml b/pubspec.yaml
index eca07a22b..c8742c81d 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -7,7 +7,7 @@ repository: https://github.com/deckerst/aves
# - play changelog: /whatsnew/whatsnew-en-US
# - izzy changelog: /fastlane/metadata/android/en-US/changelogs/XXX01.txt
# - libre changelog: /fastlane/metadata/android/en-US/changelogs/XXX.txt
-version: 1.11.2+121
+version: 1.11.3+122
publish_to: none
environment:
@@ -176,7 +176,7 @@ flutter:
# and `_PackageLicensePage` in `/material/about.dart`
#
# `OverlaySnackBar` in `/widgets/common/action_mixins/overlay_snack_bar.dart`
-# adapts from Flutter v3.16.0 `SnackBar` in `/material/snack_bar.dart`
+# adapts from Flutter v3.23.0 `SnackBar` in `/material/snack_bar.dart`
#
# `EagerScaleGestureRecognizer` in `/widgets/common/behaviour/eager_scale_gesture_recognizer.dart`
# adapts from Flutter v3.16.0 `ScaleGestureRecognizer` in `/gestures/scale.dart`
diff --git a/test/fake/media_fetch_service.dart b/test/fake/media_fetch_service.dart
index 50dd1d969..9b18b6e6e 100644
--- a/test/fake/media_fetch_service.dart
+++ b/test/fake/media_fetch_service.dart
@@ -7,7 +7,7 @@ class FakeMediaFetchService extends Fake implements MediaFetchService {
Set entries = {};
@override
- Future getEntry(String uri, String? mimeType) async {
+ Future getEntry(String uri, String? mimeType, {bool allowUnsized = false}) async {
return entries.firstWhereOrNull((v) => v.uri == uri);
}
}
diff --git a/whatsnew/whatsnew-en-US b/whatsnew/whatsnew-en-US
index 24d17aed2..dfb8b7c93 100644
--- a/whatsnew/whatsnew-en-US
+++ b/whatsnew/whatsnew-en-US
@@ -1,3 +1,3 @@
-In v1.11.2:
+In v1.11.3:
- show selected albums together in Collection
Full changelog available on GitHub
\ No newline at end of file