fix(ui): allow landscape only in the full player

Keep application pages in portrait and let player sheets inherit rotation from the full player. Restore portrait on navigation, replacement, removal, and completed dismissal; discard stale minimization geometry when the screen rotates on close.

Validated with 12 Flutter tests and targeted analysis. iOS simulator build confirms portrait startup from a landscape device. Platform orientation restrictions still apply on large Android displays and multitasking iPads.
This commit is contained in:
zarzet
2026-09-25 18:05:40 +07:00
parent 1d53609b51
commit 2ab4aaa8dc
7 changed files with 280 additions and 2 deletions
+71
View File
@@ -0,0 +1,71 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:spotiflac_android/utils/logger.dart';
const fullPlayerRouteName = '/full-player';
final _log = AppLogger('Orientation');
/// Only the full player opts into rotation. Sheets and dialogs inherit the
/// underlying page's orientation, while another page restores portrait.
Future<void> setAppOrientation({bool playerVisible = false}) =>
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
if (playerVisible) ...[
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
],
]);
class AppOrientationObserver extends NavigatorObserver {
final _routes = <Route<dynamic>>[];
bool? _playerVisible;
void _sync() {
final page = _routes.whereType<PageRoute<dynamic>>().lastOrNull;
final playerVisible = page?.settings.name == fullPlayerRouteName;
if (_playerVisible == playerVisible) return;
_playerVisible = playerVisible;
unawaited(
setAppOrientation(playerVisible: playerVisible).catchError((
Object error,
) {
_log.w('Could not update screen orientation: $error');
}),
);
}
@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
_routes.add(route);
_sync();
}
@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
_routes.remove(route);
_sync();
}
@override
void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) {
_routes.remove(route);
_sync();
}
@override
void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) {
final index = oldRoute == null ? -1 : _routes.indexOf(oldRoute);
if (index >= 0) {
if (newRoute == null) {
_routes.removeAt(index);
} else {
_routes[index] = newRoute;
}
} else if (newRoute != null) {
_routes.add(newRoute);
}
_sync();
}
}