settings: option to exclude cutout area in viewer
This commit is contained in:
parent
6c3ec82dba
commit
5f04ebaf78
9 changed files with 128 additions and 21 deletions
|
@ -1,6 +1,7 @@
|
||||||
package deckers.thibault.aves.channel.calls
|
package deckers.thibault.aves.channel.calls
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.os.Build
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
@ -16,6 +17,8 @@ class WindowHandler(private val activity: Activity) : MethodCallHandler {
|
||||||
"keepScreenOn" -> safe(call, result, ::keepScreenOn)
|
"keepScreenOn" -> safe(call, result, ::keepScreenOn)
|
||||||
"isRotationLocked" -> safe(call, result, ::isRotationLocked)
|
"isRotationLocked" -> safe(call, result, ::isRotationLocked)
|
||||||
"requestOrientation" -> safe(call, result, ::requestOrientation)
|
"requestOrientation" -> safe(call, result, ::requestOrientation)
|
||||||
|
"canSetCutoutMode" -> result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
|
||||||
|
"setCutoutMode" -> safe(call, result, ::setCutoutMode)
|
||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,6 +60,24 @@ class WindowHandler(private val activity: Activity) : MethodCallHandler {
|
||||||
result.success(true)
|
result.success(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setCutoutMode(call: MethodCall, result: MethodChannel.Result) {
|
||||||
|
val use = call.argument<Boolean>("use")
|
||||||
|
if (use == null) {
|
||||||
|
result.error("setCutoutMode-args", "failed because of missing arguments", 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
|
||||||
|
}
|
||||||
|
result.success(true)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val LOG_TAG = LogUtils.createTag<WindowHandler>()
|
private val LOG_TAG = LogUtils.createTag<WindowHandler>()
|
||||||
const val CHANNEL = "deckers.thibault/aves/window"
|
const val CHANNEL = "deckers.thibault/aves/window"
|
||||||
|
|
|
@ -591,6 +591,8 @@
|
||||||
"@settingsViewerShowShootingDetails": {},
|
"@settingsViewerShowShootingDetails": {},
|
||||||
"settingsViewerEnableOverlayBlurEffect": "Overlay blur effect",
|
"settingsViewerEnableOverlayBlurEffect": "Overlay blur effect",
|
||||||
"@settingsViewerEnableOverlayBlurEffect": {},
|
"@settingsViewerEnableOverlayBlurEffect": {},
|
||||||
|
"settingsViewerUseCutout": "Use cutout area",
|
||||||
|
"@settingsViewerUseCutout": {},
|
||||||
|
|
||||||
"settingsViewerQuickActionsTile": "Quick actions",
|
"settingsViewerQuickActionsTile": "Quick actions",
|
||||||
"@settingsViewerQuickActionsTile": {},
|
"@settingsViewerQuickActionsTile": {},
|
||||||
|
|
|
@ -276,6 +276,7 @@
|
||||||
"settingsViewerShowInformationSubtitle": "제목, 날짜, 장소 등 표시",
|
"settingsViewerShowInformationSubtitle": "제목, 날짜, 장소 등 표시",
|
||||||
"settingsViewerShowShootingDetails": "촬영 정보 표시",
|
"settingsViewerShowShootingDetails": "촬영 정보 표시",
|
||||||
"settingsViewerEnableOverlayBlurEffect": "오버레이 흐림 효과",
|
"settingsViewerEnableOverlayBlurEffect": "오버레이 흐림 효과",
|
||||||
|
"settingsViewerUseCutout": "컷아웃 영역 사용",
|
||||||
|
|
||||||
"settingsViewerQuickActionsTile": "빠른 작업",
|
"settingsViewerQuickActionsTile": "빠른 작업",
|
||||||
"settingsViewerQuickActionEditorTitle": "빠른 작업",
|
"settingsViewerQuickActionEditorTitle": "빠른 작업",
|
||||||
|
|
|
@ -66,6 +66,7 @@ class Settings extends ChangeNotifier {
|
||||||
static const showOverlayInfoKey = 'show_overlay_info';
|
static const showOverlayInfoKey = 'show_overlay_info';
|
||||||
static const showOverlayShootingDetailsKey = 'show_overlay_shooting_details';
|
static const showOverlayShootingDetailsKey = 'show_overlay_shooting_details';
|
||||||
static const enableOverlayBlurEffectKey = 'enable_overlay_blur_effect';
|
static const enableOverlayBlurEffectKey = 'enable_overlay_blur_effect';
|
||||||
|
static const viewerUseCutoutKey = 'viewer_use_cutout';
|
||||||
|
|
||||||
// video
|
// video
|
||||||
static const videoQuickActionsKey = 'video_quick_actions';
|
static const videoQuickActionsKey = 'video_quick_actions';
|
||||||
|
@ -261,6 +262,10 @@ class Settings extends ChangeNotifier {
|
||||||
|
|
||||||
set enableOverlayBlurEffect(bool newValue) => setAndNotify(enableOverlayBlurEffectKey, newValue);
|
set enableOverlayBlurEffect(bool newValue) => setAndNotify(enableOverlayBlurEffectKey, newValue);
|
||||||
|
|
||||||
|
bool get viewerUseCutout => getBoolOrDefault(viewerUseCutoutKey, true);
|
||||||
|
|
||||||
|
set viewerUseCutout(bool newValue) => setAndNotify(viewerUseCutoutKey, newValue);
|
||||||
|
|
||||||
// video
|
// video
|
||||||
|
|
||||||
List<VideoAction> get videoQuickActions => getEnumListOrDefault(videoQuickActionsKey, videoQuickActionsDefault, VideoAction.values);
|
List<VideoAction> get videoQuickActions => getEnumListOrDefault(videoQuickActionsKey, videoQuickActionsDefault, VideoAction.values);
|
||||||
|
@ -458,6 +463,7 @@ class Settings extends ChangeNotifier {
|
||||||
case showOverlayInfoKey:
|
case showOverlayInfoKey:
|
||||||
case showOverlayShootingDetailsKey:
|
case showOverlayShootingDetailsKey:
|
||||||
case enableOverlayBlurEffectKey:
|
case enableOverlayBlurEffectKey:
|
||||||
|
case viewerUseCutoutKey:
|
||||||
case enableVideoHardwareAccelerationKey:
|
case enableVideoHardwareAccelerationKey:
|
||||||
case enableVideoAutoPlayKey:
|
case enableVideoAutoPlayKey:
|
||||||
case subtitleShowOutlineKey:
|
case subtitleShowOutlineKey:
|
||||||
|
|
|
@ -8,6 +8,10 @@ abstract class WindowService {
|
||||||
Future<bool> isRotationLocked();
|
Future<bool> isRotationLocked();
|
||||||
|
|
||||||
Future<void> requestOrientation([Orientation? orientation]);
|
Future<void> requestOrientation([Orientation? orientation]);
|
||||||
|
|
||||||
|
Future<bool> canSetCutoutMode();
|
||||||
|
|
||||||
|
Future<void> setCutoutMode(bool use);
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlatformWindowService implements WindowService {
|
class PlatformWindowService implements WindowService {
|
||||||
|
@ -61,4 +65,26 @@ class PlatformWindowService implements WindowService {
|
||||||
debugPrint('requestOrientation failed with code=${e.code}, exception=${e.message}, details=${e.details}');
|
debugPrint('requestOrientation failed with code=${e.code}, exception=${e.message}, details=${e.details}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> canSetCutoutMode() async {
|
||||||
|
try {
|
||||||
|
final result = await platform.invokeMethod('canSetCutoutMode');
|
||||||
|
if (result != null) return result as bool;
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
debugPrint('canSetCutoutMode failed with code=${e.code}, exception=${e.message}, details=${e.details}');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setCutoutMode(bool use) async {
|
||||||
|
try {
|
||||||
|
await platform.invokeMethod('setCutoutMode', <String, dynamic>{
|
||||||
|
'use': use,
|
||||||
|
});
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
debugPrint('setCutoutMode failed with code=${e.code}, exception=${e.message}, details=${e.details}');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:aves/model/settings/enums.dart';
|
import 'package:aves/model/settings/enums.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
|
import 'package:aves/services/services.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/utils/color_utils.dart';
|
import 'package:aves/utils/color_utils.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
|
@ -68,6 +69,7 @@ class ViewerSection extends StatelessWidget {
|
||||||
title: Text(context.l10n.settingsViewerEnableOverlayBlurEffect),
|
title: Text(context.l10n.settingsViewerEnableOverlayBlurEffect),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const _CutoutModeSwitch(),
|
||||||
Selector<Settings, EntryBackground>(
|
Selector<Settings, EntryBackground>(
|
||||||
selector: (context, s) => s.imageBackground,
|
selector: (context, s) => s.imageBackground,
|
||||||
builder: (context, current, child) => ListTile(
|
builder: (context, current, child) => ListTile(
|
||||||
|
@ -82,3 +84,40 @@ class ViewerSection extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _CutoutModeSwitch extends StatefulWidget {
|
||||||
|
const _CutoutModeSwitch({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_CutoutModeSwitchState createState() => _CutoutModeSwitchState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CutoutModeSwitchState extends State<_CutoutModeSwitch> {
|
||||||
|
late Future<bool> _canSet;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_canSet = windowService.canSetCutoutMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FutureBuilder<bool>(
|
||||||
|
future: _canSet,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasData && snapshot.data!) {
|
||||||
|
return Selector<Settings, bool>(
|
||||||
|
selector: (context, s) => s.viewerUseCutout,
|
||||||
|
builder: (context, current, child) => SwitchListTile(
|
||||||
|
value: current,
|
||||||
|
onChanged: (v) => settings.viewerUseCutout = v,
|
||||||
|
title: Text(context.l10n.settingsViewerUseCutout),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -82,6 +82,13 @@ class _EntryViewerStackState extends State<EntryViewerStack> with FeedbackMixin,
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
if (!settings.viewerUseCutout) {
|
||||||
|
windowService.setCutoutMode(false);
|
||||||
|
}
|
||||||
|
if (settings.keepScreenOn == KeepScreenOn.viewerOnly) {
|
||||||
|
windowService.keepScreenOn(true);
|
||||||
|
}
|
||||||
|
|
||||||
// make sure initial entry is actually among the filtered collection entries
|
// make sure initial entry is actually among the filtered collection entries
|
||||||
final entry = entries.contains(widget.initialEntry) ? widget.initialEntry : entries.firstOrNull;
|
final entry = entries.contains(widget.initialEntry) ? widget.initialEntry : entries.firstOrNull;
|
||||||
// opening hero, with viewer as target
|
// opening hero, with viewer as target
|
||||||
|
@ -117,9 +124,6 @@ class _EntryViewerStackState extends State<EntryViewerStack> with FeedbackMixin,
|
||||||
_registerWidget(widget);
|
_registerWidget(widget);
|
||||||
WidgetsBinding.instance!.addObserver(this);
|
WidgetsBinding.instance!.addObserver(this);
|
||||||
WidgetsBinding.instance!.addPostFrameCallback((_) => _initOverlay());
|
WidgetsBinding.instance!.addPostFrameCallback((_) => _initOverlay());
|
||||||
if (settings.keepScreenOn == KeepScreenOn.viewerOnly) {
|
|
||||||
windowService.keepScreenOn(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -502,11 +506,15 @@ class _EntryViewerStackState extends State<EntryViewerStack> with FeedbackMixin,
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onLeave() {
|
void _onLeave() {
|
||||||
_showSystemUI();
|
if (!settings.viewerUseCutout) {
|
||||||
windowService.requestOrientation();
|
windowService.setCutoutMode(true);
|
||||||
|
}
|
||||||
if (settings.keepScreenOn == KeepScreenOn.viewerOnly) {
|
if (settings.keepScreenOn == KeepScreenOn.viewerOnly) {
|
||||||
windowService.keepScreenOn(false);
|
windowService.keepScreenOn(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_showSystemUI();
|
||||||
|
windowService.requestOrientation();
|
||||||
}
|
}
|
||||||
|
|
||||||
// system UI
|
// system UI
|
||||||
|
|
|
@ -26,7 +26,7 @@ import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class EntryPageView extends StatefulWidget {
|
class EntryPageView extends StatefulWidget {
|
||||||
final AvesEntry mainEntry, pageEntry;
|
final AvesEntry mainEntry, pageEntry;
|
||||||
final Size? viewportSize;
|
final Size viewportSize;
|
||||||
final VoidCallback? onDisposed;
|
final VoidCallback? onDisposed;
|
||||||
|
|
||||||
static const decorationCheckSize = 20.0;
|
static const decorationCheckSize = 20.0;
|
||||||
|
@ -35,7 +35,7 @@ class EntryPageView extends StatefulWidget {
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.mainEntry,
|
required this.mainEntry,
|
||||||
required this.pageEntry,
|
required this.pageEntry,
|
||||||
this.viewportSize,
|
required this.viewportSize,
|
||||||
this.onDisposed,
|
this.onDisposed,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ class _EntryPageViewState extends State<EntryPageView> {
|
||||||
|
|
||||||
AvesEntry get entry => widget.pageEntry;
|
AvesEntry get entry => widget.pageEntry;
|
||||||
|
|
||||||
Size? get viewportSize => widget.viewportSize;
|
Size get viewportSize => widget.viewportSize;
|
||||||
|
|
||||||
static const initialScale = ScaleLevel(ref: ScaleReference.contained);
|
static const initialScale = ScaleLevel(ref: ScaleReference.contained);
|
||||||
static const minScale = ScaleLevel(ref: ScaleReference.contained);
|
static const minScale = ScaleLevel(ref: ScaleReference.contained);
|
||||||
|
@ -85,19 +85,17 @@ class _EntryPageViewState extends State<EntryPageView> {
|
||||||
|
|
||||||
void _registerWidget() {
|
void _registerWidget() {
|
||||||
// try to initialize the view state to match magnifier initial state
|
// try to initialize the view state to match magnifier initial state
|
||||||
_viewStateNotifier.value = viewportSize != null
|
_viewStateNotifier.value = ViewState(
|
||||||
? ViewState(
|
|
||||||
Offset.zero,
|
Offset.zero,
|
||||||
ScaleBoundaries(
|
ScaleBoundaries(
|
||||||
minScale: minScale,
|
minScale: minScale,
|
||||||
maxScale: maxScale,
|
maxScale: maxScale,
|
||||||
initialScale: initialScale,
|
initialScale: initialScale,
|
||||||
viewportSize: viewportSize!,
|
viewportSize: viewportSize,
|
||||||
childSize: entry.displaySize,
|
childSize: entry.displaySize,
|
||||||
).initialScale,
|
).initialScale,
|
||||||
viewportSize,
|
viewportSize,
|
||||||
)
|
);
|
||||||
: ViewState.zero;
|
|
||||||
|
|
||||||
_magnifierController = MagnifierController();
|
_magnifierController = MagnifierController();
|
||||||
_subscriptions.add(_magnifierController.stateStream.listen(_onViewStateChanged));
|
_subscriptions.add(_magnifierController.stateStream.listen(_onViewStateChanged));
|
||||||
|
|
|
@ -53,4 +53,10 @@ class FakeWindowService implements WindowService {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> requestOrientation([Orientation? orientation]) => SynchronousFuture(null);
|
Future<void> requestOrientation([Orientation? orientation]) => SynchronousFuture(null);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> canSetCutoutMode() => SynchronousFuture(false);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setCutoutMode(bool use) => SynchronousFuture(null);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue