mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-29 21:02:09 +02:00
feat(onboarding): add theme-aware 5.0 upgrade introduction
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
import 'dart:io';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/models/theme_settings.dart';
|
||||
import 'package:spotiflac_android/providers/theme_provider.dart';
|
||||
import 'package:spotiflac_android/screens/upgrade_intro_screen.dart';
|
||||
import 'package:spotiflac_android/services/upgrade_intro_service.dart';
|
||||
import 'package:spotiflac_android/theme/app_theme.dart';
|
||||
import 'package:spotiflac_android/theme/mornye_theme.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
setUpAll(() async {
|
||||
await (FontLoader(
|
||||
'MaterialIcons',
|
||||
)..addFont(rootBundle.load('fonts/MaterialIcons-Regular.otf'))).load();
|
||||
await (FontLoader(
|
||||
'Google Sans Flex',
|
||||
)..addFont(rootBundle.load('assets/fonts/GoogleSansFlex.ttf'))).load();
|
||||
await (FontLoader(
|
||||
'Inter',
|
||||
)..addFont(rootBundle.load('assets/fonts/InterVariable.ttf'))).load();
|
||||
await (FontLoader('packages/cupertino_icons/CupertinoIcons')..addFont(
|
||||
rootBundle.load('packages/cupertino_icons/assets/CupertinoIcons.ttf'),
|
||||
))
|
||||
.load();
|
||||
});
|
||||
|
||||
test(
|
||||
'upgrade tour is acknowledged separately from settings migrations',
|
||||
() async {
|
||||
SharedPreferences.setMockInitialValues({
|
||||
'settings_migration_version': 11,
|
||||
});
|
||||
var prefs = await SharedPreferences.getInstance();
|
||||
bool pending(String version, {bool existing = true}) =>
|
||||
UpgradeIntroService.shouldShow(
|
||||
prefs,
|
||||
version: version,
|
||||
existingInstallation: existing,
|
||||
);
|
||||
expect(pending('5.0.0'), isTrue);
|
||||
expect(pending('5.0.1'), isTrue);
|
||||
expect(pending('4.9.0'), isFalse);
|
||||
expect(pending('6.0.0'), isFalse);
|
||||
expect(pending('5.0.0', existing: false), isFalse);
|
||||
await UpgradeIntroService.markSeen(prefs);
|
||||
prefs = await SharedPreferences.getInstance();
|
||||
expect(pending('5.0.0'), isFalse);
|
||||
expect(prefs.getInt('settings_migration_version'), 11);
|
||||
},
|
||||
);
|
||||
|
||||
final capture = GlobalKey();
|
||||
Future<void> openTour(
|
||||
WidgetTester tester, {
|
||||
Size size = const Size(390, 844),
|
||||
double textScale = 1,
|
||||
Brightness brightness = Brightness.light,
|
||||
AppThemeStyle style = AppThemeStyle.mornye,
|
||||
}) async {
|
||||
SharedPreferences.setMockInitialValues({kThemeStyleKey: style.name});
|
||||
tester.view.physicalSize = size;
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.reset);
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [
|
||||
initialThemeSettingsProvider.overrideWithValue(
|
||||
ThemeSettings(style: style),
|
||||
),
|
||||
],
|
||||
child: Consumer(
|
||||
builder: (context, ref, _) {
|
||||
final selectedStyle = ref.watch(
|
||||
themeProvider.select((value) => value.style),
|
||||
);
|
||||
return MaterialApp(
|
||||
theme: selectedStyle == AppThemeStyle.mornye
|
||||
? MornyeTheme.build(brightness)
|
||||
: brightness == Brightness.dark
|
||||
? AppTheme.dark()
|
||||
: AppTheme.light(),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
builder: (context, child) => MediaQuery(
|
||||
data: MediaQuery.of(
|
||||
context,
|
||||
).copyWith(textScaler: TextScaler.linear(textScale)),
|
||||
child: RepaintBoundary(key: capture, child: child),
|
||||
),
|
||||
home: Scaffold(
|
||||
body: Builder(
|
||||
builder: (context) => TextButton(
|
||||
onPressed: () => Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => const UpgradeIntroScreen(),
|
||||
),
|
||||
),
|
||||
child: const Text('Open tour'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.tap(find.text('Open tour'));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
Future<void> capturePage(WidgetTester tester, String name) async {
|
||||
if (!const bool.fromEnvironment('CAPTURE_UPGRADE_INTRO')) return;
|
||||
await tester.runAsync(() async {
|
||||
final boundary =
|
||||
capture.currentContext!.findRenderObject()! as RenderRepaintBoundary;
|
||||
final image = await boundary.toImage(pixelRatio: 2);
|
||||
final bytes = await image.toByteData(format: ui.ImageByteFormat.png);
|
||||
await File(
|
||||
'/tmp/spotiflac-intro-$name.png',
|
||||
).writeAsBytes(bytes!.buffer.asUint8List());
|
||||
image.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
for (final brightness in Brightness.values) {
|
||||
testWidgets('tour advances, goes back and finishes ($brightness)', (
|
||||
tester,
|
||||
) async {
|
||||
await openTour(tester, brightness: brightness);
|
||||
expect(find.text('Welcome to\nSpotiFLAC 5.0'), findsOneWidget);
|
||||
expect(find.byType(Image), findsNothing);
|
||||
await capturePage(tester, '${brightness.name}-welcome');
|
||||
await tester.tap(find.text('Continue'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Make it\nyour own.'), findsOneWidget);
|
||||
await capturePage(tester, '${brightness.name}-themes');
|
||||
await tester.binding.handlePopRoute();
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Welcome to\nSpotiFLAC 5.0'), findsOneWidget);
|
||||
for (var i = 0; i < 4; i++) {
|
||||
await tester.tap(find.text('Continue'));
|
||||
await tester.pumpAndSettle();
|
||||
if (i == 1) {
|
||||
expect(find.text('Get to know\nyour Library.'), findsOneWidget);
|
||||
await capturePage(tester, '${brightness.name}-library');
|
||||
}
|
||||
if (i == 2) {
|
||||
expect(find.text('A new engine.\nBuilt with Rust.'), findsOneWidget);
|
||||
await capturePage(tester, '${brightness.name}-rust');
|
||||
}
|
||||
}
|
||||
await capturePage(tester, '${brightness.name}-player');
|
||||
await tester.tap(find.text('Start listening'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(UpgradeIntroScreen), findsNothing);
|
||||
expect(find.text('Open tour'), findsOneWidget);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
}
|
||||
|
||||
testWidgets('theme choice applies live, persists and updates the guide', (
|
||||
tester,
|
||||
) async {
|
||||
await openTour(tester, style: AppThemeStyle.material);
|
||||
await capturePage(tester, 'material-welcome');
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
expect(prefs.getString(kThemeStyleKey), 'material');
|
||||
await tester.tap(find.text('Continue'));
|
||||
await tester.pumpAndSettle();
|
||||
final mornye = find.byKey(const ValueKey('intro-theme-mornye'));
|
||||
await tester.ensureVisible(mornye);
|
||||
await tester.tap(mornye);
|
||||
await tester.pumpAndSettle();
|
||||
expect(prefs.getString(kThemeStyleKey), 'mornye');
|
||||
expect(tester.element(find.byType(UpgradeIntroScreen)).isMornye, isTrue);
|
||||
await tester.tap(find.text('Continue'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.textContaining('Recently Added.'), findsWidgets);
|
||||
await tester.binding.handlePopRoute();
|
||||
await tester.pumpAndSettle();
|
||||
final material = find.byKey(const ValueKey('intro-theme-material'));
|
||||
await tester.ensureVisible(material);
|
||||
await tester.tap(material);
|
||||
await tester.pumpAndSettle();
|
||||
expect(prefs.getString(kThemeStyleKey), 'material');
|
||||
expect(tester.element(find.byType(UpgradeIntroScreen)).isMornye, isFalse);
|
||||
await capturePage(tester, 'material-themes');
|
||||
await tester.tap(find.text('Continue'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('A new engine.\nBuilt with Rust.'), findsOneWidget);
|
||||
expect(find.text('Get to know\nyour Library.'), findsNothing);
|
||||
expect(find.text('Set up\nyour player.'), findsNothing);
|
||||
expect(find.bySemanticsLabel('3 / 3'), findsOneWidget);
|
||||
expect(find.text('Continue'), findsNothing);
|
||||
await capturePage(tester, 'material-rust');
|
||||
await tester.binding.handlePopRoute();
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Make it\nyour own.'), findsOneWidget);
|
||||
await tester.drag(find.byType(PageView), const Offset(-500, 0));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('A new engine.\nBuilt with Rust.'), findsOneWidget);
|
||||
await tester.tap(find.text('Start listening'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(prefs.getString(kThemeStyleKey), 'material');
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
for (final size in [const Size(320, 568), const Size(844, 390)]) {
|
||||
testWidgets(
|
||||
'tour scrolls with large text and can skip from any page ($size)',
|
||||
(tester) async {
|
||||
await openTour(tester, size: size, textScale: 2);
|
||||
await tester.tap(find.text('Continue'));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.drag(
|
||||
find.byType(SingleChildScrollView).first,
|
||||
const Offset(0, -400),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(tester.takeException(), isNull);
|
||||
await tester.tap(find.text('Skip'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(UpgradeIntroScreen), findsNothing);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user