mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-29 04:42:02 +02:00
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:
@@ -0,0 +1,156 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:spotiflac_android/screens/now_playing_screen.dart';
|
||||
import 'package:spotiflac_android/services/app_orientation.dart';
|
||||
|
||||
class _PlayerRoute extends NowPlayingRoute {
|
||||
@override
|
||||
Widget buildPage(
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
) => const SizedBox.expand();
|
||||
}
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
const portrait = ['DeviceOrientation.portraitUp'];
|
||||
const player = [
|
||||
'DeviceOrientation.portraitUp',
|
||||
'DeviceOrientation.landscapeLeft',
|
||||
'DeviceOrientation.landscapeRight',
|
||||
];
|
||||
final requests = <List<String>>[];
|
||||
final messenger =
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
|
||||
|
||||
setUp(() {
|
||||
requests.clear();
|
||||
messenger.setMockMethodCallHandler(SystemChannels.platform, (call) async {
|
||||
if (call.method == 'SystemChrome.setPreferredOrientations') {
|
||||
requests.add((call.arguments as List<dynamic>).cast<String>());
|
||||
}
|
||||
return null;
|
||||
});
|
||||
});
|
||||
tearDown(
|
||||
() => messenger.setMockMethodCallHandler(SystemChannels.platform, null),
|
||||
);
|
||||
|
||||
Future<NavigatorState> pumpApp(WidgetTester tester) async {
|
||||
final key = GlobalKey<NavigatorState>();
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
navigatorKey: key,
|
||||
navigatorObservers: [AppOrientationObserver()],
|
||||
home: const Scaffold(body: Text('Library')),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(requests, [portrait]);
|
||||
return key.currentState!;
|
||||
}
|
||||
|
||||
test('startup requests portrait before the first route is built', () async {
|
||||
await setAppOrientation();
|
||||
expect(requests, [portrait]);
|
||||
});
|
||||
|
||||
testWidgets('only the full player enables landscape; sheets inherit it', (
|
||||
tester,
|
||||
) async {
|
||||
final navigator = await pumpApp(tester);
|
||||
navigator.push(_PlayerRoute());
|
||||
await tester.pumpAndSettle();
|
||||
expect(requests, [portrait, player]);
|
||||
|
||||
showModalBottomSheet<void>(
|
||||
context: navigator.context,
|
||||
builder: (_) =>
|
||||
const SizedBox(height: 100, child: Text('Player options')),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(requests, [portrait, player]);
|
||||
navigator.pop();
|
||||
await tester.pumpAndSettle();
|
||||
expect(requests, [portrait, player]);
|
||||
|
||||
navigator.push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => const Scaffold(body: Text('Album')),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(requests, [portrait, player, portrait]);
|
||||
navigator.pop();
|
||||
await tester.pumpAndSettle();
|
||||
expect(requests, [portrait, player, portrait, player]);
|
||||
await tester.binding.handlePopRoute();
|
||||
await tester.pumpAndSettle();
|
||||
expect(requests, [portrait, player, portrait, player, portrait]);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
testWidgets('cancelled dismissal keeps rotation; completed drag locks it', (
|
||||
tester,
|
||||
) async {
|
||||
final navigator = await pumpApp(tester);
|
||||
final route = _PlayerRoute();
|
||||
navigator.push(route);
|
||||
await tester.pumpAndSettle();
|
||||
route.startDrag();
|
||||
route.updateDrag(
|
||||
DragUpdateDetails(
|
||||
globalPosition: const Offset(0, 240),
|
||||
delta: const Offset(0, 240),
|
||||
primaryDelta: 240,
|
||||
),
|
||||
600,
|
||||
);
|
||||
await tester.pump();
|
||||
route.cancelDrag();
|
||||
await tester.pumpAndSettle();
|
||||
expect(requests, [portrait, player]);
|
||||
route.startDrag();
|
||||
route.endDrag(
|
||||
DragEndDetails(
|
||||
primaryVelocity: 1200,
|
||||
velocity: const Velocity(pixelsPerSecond: Offset(0, 1200)),
|
||||
),
|
||||
600,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(requests, [portrait, player, portrait]);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
testWidgets('removing and replacing routes cannot leave landscape enabled', (
|
||||
tester,
|
||||
) async {
|
||||
final navigator = await pumpApp(tester);
|
||||
final firstPlayer = _PlayerRoute();
|
||||
navigator.push(firstPlayer);
|
||||
await tester.pumpAndSettle();
|
||||
final secondPlayer = _PlayerRoute();
|
||||
navigator.push(secondPlayer);
|
||||
await tester.pumpAndSettle();
|
||||
navigator.removeRoute(firstPlayer);
|
||||
await tester.pumpAndSettle();
|
||||
expect(requests, [portrait, player]);
|
||||
navigator.pushReplacement(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => const Scaffold(body: Text('Settings')),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(requests, [portrait, player, portrait]);
|
||||
final thirdPlayer = _PlayerRoute();
|
||||
navigator.push(thirdPlayer);
|
||||
await tester.pumpAndSettle();
|
||||
navigator.removeRoute(thirdPlayer);
|
||||
await tester.pumpAndSettle();
|
||||
expect(requests, [portrait, player, portrait, player, portrait]);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
}
|
||||
@@ -12,6 +12,8 @@ import 'package:spotiflac_android/theme/mornye_theme.dart';
|
||||
const _sheet = Key('player-sheet');
|
||||
|
||||
class _TestPlayerRoute extends NowPlayingRoute {
|
||||
_TestPlayerRoute({super.miniPlayerGeometry});
|
||||
|
||||
@override
|
||||
Widget buildPage(
|
||||
BuildContext context,
|
||||
@@ -21,6 +23,43 @@ class _TestPlayerRoute extends NowPlayingRoute {
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('rotation on close discards stale mini-player bounds', (
|
||||
tester,
|
||||
) async {
|
||||
tester.view.physicalSize = const Size(852, 393);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.reset);
|
||||
final navigator = GlobalKey<NavigatorState>();
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
navigatorKey: navigator,
|
||||
theme: MornyeTheme.build(Brightness.dark),
|
||||
home: const SizedBox(),
|
||||
),
|
||||
);
|
||||
navigator.currentState!.push(
|
||||
_TestPlayerRoute(
|
||||
miniPlayerGeometry: () => (
|
||||
surface: const Rect.fromLTWH(80, 310, 700, 52),
|
||||
artwork: const Rect.fromLTWH(88, 314, 44, 44),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
navigator.currentState!.pop();
|
||||
await tester.pump();
|
||||
expect(
|
||||
find.byKey(const ValueKey('player-minimize-surface')),
|
||||
findsOneWidget,
|
||||
);
|
||||
tester.view.physicalSize = const Size(393, 852);
|
||||
await tester.pump(const Duration(milliseconds: 16));
|
||||
expect(find.byKey(const ValueKey('player-minimize-surface')), findsNothing);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byKey(_sheet), findsNothing);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
for (final brightness in Brightness.values) {
|
||||
testWidgets(
|
||||
'player fills safe areas and reveals the page only when dragged in $brightness',
|
||||
|
||||
Reference in New Issue
Block a user