Merge branch 'develop'
This commit is contained in:
commit
90fa60df69
49 changed files with 238 additions and 123 deletions
|
@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## <a id="unreleased"></a>[Unreleased]
|
## <a id="unreleased"></a>[Unreleased]
|
||||||
|
|
||||||
|
## <a id="v1.11.3"></a>[v1.11.3] - 2024-06-17
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- handle `MediaStore.ACTION_REVIEW` intent
|
||||||
|
|
||||||
## <a id="v1.11.2"></a>[v1.11.2] - 2024-06-11
|
## <a id="v1.11.2"></a>[v1.11.2] - 2024-06-11
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -142,6 +142,7 @@
|
||||||
<action android:name="android.intent.action.PICK" />
|
<action android:name="android.intent.action.PICK" />
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<action android:name="android.provider.action.REVIEW" />
|
||||||
<action android:name="com.android.camera.action.REVIEW" />
|
<action android:name="com.android.camera.action.REVIEW" />
|
||||||
<action android:name="com.android.camera.action.SPLIT_SCREEN_REVIEW" />
|
<action android:name="com.android.camera.action.SPLIT_SCREEN_REVIEW" />
|
||||||
|
|
||||||
|
@ -161,6 +162,7 @@
|
||||||
<action android:name="android.intent.action.PICK" />
|
<action android:name="android.intent.action.PICK" />
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<action android:name="android.provider.action.REVIEW" />
|
||||||
<action android:name="com.android.camera.action.REVIEW" />
|
<action android:name="com.android.camera.action.REVIEW" />
|
||||||
<action android:name="com.android.camera.action.SPLIT_SCREEN_REVIEW" />
|
<action android:name="com.android.camera.action.SPLIT_SCREEN_REVIEW" />
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
import android.provider.MediaStore
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.content.pm.ShortcutInfoCompat
|
import androidx.core.content.pm.ShortcutInfoCompat
|
||||||
|
@ -299,6 +300,7 @@ open class MainActivity : FlutterFragmentActivity() {
|
||||||
|
|
||||||
Intent.ACTION_VIEW,
|
Intent.ACTION_VIEW,
|
||||||
Intent.ACTION_SEND,
|
Intent.ACTION_SEND,
|
||||||
|
MediaStore.ACTION_REVIEW,
|
||||||
"com.android.camera.action.REVIEW",
|
"com.android.camera.action.REVIEW",
|
||||||
"com.android.camera.action.SPLIT_SCREEN_REVIEW" -> {
|
"com.android.camera.action.SPLIT_SCREEN_REVIEW" -> {
|
||||||
(intent.data ?: intent.getParcelableExtraCompat<Uri>(Intent.EXTRA_STREAM))?.let { uri ->
|
(intent.data ?: intent.getParcelableExtraCompat<Uri>(Intent.EXTRA_STREAM))?.let { uri ->
|
||||||
|
|
|
@ -340,7 +340,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
ioScope.launch {
|
ioScope.launch {
|
||||||
provider.fetchSingle(context, uri, mimeType, object : ImageProvider.ImageOpCallback {
|
provider.fetchSingle(context, uri, mimeType, false, object : ImageProvider.ImageOpCallback {
|
||||||
override fun onSuccess(fields: FieldMap) {
|
override fun onSuccess(fields: FieldMap) {
|
||||||
resultFields.putAll(fields)
|
resultFields.putAll(fields)
|
||||||
result.success(resultFields)
|
result.success(resultFields)
|
||||||
|
|
|
@ -29,6 +29,7 @@ class MediaFetchObjectHandler(private val context: Context) : MethodCallHandler
|
||||||
private fun getEntry(call: MethodCall, result: MethodChannel.Result) {
|
private fun getEntry(call: MethodCall, result: MethodChannel.Result) {
|
||||||
val mimeType = call.argument<String>("mimeType") // MIME type is optional
|
val mimeType = call.argument<String>("mimeType") // MIME type is optional
|
||||||
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
||||||
|
val allowUnsized = call.argument<Boolean>("allowUnsized") ?: false
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
result.error("getEntry-args", "missing arguments", null)
|
result.error("getEntry-args", "missing arguments", null)
|
||||||
return
|
return
|
||||||
|
@ -40,7 +41,7 @@ class MediaFetchObjectHandler(private val context: Context) : MethodCallHandler
|
||||||
return
|
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 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)
|
override fun onFailure(throwable: Throwable) = result.error("getEntry-failure", "failed to get entry for uri=$uri mimeType=$mimeType", throwable.message)
|
||||||
})
|
})
|
||||||
|
|
|
@ -12,7 +12,7 @@ import deckers.thibault.aves.utils.LogUtils
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
internal class FileImageProvider : ImageProvider() {
|
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
|
var mimeType = sourceMimeType
|
||||||
|
|
||||||
if (mimeType == null) {
|
if (mimeType == null) {
|
||||||
|
@ -54,7 +54,7 @@ internal class FileImageProvider : ImageProvider() {
|
||||||
}
|
}
|
||||||
entry.fillPreCatalogMetadata(context)
|
entry.fillPreCatalogMetadata(context)
|
||||||
|
|
||||||
if (entry.isSized || entry.isSvg || entry.isVideo) {
|
if (allowUnsized || entry.isSized || entry.isSvg || entry.isVideo) {
|
||||||
callback.onSuccess(entry.toMap())
|
callback.onSuccess(entry.toMap())
|
||||||
} else {
|
} else {
|
||||||
callback.onFailure(Exception("entry has no size"))
|
callback.onFailure(Exception("entry has no size"))
|
||||||
|
|
|
@ -70,7 +70,7 @@ import java.util.TimeZone
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
abstract class ImageProvider {
|
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"))
|
callback.onFailure(UnsupportedOperationException("`fetchSingle` is not supported by this image provider"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -89,7 +89,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
// the provided URI can point to the wrong media collection,
|
// the provided URI can point to the wrong media collection,
|
||||||
// e.g. a GIF image with the URI `content://media/external/video/media/[ID]`
|
// 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
|
// 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
|
var found = false
|
||||||
val fetched = arrayListOf<FieldMap>()
|
val fetched = arrayListOf<FieldMap>()
|
||||||
val id = uri.tryParseId()
|
val id = uri.tryParseId()
|
||||||
|
|
|
@ -17,7 +17,7 @@ open class UnknownContentProvider : ImageProvider() {
|
||||||
open val reliableProviderMimeType: Boolean
|
open val reliableProviderMimeType: Boolean
|
||||||
get() = false
|
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
|
var mimeType = sourceMimeType
|
||||||
if (sourceMimeType == null || !reliableProviderMimeType) {
|
if (sourceMimeType == null || !reliableProviderMimeType) {
|
||||||
// source MIME type may be incorrect, so we get a second opinion if possible
|
// 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)
|
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())
|
callback.onSuccess(entry.toMap())
|
||||||
} else {
|
} else {
|
||||||
callback.onFailure(Exception("entry has no size"))
|
callback.onFailure(Exception("entry has no size"))
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
buildscript {
|
buildscript {
|
||||||
ext {
|
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'
|
glide_version = '4.16.0'
|
||||||
// AppGallery Connect plugin versions: https://developer.huawei.com/consumer/en/doc/development/AppGallery-connect-Guides/agc-sdk-changenotes-0000001058732550
|
// 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'
|
huawei_agconnect_version = '1.9.1.300'
|
||||||
|
|
|
@ -10,7 +10,7 @@ pluginManagement {
|
||||||
|
|
||||||
settings.ext.kotlin_version = '1.9.24'
|
settings.ext.kotlin_version = '1.9.24'
|
||||||
settings.ext.ksp_version = "$kotlin_version-1.0.20"
|
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")
|
includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
|
||||||
|
|
||||||
|
|
3
fastlane/metadata/android/en-US/changelogs/122.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/122.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
In v1.11.3:
|
||||||
|
- show selected albums together in Collection
|
||||||
|
Full changelog available on GitHub
|
3
fastlane/metadata/android/en-US/changelogs/12201.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/12201.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
In v1.11.3:
|
||||||
|
- show selected albums together in Collection
|
||||||
|
Full changelog available on GitHub
|
|
@ -360,7 +360,7 @@ class MediaStoreSource extends CollectionSource {
|
||||||
existingDirectories.add(existingDirectory);
|
existingDirectories.add(existingDirectory);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final sourceEntry = await mediaFetchService.getEntry(uri, null);
|
final sourceEntry = await mediaFetchService.getEntry(uri, null, allowUnsized: true);
|
||||||
if (sourceEntry != null) {
|
if (sourceEntry != null) {
|
||||||
newEntries.add(sourceEntry.copyWith(
|
newEntries.add(sourceEntry.copyWith(
|
||||||
id: metadataDb.nextId,
|
id: metadataDb.nextId,
|
||||||
|
|
|
@ -67,7 +67,7 @@ mixin TrashMixin on SourceBase {
|
||||||
await metadataDb.updateTrash(id, entry.trashDetails);
|
await metadataDb.updateTrash(id, entry.trashDetails);
|
||||||
} else {
|
} else {
|
||||||
// there is no matching entry
|
// there is no matching entry
|
||||||
final sourceEntry = await mediaFetchService.getEntry(uri, null);
|
final sourceEntry = await mediaFetchService.getEntry(uri, null, allowUnsized: true);
|
||||||
if (sourceEntry != null) {
|
if (sourceEntry != null) {
|
||||||
final id = metadataDb.nextId;
|
final id = metadataDb.nextId;
|
||||||
sourceEntry.id = id;
|
sourceEntry.id = id;
|
||||||
|
|
|
@ -166,7 +166,7 @@ class Vaults extends ChangeNotifier {
|
||||||
debugPrint('Recovering ${untrackedPaths.length} untracked vault items');
|
debugPrint('Recovering ${untrackedPaths.length} untracked vault items');
|
||||||
await Future.forEach(untrackedPaths, (untrackedPath) async {
|
await Future.forEach(untrackedPaths, (untrackedPath) async {
|
||||||
final uri = Uri.file(untrackedPath).toString();
|
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) {
|
if (sourceEntry != null) {
|
||||||
sourceEntry.id = metadataDb.nextId;
|
sourceEntry.id = metadataDb.nextId;
|
||||||
sourceEntry.origin = EntryOrigins.vault;
|
sourceEntry.origin = EntryOrigins.vault;
|
||||||
|
|
|
@ -13,7 +13,7 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:streams_channel/streams_channel.dart';
|
import 'package:streams_channel/streams_channel.dart';
|
||||||
|
|
||||||
abstract class MediaFetchService {
|
abstract class MediaFetchService {
|
||||||
Future<AvesEntry?> getEntry(String uri, String? mimeType);
|
Future<AvesEntry?> getEntry(String uri, String? mimeType, {bool allowUnsized = false});
|
||||||
|
|
||||||
Future<Uint8List> getSvg(
|
Future<Uint8List> getSvg(
|
||||||
String uri,
|
String uri,
|
||||||
|
@ -75,11 +75,12 @@ class PlatformMediaFetchService implements MediaFetchService {
|
||||||
static const double _thumbnailDefaultSize = 64.0;
|
static const double _thumbnailDefaultSize = 64.0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<AvesEntry?> getEntry(String uri, String? mimeType) async {
|
Future<AvesEntry?> getEntry(String uri, String? mimeType, {bool allowUnsized = false}) async {
|
||||||
try {
|
try {
|
||||||
final result = await _platformObject.invokeMethod('getEntry', <String, dynamic>{
|
final result = await _platformObject.invokeMethod('getEntry', <String, dynamic>{
|
||||||
'uri': uri,
|
'uri': uri,
|
||||||
'mimeType': mimeType,
|
'mimeType': mimeType,
|
||||||
|
'allowUnsized': allowUnsized,
|
||||||
}) as Map;
|
}) as Map;
|
||||||
return AvesEntry.fromMap(result);
|
return AvesEntry.fromMap(result);
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
|
|
|
@ -51,6 +51,7 @@ class _RandomTextSpanHighlighter extends StatefulWidget {
|
||||||
|
|
||||||
class _RandomTextSpanHighlighterState extends State<_RandomTextSpanHighlighter> with SingleTickerProviderStateMixin {
|
class _RandomTextSpanHighlighterState extends State<_RandomTextSpanHighlighter> with SingleTickerProviderStateMixin {
|
||||||
late final AnimationController _controller;
|
late final AnimationController _controller;
|
||||||
|
late final CurvedAnimation _animation;
|
||||||
late final Animation<TextStyle> _animatedStyle;
|
late final Animation<TextStyle> _animatedStyle;
|
||||||
late final TextStyle _baseStyle;
|
late final TextStyle _baseStyle;
|
||||||
int _highlightedIndex = 0;
|
int _highlightedIndex = 0;
|
||||||
|
@ -90,14 +91,16 @@ class _RandomTextSpanHighlighterState extends State<_RandomTextSpanHighlighter>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
..repeat(reverse: true);
|
..repeat(reverse: true);
|
||||||
_animatedStyle = ShadowedTextStyleTween(begin: _baseStyle, end: highlightStyle).animate(CurvedAnimation(
|
_animation = CurvedAnimation(
|
||||||
parent: _controller,
|
parent: _controller,
|
||||||
curve: Curves.easeInOutCubic,
|
curve: Curves.easeInOutCubic,
|
||||||
));
|
);
|
||||||
|
_animatedStyle = ShadowedTextStyleTween(begin: _baseStyle, end: highlightStyle).animate(_animation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_animation.dispose();
|
||||||
_controller.dispose();
|
_controller.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,11 +113,13 @@ class _CollectionGridContent extends StatefulWidget {
|
||||||
class _CollectionGridContentState extends State<_CollectionGridContent> {
|
class _CollectionGridContentState extends State<_CollectionGridContent> {
|
||||||
final ValueNotifier<AvesEntry?> _focusedItemNotifier = ValueNotifier(null);
|
final ValueNotifier<AvesEntry?> _focusedItemNotifier = ValueNotifier(null);
|
||||||
final ValueNotifier<bool> _isScrollingNotifier = ValueNotifier(false);
|
final ValueNotifier<bool> _isScrollingNotifier = ValueNotifier(false);
|
||||||
|
final ValueNotifier<AppMode> _selectingAppModeNotifier = ValueNotifier(AppMode.pickFilteredMediaInternal);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_focusedItemNotifier.dispose();
|
_focusedItemNotifier.dispose();
|
||||||
_isScrollingNotifier.dispose();
|
_isScrollingNotifier.dispose();
|
||||||
|
_selectingAppModeNotifier.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,7 +254,7 @@ class _CollectionGridContentState extends State<_CollectionGridContent> {
|
||||||
if (selection.isSelecting) {
|
if (selection.isSelecting) {
|
||||||
child = MultiProvider(
|
child = MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
ListenableProvider<ValueNotifier<AppMode>>.value(value: ValueNotifier(AppMode.pickFilteredMediaInternal)),
|
ListenableProvider<ValueNotifier<AppMode>>.value(value: _selectingAppModeNotifier),
|
||||||
ChangeNotifierProvider<Selection<AvesEntry>>.value(value: selection),
|
ChangeNotifierProvider<Selection<AvesEntry>>.value(value: selection),
|
||||||
],
|
],
|
||||||
child: child,
|
child: child,
|
||||||
|
|
|
@ -22,7 +22,7 @@ abstract class ChooserQuickButton<T> extends StatefulWidget {
|
||||||
|
|
||||||
abstract class ChooserQuickButtonState<T extends ChooserQuickButton<U>, U> extends State<T> with SingleTickerProviderStateMixin {
|
abstract class ChooserQuickButtonState<T extends ChooserQuickButton<U>, U> extends State<T> with SingleTickerProviderStateMixin {
|
||||||
AnimationController? _animationController;
|
AnimationController? _animationController;
|
||||||
Animation<double>? _animation;
|
CurvedAnimation? _animation;
|
||||||
OverlayEntry? _chooserOverlayEntry;
|
OverlayEntry? _chooserOverlayEntry;
|
||||||
final ValueNotifier<U?> _chooserValueNotifier = ValueNotifier(null);
|
final ValueNotifier<U?> _chooserValueNotifier = ValueNotifier(null);
|
||||||
final StreamController<LongPressMoveUpdateDetails> _moveUpdateStreamController = StreamController.broadcast();
|
final StreamController<LongPressMoveUpdateDetails> _moveUpdateStreamController = StreamController.broadcast();
|
||||||
|
@ -47,6 +47,7 @@ abstract class ChooserQuickButtonState<T extends ChooserQuickButton<U>, U> exten
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_animation?.dispose();
|
||||||
_animationController?.dispose();
|
_animationController?.dispose();
|
||||||
_clearChooserOverlayEntry();
|
_clearChooserOverlayEntry();
|
||||||
_chooserValueNotifier.dispose();
|
_chooserValueNotifier.dispose();
|
||||||
|
|
|
@ -178,7 +178,7 @@ class ReportOverlay<T> extends StatefulWidget {
|
||||||
class _ReportOverlayState<T> extends State<ReportOverlay<T>> with SingleTickerProviderStateMixin {
|
class _ReportOverlayState<T> extends State<ReportOverlay<T>> with SingleTickerProviderStateMixin {
|
||||||
final processed = <T>{};
|
final processed = <T>{};
|
||||||
late AnimationController _animationController;
|
late AnimationController _animationController;
|
||||||
late Animation<double> _animation;
|
late CurvedAnimation _animation;
|
||||||
|
|
||||||
Stream<T> get opStream => widget.opStream;
|
Stream<T> get opStream => widget.opStream;
|
||||||
|
|
||||||
|
@ -212,6 +212,7 @@ class _ReportOverlayState<T> extends State<ReportOverlay<T>> with SingleTickerPr
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_animation.dispose();
|
||||||
_animationController.dispose();
|
_animationController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
@ -317,6 +318,7 @@ class _FeedbackMessage extends StatefulWidget {
|
||||||
|
|
||||||
class _FeedbackMessageState extends State<_FeedbackMessage> with SingleTickerProviderStateMixin {
|
class _FeedbackMessageState extends State<_FeedbackMessage> with SingleTickerProviderStateMixin {
|
||||||
AnimationController? _animationController;
|
AnimationController? _animationController;
|
||||||
|
CurvedAnimation? _animation;
|
||||||
Animation<int>? _remainingDurationMillis;
|
Animation<int>? _remainingDurationMillis;
|
||||||
int? _totalDurationMillis;
|
int? _totalDurationMillis;
|
||||||
|
|
||||||
|
@ -333,19 +335,21 @@ class _FeedbackMessageState extends State<_FeedbackMessage> with SingleTickerPro
|
||||||
duration: effectiveDuration,
|
duration: effectiveDuration,
|
||||||
vsync: this,
|
vsync: this,
|
||||||
);
|
);
|
||||||
|
_animation = CurvedAnimation(
|
||||||
|
parent: _animationController!,
|
||||||
|
curve: Curves.linear,
|
||||||
|
);
|
||||||
_remainingDurationMillis = IntTween(
|
_remainingDurationMillis = IntTween(
|
||||||
begin: effectiveDuration.inMilliseconds,
|
begin: effectiveDuration.inMilliseconds,
|
||||||
end: 0,
|
end: 0,
|
||||||
).animate(CurvedAnimation(
|
).animate(_animation!);
|
||||||
parent: _animationController!,
|
|
||||||
curve: Curves.linear,
|
|
||||||
));
|
|
||||||
_animationController!.forward();
|
_animationController!.forward();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_animation?.dispose();
|
||||||
_animationController?.dispose();
|
_animationController?.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
// adapted from Flutter `SnackBar` in `/material/snack_bar.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.
|
// 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`
|
// This overlay entry is not under a `Scaffold` (which is expected by `SnackBar`
|
||||||
// and `SnackBarAction`), and is not dismissed the same way.
|
// and `SnackBarAction`), and is not dismissed the same way.
|
||||||
|
@ -73,10 +73,17 @@ class OverlaySnackBar extends StatefulWidget {
|
||||||
class _OverlaySnackBarState extends State<OverlaySnackBar> {
|
class _OverlaySnackBarState extends State<OverlaySnackBar> {
|
||||||
bool _wasVisible = false;
|
bool _wasVisible = false;
|
||||||
|
|
||||||
|
CurvedAnimation? _heightAnimation;
|
||||||
|
CurvedAnimation? _fadeInAnimation;
|
||||||
|
CurvedAnimation? _fadeInM3Animation;
|
||||||
|
CurvedAnimation? _fadeOutAnimation;
|
||||||
|
CurvedAnimation? _heightM3Animation;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
widget.animation!.addStatusListener(_onAnimationStatusChanged);
|
widget.animation!.addStatusListener(_onAnimationStatusChanged);
|
||||||
|
_setAnimations();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -85,26 +92,55 @@ class _OverlaySnackBarState extends State<OverlaySnackBar> {
|
||||||
if (widget.animation != oldWidget.animation) {
|
if (widget.animation != oldWidget.animation) {
|
||||||
oldWidget.animation!.removeStatusListener(_onAnimationStatusChanged);
|
oldWidget.animation!.removeStatusListener(_onAnimationStatusChanged);
|
||||||
widget.animation!.addStatusListener(_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
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
widget.animation!.removeStatusListener(_onAnimationStatusChanged);
|
widget.animation!.removeStatusListener(_onAnimationStatusChanged);
|
||||||
|
_disposeAnimations();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onAnimationStatusChanged(AnimationStatus animationStatus) {
|
void _onAnimationStatusChanged(AnimationStatus animationStatus) {
|
||||||
switch (animationStatus) {
|
if (animationStatus == AnimationStatus.completed) {
|
||||||
case AnimationStatus.dismissed:
|
if (widget.onVisible != null && !_wasVisible) {
|
||||||
case AnimationStatus.forward:
|
widget.onVisible!();
|
||||||
case AnimationStatus.reverse:
|
}
|
||||||
break;
|
_wasVisible = true;
|
||||||
case AnimationStatus.completed:
|
|
||||||
if (widget.onVisible != null && !_wasVisible) {
|
|
||||||
widget.onVisible!();
|
|
||||||
}
|
|
||||||
_wasVisible = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,28 +209,13 @@ class _OverlaySnackBarState extends State<OverlaySnackBar> {
|
||||||
|
|
||||||
final double iconHorizontalMargin = (widget.padding?.resolve(TextDirection.ltr).right ?? horizontalPadding) / 12.0;
|
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
|
final IconButton? iconButton = showCloseIcon
|
||||||
? IconButton(
|
? IconButton(
|
||||||
icon: const Icon(Icons.close),
|
icon: const Icon(Icons.close),
|
||||||
iconSize: 24.0,
|
iconSize: 24.0,
|
||||||
color: widget.closeIconColor ?? snackBarTheme.closeIconColor ?? defaults.closeIconColor,
|
color: widget.closeIconColor ?? snackBarTheme.closeIconColor ?? defaults.closeIconColor,
|
||||||
onPressed: () => ScaffoldMessenger.of(context).hideCurrentSnackBar(reason: SnackBarClosedReason.dismiss),
|
onPressed: () => ScaffoldMessenger.of(context).hideCurrentSnackBar(reason: SnackBarClosedReason.dismiss),
|
||||||
|
tooltip: MaterialLocalizations.of(context).closeButtonTooltip,
|
||||||
)
|
)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
@ -253,7 +274,7 @@ class _OverlaySnackBarState extends State<OverlaySnackBar> {
|
||||||
child: accessibleNavigation || theme.useMaterial3
|
child: accessibleNavigation || theme.useMaterial3
|
||||||
? snackBar
|
? snackBar
|
||||||
: FadeTransition(
|
: FadeTransition(
|
||||||
opacity: fadeOutAnimation,
|
opacity: _fadeOutAnimation!,
|
||||||
child: snackBar,
|
child: snackBar,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -288,7 +309,7 @@ class _OverlaySnackBarState extends State<OverlaySnackBar> {
|
||||||
key: const Key('dismissible'),
|
key: const Key('dismissible'),
|
||||||
direction: widget.dismissDirection,
|
direction: widget.dismissDirection,
|
||||||
resizeDuration: null,
|
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(),
|
onDismissed: (direction) => widget.onDismiss(),
|
||||||
child: snackBar,
|
child: snackBar,
|
||||||
),
|
),
|
||||||
|
@ -299,19 +320,19 @@ class _OverlaySnackBarState extends State<OverlaySnackBar> {
|
||||||
snackBarTransition = snackBar;
|
snackBarTransition = snackBar;
|
||||||
} else if (isFloatingSnackBar && !theme.useMaterial3) {
|
} else if (isFloatingSnackBar && !theme.useMaterial3) {
|
||||||
snackBarTransition = FadeTransition(
|
snackBarTransition = FadeTransition(
|
||||||
opacity: fadeInAnimation,
|
opacity: _fadeInAnimation!,
|
||||||
child: snackBar,
|
child: snackBar,
|
||||||
);
|
);
|
||||||
// Is Material 3 Floating Snack Bar.
|
// Is Material 3 Floating Snack Bar.
|
||||||
} else if (isFloatingSnackBar && theme.useMaterial3) {
|
} else if (isFloatingSnackBar && theme.useMaterial3) {
|
||||||
snackBarTransition = FadeTransition(
|
snackBarTransition = FadeTransition(
|
||||||
opacity: fadeInM3Animation,
|
opacity: _fadeInM3Animation!,
|
||||||
child: AnimatedBuilder(
|
child: ValueListenableBuilder<double>(
|
||||||
animation: heightM3Animation,
|
valueListenable: _heightM3Animation!,
|
||||||
builder: (context, child) {
|
builder: (context, value, child) {
|
||||||
return Align(
|
return Align(
|
||||||
alignment: AlignmentDirectional.bottomStart,
|
alignment: Alignment.bottomLeft,
|
||||||
heightFactor: heightM3Animation.value,
|
heightFactor: value,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -319,12 +340,12 @@ class _OverlaySnackBarState extends State<OverlaySnackBar> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
snackBarTransition = AnimatedBuilder(
|
snackBarTransition = ValueListenableBuilder<double>(
|
||||||
animation: heightAnimation,
|
valueListenable: _heightAnimation!,
|
||||||
builder: (context, child) {
|
builder: (context, value, child) {
|
||||||
return Align(
|
return Align(
|
||||||
alignment: AlignmentDirectional.topStart,
|
alignment: AlignmentDirectional.topStart,
|
||||||
heightFactor: heightAnimation.value,
|
heightFactor: value,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -121,9 +121,9 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv
|
||||||
late Offset _longPressLastGlobalPosition;
|
late Offset _longPressLastGlobalPosition;
|
||||||
|
|
||||||
late AnimationController _thumbAnimationController;
|
late AnimationController _thumbAnimationController;
|
||||||
late Animation<double> _thumbAnimation;
|
late CurvedAnimation _thumbAnimation;
|
||||||
late AnimationController _labelAnimationController;
|
late AnimationController _labelAnimationController;
|
||||||
late Animation<double> _labelAnimation;
|
late CurvedAnimation _labelAnimation;
|
||||||
Timer? _fadeoutTimer;
|
Timer? _fadeoutTimer;
|
||||||
Map<double, String>? _percentCrumbs;
|
Map<double, String>? _percentCrumbs;
|
||||||
final Map<double, String> _viewportCrumbs = {};
|
final Map<double, String> _viewportCrumbs = {};
|
||||||
|
@ -167,7 +167,9 @@ class _DraggableScrollbarState extends State<DraggableScrollbar> with TickerProv
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_thumbAnimation.dispose();
|
||||||
_thumbAnimationController.dispose();
|
_thumbAnimationController.dispose();
|
||||||
|
_labelAnimation.dispose();
|
||||||
_labelAnimationController.dispose();
|
_labelAnimationController.dispose();
|
||||||
_fadeoutTimer?.cancel();
|
_fadeoutTimer?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
|
|
@ -26,7 +26,7 @@ class AnimatedDiffText extends StatefulWidget {
|
||||||
|
|
||||||
class _AnimatedDiffTextState extends State<AnimatedDiffText> with SingleTickerProviderStateMixin {
|
class _AnimatedDiffTextState extends State<AnimatedDiffText> with SingleTickerProviderStateMixin {
|
||||||
late final AnimationController _controller;
|
late final AnimationController _controller;
|
||||||
late final Animation<double> _animation;
|
late final CurvedAnimation _animation;
|
||||||
final List<_TextDiff> _diffs = [];
|
final List<_TextDiff> _diffs = [];
|
||||||
|
|
||||||
TextStyle get _textStyle {
|
TextStyle get _textStyle {
|
||||||
|
@ -66,6 +66,7 @@ class _AnimatedDiffTextState extends State<AnimatedDiffText> with SingleTickerPr
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_animation.dispose();
|
||||||
_controller.dispose();
|
_controller.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ class ChangeHighlightText extends StatefulWidget {
|
||||||
|
|
||||||
class _ChangeHighlightTextState extends State<ChangeHighlightText> with SingleTickerProviderStateMixin {
|
class _ChangeHighlightTextState extends State<ChangeHighlightText> with SingleTickerProviderStateMixin {
|
||||||
late final AnimationController _controller;
|
late final AnimationController _controller;
|
||||||
|
late final CurvedAnimation _animation;
|
||||||
late final Animation<TextStyle> _style;
|
late final Animation<TextStyle> _style;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -33,10 +34,11 @@ class _ChangeHighlightTextState extends State<ChangeHighlightText> with SingleTi
|
||||||
)
|
)
|
||||||
..value = 1
|
..value = 1
|
||||||
..addListener(() => setState(() {}));
|
..addListener(() => setState(() {}));
|
||||||
_style = ShadowedTextStyleTween(begin: widget.changedStyle, end: widget.style).animate(CurvedAnimation(
|
_animation = CurvedAnimation(
|
||||||
parent: _controller,
|
parent: _controller,
|
||||||
curve: widget.curve,
|
curve: widget.curve,
|
||||||
));
|
);
|
||||||
|
_style = ShadowedTextStyleTween(begin: widget.changedStyle, end: widget.style).animate(_animation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -51,6 +53,7 @@ class _ChangeHighlightTextState extends State<ChangeHighlightText> with SingleTi
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_animation.dispose();
|
||||||
_controller.dispose();
|
_controller.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ class Sweeper extends StatefulWidget {
|
||||||
|
|
||||||
class _SweeperState extends State<Sweeper> with SingleTickerProviderStateMixin {
|
class _SweeperState extends State<Sweeper> with SingleTickerProviderStateMixin {
|
||||||
late AnimationController _angleAnimationController;
|
late AnimationController _angleAnimationController;
|
||||||
|
late CurvedAnimation _angleAnimation;
|
||||||
late Animation<double> _angle;
|
late Animation<double> _angle;
|
||||||
bool _isAppearing = false;
|
bool _isAppearing = false;
|
||||||
|
|
||||||
|
@ -46,13 +47,14 @@ class _SweeperState extends State<Sweeper> with SingleTickerProviderStateMixin {
|
||||||
final startAngle = widget.startAngle;
|
final startAngle = widget.startAngle;
|
||||||
final sweepAngle = widget.sweepAngle;
|
final sweepAngle = widget.sweepAngle;
|
||||||
final centerSweep = widget.centerSweep;
|
final centerSweep = widget.centerSweep;
|
||||||
|
_angleAnimation = CurvedAnimation(
|
||||||
|
parent: _angleAnimationController,
|
||||||
|
curve: widget.curve,
|
||||||
|
);
|
||||||
_angle = Tween(
|
_angle = Tween(
|
||||||
begin: startAngle - sweepAngle * (centerSweep ? .5 : 0),
|
begin: startAngle - sweepAngle * (centerSweep ? .5 : 0),
|
||||||
end: startAngle + pi * 2 - sweepAngle * (centerSweep ? .5 : 1),
|
end: startAngle + pi * 2 - sweepAngle * (centerSweep ? .5 : 1),
|
||||||
).animate(CurvedAnimation(
|
).animate(_angleAnimation);
|
||||||
parent: _angleAnimationController,
|
|
||||||
curve: widget.curve,
|
|
||||||
));
|
|
||||||
_angleAnimationController.addStatusListener(_onAnimationStatusChanged);
|
_angleAnimationController.addStatusListener(_onAnimationStatusChanged);
|
||||||
_registerWidget(widget);
|
_registerWidget(widget);
|
||||||
}
|
}
|
||||||
|
@ -66,7 +68,7 @@ class _SweeperState extends State<Sweeper> with SingleTickerProviderStateMixin {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_angleAnimationController.removeStatusListener(_onAnimationStatusChanged);
|
_angleAnimation.dispose();
|
||||||
_angleAnimationController.dispose();
|
_angleAnimationController.dispose();
|
||||||
_unregisterWidget(widget);
|
_unregisterWidget(widget);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
|
|
@ -286,9 +286,8 @@ class _EntryLeafletMapState<T> extends State<EntryLeafletMap<T>> with TickerProv
|
||||||
final animation = CurvedAnimation(parent: controller, curve: Curves.fastOutSlowIn);
|
final animation = CurvedAnimation(parent: controller, curve: Curves.fastOutSlowIn);
|
||||||
controller.addListener(() => animate(animation));
|
controller.addListener(() => animate(animation));
|
||||||
animation.addStatusListener((status) {
|
animation.addStatusListener((status) {
|
||||||
if (status == AnimationStatus.completed) {
|
if (status == AnimationStatus.completed || status == AnimationStatus.dismissed) {
|
||||||
controller.dispose();
|
animation.dispose();
|
||||||
} else if (status == AnimationStatus.dismissed) {
|
|
||||||
controller.dispose();
|
controller.dispose();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -64,6 +64,7 @@ class _AlbumPickPage extends StatefulWidget {
|
||||||
|
|
||||||
class _AlbumPickPageState extends State<_AlbumPickPage> {
|
class _AlbumPickPageState extends State<_AlbumPickPage> {
|
||||||
final ValueNotifier<double> _appBarHeightNotifier = ValueNotifier(0);
|
final ValueNotifier<double> _appBarHeightNotifier = ValueNotifier(0);
|
||||||
|
final ValueNotifier<AppMode> _appModeNotifier = ValueNotifier(AppMode.pickFilterInternal);
|
||||||
|
|
||||||
CollectionSource get source => widget.source;
|
CollectionSource get source => widget.source;
|
||||||
|
|
||||||
|
@ -93,13 +94,14 @@ class _AlbumPickPageState extends State<_AlbumPickPage> {
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_appBarHeightNotifier.dispose();
|
_appBarHeightNotifier.dispose();
|
||||||
|
_appModeNotifier.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListenableProvider<ValueNotifier<AppMode>>.value(
|
return ListenableProvider<ValueNotifier<AppMode>>.value(
|
||||||
value: ValueNotifier(AppMode.pickFilterInternal),
|
value: _appModeNotifier,
|
||||||
child: Selector<Settings, (AlbumChipGroupFactor, ChipSortFactor)>(
|
child: Selector<Settings, (AlbumChipGroupFactor, ChipSortFactor)>(
|
||||||
selector: (context, s) => (s.albumGroupFactor, s.albumSortFactor),
|
selector: (context, s) => (s.albumGroupFactor, s.albumSortFactor),
|
||||||
builder: (context, s, child) {
|
builder: (context, s, child) {
|
||||||
|
|
|
@ -29,20 +29,23 @@ class ItemPickPage extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ItemPickPageState extends State<ItemPickPage> {
|
class _ItemPickPageState extends State<ItemPickPage> {
|
||||||
|
final ValueNotifier<AppMode> _appModeNotifier = ValueNotifier(AppMode.initialization);
|
||||||
|
|
||||||
CollectionLens get collection => widget.collection;
|
CollectionLens get collection => widget.collection;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
collection.dispose();
|
collection.dispose();
|
||||||
|
_appModeNotifier.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final liveFilter = collection.filters.firstWhereOrNull((v) => v is QueryFilter && v.live) as QueryFilter?;
|
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<ValueNotifier<AppMode>>.value(
|
return ListenableProvider<ValueNotifier<AppMode>>.value(
|
||||||
value: ValueNotifier(mode),
|
value: _appModeNotifier,
|
||||||
child: AvesScaffold(
|
child: AvesScaffold(
|
||||||
body: SelectionProvider<AvesEntry>(
|
body: SelectionProvider<AvesEntry>(
|
||||||
child: QueryProvider(
|
child: QueryProvider(
|
||||||
|
|
|
@ -43,7 +43,7 @@ class _CropperState extends State<Cropper> with SingleTickerProviderStateMixin {
|
||||||
final ValueNotifier<Rect> _outlineNotifier = ValueNotifier(Rect.zero);
|
final ValueNotifier<Rect> _outlineNotifier = ValueNotifier(Rect.zero);
|
||||||
final ValueNotifier<int> _gridDivisionNotifier = ValueNotifier(0);
|
final ValueNotifier<int> _gridDivisionNotifier = ValueNotifier(0);
|
||||||
late AnimationController _gridAnimationController;
|
late AnimationController _gridAnimationController;
|
||||||
late Animation<double> _gridOpacity;
|
late CurvedAnimation _gridOpacity;
|
||||||
|
|
||||||
static const double minDimension = Cropper.handleDimension;
|
static const double minDimension = Cropper.handleDimension;
|
||||||
static const int panResizeGridDivision = 3;
|
static const int panResizeGridDivision = 3;
|
||||||
|
@ -87,6 +87,7 @@ class _CropperState extends State<Cropper> with SingleTickerProviderStateMixin {
|
||||||
_viewportSizeNotifier.dispose();
|
_viewportSizeNotifier.dispose();
|
||||||
_outlineNotifier.dispose();
|
_outlineNotifier.dispose();
|
||||||
_gridDivisionNotifier.dispose();
|
_gridDivisionNotifier.dispose();
|
||||||
|
_gridOpacity.dispose();
|
||||||
_gridAnimationController.dispose();
|
_gridAnimationController.dispose();
|
||||||
_unregisterWidget(widget);
|
_unregisterWidget(widget);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
|
|
@ -103,7 +103,7 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
|
||||||
final ValueNotifier<double> _overlayOpacityNotifier = ValueNotifier(1);
|
final ValueNotifier<double> _overlayOpacityNotifier = ValueNotifier(1);
|
||||||
final ValueNotifier<bool> _overlayVisible = ValueNotifier(true);
|
final ValueNotifier<bool> _overlayVisible = ValueNotifier(true);
|
||||||
late AnimationController _overlayAnimationController;
|
late AnimationController _overlayAnimationController;
|
||||||
late Animation<double> _overlayScale, _scrollerSize;
|
late CurvedAnimation _overlayScale, _scrollerSize;
|
||||||
CoordinateFilter? _regionFilter;
|
CoordinateFilter? _regionFilter;
|
||||||
|
|
||||||
CollectionLens? get regionCollection => _regionCollectionNotifier.value;
|
CollectionLens? get regionCollection => _regionCollectionNotifier.value;
|
||||||
|
@ -170,6 +170,8 @@ class _ContentState extends State<_Content> with SingleTickerProviderStateMixin
|
||||||
_dotEntryNotifier.dispose();
|
_dotEntryNotifier.dispose();
|
||||||
_overlayOpacityNotifier.dispose();
|
_overlayOpacityNotifier.dispose();
|
||||||
_overlayVisible.dispose();
|
_overlayVisible.dispose();
|
||||||
|
_overlayScale.dispose();
|
||||||
|
_scrollerSize.dispose();
|
||||||
_overlayAnimationController.dispose();
|
_overlayAnimationController.dispose();
|
||||||
|
|
||||||
// provided collection should be a new instance specifically created
|
// provided collection should be a new instance specifically created
|
||||||
|
|
|
@ -25,7 +25,8 @@ class FloatingNavBar extends StatefulWidget {
|
||||||
class _FloatingNavBarState extends State<FloatingNavBar> with SingleTickerProviderStateMixin {
|
class _FloatingNavBarState extends State<FloatingNavBar> with SingleTickerProviderStateMixin {
|
||||||
final List<StreamSubscription> _subscriptions = [];
|
final List<StreamSubscription> _subscriptions = [];
|
||||||
late AnimationController _controller;
|
late AnimationController _controller;
|
||||||
late Animation<Offset> _offsetAnimation;
|
late CurvedAnimation _animation;
|
||||||
|
late Animation<Offset> _offset;
|
||||||
double? _lastOffset;
|
double? _lastOffset;
|
||||||
bool _isDragging = false;
|
bool _isDragging = false;
|
||||||
|
|
||||||
|
@ -36,13 +37,14 @@ class _FloatingNavBarState extends State<FloatingNavBar> with SingleTickerProvid
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
vsync: this,
|
vsync: this,
|
||||||
);
|
);
|
||||||
_offsetAnimation = Tween<Offset>(
|
_animation = CurvedAnimation(
|
||||||
begin: const Offset(0, 0),
|
|
||||||
end: const Offset(0, 1),
|
|
||||||
).animate(CurvedAnimation(
|
|
||||||
parent: _controller,
|
parent: _controller,
|
||||||
curve: Curves.linear,
|
curve: Curves.linear,
|
||||||
))
|
);
|
||||||
|
_offset = Tween<Offset>(
|
||||||
|
begin: const Offset(0, 0),
|
||||||
|
end: const Offset(0, 1),
|
||||||
|
).animate(_animation)
|
||||||
..addListener(() {
|
..addListener(() {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
@ -63,6 +65,8 @@ class _FloatingNavBarState extends State<FloatingNavBar> with SingleTickerProvid
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_unregisterWidget(widget);
|
_unregisterWidget(widget);
|
||||||
|
_animation.dispose();
|
||||||
|
_controller.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +86,7 @@ class _FloatingNavBarState extends State<FloatingNavBar> with SingleTickerProvid
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SlideTransition(
|
return SlideTransition(
|
||||||
position: _offsetAnimation,
|
position: _offset,
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ class ViewerController with CastMixin {
|
||||||
late final ValueNotifier<bool> _autopilotNotifier;
|
late final ValueNotifier<bool> _autopilotNotifier;
|
||||||
Timer? _playTimer;
|
Timer? _playTimer;
|
||||||
final StreamController _streamController = StreamController.broadcast();
|
final StreamController _streamController = StreamController.broadcast();
|
||||||
final Map<TickerProvider, AnimationController> _autopilotAnimationControllers = {};
|
final Map<TickerProvider, _AutopilotAnimators> _autopilotAnimators = {};
|
||||||
ScaleLevel? _autopilotInitialScale;
|
ScaleLevel? _autopilotInitialScale;
|
||||||
|
|
||||||
Stream<dynamic> get _events => _streamController.stream;
|
Stream<dynamic> get _events => _streamController.stream;
|
||||||
|
@ -94,9 +94,12 @@ class ViewerController with CastMixin {
|
||||||
|
|
||||||
void _stopPlayTimer() => _playTimer?.cancel();
|
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({
|
void startAutopilotAnimation({
|
||||||
required TickerProvider vsync,
|
required TickerProvider vsync,
|
||||||
|
@ -113,16 +116,37 @@ class ViewerController with CastMixin {
|
||||||
duration: autopilotInterval,
|
duration: autopilotInterval,
|
||||||
vsync: vsync,
|
vsync: vsync,
|
||||||
);
|
);
|
||||||
animationController.addListener(() => onUpdate.call(
|
final animation = CurvedAnimation(
|
||||||
scaleLevel: ScaleLevel(
|
parent: animationController,
|
||||||
ref: scaleLevelRef,
|
curve: Curves.linear,
|
||||||
factor: scaleFactorTween.evaluate(CurvedAnimation(
|
);
|
||||||
parent: animationController,
|
animationController.addListener(() {
|
||||||
curve: Curves.linear,
|
return onUpdate.call(
|
||||||
)),
|
scaleLevel: ScaleLevel(
|
||||||
),
|
ref: scaleLevelRef,
|
||||||
));
|
factor: scaleFactorTween.evaluate(animation),
|
||||||
_autopilotAnimationControllers[vsync] = animationController;
|
),
|
||||||
Future.delayed(ADurations.viewerHorizontalPageAnimation).then((_) => _autopilotAnimationControllers[vsync]?.forward());
|
);
|
||||||
|
});
|
||||||
|
_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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
final ValueNotifier<bool> _viewLocked = ValueNotifier(false);
|
final ValueNotifier<bool> _viewLocked = ValueNotifier(false);
|
||||||
final ValueNotifier<bool> _overlayExpandedNotifier = ValueNotifier(false);
|
final ValueNotifier<bool> _overlayExpandedNotifier = ValueNotifier(false);
|
||||||
late AnimationController _verticalPageAnimationController, _overlayAnimationController;
|
late AnimationController _verticalPageAnimationController, _overlayAnimationController;
|
||||||
late Animation<double> _overlayButtonScale, _overlayVideoControlScale, _overlayOpacity;
|
late CurvedAnimation _overlayButtonScale, _overlayVideoControlScale, _overlayOpacity, _overlayTopOffsetAnimation;
|
||||||
late Animation<Offset> _overlayTopOffset;
|
late Animation<Offset> _overlayTopOffset;
|
||||||
EdgeInsets? _frozenViewInsets, _frozenViewPadding;
|
EdgeInsets? _frozenViewInsets, _frozenViewPadding;
|
||||||
late VideoActionDelegate _videoActionDelegate;
|
late VideoActionDelegate _videoActionDelegate;
|
||||||
|
@ -158,10 +158,11 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
parent: _overlayAnimationController,
|
parent: _overlayAnimationController,
|
||||||
curve: Curves.easeOutQuad,
|
curve: Curves.easeOutQuad,
|
||||||
);
|
);
|
||||||
_overlayTopOffset = Tween(begin: const Offset(0, -1), end: const Offset(0, 0)).animate(CurvedAnimation(
|
_overlayTopOffsetAnimation = CurvedAnimation(
|
||||||
parent: _overlayAnimationController,
|
parent: _overlayAnimationController,
|
||||||
curve: Curves.easeOutQuad,
|
curve: Curves.easeOutQuad,
|
||||||
));
|
);
|
||||||
|
_overlayTopOffset = Tween(begin: const Offset(0, -1), end: const Offset(0, 0)).animate(_overlayTopOffsetAnimation);
|
||||||
_overlayVisible.value = settings.showOverlayOnOpening && !viewerController.autopilot;
|
_overlayVisible.value = settings.showOverlayOnOpening && !viewerController.autopilot;
|
||||||
_overlayVisible.addListener(_onOverlayVisibleChanged);
|
_overlayVisible.addListener(_onOverlayVisibleChanged);
|
||||||
_viewLocked.addListener(_onViewLockedChanged);
|
_viewLocked.addListener(_onViewLockedChanged);
|
||||||
|
@ -188,6 +189,10 @@ class _EntryViewerStackState extends State<EntryViewerStack> with EntryViewContr
|
||||||
cleanEntryControllers(entryNotifier.value);
|
cleanEntryControllers(entryNotifier.value);
|
||||||
_videoActionDelegate.dispose();
|
_videoActionDelegate.dispose();
|
||||||
_verticalPageAnimationController.dispose();
|
_verticalPageAnimationController.dispose();
|
||||||
|
_overlayButtonScale.dispose();
|
||||||
|
_overlayVideoControlScale.dispose();
|
||||||
|
_overlayOpacity.dispose();
|
||||||
|
_overlayTopOffsetAnimation.dispose();
|
||||||
_overlayAnimationController.dispose();
|
_overlayAnimationController.dispose();
|
||||||
_overlayVisible.dispose();
|
_overlayVisible.dispose();
|
||||||
_viewLocked.dispose();
|
_viewLocked.dispose();
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,7 +133,7 @@ class _BottomOverlayContent extends StatefulWidget {
|
||||||
|
|
||||||
class _BottomOverlayContentState extends State<_BottomOverlayContent> {
|
class _BottomOverlayContentState extends State<_BottomOverlayContent> {
|
||||||
final FocusScopeNode _buttonRowFocusScopeNode = FocusScopeNode();
|
final FocusScopeNode _buttonRowFocusScopeNode = FocusScopeNode();
|
||||||
late Animation<double> _buttonScale, _thumbnailOpacity;
|
late CurvedAnimation _buttonScale, _thumbnailOpacity;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -170,7 +170,8 @@ class _BottomOverlayContentState extends State<_BottomOverlayContent> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _unregisterWidget(_BottomOverlayContent widget) {
|
void _unregisterWidget(_BottomOverlayContent widget) {
|
||||||
// nothing
|
_buttonScale.dispose();
|
||||||
|
_thumbnailOpacity.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -53,8 +53,10 @@ class _ImageHistogramState extends State<ImageHistogram> {
|
||||||
|
|
||||||
void _registerWidget(ImageHistogram widget) {
|
void _registerWidget(ImageHistogram widget) {
|
||||||
_imageStream = imageProvider.resolve(ImageConfiguration.empty);
|
_imageStream = imageProvider.resolve(ImageConfiguration.empty);
|
||||||
_imageListener = ImageStreamListener((image, synchronousCall) {
|
_imageListener = ImageStreamListener((image, synchronousCall) async {
|
||||||
_updateLevels(image);
|
// implementer is responsible for disposing the provided `ImageInfo`
|
||||||
|
await _updateLevels(image);
|
||||||
|
image.dispose();
|
||||||
});
|
});
|
||||||
_imageStream?.addListener(_imageListener);
|
_imageStream?.addListener(_imageListener);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ class ViewerLockedOverlay extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ViewerLockedOverlayState extends State<ViewerLockedOverlay> {
|
class _ViewerLockedOverlayState extends State<ViewerLockedOverlay> {
|
||||||
late Animation<double> _buttonScale;
|
late CurvedAnimation _buttonScale;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -55,7 +55,7 @@ class _ViewerLockedOverlayState extends State<ViewerLockedOverlay> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _unregisterWidget(ViewerLockedOverlay widget) {
|
void _unregisterWidget(ViewerLockedOverlay widget) {
|
||||||
// nothing
|
_buttonScale.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -25,7 +25,7 @@ class SlideshowButtons extends StatefulWidget {
|
||||||
|
|
||||||
class _SlideshowButtonsState extends State<SlideshowButtons> {
|
class _SlideshowButtonsState extends State<SlideshowButtons> {
|
||||||
final FocusScopeNode _buttonRowFocusScopeNode = FocusScopeNode();
|
final FocusScopeNode _buttonRowFocusScopeNode = FocusScopeNode();
|
||||||
late Animation<double> _buttonScale;
|
late CurvedAnimation _buttonScale;
|
||||||
|
|
||||||
static const List<SlideshowAction> _actions = [
|
static const List<SlideshowAction> _actions = [
|
||||||
SlideshowAction.resume,
|
SlideshowAction.resume,
|
||||||
|
@ -65,7 +65,7 @@ class _SlideshowButtonsState extends State<SlideshowButtons> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _unregisterWidget(SlideshowButtons widget) {
|
void _unregisterWidget(SlideshowButtons widget) {
|
||||||
// nothing
|
_buttonScale.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -222,6 +222,6 @@ class _VideoProgressBarState extends State<VideoProgressBar> {
|
||||||
|
|
||||||
double? _progressToDx(double progress) {
|
double? _progressToDx(double progress) {
|
||||||
final box = _getProgressBarRenderBox();
|
final box = _getProgressBarRenderBox();
|
||||||
return box == null ? null : progress * box.size.width;
|
return box != null && box.hasSize ? progress * box.size.width : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ class SlideshowPage extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SlideshowPageState extends State<SlideshowPage> {
|
class _SlideshowPageState extends State<SlideshowPage> {
|
||||||
|
final ValueNotifier<AppMode> _appModeNotifier = ValueNotifier(AppMode.slideshow);
|
||||||
late ViewerController _viewerController;
|
late ViewerController _viewerController;
|
||||||
late CollectionLens _slideshowCollection;
|
late CollectionLens _slideshowCollection;
|
||||||
AvesEntry? _initialEntry;
|
AvesEntry? _initialEntry;
|
||||||
|
@ -51,6 +52,7 @@ class _SlideshowPageState extends State<SlideshowPage> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_appModeNotifier.dispose();
|
||||||
_disposeViewerController();
|
_disposeViewerController();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
@ -59,7 +61,7 @@ class _SlideshowPageState extends State<SlideshowPage> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final initialEntry = _initialEntry;
|
final initialEntry = _initialEntry;
|
||||||
return ListenableProvider<ValueNotifier<AppMode>>.value(
|
return ListenableProvider<ValueNotifier<AppMode>>.value(
|
||||||
value: ValueNotifier(AppMode.slideshow),
|
value: _appModeNotifier,
|
||||||
child: AvesScaffold(
|
child: AvesScaffold(
|
||||||
body: initialEntry == null
|
body: initialEntry == null
|
||||||
? EmptyContent(
|
? EmptyContent(
|
||||||
|
|
|
@ -41,6 +41,7 @@ class _RasterImageViewState extends State<RasterImageView> {
|
||||||
ImageStream? _fullImageStream;
|
ImageStream? _fullImageStream;
|
||||||
late ImageStreamListener _fullImageListener;
|
late ImageStreamListener _fullImageListener;
|
||||||
final ValueNotifier<bool> _fullImageLoaded = ValueNotifier(false);
|
final ValueNotifier<bool> _fullImageLoaded = ValueNotifier(false);
|
||||||
|
ImageInfo? _fullImageInfo;
|
||||||
|
|
||||||
AvesEntry get entry => widget.entry;
|
AvesEntry get entry => widget.entry;
|
||||||
|
|
||||||
|
@ -101,10 +102,13 @@ class _RasterImageViewState extends State<RasterImageView> {
|
||||||
void _unregisterFullImage() {
|
void _unregisterFullImage() {
|
||||||
_fullImageStream?.removeListener(_fullImageListener);
|
_fullImageStream?.removeListener(_fullImageListener);
|
||||||
_fullImageStream = null;
|
_fullImageStream = null;
|
||||||
|
_fullImageInfo?.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onFullImageCompleted(ImageInfo image, bool synchronousCall) {
|
void _onFullImageCompleted(ImageInfo image, bool synchronousCall) {
|
||||||
|
// implementer is responsible for disposing the provided `ImageInfo`
|
||||||
_unregisterFullImage();
|
_unregisterFullImage();
|
||||||
|
_fullImageInfo = image;
|
||||||
_fullImageLoaded.value = true;
|
_fullImageLoaded.value = true;
|
||||||
FullImageLoadedNotification(entry, fullImageProvider).dispatch(context);
|
FullImageLoadedNotification(entry, fullImageProvider).dispatch(context);
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ class _VectorImageViewState extends State<VectorImageView> {
|
||||||
ImageStream? _fullImageStream;
|
ImageStream? _fullImageStream;
|
||||||
late ImageStreamListener _fullImageListener;
|
late ImageStreamListener _fullImageListener;
|
||||||
final ValueNotifier<bool> _fullImageLoaded = ValueNotifier(false);
|
final ValueNotifier<bool> _fullImageLoaded = ValueNotifier(false);
|
||||||
|
ImageInfo? _fullImageInfo;
|
||||||
|
|
||||||
AvesEntry get entry => widget.entry;
|
AvesEntry get entry => widget.entry;
|
||||||
|
|
||||||
|
@ -91,10 +92,13 @@ class _VectorImageViewState extends State<VectorImageView> {
|
||||||
void _unregisterFullImage() {
|
void _unregisterFullImage() {
|
||||||
_fullImageStream?.removeListener(_fullImageListener);
|
_fullImageStream?.removeListener(_fullImageListener);
|
||||||
_fullImageStream = null;
|
_fullImageStream = null;
|
||||||
|
_fullImageInfo?.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onFullImageCompleted(ImageInfo image, bool synchronousCall) {
|
void _onFullImageCompleted(ImageInfo image, bool synchronousCall) {
|
||||||
|
// implementer is responsible for disposing the provided `ImageInfo`
|
||||||
_unregisterFullImage();
|
_unregisterFullImage();
|
||||||
|
_fullImageInfo = image;
|
||||||
_fullImageLoaded.value = true;
|
_fullImageLoaded.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,7 @@ class EntryEditor extends StatefulWidget {
|
||||||
class _EntryEditorState extends State<EntryEditor> with EntryViewControllerMixin, SingleTickerProviderStateMixin {
|
class _EntryEditorState extends State<EntryEditor> with EntryViewControllerMixin, SingleTickerProviderStateMixin {
|
||||||
final ValueNotifier<bool> _overlayVisible = ValueNotifier(true);
|
final ValueNotifier<bool> _overlayVisible = ValueNotifier(true);
|
||||||
late AnimationController _overlayAnimationController;
|
late AnimationController _overlayAnimationController;
|
||||||
late Animation<double> _overlayVideoControlScale;
|
late CurvedAnimation _overlayVideoControlScale;
|
||||||
EdgeInsets? _frozenViewInsets, _frozenViewPadding;
|
EdgeInsets? _frozenViewInsets, _frozenViewPadding;
|
||||||
late VideoActionDelegate _videoActionDelegate;
|
late VideoActionDelegate _videoActionDelegate;
|
||||||
late final ViewerController _viewerController;
|
late final ViewerController _viewerController;
|
||||||
|
@ -120,6 +120,7 @@ class _EntryEditorState extends State<EntryEditor> with EntryViewControllerMixin
|
||||||
void dispose() {
|
void dispose() {
|
||||||
cleanEntryControllers(entry);
|
cleanEntryControllers(entry);
|
||||||
_overlayVisible.dispose();
|
_overlayVisible.dispose();
|
||||||
|
_overlayVideoControlScale.dispose();
|
||||||
_overlayAnimationController.dispose();
|
_overlayAnimationController.dispose();
|
||||||
_videoActionDelegate.dispose();
|
_videoActionDelegate.dispose();
|
||||||
_viewerController.dispose();
|
_viewerController.dispose();
|
||||||
|
|
|
@ -4,7 +4,7 @@ version '1.0-SNAPSHOT'
|
||||||
buildscript {
|
buildscript {
|
||||||
ext {
|
ext {
|
||||||
kotlin_version = '1.9.24'
|
kotlin_version = '1.9.24'
|
||||||
agp_version = '8.4.1'
|
agp_version = '8.5.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
|
|
@ -47,6 +47,7 @@ abstract class AvesVideoController with ABRepeatMixin {
|
||||||
if (kFlutterMemoryAllocationsEnabled) {
|
if (kFlutterMemoryAllocationsEnabled) {
|
||||||
FlutterMemoryAllocations.instance.dispatchObjectDisposed(object: this);
|
FlutterMemoryAllocations.instance.dispatchObjectDisposed(object: this);
|
||||||
}
|
}
|
||||||
|
abRepeatNotifier.dispose();
|
||||||
_entry.visualChangeNotifier.removeListener(onVisualChanged);
|
_entry.visualChangeNotifier.removeListener(onVisualChanged);
|
||||||
await _savePlaybackState();
|
await _savePlaybackState();
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ repository: https://github.com/deckerst/aves
|
||||||
# - play changelog: /whatsnew/whatsnew-en-US
|
# - play changelog: /whatsnew/whatsnew-en-US
|
||||||
# - izzy changelog: /fastlane/metadata/android/en-US/changelogs/XXX01.txt
|
# - izzy changelog: /fastlane/metadata/android/en-US/changelogs/XXX01.txt
|
||||||
# - libre changelog: /fastlane/metadata/android/en-US/changelogs/XXX.txt
|
# - libre changelog: /fastlane/metadata/android/en-US/changelogs/XXX.txt
|
||||||
version: 1.11.2+121
|
version: 1.11.3+122
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
@ -176,7 +176,7 @@ flutter:
|
||||||
# and `_PackageLicensePage` in `/material/about.dart`
|
# and `_PackageLicensePage` in `/material/about.dart`
|
||||||
#
|
#
|
||||||
# `OverlaySnackBar` in `/widgets/common/action_mixins/overlay_snack_bar.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`
|
# `EagerScaleGestureRecognizer` in `/widgets/common/behaviour/eager_scale_gesture_recognizer.dart`
|
||||||
# adapts from Flutter v3.16.0 `ScaleGestureRecognizer` in `/gestures/scale.dart`
|
# adapts from Flutter v3.16.0 `ScaleGestureRecognizer` in `/gestures/scale.dart`
|
||||||
|
|
|
@ -7,7 +7,7 @@ class FakeMediaFetchService extends Fake implements MediaFetchService {
|
||||||
Set<AvesEntry> entries = {};
|
Set<AvesEntry> entries = {};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<AvesEntry?> getEntry(String uri, String? mimeType) async {
|
Future<AvesEntry?> getEntry(String uri, String? mimeType, {bool allowUnsized = false}) async {
|
||||||
return entries.firstWhereOrNull((v) => v.uri == uri);
|
return entries.firstWhereOrNull((v) => v.uri == uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
In v1.11.2:
|
In v1.11.3:
|
||||||
- show selected albums together in Collection
|
- show selected albums together in Collection
|
||||||
Full changelog available on GitHub
|
Full changelog available on GitHub
|
Loading…
Reference in a new issue