Refactor supported orientations logic to be more robust. Fixes bug where some Android Tablets could not rotate to landscape.

This commit is contained in:
Shawn 2023-02-13 23:24:07 -07:00
parent 61e93029d7
commit 9a9ccff85c
4 changed files with 45 additions and 33 deletions

View File

@ -4,6 +4,7 @@ import 'package:desktop_window/desktop_window.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:wonders/common_libs.dart'; import 'package:wonders/common_libs.dart';
import 'package:wonders/logic/common/platform_info.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'; import 'package:wonders/ui/common/utils/page_routes.dart';
class AppLogic { class AppLogic {
@ -11,34 +12,31 @@ class AppLogic {
/// The router will use this to prevent redirects while bootstrapping. /// The router will use this to prevent redirects while bootstrapping.
bool isBootstrapComplete = false; 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<Axis> supportedOrientations = [Axis.vertical, Axis.horizontal];
/// Support portrait and landscape on desktop, web and tablets. Stick to portrait for phones. /// Allow a view to override the currently supported orientations. For example, [FullscreenVideoViewer] always wants to enable both landscape and portrait.
/// A return value of null indicated both orientations are supported. /// If a view sets this override, they are responsible for setting it back to null when finished.
Axis? get supportedOrientations => isLandscapeEnabled ? null : Axis.vertical; List<Axis>? _supportedOrientationsOverride;
set supportedOrientationsOverride(List<Axis>? value) {
Size get deviceSize { if (_supportedOrientationsOverride != value) {
final w = WidgetsBinding.instance.platformDispatcher.views.first; _supportedOrientationsOverride = value;
return w.physicalSize / w.devicePixelRatio; _updateSystemOrientation();
}
} }
/// Initialize the app and all main actors. /// Initialize the app and all main actors.
/// Loads settings, sets up services etc. /// Loads settings, sets up services etc.
Future<void> bootstrap() async { Future<void> bootstrap() async {
// TODO: Switch to `debugPrint` here once landscape issues on android have been resolved debugPrint('bootstrap start...');
print('bootstrap app, deviceSize: $deviceSize, isTablet: $isLandscapeEnabled');
print('supportedOrientations: ${supportedOrientations ?? 'All'}');
// Set min-sizes for desktop apps // Set min-sizes for desktop apps
if (PlatformInfo.isDesktop) { if (PlatformInfo.isDesktop) {
await DesktopWindow.setMinWindowSize($styles.sizes.minAppSize); await DesktopWindow.setMinWindowSize($styles.sizes.minAppSize);
} }
// Load any bitmaps the views might need // Load any bitmaps the views might need
await AppBitmaps.init(); await AppBitmaps.init();
// Set the initial supported orientations
setDeviceOrientation(supportedOrientations);
// Set preferred refresh rate to the max possible (the OS may ignore this) // Set preferred refresh rate to the max possible (the OS may ignore this)
if (PlatformInfo.isAndroid) { if (PlatformInfo.isAndroid) {
await FlutterDisplayMode.setHighRefreshRate(); await FlutterDisplayMode.setHighRefreshRate();
@ -71,15 +69,34 @@ class AppLogic {
} }
} }
void setDeviceOrientation(Axis? axis) { Future<T?> showFullscreenDialogRoute<T>(BuildContext context, Widget child, {bool transparent = false}) async {
return await Navigator.of(context).push<T>(
PageRoutes.dialog<T>(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 = <DeviceOrientation>[]; final orientations = <DeviceOrientation>[];
if (axis == null || axis == Axis.vertical) { if (axisList.contains(Axis.vertical)) {
orientations.addAll([ orientations.addAll([
DeviceOrientation.portraitUp, DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown, DeviceOrientation.portraitDown,
]); ]);
} }
if (axis == null || axis == Axis.horizontal) { if (axisList.contains(Axis.horizontal)) {
orientations.addAll([ orientations.addAll([
DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight, DeviceOrientation.landscapeRight,
@ -87,10 +104,4 @@ class AppLogic {
} }
SystemChrome.setPreferredOrientations(orientations); SystemChrome.setPreferredOrientations(orientations);
} }
Future<T?> showFullscreenDialogRoute<T>(BuildContext context, Widget child, {bool transparent = false}) async {
return await Navigator.of(context).push<T>(
PageRoutes.dialog<T>(child, duration: $styles.times.pageTransition),
);
}
} }

View File

@ -48,7 +48,7 @@ final appRouter = GoRouter(
return TimelineScreen(type: _tryParseWonderType(s.queryParams['type']!)); return TimelineScreen(type: _tryParseWonderType(s.queryParams['type']!));
}), }),
AppRoute('/video/:id', (s) { AppRoute('/video/:id', (s) {
return FullscreenVideoPage(id: s.params['id']!); return FullscreenVideoViewer(id: s.params['id']!);
}), }),
AppRoute('/highlights/:type', (s) { AppRoute('/highlights/:type', (s) {
return ArtifactCarouselScreen(type: _parseWonderType(s.params['type'])); return ArtifactCarouselScreen(type: _parseWonderType(s.params['type']));

View File

@ -11,7 +11,8 @@ class WondersAppScaffold extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Listen to the device size, and update AppStyle when it changes // Listen to the device size, and update AppStyle when it changes
_style = AppStyle(screenSize: context.sizePx); _style = AppStyle(screenSize: context.sizePx);
Animate.defaultDuration = $styles.times.fast; Animate.defaultDuration = _style.times.fast;
appLogic.handleAppSizeChanged(context.mq.size);
return Stack( return Stack(
key: ValueKey($styles.scale), key: ValueKey($styles.scale),
children: [ children: [

View File

@ -2,15 +2,15 @@ import 'package:wonders/common_libs.dart';
import 'package:wonders/ui/common/controls/app_loading_indicator.dart'; import 'package:wonders/ui/common/controls/app_loading_indicator.dart';
import 'package:youtube_player_iframe/youtube_player_iframe.dart'; import 'package:youtube_player_iframe/youtube_player_iframe.dart';
class FullscreenVideoPage extends StatefulWidget { class FullscreenVideoViewer extends StatefulWidget {
const FullscreenVideoPage({Key? key, required this.id}) : super(key: key); const FullscreenVideoViewer({Key? key, required this.id}) : super(key: key);
final String id; final String id;
@override @override
State<FullscreenVideoPage> createState() => _FullscreenVideoPageState(); State<FullscreenVideoViewer> createState() => _FullscreenVideoViewerState();
} }
class _FullscreenVideoPageState extends State<FullscreenVideoPage> { class _FullscreenVideoViewerState extends State<FullscreenVideoViewer> {
late final _controller = YoutubePlayerController( late final _controller = YoutubePlayerController(
initialVideoId: widget.id, initialVideoId: widget.id,
params: const YoutubePlayerParams(autoPlay: true, startAt: Duration(seconds: 1)), params: const YoutubePlayerParams(autoPlay: true, startAt: Duration(seconds: 1)),
@ -19,13 +19,13 @@ class _FullscreenVideoPageState extends State<FullscreenVideoPage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
appLogic.setDeviceOrientation(null); appLogic.supportedOrientationsOverride = [Axis.horizontal, Axis.vertical];
} }
@override @override
void dispose() { void dispose() {
// when view closes, restore the supported orientations // when view closes, remove the override
appLogic.setDeviceOrientation(appLogic.supportedOrientations); appLogic.supportedOrientationsOverride = null;
super.dispose(); super.dispose();
} }