settings: option to exclude cutout area in viewer

This commit is contained in:
Thibault Deckers 2021-07-17 16:49:17 +09:00
parent 6c3ec82dba
commit 5f04ebaf78
9 changed files with 128 additions and 21 deletions

View file

@ -1,6 +1,7 @@
package deckers.thibault.aves.channel.calls
import android.app.Activity
import android.os.Build
import android.provider.Settings
import android.util.Log
import android.view.WindowManager
@ -16,6 +17,8 @@ class WindowHandler(private val activity: Activity) : MethodCallHandler {
"keepScreenOn" -> safe(call, result, ::keepScreenOn)
"isRotationLocked" -> safe(call, result, ::isRotationLocked)
"requestOrientation" -> safe(call, result, ::requestOrientation)
"canSetCutoutMode" -> result.success(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
"setCutoutMode" -> safe(call, result, ::setCutoutMode)
else -> result.notImplemented()
}
}
@ -57,6 +60,24 @@ class WindowHandler(private val activity: Activity) : MethodCallHandler {
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 {
private val LOG_TAG = LogUtils.createTag<WindowHandler>()
const val CHANNEL = "deckers.thibault/aves/window"

View file

@ -591,6 +591,8 @@
"@settingsViewerShowShootingDetails": {},
"settingsViewerEnableOverlayBlurEffect": "Overlay blur effect",
"@settingsViewerEnableOverlayBlurEffect": {},
"settingsViewerUseCutout": "Use cutout area",
"@settingsViewerUseCutout": {},
"settingsViewerQuickActionsTile": "Quick actions",
"@settingsViewerQuickActionsTile": {},

View file

@ -276,6 +276,7 @@
"settingsViewerShowInformationSubtitle": "제목, 날짜, 장소 등 표시",
"settingsViewerShowShootingDetails": "촬영 정보 표시",
"settingsViewerEnableOverlayBlurEffect": "오버레이 흐림 효과",
"settingsViewerUseCutout": "컷아웃 영역 사용",
"settingsViewerQuickActionsTile": "빠른 작업",
"settingsViewerQuickActionEditorTitle": "빠른 작업",

View file

@ -66,6 +66,7 @@ class Settings extends ChangeNotifier {
static const showOverlayInfoKey = 'show_overlay_info';
static const showOverlayShootingDetailsKey = 'show_overlay_shooting_details';
static const enableOverlayBlurEffectKey = 'enable_overlay_blur_effect';
static const viewerUseCutoutKey = 'viewer_use_cutout';
// video
static const videoQuickActionsKey = 'video_quick_actions';
@ -261,6 +262,10 @@ class Settings extends ChangeNotifier {
set enableOverlayBlurEffect(bool newValue) => setAndNotify(enableOverlayBlurEffectKey, newValue);
bool get viewerUseCutout => getBoolOrDefault(viewerUseCutoutKey, true);
set viewerUseCutout(bool newValue) => setAndNotify(viewerUseCutoutKey, newValue);
// video
List<VideoAction> get videoQuickActions => getEnumListOrDefault(videoQuickActionsKey, videoQuickActionsDefault, VideoAction.values);
@ -458,6 +463,7 @@ class Settings extends ChangeNotifier {
case showOverlayInfoKey:
case showOverlayShootingDetailsKey:
case enableOverlayBlurEffectKey:
case viewerUseCutoutKey:
case enableVideoHardwareAccelerationKey:
case enableVideoAutoPlayKey:
case subtitleShowOutlineKey:

View file

@ -8,6 +8,10 @@ abstract class WindowService {
Future<bool> isRotationLocked();
Future<void> requestOrientation([Orientation? orientation]);
Future<bool> canSetCutoutMode();
Future<void> setCutoutMode(bool use);
}
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}');
}
}
@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}');
}
}
}

View file

@ -1,5 +1,6 @@
import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/services.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/utils/color_utils.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
@ -68,6 +69,7 @@ class ViewerSection extends StatelessWidget {
title: Text(context.l10n.settingsViewerEnableOverlayBlurEffect),
),
),
const _CutoutModeSwitch(),
Selector<Settings, EntryBackground>(
selector: (context, s) => s.imageBackground,
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();
},
);
}
}

View file

@ -82,6 +82,13 @@ class _EntryViewerStackState extends State<EntryViewerStack> with FeedbackMixin,
@override
void 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
final entry = entries.contains(widget.initialEntry) ? widget.initialEntry : entries.firstOrNull;
// opening hero, with viewer as target
@ -117,9 +124,6 @@ class _EntryViewerStackState extends State<EntryViewerStack> with FeedbackMixin,
_registerWidget(widget);
WidgetsBinding.instance!.addObserver(this);
WidgetsBinding.instance!.addPostFrameCallback((_) => _initOverlay());
if (settings.keepScreenOn == KeepScreenOn.viewerOnly) {
windowService.keepScreenOn(true);
}
}
@override
@ -502,11 +506,15 @@ class _EntryViewerStackState extends State<EntryViewerStack> with FeedbackMixin,
}
void _onLeave() {
_showSystemUI();
windowService.requestOrientation();
if (!settings.viewerUseCutout) {
windowService.setCutoutMode(true);
}
if (settings.keepScreenOn == KeepScreenOn.viewerOnly) {
windowService.keepScreenOn(false);
}
_showSystemUI();
windowService.requestOrientation();
}
// system UI

View file

@ -26,7 +26,7 @@ import 'package:provider/provider.dart';
class EntryPageView extends StatefulWidget {
final AvesEntry mainEntry, pageEntry;
final Size? viewportSize;
final Size viewportSize;
final VoidCallback? onDisposed;
static const decorationCheckSize = 20.0;
@ -35,7 +35,7 @@ class EntryPageView extends StatefulWidget {
Key? key,
required this.mainEntry,
required this.pageEntry,
this.viewportSize,
required this.viewportSize,
this.onDisposed,
}) : super(key: key);
@ -52,7 +52,7 @@ class _EntryPageViewState extends State<EntryPageView> {
AvesEntry get entry => widget.pageEntry;
Size? get viewportSize => widget.viewportSize;
Size get viewportSize => widget.viewportSize;
static const initialScale = ScaleLevel(ref: ScaleReference.contained);
static const minScale = ScaleLevel(ref: ScaleReference.contained);
@ -85,19 +85,17 @@ class _EntryPageViewState extends State<EntryPageView> {
void _registerWidget() {
// try to initialize the view state to match magnifier initial state
_viewStateNotifier.value = viewportSize != null
? ViewState(
Offset.zero,
ScaleBoundaries(
minScale: minScale,
maxScale: maxScale,
initialScale: initialScale,
viewportSize: viewportSize!,
childSize: entry.displaySize,
).initialScale,
viewportSize,
)
: ViewState.zero;
_viewStateNotifier.value = ViewState(
Offset.zero,
ScaleBoundaries(
minScale: minScale,
maxScale: maxScale,
initialScale: initialScale,
viewportSize: viewportSize,
childSize: entry.displaySize,
).initialScale,
viewportSize,
);
_magnifierController = MagnifierController();
_subscriptions.add(_magnifierController.stateStream.listen(_onViewStateChanged));

View file

@ -53,4 +53,10 @@ class FakeWindowService implements WindowService {
@override
Future<void> requestOrientation([Orientation? orientation]) => SynchronousFuture(null);
@override
Future<bool> canSetCutoutMode() => SynchronousFuture(false);
@override
Future<void> setCutoutMode(bool use) => SynchronousFuture(null);
}