From 9a9ccff85c68eed8d48b96cfa6776e6633857280 Mon Sep 17 00:00:00 2001 From: Shawn Date: Mon, 13 Feb 2023 23:24:07 -0700 Subject: [PATCH] Refactor supported orientations logic to be more robust. Fixes bug where some Android Tablets could not rotate to landscape. --- lib/logic/app_logic.dart | 59 +++++++++++-------- lib/router.dart | 2 +- lib/ui/app_scaffold.dart | 3 +- .../modals/fullscreen_video_viewer.dart | 14 ++--- 4 files changed, 45 insertions(+), 33 deletions(-) diff --git a/lib/logic/app_logic.dart b/lib/logic/app_logic.dart index 2dcb3c28..93eea3e4 100644 --- a/lib/logic/app_logic.dart +++ b/lib/logic/app_logic.dart @@ -4,6 +4,7 @@ import 'package:desktop_window/desktop_window.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:wonders/common_libs.dart'; import 'package:wonders/logic/common/platform_info.dart'; +import 'package:wonders/ui/common/modals/fullscreen_video_viewer.dart'; import 'package:wonders/ui/common/utils/page_routes.dart'; class AppLogic { @@ -11,34 +12,31 @@ class AppLogic { /// The router will use this to prevent redirects while bootstrapping. bool isBootstrapComplete = false; - bool get isLandscapeEnabled => PlatformInfo.isDesktopOrWeb || deviceSize.shortestSide > 500; + /// Indicates which orientations the app will allow be default. Affects Android/iOS devices only. + /// Defaults to both landscape (hz) and portrait (vt) + List supportedOrientations = [Axis.vertical, Axis.horizontal]; - /// Support portrait and landscape on desktop, web and tablets. Stick to portrait for phones. - /// A return value of null indicated both orientations are supported. - Axis? get supportedOrientations => isLandscapeEnabled ? null : Axis.vertical; - - Size get deviceSize { - final w = WidgetsBinding.instance.platformDispatcher.views.first; - return w.physicalSize / w.devicePixelRatio; + /// Allow a view to override the currently supported orientations. For example, [FullscreenVideoViewer] always wants to enable both landscape and portrait. + /// If a view sets this override, they are responsible for setting it back to null when finished. + List? _supportedOrientationsOverride; + set supportedOrientationsOverride(List? value) { + if (_supportedOrientationsOverride != value) { + _supportedOrientationsOverride = value; + _updateSystemOrientation(); + } } /// Initialize the app and all main actors. /// Loads settings, sets up services etc. Future bootstrap() async { - // TODO: Switch to `debugPrint` here once landscape issues on android have been resolved - print('bootstrap app, deviceSize: $deviceSize, isTablet: $isLandscapeEnabled'); - print('supportedOrientations: ${supportedOrientations ?? 'All'}'); + debugPrint('bootstrap start...'); // Set min-sizes for desktop apps if (PlatformInfo.isDesktop) { await DesktopWindow.setMinWindowSize($styles.sizes.minAppSize); } - // Load any bitmaps the views might need await AppBitmaps.init(); - // Set the initial supported orientations - setDeviceOrientation(supportedOrientations); - // Set preferred refresh rate to the max possible (the OS may ignore this) if (PlatformInfo.isAndroid) { await FlutterDisplayMode.setHighRefreshRate(); @@ -71,15 +69,34 @@ class AppLogic { } } - void setDeviceOrientation(Axis? axis) { + Future showFullscreenDialogRoute(BuildContext context, Widget child, {bool transparent = false}) async { + return await Navigator.of(context).push( + PageRoutes.dialog(child, duration: $styles.times.pageTransition), + ); + } + + /// Called from the UI layer once a MediaQuery has been obtained + void handleAppSizeChanged(Size size) { + /// Disable landscape layout on smaller form factors + bool isSmall = size.shortestSide < 500 && size != Size.zero; + supportedOrientations = isSmall ? [Axis.vertical] : [Axis.vertical, Axis.horizontal]; + _updateSystemOrientation(); + } + + /// Enable landscape, portrait or both. Views can call this method to override the default settings. + /// For example, the [FullscreenVideoViewer] always wants to enable both landscape and portrait. + /// If a view overrides this, it is responsible for setting it back to [supportedOrientations] when disposed. + void _updateSystemOrientation() { + final axisList = _supportedOrientationsOverride ?? supportedOrientations; + debugPrint('updateDeviceOrientation, supportedAxis: $axisList'); final orientations = []; - if (axis == null || axis == Axis.vertical) { + if (axisList.contains(Axis.vertical)) { orientations.addAll([ DeviceOrientation.portraitUp, DeviceOrientation.portraitDown, ]); } - if (axis == null || axis == Axis.horizontal) { + if (axisList.contains(Axis.horizontal)) { orientations.addAll([ DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight, @@ -87,10 +104,4 @@ class AppLogic { } SystemChrome.setPreferredOrientations(orientations); } - - Future showFullscreenDialogRoute(BuildContext context, Widget child, {bool transparent = false}) async { - return await Navigator.of(context).push( - PageRoutes.dialog(child, duration: $styles.times.pageTransition), - ); - } } diff --git a/lib/router.dart b/lib/router.dart index 4843017a..5c550fd8 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -48,7 +48,7 @@ final appRouter = GoRouter( return TimelineScreen(type: _tryParseWonderType(s.queryParams['type']!)); }), AppRoute('/video/:id', (s) { - return FullscreenVideoPage(id: s.params['id']!); + return FullscreenVideoViewer(id: s.params['id']!); }), AppRoute('/highlights/:type', (s) { return ArtifactCarouselScreen(type: _parseWonderType(s.params['type'])); diff --git a/lib/ui/app_scaffold.dart b/lib/ui/app_scaffold.dart index 1adbeed0..e23821b0 100644 --- a/lib/ui/app_scaffold.dart +++ b/lib/ui/app_scaffold.dart @@ -11,7 +11,8 @@ class WondersAppScaffold extends StatelessWidget { Widget build(BuildContext context) { // Listen to the device size, and update AppStyle when it changes _style = AppStyle(screenSize: context.sizePx); - Animate.defaultDuration = $styles.times.fast; + Animate.defaultDuration = _style.times.fast; + appLogic.handleAppSizeChanged(context.mq.size); return Stack( key: ValueKey($styles.scale), children: [ diff --git a/lib/ui/common/modals/fullscreen_video_viewer.dart b/lib/ui/common/modals/fullscreen_video_viewer.dart index 4caa64d5..1c1eec3e 100644 --- a/lib/ui/common/modals/fullscreen_video_viewer.dart +++ b/lib/ui/common/modals/fullscreen_video_viewer.dart @@ -2,15 +2,15 @@ import 'package:wonders/common_libs.dart'; import 'package:wonders/ui/common/controls/app_loading_indicator.dart'; import 'package:youtube_player_iframe/youtube_player_iframe.dart'; -class FullscreenVideoPage extends StatefulWidget { - const FullscreenVideoPage({Key? key, required this.id}) : super(key: key); +class FullscreenVideoViewer extends StatefulWidget { + const FullscreenVideoViewer({Key? key, required this.id}) : super(key: key); final String id; @override - State createState() => _FullscreenVideoPageState(); + State createState() => _FullscreenVideoViewerState(); } -class _FullscreenVideoPageState extends State { +class _FullscreenVideoViewerState extends State { late final _controller = YoutubePlayerController( initialVideoId: widget.id, params: const YoutubePlayerParams(autoPlay: true, startAt: Duration(seconds: 1)), @@ -19,13 +19,13 @@ class _FullscreenVideoPageState extends State { @override void initState() { super.initState(); - appLogic.setDeviceOrientation(null); + appLogic.supportedOrientationsOverride = [Axis.horizontal, Axis.vertical]; } @override void dispose() { - // when view closes, restore the supported orientations - appLogic.setDeviceOrientation(appLogic.supportedOrientations); + // when view closes, remove the override + appLogic.supportedOrientationsOverride = null; super.dispose(); }