diff --git a/CHANGELOG.md b/CHANGELOG.md
index 68c22b1b7..87b437164 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,10 @@ All notable changes to this project will be documented in this file.
- editing description writes XMP `dc:description`, and clears Exif `ImageDescription` / `UserComment`
+### Fixed
+
+- transition between collection and viewer when cutout area is not used
+
## [v1.7.8] - 2022-12-20
### Added
diff --git a/android/app/build.gradle b/android/app/build.gradle
index abcc4d0ef..051231057 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -191,7 +191,7 @@ dependencies {
implementation 'com.drewnoakes:metadata-extractor:2.18.0'
implementation 'com.github.bumptech.glide:glide:4.14.2'
// SLF4J implementation for `mp4parser`
- implementation 'org.slf4j:slf4j-simple:2.0.3'
+ implementation 'org.slf4j:slf4j-simple:2.0.6'
// forked, built by JitPack:
// - https://jitpack.io/p/deckerst/Android-TiffBitmapFactory
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/ActivityWindowHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/ActivityWindowHandler.kt
index 789f57f41..4ffd85e21 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/ActivityWindowHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/ActivityWindowHandler.kt
@@ -3,6 +3,7 @@ package deckers.thibault.aves.channel.calls.window
import android.app.Activity
import android.os.Build
import android.view.WindowManager
+import deckers.thibault.aves.utils.getDisplayCompat
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
@@ -42,25 +43,34 @@ class ActivityWindowHandler(private val activity: Activity) : WindowHandler(acti
result.success(true)
}
- override fun canSetCutoutMode(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
+ override fun isCutoutAware(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
}
- override fun setCutoutMode(call: MethodCall, result: MethodChannel.Result) {
- val use = call.argument("use")
- if (use == null) {
- result.error("setCutoutMode-args", "missing arguments", null)
+ override fun getCutoutInsets(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
+ result.error("getCutoutInsets-sdk", "unsupported SDK version=${Build.VERSION.SDK_INT}", null)
return
}
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- val mode = if (use) {
- WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
- } else {
- WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
- }
- activity.window.attributes.layoutInDisplayCutoutMode = mode
+ val cutout = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ activity.getDisplayCompat()?.cutout
+ } else {
+ activity.window.decorView.rootWindowInsets.displayCutout
}
- result.success(true)
+ if (cutout == null) {
+ result.error("getCutoutInsets-null", "cutout insets are null", null)
+ return
+ }
+
+ val density = activity.resources.displayMetrics.density
+ result.success(
+ hashMapOf(
+ "left" to cutout.safeInsetLeft / density,
+ "top" to cutout.safeInsetTop / density,
+ "right" to cutout.safeInsetRight / density,
+ "bottom" to cutout.safeInsetBottom / density,
+ )
+ )
}
}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/ServiceWindowHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/ServiceWindowHandler.kt
index 55794ade4..46d1e43b8 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/ServiceWindowHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/ServiceWindowHandler.kt
@@ -17,11 +17,11 @@ class ServiceWindowHandler(service: Service) : WindowHandler(service) {
result.success(false)
}
- override fun canSetCutoutMode(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
+ override fun isCutoutAware(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
result.success(false)
}
- override fun setCutoutMode(call: MethodCall, result: MethodChannel.Result) {
- result.success(false)
+ override fun getCutoutInsets(call: MethodCall, result: MethodChannel.Result) {
+ result.success(HashMap())
}
}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/WindowHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/WindowHandler.kt
index 184d2398d..0a6f41249 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/WindowHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/window/WindowHandler.kt
@@ -15,8 +15,8 @@ abstract class WindowHandler(private val contextWrapper: ContextWrapper) : Metho
"keepScreenOn" -> Coresult.safe(call, result, ::keepScreenOn)
"isRotationLocked" -> Coresult.safe(call, result, ::isRotationLocked)
"requestOrientation" -> Coresult.safe(call, result, ::requestOrientation)
- "canSetCutoutMode" -> Coresult.safe(call, result, ::canSetCutoutMode)
- "setCutoutMode" -> Coresult.safe(call, result, ::setCutoutMode)
+ "isCutoutAware" -> Coresult.safe(call, result, ::isCutoutAware)
+ "getCutoutInsets" -> Coresult.safe(call, result, ::getCutoutInsets)
else -> result.notImplemented()
}
}
@@ -37,9 +37,9 @@ abstract class WindowHandler(private val contextWrapper: ContextWrapper) : Metho
abstract fun requestOrientation(call: MethodCall, result: MethodChannel.Result)
- abstract fun canSetCutoutMode(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result)
+ abstract fun isCutoutAware(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result)
- abstract fun setCutoutMode(call: MethodCall, result: MethodChannel.Result)
+ abstract fun getCutoutInsets(call: MethodCall, result: MethodChannel.Result)
companion object {
private val LOG_TAG = LogUtils.createTag()
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ContentImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ContentImageProvider.kt
index 6d5bb2315..7ff773d21 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ContentImageProvider.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ContentImageProvider.kt
@@ -55,7 +55,7 @@ internal class ContentImageProvider : ImageProvider() {
if (cursor != null && cursor.moveToFirst()) {
cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME).let { if (it != -1) fields["title"] = cursor.getString(it) }
cursor.getColumnIndex(OpenableColumns.SIZE).let { if (it != -1) fields["sizeBytes"] = cursor.getLong(it) }
- cursor.getColumnIndex(PATH).let { if (it != -1) fields["path"] = cursor.getString(it) }
+ cursor.getColumnIndex(MediaStore.MediaColumns.DATA).let { if (it != -1) fields["path"] = cursor.getString(it) }
cursor.close()
}
} catch (e: Exception) {
@@ -73,8 +73,5 @@ internal class ContentImageProvider : ImageProvider() {
companion object {
private val LOG_TAG = LogUtils.createTag()
-
- @Suppress("deprecation")
- const val PATH = MediaStore.MediaColumns.DATA
}
}
\ No newline at end of file
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 1e92c2e86..3b29bcf55 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
@@ -55,10 +55,10 @@ class MediaStoreImageProvider : ImageProvider() {
val relativePathDirectory = ensureTrailingSeparator(directory)
val relativePath = PathSegments(context, relativePathDirectory).relativeDir
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && relativePath != null) {
- selection = "${MediaStore.MediaColumns.RELATIVE_PATH} = ? AND ${MediaColumns.PATH} LIKE ?"
+ selection = "${MediaStore.MediaColumns.RELATIVE_PATH} = ? AND ${MediaStore.MediaColumns.DATA} LIKE ?"
selectionArgs = arrayOf(relativePath, "$relativePathDirectory%")
} else {
- selection = "${MediaColumns.PATH} LIKE ?"
+ selection = "${MediaStore.MediaColumns.DATA} LIKE ?"
selectionArgs = arrayOf("$relativePathDirectory%")
}
@@ -139,12 +139,12 @@ class MediaStoreImageProvider : ImageProvider() {
fun checkObsoletePaths(context: Context, knownPathById: Map): List {
val obsoleteIds = ArrayList()
fun check(context: Context, contentUri: Uri) {
- val projection = arrayOf(MediaStore.MediaColumns._ID, MediaColumns.PATH)
+ val projection = arrayOf(MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DATA)
try {
val cursor = context.contentResolver.query(contentUri, projection, null, null, null)
if (cursor != null) {
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
- val pathColumn = cursor.getColumnIndexOrThrow(MediaColumns.PATH)
+ val pathColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)
while (cursor.moveToNext()) {
val id = cursor.getInt(idColumn)
val path = cursor.getString(pathColumn)
@@ -185,7 +185,7 @@ class MediaStoreImageProvider : ImageProvider() {
// image & video
val idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
- val pathColumn = cursor.getColumnIndexOrThrow(MediaColumns.PATH)
+ val pathColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)
val mimeTypeColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE)
val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE)
val widthColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.WIDTH)
@@ -863,7 +863,7 @@ class MediaStoreImageProvider : ImageProvider() {
fun getContentUriForPath(context: Context, path: String): Uri? {
val projection = arrayOf(MediaStore.MediaColumns._ID)
- val selection = "${MediaColumns.PATH} = ?"
+ val selection = "${MediaStore.MediaColumns.DATA} = ?"
val selectionArgs = arrayOf(path)
fun check(context: Context, contentUri: Uri): Uri? {
@@ -892,7 +892,7 @@ class MediaStoreImageProvider : ImageProvider() {
private val BASE_PROJECTION = arrayOf(
MediaStore.MediaColumns._ID,
- MediaColumns.PATH,
+ MediaStore.MediaColumns.DATA,
MediaStore.MediaColumns.MIME_TYPE,
MediaStore.MediaColumns.SIZE,
MediaStore.MediaColumns.WIDTH,
@@ -931,9 +931,6 @@ object MediaColumns {
@SuppressLint("InlinedApi")
const val DURATION = MediaStore.MediaColumns.DURATION
-
- @Suppress("deprecation")
- const val PATH = MediaStore.MediaColumns.DATA
}
typealias NewEntryHandler = (entry: FieldMap) -> Unit
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/Compat.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/Compat.kt
index 9be2fbe67..4aa362a1b 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/Compat.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/Compat.kt
@@ -1,11 +1,13 @@
package deckers.thibault.aves.utils
+import android.app.Activity
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.os.Build
import android.os.Parcelable
+import android.view.Display
inline fun Intent.getParcelableExtraCompat(name: String): T? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
@@ -16,6 +18,14 @@ inline fun Intent.getParcelableExtraCompat(name: String): T? {
}
}
+fun Activity.getDisplayCompat(): Display? {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ display
+ } else {
+ @Suppress("deprecation")
+ windowManager.defaultDisplay
+ }
+}
fun PackageManager.getApplicationInfoCompat(packageName: String, flags: Int): ApplicationInfo {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
diff --git a/lib/services/window_service.dart b/lib/services/window_service.dart
index 1c99b1161..f9da6493c 100644
--- a/lib/services/window_service.dart
+++ b/lib/services/window_service.dart
@@ -11,9 +11,9 @@ abstract class WindowService {
Future requestOrientation([Orientation? orientation]);
- Future canSetCutoutMode();
+ Future isCutoutAware();
- Future setCutoutMode(bool use);
+ Future getCutoutInsets();
}
class PlatformWindowService implements WindowService {
@@ -80,9 +80,9 @@ class PlatformWindowService implements WindowService {
}
@override
- Future canSetCutoutMode() async {
+ Future isCutoutAware() async {
try {
- final result = await _platform.invokeMethod('canSetCutoutMode');
+ final result = await _platform.invokeMethod('isCutoutAware');
if (result != null) return result as bool;
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
@@ -91,13 +91,20 @@ class PlatformWindowService implements WindowService {
}
@override
- Future setCutoutMode(bool use) async {
+ Future getCutoutInsets() async {
try {
- await _platform.invokeMethod('setCutoutMode', {
- 'use': use,
- });
+ final result = await _platform.invokeMethod('getCutoutInsets');
+ if (result != null) {
+ return EdgeInsets.only(
+ left: result['left']?.toDouble() ?? 0,
+ top: result['top']?.toDouble() ?? 0,
+ right: result['right']?.toDouble() ?? 0,
+ bottom: result['bottom']?.toDouble() ?? 0,
+ );
+ }
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
}
+ return EdgeInsets.zero;
}
}
diff --git a/lib/widgets/aves_app.dart b/lib/widgets/aves_app.dart
index a7ff2eae7..116d693ae 100644
--- a/lib/widgets/aves_app.dart
+++ b/lib/widgets/aves_app.dart
@@ -55,6 +55,7 @@ class AvesApp extends StatefulWidget {
// temporary exclude locales not ready yet for prime time
static final _unsupportedLocales = {'ar', 'fa', 'gl', 'nn', 'pl', 'th'}.map(Locale.new).toSet();
static final List supportedLocales = AppLocalizations.supportedLocales.where((v) => !_unsupportedLocales.contains(v)).toList();
+ static final ValueNotifier cutoutInsetsNotifier = ValueNotifier(EdgeInsets.zero);
static final GlobalKey navigatorKey = GlobalKey(debugLabel: 'app-navigator');
// do not monitor all `ModalRoute`s, which would include popup menus,
@@ -164,6 +165,7 @@ class _AvesAppState extends State with WidgetsBindingObserver {
_subscriptions.add(_newIntentChannel.receiveBroadcastStream().listen((event) => _onNewIntent(event as Map?)));
_subscriptions.add(_analysisCompletionChannel.receiveBroadcastStream().listen((event) => _onAnalysisCompletion()));
_subscriptions.add(_errorChannel.receiveBroadcastStream().listen((event) => _onError(event as String?)));
+ _updateCutoutInsets();
WidgetsBinding.instance.addObserver(this);
}
@@ -375,6 +377,13 @@ class _AvesAppState extends State with WidgetsBindingObserver {
}
}
+ @override
+ void didChangeMetrics() => _updateCutoutInsets();
+
+ Future _updateCutoutInsets() async {
+ AvesApp.cutoutInsetsNotifier.value = await windowService.getCutoutInsets();
+ }
+
Widget _getFirstPage({Map? intentData}) => settings.hasAcceptedTerms ? HomePage(intentData: intentData) : const WelcomePage();
Size? _getScreenSize() {
diff --git a/lib/widgets/collection/app_bar.dart b/lib/widgets/collection/app_bar.dart
index 881d583b4..c90e28aa0 100644
--- a/lib/widgets/collection/app_bar.dart
+++ b/lib/widgets/collection/app_bar.dart
@@ -143,9 +143,7 @@ class _CollectionAppBarState extends State with SingleTickerPr
}
@override
- void didChangeMetrics() {
- _updateStatusBarHeight();
- }
+ void didChangeMetrics() => _updateStatusBarHeight();
@override
Widget build(BuildContext context) {
diff --git a/lib/widgets/common/basic/insets.dart b/lib/widgets/common/basic/insets.dart
index 33e91eb0b..d7610b93a 100644
--- a/lib/widgets/common/basic/insets.dart
+++ b/lib/widgets/common/basic/insets.dart
@@ -1,5 +1,9 @@
+import 'dart:math';
+
import 'package:aves/model/device.dart';
+import 'package:aves/widgets/aves_app.dart';
import 'package:aves/widgets/common/extensions/media_query.dart';
+import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/common/tile_extent_controller.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@@ -128,3 +132,59 @@ class TvTileGridBottomPaddingSliver extends StatelessWidget {
);
}
}
+
+// `MediaQuery.padding` matches cutout areas but also includes other system UI like the status bar
+// so we cannot use `SafeArea` along `MediaQuery.removePadding()` to remove cutout areas
+class SafeCutoutArea extends StatelessWidget {
+ final Animation? animation;
+ final Widget child;
+
+ const SafeCutoutArea({
+ super.key,
+ this.animation,
+ required this.child,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return ValueListenableBuilder(
+ valueListenable: AvesApp.cutoutInsetsNotifier,
+ builder: (context, cutoutInsets, child) {
+ return ValueListenableBuilder(
+ valueListenable: animation ?? ValueNotifier(1),
+ builder: (context, factor, child) {
+ final effectiveInsets = cutoutInsets * factor;
+ return Padding(
+ padding: effectiveInsets,
+ child: MediaQueryDataProvider(
+ value: MediaQuery.of(context).removeCutoutInsets(effectiveInsets),
+ child: child!,
+ ),
+ );
+ },
+ child: child,
+ );
+ },
+ child: child,
+ );
+ }
+}
+
+extension ExtraMediaQueryData on MediaQueryData {
+ MediaQueryData removeCutoutInsets(EdgeInsets cutoutInsets) {
+ return copyWith(
+ padding: EdgeInsets.only(
+ left: max(0.0, padding.left - cutoutInsets.left),
+ top: max(0.0, padding.top - cutoutInsets.top),
+ right: max(0.0, padding.right - cutoutInsets.right),
+ bottom: max(0.0, padding.bottom - cutoutInsets.bottom),
+ ),
+ viewPadding: EdgeInsets.only(
+ left: max(0.0, viewPadding.left - cutoutInsets.left),
+ top: max(0.0, viewPadding.top - cutoutInsets.top),
+ right: max(0.0, viewPadding.right - cutoutInsets.right),
+ bottom: max(0.0, viewPadding.bottom - cutoutInsets.bottom),
+ ),
+ );
+ }
+}
diff --git a/lib/widgets/common/providers/media_query_data_provider.dart b/lib/widgets/common/providers/media_query_data_provider.dart
index 71c45c4a4..742c775fc 100644
--- a/lib/widgets/common/providers/media_query_data_provider.dart
+++ b/lib/widgets/common/providers/media_query_data_provider.dart
@@ -2,17 +2,19 @@ import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
class MediaQueryDataProvider extends StatelessWidget {
+ final MediaQueryData? value;
final Widget child;
const MediaQueryDataProvider({
super.key,
+ this.value,
required this.child,
});
@override
Widget build(BuildContext context) {
return Provider.value(
- value: MediaQuery.of(context),
+ value: value ?? MediaQuery.of(context),
child: child,
);
}
diff --git a/lib/widgets/common/thumbnail/image.dart b/lib/widgets/common/thumbnail/image.dart
index ae89a1577..9d7dd5c55 100644
--- a/lib/widgets/common/thumbnail/image.dart
+++ b/lib/widgets/common/thumbnail/image.dart
@@ -9,6 +9,7 @@ import 'package:aves/model/settings/enums/entry_background.dart';
import 'package:aves/model/settings/enums/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/common/services.dart';
+import 'package:aves/widgets/common/basic/insets.dart';
import 'package:aves/widgets/common/fx/checkered_decoration.dart';
import 'package:aves/widgets/common/fx/transition_image.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
@@ -271,13 +272,20 @@ class _ThumbnailImageState extends State {
image = Hero(
tag: widget.heroTag!,
flightShuttleBuilder: (flight, animation, direction, fromHero, toHero) {
- return TransitionImage(
+ Widget child = TransitionImage(
image: entry.bestCachedThumbnail,
animation: animation,
thumbnailFit: isMosaic ? BoxFit.contain : BoxFit.cover,
viewerFit: BoxFit.contain,
background: backgroundColor,
);
+ if (!settings.viewerUseCutout) {
+ child = SafeCutoutArea(
+ animation: animation,
+ child: child,
+ );
+ }
+ return child;
},
transitionOnUserGestures: true,
child: image,
diff --git a/lib/widgets/settings/viewer/viewer.dart b/lib/widgets/settings/viewer/viewer.dart
index 74bd92888..557747f49 100644
--- a/lib/widgets/settings/viewer/viewer.dart
+++ b/lib/widgets/settings/viewer/viewer.dart
@@ -32,13 +32,13 @@ class ViewerSection extends SettingsSection {
@override
FutureOr> tiles(BuildContext context) async {
- final canSetCutoutMode = await windowService.canSetCutoutMode();
+ final isCutoutAware = await windowService.isCutoutAware();
return [
if (!device.isTelevision) SettingsTileViewerQuickActions(),
SettingsTileViewerOverlay(),
SettingsTileViewerSlideshow(),
if (!device.isTelevision) SettingsTileViewerGestureSideTapNext(),
- if (!device.isTelevision && canSetCutoutMode) SettingsTileViewerCutoutMode(),
+ if (!device.isTelevision && isCutoutAware) SettingsTileViewerUseCutout(),
if (!device.isTelevision) SettingsTileViewerMaxBrightness(),
SettingsTileViewerMotionPhotoAutoPlay(),
SettingsTileViewerImageBackground(),
@@ -94,7 +94,7 @@ class SettingsTileViewerGestureSideTapNext extends SettingsTile {
);
}
-class SettingsTileViewerCutoutMode extends SettingsTile {
+class SettingsTileViewerUseCutout extends SettingsTile {
@override
String title(BuildContext context) => context.l10n.settingsViewerUseCutout;
diff --git a/lib/widgets/viewer/entry_viewer_stack.dart b/lib/widgets/viewer/entry_viewer_stack.dart
index b74579cdf..457a1bae0 100644
--- a/lib/widgets/viewer/entry_viewer_stack.dart
+++ b/lib/widgets/viewer/entry_viewer_stack.dart
@@ -95,9 +95,6 @@ class _EntryViewerStackState extends State with EntryViewContr
@override
void initState() {
super.initState();
- if (!settings.viewerUseCutout) {
- windowService.setCutoutMode(false);
- }
if (settings.viewerMaxBrightness) {
ScreenBrightness().setScreenBrightness(1);
}
@@ -205,88 +202,35 @@ class _EntryViewerStackState extends State with EntryViewContr
child: ValueListenableProvider.value(
value: _heroInfoNotifier,
child: NotificationListener(
- onNotification: (dynamic notification) {
- if (notification is FilterSelectedNotification) {
- _goToCollection(notification.filter);
- } else if (notification is EntryDeletedNotification) {
- _onEntryRemoved(context, notification.entries);
- } else if (notification is EntryMovedNotification) {
- // only add or remove entries following user actions,
- // instead of applying all collection source changes
- final isBin = collection?.filters.contains(TrashFilter.instance) ?? false;
- final entries = notification.entries;
- switch (notification.moveType) {
- case MoveType.move:
- _onEntryRemoved(context, entries);
- break;
- case MoveType.toBin:
- if (!isBin) {
- _onEntryRemoved(context, entries);
- }
- break;
- case MoveType.fromBin:
- if (isBin) {
- _onEntryRemoved(context, entries);
- } else {
- _onEntryRestored(entries);
- }
- break;
- case MoveType.copy:
- case MoveType.export:
- break;
- }
- } else if (notification is ToggleOverlayNotification) {
- _overlayVisible.value = notification.visible ?? !_overlayVisible.value;
- } else if (notification is TvShowLessInfoNotification) {
- if (_overlayVisible.value) {
- _overlayVisible.value = false;
- } else {
- _onWillPop();
- }
- } else if (notification is TvShowMoreInfoNotification) {
- if (!_overlayVisible.value) {
- _overlayVisible.value = true;
- }
- } else if (notification is ShowInfoPageNotification) {
- _goToVerticalPage(infoPage);
- } else if (notification is JumpToPreviousEntryNotification) {
- _jumpToHorizontalPageByDelta(-1);
- } else if (notification is JumpToNextEntryNotification) {
- _jumpToHorizontalPageByDelta(1);
- } else if (notification is JumpToEntryNotification) {
- _jumpToHorizontalPageByIndex(notification.index);
- } else if (notification is VideoActionNotification) {
- final controller = notification.controller;
- final action = notification.action;
- _onVideoAction(context, controller, action);
- } else {
- return false;
- }
- return true;
- },
- child: Stack(
- children: [
- ViewerVerticalPageView(
- collection: collection,
- entryNotifier: entryNotifier,
- viewerController: viewerController,
- overlayOpacity: _overlayInitialized
- ? _overlayOpacity
- : settings.showOverlayOnOpening
- ? kAlwaysCompleteAnimation
- : kAlwaysDismissedAnimation,
- verticalPager: _verticalPager,
- horizontalPager: _horizontalPager,
- onVerticalPageChanged: _onVerticalPageChanged,
- onHorizontalPageChanged: _onHorizontalPageChanged,
- onImagePageRequested: () => _goToVerticalPage(imagePage),
- onViewDisposed: (mainEntry, pageEntry) => viewStateConductor.reset(pageEntry ?? mainEntry),
- ),
- ..._buildOverlays().map(_decorateOverlay),
- const TopGestureAreaProtector(),
- const SideGestureAreaProtector(),
- const BottomGestureAreaProtector(),
- ],
+ onNotification: _handleNotification,
+ child: LayoutBuilder(
+ builder: (context, constraints) {
+ final availableSize = Size(constraints.maxWidth, constraints.maxHeight);
+ return Stack(
+ children: [
+ ViewerVerticalPageView(
+ collection: collection,
+ entryNotifier: entryNotifier,
+ viewerController: viewerController,
+ overlayOpacity: _overlayInitialized
+ ? _overlayOpacity
+ : settings.showOverlayOnOpening
+ ? kAlwaysCompleteAnimation
+ : kAlwaysDismissedAnimation,
+ verticalPager: _verticalPager,
+ horizontalPager: _horizontalPager,
+ onVerticalPageChanged: _onVerticalPageChanged,
+ onHorizontalPageChanged: _onHorizontalPageChanged,
+ onImagePageRequested: () => _goToVerticalPage(imagePage),
+ onViewDisposed: (mainEntry, pageEntry) => viewStateConductor.reset(pageEntry ?? mainEntry),
+ ),
+ ..._buildOverlays(availableSize).map(_decorateOverlay),
+ const TopGestureAreaProtector(),
+ const SideGestureAreaProtector(),
+ const BottomGestureAreaProtector(),
+ ],
+ );
+ },
),
),
),
@@ -306,46 +250,41 @@ class _EntryViewerStackState extends State with EntryViewContr
);
}
- List _buildOverlays() {
+ List _buildOverlays(Size availableSize) {
final appMode = context.read>().value;
switch (appMode) {
case AppMode.screenSaver:
return [];
case AppMode.slideshow:
return [
- _buildSlideshowBottomOverlay(),
+ _buildSlideshowBottomOverlay(availableSize),
];
default:
return [
- _buildViewerTopOverlay(),
- _buildViewerBottomOverlay(),
+ _buildViewerTopOverlay(availableSize),
+ _buildViewerBottomOverlay(availableSize),
];
}
}
- Widget _buildSlideshowBottomOverlay() {
- return Selector(
- selector: (context, mq) => mq.size,
- builder: (context, mqSize, child) {
- return SizedBox.fromSize(
- size: mqSize,
- child: Align(
- alignment: AlignmentDirectional.bottomEnd,
- child: TooltipTheme(
- data: TooltipTheme.of(context).copyWith(
- preferBelow: false,
- ),
- child: SlideshowButtons(
- animationController: _overlayAnimationController,
- ),
- ),
+ Widget _buildSlideshowBottomOverlay(Size availableSize) {
+ return SizedBox.fromSize(
+ size: availableSize,
+ child: Align(
+ alignment: AlignmentDirectional.bottomEnd,
+ child: TooltipTheme(
+ data: TooltipTheme.of(context).copyWith(
+ preferBelow: false,
),
- );
- },
+ child: SlideshowButtons(
+ animationController: _overlayAnimationController,
+ ),
+ ),
+ ),
);
}
- Widget _buildViewerTopOverlay() {
+ Widget _buildViewerTopOverlay(Size availableSize) {
Widget child = ValueListenableBuilder(
valueListenable: entryNotifier,
builder: (context, mainEntry, child) {
@@ -359,6 +298,7 @@ class _EntryViewerStackState extends State with EntryViewContr
hasCollection: hasCollection,
mainEntry: mainEntry,
scale: _overlayButtonScale,
+ availableSize: availableSize,
viewInsets: _frozenViewInsets,
viewPadding: _frozenViewPadding,
),
@@ -380,7 +320,7 @@ class _EntryViewerStackState extends State with EntryViewContr
return child;
}
- Widget _buildViewerBottomOverlay() {
+ Widget _buildViewerBottomOverlay(Size availableSize) {
Widget child = ValueListenableBuilder(
valueListenable: entryNotifier,
builder: (context, mainEntry, child) {
@@ -447,6 +387,7 @@ class _EntryViewerStackState extends State with EntryViewContr
index: _currentEntryIndex,
collection: collection,
animationController: _overlayAnimationController,
+ availableSize: availableSize,
viewInsets: _frozenViewInsets,
viewPadding: _frozenViewPadding,
multiPageController: multiPageController,
@@ -466,7 +407,7 @@ class _EntryViewerStackState extends State with EntryViewContr
return AnimatedBuilder(
animation: _verticalScrollNotifier,
builder: (context, child) => Positioned(
- bottom: (_verticalPager.hasClients && _verticalPager.position.hasPixels ? _verticalPager.offset : 0) - mqHeight,
+ bottom: (_verticalPager.hasClients && _verticalPager.position.hasPixels ? _verticalPager.offset : 0) - availableSize.height,
child: child!,
),
child: child,
@@ -478,6 +419,66 @@ class _EntryViewerStackState extends State with EntryViewContr
return child;
}
+ bool _handleNotification(dynamic notification) {
+ if (notification is FilterSelectedNotification) {
+ _goToCollection(notification.filter);
+ } else if (notification is EntryDeletedNotification) {
+ _onEntryRemoved(context, notification.entries);
+ } else if (notification is EntryMovedNotification) {
+ // only add or remove entries following user actions,
+ // instead of applying all collection source changes
+ final isBin = collection?.filters.contains(TrashFilter.instance) ?? false;
+ final entries = notification.entries;
+ switch (notification.moveType) {
+ case MoveType.move:
+ _onEntryRemoved(context, entries);
+ break;
+ case MoveType.toBin:
+ if (!isBin) {
+ _onEntryRemoved(context, entries);
+ }
+ break;
+ case MoveType.fromBin:
+ if (isBin) {
+ _onEntryRemoved(context, entries);
+ } else {
+ _onEntryRestored(entries);
+ }
+ break;
+ case MoveType.copy:
+ case MoveType.export:
+ break;
+ }
+ } else if (notification is ToggleOverlayNotification) {
+ _overlayVisible.value = notification.visible ?? !_overlayVisible.value;
+ } else if (notification is TvShowLessInfoNotification) {
+ if (_overlayVisible.value) {
+ _overlayVisible.value = false;
+ } else {
+ _onWillPop();
+ }
+ } else if (notification is TvShowMoreInfoNotification) {
+ if (!_overlayVisible.value) {
+ _overlayVisible.value = true;
+ }
+ } else if (notification is ShowInfoPageNotification) {
+ _goToVerticalPage(infoPage);
+ } else if (notification is JumpToPreviousEntryNotification) {
+ _jumpToHorizontalPageByDelta(-1);
+ } else if (notification is JumpToNextEntryNotification) {
+ _jumpToHorizontalPageByDelta(1);
+ } else if (notification is JumpToEntryNotification) {
+ _jumpToHorizontalPageByIndex(notification.index);
+ } else if (notification is VideoActionNotification) {
+ final controller = notification.controller;
+ final action = notification.action;
+ _onVideoAction(context, controller, action);
+ } else {
+ return false;
+ }
+ return true;
+ }
+
Future _onVideoAction(BuildContext context, AvesVideoController controller, EntryAction action) async {
await _videoActionDelegate.onActionSelected(context, controller, action);
if (action == EntryAction.videoToggleMute) {
@@ -673,9 +674,6 @@ class _EntryViewerStackState extends State with EntryViewContr
}
Future _onLeave() async {
- if (!settings.viewerUseCutout) {
- await windowService.setCutoutMode(true);
- }
if (settings.viewerMaxBrightness) {
await ScreenBrightness().resetScreenBrightness();
}
diff --git a/lib/widgets/viewer/overlay/bottom.dart b/lib/widgets/viewer/overlay/bottom.dart
index 8523e3f4a..47f68f93a 100644
--- a/lib/widgets/viewer/overlay/bottom.dart
+++ b/lib/widgets/viewer/overlay/bottom.dart
@@ -25,6 +25,7 @@ class ViewerBottomOverlay extends StatefulWidget {
final int index;
final CollectionLens? collection;
final AnimationController animationController;
+ final Size availableSize;
final EdgeInsets? viewInsets, viewPadding;
final MultiPageController? multiPageController;
@@ -34,6 +35,7 @@ class ViewerBottomOverlay extends StatefulWidget {
required this.index,
required this.collection,
required this.animationController,
+ required this.availableSize,
this.viewInsets,
this.viewPadding,
required this.multiPageController,
@@ -72,6 +74,7 @@ class _ViewerBottomOverlayState extends State {
mainEntry: mainEntry,
pageEntry: pageEntry ?? mainEntry,
collection: widget.collection,
+ availableSize: widget.availableSize,
viewInsets: widget.viewInsets,
viewPadding: widget.viewPadding,
multiPageController: multiPageController,
@@ -103,6 +106,7 @@ class _BottomOverlayContent extends StatefulWidget {
final int index;
final AvesEntry mainEntry, pageEntry;
final CollectionLens? collection;
+ final Size availableSize;
final EdgeInsets? viewInsets, viewPadding;
final MultiPageController? multiPageController;
final AnimationController animationController;
@@ -113,6 +117,7 @@ class _BottomOverlayContent extends StatefulWidget {
required this.mainEntry,
required this.pageEntry,
required this.collection,
+ required this.availableSize,
required this.viewInsets,
required this.viewPadding,
required this.multiPageController,
@@ -178,89 +183,85 @@ class _BottomOverlayContentState extends State<_BottomOverlayContent> {
pageEntry.metadataChangeNotifier,
]),
builder: (context, child) {
- return Selector(
- selector: (context, mq) => mq.size.width,
- builder: (context, mqWidth, child) {
- final viewInsetsPadding = (widget.viewInsets ?? EdgeInsets.zero) + (widget.viewPadding ?? EdgeInsets.zero);
- final viewerButtonRow = FocusableActionDetector(
- focusNode: _buttonRowFocusScopeNode,
- shortcuts: device.isTelevision ? const {SingleActivator(LogicalKeyboardKey.arrowUp): TvShowLessInfoIntent()} : null,
- actions: {TvShowLessInfoIntent: CallbackAction(onInvoke: (intent) => TvShowLessInfoNotification().dispatch(context))},
- child: SafeArea(
- top: false,
- bottom: false,
- minimum: EdgeInsets.only(
- left: viewInsetsPadding.left,
- right: viewInsetsPadding.right,
+ final viewInsetsPadding = (widget.viewInsets ?? EdgeInsets.zero) + (widget.viewPadding ?? EdgeInsets.zero);
+ final viewerButtonRow = FocusableActionDetector(
+ focusNode: _buttonRowFocusScopeNode,
+ shortcuts: device.isTelevision ? const {SingleActivator(LogicalKeyboardKey.arrowUp): TvShowLessInfoIntent()} : null,
+ actions: {TvShowLessInfoIntent: CallbackAction(onInvoke: (intent) => TvShowLessInfoNotification().dispatch(context))},
+ child: SafeArea(
+ top: false,
+ bottom: false,
+ minimum: EdgeInsets.only(
+ left: viewInsetsPadding.left,
+ right: viewInsetsPadding.right,
+ ),
+ child: isWallpaperMode
+ ? WallpaperButtons(
+ entry: pageEntry,
+ scale: _buttonScale,
+ )
+ : ViewerButtons(
+ mainEntry: mainEntry,
+ pageEntry: pageEntry,
+ collection: widget.collection,
+ scale: _buttonScale,
+ ),
+ ),
+ );
+
+ final showMultiPageOverlay = mainEntry.isMultiPage && multiPageController != null;
+ final collapsedPageScroller = mainEntry.isMotionPhoto;
+
+ final availableWidth = widget.availableSize.width;
+ return SizedBox(
+ width: availableWidth,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ if (showMultiPageOverlay && !collapsedPageScroller)
+ Padding(
+ padding: const EdgeInsets.only(bottom: 8),
+ child: FadeTransition(
+ opacity: _thumbnailOpacity,
+ child: MultiPageOverlay(
+ controller: multiPageController,
+ availableWidth: availableWidth,
+ scrollable: true,
+ ),
+ ),
),
- child: isWallpaperMode
- ? WallpaperButtons(
- entry: pageEntry,
- scale: _buttonScale,
- )
- : ViewerButtons(
- mainEntry: mainEntry,
- pageEntry: pageEntry,
- collection: widget.collection,
- scale: _buttonScale,
- ),
- ),
- );
-
- final showMultiPageOverlay = mainEntry.isMultiPage && multiPageController != null;
- final collapsedPageScroller = mainEntry.isMotionPhoto;
-
- return SizedBox(
- width: mqWidth,
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- if (showMultiPageOverlay && !collapsedPageScroller)
- Padding(
- padding: const EdgeInsets.only(bottom: 8),
- child: FadeTransition(
- opacity: _thumbnailOpacity,
- child: MultiPageOverlay(
- controller: multiPageController,
- availableWidth: mqWidth,
- scrollable: true,
- ),
- ),
- ),
- (showMultiPageOverlay && collapsedPageScroller)
- ? Row(
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- SafeArea(
- top: false,
- bottom: false,
- child: Padding(
- padding: const EdgeInsets.only(bottom: 8),
- child: MultiPageOverlay(
- controller: multiPageController,
- availableWidth: mqWidth,
- scrollable: false,
- ),
- ),
+ (showMultiPageOverlay && collapsedPageScroller)
+ ? Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ SafeArea(
+ top: false,
+ bottom: false,
+ child: Padding(
+ padding: const EdgeInsets.only(bottom: 8),
+ child: MultiPageOverlay(
+ controller: multiPageController,
+ availableWidth: availableWidth,
+ scrollable: false,
),
- Expanded(child: viewerButtonRow),
- ],
- )
- : viewerButtonRow,
- if (settings.showOverlayThumbnailPreview && !isWallpaperMode)
- FadeTransition(
- opacity: _thumbnailOpacity,
- child: ViewerThumbnailPreview(
- availableWidth: mqWidth,
- displayedIndex: widget.index,
- entries: widget.entries,
- ),
- ),
- ],
- ),
- );
- },
+ ),
+ ),
+ Expanded(child: viewerButtonRow),
+ ],
+ )
+ : viewerButtonRow,
+ if (settings.showOverlayThumbnailPreview && !isWallpaperMode)
+ FadeTransition(
+ opacity: _thumbnailOpacity,
+ child: ViewerThumbnailPreview(
+ availableWidth: availableWidth,
+ displayedIndex: widget.index,
+ entries: widget.entries,
+ ),
+ ),
+ ],
+ ),
);
},
);
diff --git a/lib/widgets/viewer/overlay/details/details.dart b/lib/widgets/viewer/overlay/details/details.dart
index fe0414169..0e7be08e7 100644
--- a/lib/widgets/viewer/overlay/details/details.dart
+++ b/lib/widgets/viewer/overlay/details/details.dart
@@ -23,6 +23,7 @@ class ViewerDetailOverlay extends StatefulWidget {
final int index;
final bool hasCollection;
final MultiPageController? multiPageController;
+ final Size availableSize;
const ViewerDetailOverlay({
super.key,
@@ -30,6 +31,7 @@ class ViewerDetailOverlay extends StatefulWidget {
required this.index,
required this.hasCollection,
required this.multiPageController,
+ required this.availableSize,
});
@override
@@ -79,41 +81,35 @@ class _ViewerDetailOverlayState extends State {
return SafeArea(
top: false,
bottom: false,
- child: LayoutBuilder(
- builder: (context, constraints) {
- final availableWidth = constraints.maxWidth;
+ child: FutureBuilder?>(
+ future: _detailLoader,
+ builder: (context, snapshot) {
+ if (snapshot.connectionState == ConnectionState.done && !snapshot.hasError) {
+ _lastDetails = snapshot.data;
+ _lastEntry = entry;
+ }
+ if (_lastEntry == null) return const SizedBox();
+ final mainEntry = _lastEntry!;
- return FutureBuilder?>(
- future: _detailLoader,
- builder: (context, snapshot) {
- if (snapshot.connectionState == ConnectionState.done && !snapshot.hasError) {
- _lastDetails = snapshot.data;
- _lastEntry = entry;
- }
- if (_lastEntry == null) return const SizedBox();
- final mainEntry = _lastEntry!;
+ final shootingDetails = _lastDetails![0];
+ final description = _lastDetails![1];
- final shootingDetails = _lastDetails![0];
- final description = _lastDetails![1];
+ final multiPageController = widget.multiPageController;
+ Widget _buildContent({AvesEntry? pageEntry}) => ViewerDetailOverlayContent(
+ pageEntry: pageEntry ?? mainEntry,
+ shootingDetails: shootingDetails,
+ description: description,
+ position: widget.hasCollection ? '${widget.index + 1}/${entries.length}' : null,
+ availableWidth: widget.availableSize.width,
+ multiPageController: multiPageController,
+ );
- final multiPageController = widget.multiPageController;
- Widget _buildContent({AvesEntry? pageEntry}) => ViewerDetailOverlayContent(
- pageEntry: pageEntry ?? mainEntry,
- shootingDetails: shootingDetails,
- description: description,
- position: widget.hasCollection ? '${widget.index + 1}/${entries.length}' : null,
- availableWidth: availableWidth,
- multiPageController: multiPageController,
- );
-
- return multiPageController != null
- ? PageEntryBuilder(
- multiPageController: multiPageController,
- builder: (pageEntry) => _buildContent(pageEntry: pageEntry),
- )
- : _buildContent();
- },
- );
+ return multiPageController != null
+ ? PageEntryBuilder(
+ multiPageController: multiPageController,
+ builder: (pageEntry) => _buildContent(pageEntry: pageEntry),
+ )
+ : _buildContent();
},
),
);
diff --git a/lib/widgets/viewer/overlay/top.dart b/lib/widgets/viewer/overlay/top.dart
index 657eee86d..3d3e8ddaa 100644
--- a/lib/widgets/viewer/overlay/top.dart
+++ b/lib/widgets/viewer/overlay/top.dart
@@ -16,6 +16,7 @@ class ViewerTopOverlay extends StatelessWidget {
final AvesEntry mainEntry;
final Animation scale;
final bool hasCollection;
+ final Size availableSize;
final EdgeInsets? viewInsets, viewPadding;
const ViewerTopOverlay({
@@ -25,6 +26,7 @@ class ViewerTopOverlay extends StatelessWidget {
required this.mainEntry,
required this.scale,
required this.hasCollection,
+ required this.availableSize,
required this.viewInsets,
required this.viewPadding,
});
@@ -65,6 +67,7 @@ class ViewerTopOverlay extends StatelessWidget {
entries: entries,
hasCollection: hasCollection,
multiPageController: multiPageController,
+ availableSize: availableSize,
),
),
),
diff --git a/lib/widgets/viewer/visual/entry_page_view.dart b/lib/widgets/viewer/visual/entry_page_view.dart
index c263cad74..301eef9e8 100644
--- a/lib/widgets/viewer/visual/entry_page_view.dart
+++ b/lib/widgets/viewer/visual/entry_page_view.dart
@@ -9,6 +9,7 @@ import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/action_mixins/feedback.dart';
+import 'package:aves/widgets/common/basic/insets.dart';
import 'package:aves/widgets/common/thumbnail/image.dart';
import 'package:aves/widgets/viewer/controller.dart';
import 'package:aves/widgets/viewer/hero.dart';
@@ -147,6 +148,7 @@ class _EntryPageViewState extends State with SingleTickerProvider
child = _buildRasterView();
}
}
+
child ??= ErrorView(
entry: entry,
onTap: _onTap,
@@ -155,6 +157,14 @@ class _EntryPageViewState extends State with SingleTickerProvider
},
);
+ if (!settings.viewerUseCutout) {
+ child = SafeCutoutArea(
+ child: ClipRect(
+ child: child,
+ ),
+ );
+ }
+
final animate = context.select((v) => v.accessibilityAnimations.animate);
if (animate) {
child = Consumer(
@@ -166,6 +176,7 @@ class _EntryPageViewState extends State with SingleTickerProvider
child: child,
);
}
+
return child;
}
diff --git a/lib/widgets/wallpaper_page.dart b/lib/widgets/wallpaper_page.dart
index fbac45bbc..dc2773844 100644
--- a/lib/widgets/wallpaper_page.dart
+++ b/lib/widgets/wallpaper_page.dart
@@ -83,9 +83,6 @@ class _EntryEditorState extends State with EntryViewControllerMixin
@override
void initState() {
super.initState();
- if (!settings.viewerUseCutout) {
- windowService.setCutoutMode(false);
- }
if (settings.viewerMaxBrightness) {
ScreenBrightness().setScreenBrightness(1);
}
@@ -134,25 +131,30 @@ class _EntryEditorState extends State with EntryViewControllerMixin
}
return true;
},
- child: Stack(
- children: [
- SingleEntryScroller(
- entry: entry,
- viewerController: _viewerController,
- ),
- Positioned(
- bottom: 0,
- child: _buildBottomOverlay(),
- ),
- const TopGestureAreaProtector(),
- const SideGestureAreaProtector(),
- const BottomGestureAreaProtector(),
- ],
+ child: LayoutBuilder(
+ builder: (context, constraints) {
+ final viewSize = Size(constraints.maxWidth, constraints.maxHeight);
+ return Stack(
+ children: [
+ SingleEntryScroller(
+ entry: entry,
+ viewerController: _viewerController,
+ ),
+ Positioned(
+ bottom: 0,
+ child: _buildBottomOverlay(viewSize),
+ ),
+ const TopGestureAreaProtector(),
+ const SideGestureAreaProtector(),
+ const BottomGestureAreaProtector(),
+ ],
+ );
+ },
),
);
}
- Widget _buildBottomOverlay() {
+ Widget _buildBottomOverlay(Size viewSize) {
final mainEntry = entry;
final multiPageController = mainEntry.isMultiPage ? context.read().getController(mainEntry) : null;
@@ -210,6 +212,7 @@ class _EntryEditorState extends State with EntryViewControllerMixin
index: 0,
collection: null,
animationController: _overlayAnimationController,
+ availableSize: viewSize,
viewInsets: _frozenViewInsets,
viewPadding: _frozenViewPadding,
multiPageController: multiPageController,
diff --git a/plugins/aves_magnifier/lib/src/core/core.dart b/plugins/aves_magnifier/lib/src/core/core.dart
index d8512c37c..eb24275ba 100644
--- a/plugins/aves_magnifier/lib/src/core/core.dart
+++ b/plugins/aves_magnifier/lib/src/core/core.dart
@@ -119,7 +119,7 @@ class _MagnifierCoreState extends State with TickerProviderStateM
if (_doubleTap) {
// quick scale, aka one finger zoom
// magic numbers from `davemorrissey/subsampling-scale-image-view`
- final focalPointY = details.focalPoint.dy;
+ final focalPointY = details.localFocalPoint.dy;
final distance = (focalPointY - _startFocalPoint!.dy).abs() * 2 + 20;
_quickScaleLastDistance ??= distance;
final spanDiff = (1 - (distance / _quickScaleLastDistance!)).abs() * .5;
@@ -131,7 +131,7 @@ class _MagnifierCoreState extends State with TickerProviderStateM
} else {
newScale = _startScale! * details.scale;
}
- final scaleFocalPoint = _doubleTap ? _startFocalPoint! : details.focalPoint;
+ final scaleFocalPoint = _doubleTap ? _startFocalPoint! : details.localFocalPoint;
final panPositionDelta = scaleFocalPoint - _lastViewportFocalPosition!;
final scalePositionDelta = boundaries.viewportToStatePosition(controller, scaleFocalPoint) * (scale! / newScale - 1);
diff --git a/test/fake/window_service.dart b/test/fake/window_service.dart
index 500d275c3..b1a293f10 100644
--- a/test/fake/window_service.dart
+++ b/test/fake/window_service.dart
@@ -17,8 +17,11 @@ class FakeWindowService extends Fake implements WindowService {
Future requestOrientation([Orientation? orientation]) => SynchronousFuture(null);
@override
- Future canSetCutoutMode() => SynchronousFuture(true);
+ Future isCutoutAware() => SynchronousFuture(true);
@override
Future setCutoutMode(bool use) => SynchronousFuture(null);
+
+ @override
+ Future getCutoutInsets() => SynchronousFuture(EdgeInsets.zero);
}