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: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<Axis> 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<Axis>? _supportedOrientationsOverride;
set supportedOrientationsOverride(List<Axis>? value) {
if (_supportedOrientationsOverride != value) {
_supportedOrientationsOverride = value;
_updateSystemOrientation();
}
}
/// Initialize the app and all main actors.
/// Loads settings, sets up services etc.
Future<void> 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<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>[];
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<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']!));
}),
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']));

View File

@ -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: [

View File

@ -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<FullscreenVideoPage> createState() => _FullscreenVideoPageState();
State<FullscreenVideoViewer> createState() => _FullscreenVideoViewerState();
}
class _FullscreenVideoPageState extends State<FullscreenVideoPage> {
class _FullscreenVideoViewerState extends State<FullscreenVideoViewer> {
late final _controller = YoutubePlayerController(
initialVideoId: widget.id,
params: const YoutubePlayerParams(autoPlay: true, startAt: Duration(seconds: 1)),
@ -19,13 +19,13 @@ class _FullscreenVideoPageState extends State<FullscreenVideoPage> {
@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();
}