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
|
||||
|
||||
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"
|
||||
|
|
|
@ -591,6 +591,8 @@
|
|||
"@settingsViewerShowShootingDetails": {},
|
||||
"settingsViewerEnableOverlayBlurEffect": "Overlay blur effect",
|
||||
"@settingsViewerEnableOverlayBlurEffect": {},
|
||||
"settingsViewerUseCutout": "Use cutout area",
|
||||
"@settingsViewerUseCutout": {},
|
||||
|
||||
"settingsViewerQuickActionsTile": "Quick actions",
|
||||
"@settingsViewerQuickActionsTile": {},
|
||||
|
|
|
@ -276,6 +276,7 @@
|
|||
"settingsViewerShowInformationSubtitle": "제목, 날짜, 장소 등 표시",
|
||||
"settingsViewerShowShootingDetails": "촬영 정보 표시",
|
||||
"settingsViewerEnableOverlayBlurEffect": "오버레이 흐림 효과",
|
||||
"settingsViewerUseCutout": "컷아웃 영역 사용",
|
||||
|
||||
"settingsViewerQuickActionsTile": "빠른 작업",
|
||||
"settingsViewerQuickActionEditorTitle": "빠른 작업",
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue