feat(trackers): show a QR code in every tracker sign-in dialog and fit them on TV screens
Every tracker auth dialog (Trakt/Simkl/MDBList device-code and MAL/AniList OAuth proxy) now shares the same PendingAuthDialog affordances: a QR code for the sign-in URL, a large copyable URL with the scheme stripped, the browser launch button (hidden on Apple TV, which has no browser), and the polling spinner. On wide viewports (TV logical 960x540, desktop, phone landscape) the QR pane sits beside the instructions so the dialog no longer clips on tvOS, and the content is scrollable as an overflow safety net. Device activation codes scale down instead of wrapping.
This commit is contained in:
@@ -13,7 +13,7 @@ void main() {
|
||||
'watchTogether.openSessionControls': translations.watchTogether.openSessionControls,
|
||||
'watchTogether.copySessionCode': translations.watchTogether.copySessionCode,
|
||||
'services.deviceCode.copyCode': translations.services.deviceCode.copyCode,
|
||||
'services.oauthProxy.copyUrl': translations.services.oauthProxy.copyUrl,
|
||||
'services.pendingAuth.copyUrl': translations.services.pendingAuth.copyUrl,
|
||||
};
|
||||
|
||||
for (final MapEntry(key: key, value: value) in labels.entries) {
|
||||
|
||||
@@ -5,19 +5,22 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:plezy/i18n/strings.g.dart';
|
||||
import 'package:plezy/models/trackers/device_code.dart';
|
||||
import 'package:plezy/widgets/device_code_dialog.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
|
||||
void main() {
|
||||
setUpAll(() => LocaleSettings.setLocaleSync(AppLocale.en));
|
||||
|
||||
const code = DeviceCode(
|
||||
deviceCode: 'device-token',
|
||||
userCode: 'ABCD-EFGH',
|
||||
verificationUrl: 'https://example.com/activate',
|
||||
verificationUrlComplete: 'https://example.com/activate/ABCD-EFGH',
|
||||
expiresIn: 600,
|
||||
interval: 5,
|
||||
);
|
||||
|
||||
testWidgets('copy control exposes the activation code as its value', (tester) async {
|
||||
final semantics = tester.ensureSemantics();
|
||||
const code = DeviceCode(
|
||||
deviceCode: 'device-token',
|
||||
userCode: 'ABCD-EFGH',
|
||||
verificationUrl: 'https://example.com/activate',
|
||||
expiresIn: 600,
|
||||
interval: 5,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
@@ -35,4 +38,23 @@ void main() {
|
||||
expect(find.bySemanticsLabel(code.userCode), findsNothing);
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('shows a QR code for the complete verification URL and a copyable readable URL', (tester) async {
|
||||
final semantics = tester.ensureSemantics();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: DeviceCodeDialog(code: code, serviceName: 'Example', onCancel: () {}),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.byType(QrImageView), findsOneWidget);
|
||||
// The visible URL is the short verification URL with the scheme stripped,
|
||||
// but copying yields the full code-prefilled URL.
|
||||
expect(find.text('example.com/activate'), findsOneWidget);
|
||||
final urlFinder = find.bySemanticsLabel(t.services.pendingAuth.copyUrl);
|
||||
expect(urlFinder, findsOneWidget);
|
||||
expect(tester.getSemantics(urlFinder).getSemanticsData().value, code.verificationUrlComplete);
|
||||
semantics.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,17 +5,19 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:plezy/i18n/strings.g.dart';
|
||||
import 'package:plezy/services/trackers/oauth_proxy_client.dart';
|
||||
import 'package:plezy/widgets/oauth_proxy_dialog.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
|
||||
void main() {
|
||||
setUpAll(() => LocaleSettings.setLocaleSync(AppLocale.en));
|
||||
|
||||
const start = OAuthProxyStart(session: 'session-token', url: 'https://example.com/oauth/start', expiresIn: 600);
|
||||
|
||||
testWidgets('copy control exposes the OAuth URL as its value', (tester) async {
|
||||
final semantics = tester.ensureSemantics();
|
||||
tester.view.devicePixelRatio = 1;
|
||||
tester.view.physicalSize = const Size(1024, 768);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
const start = OAuthProxyStart(session: 'session-token', url: 'https://example.com/oauth/start', expiresIn: 600);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
@@ -23,7 +25,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
final finder = find.bySemanticsLabel(t.services.oauthProxy.copyUrl);
|
||||
final finder = find.bySemanticsLabel(t.services.pendingAuth.copyUrl);
|
||||
expect(finder, findsOneWidget);
|
||||
final data = tester.getSemantics(finder).getSemanticsData();
|
||||
expect(data.value, start.url);
|
||||
@@ -33,4 +35,33 @@ void main() {
|
||||
expect(find.bySemanticsLabel(start.url), findsNothing);
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('always shows a QR code encoding the sign-in URL', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: OAuthProxyDialog(start: start, serviceName: 'Example', onCancel: () {}),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.byType(QrImageView), findsOneWidget);
|
||||
expect(find.text('example.com/oauth/start'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('fits within a tvOS-sized 960x540 logical viewport without overflowing', (tester) async {
|
||||
tester.view.devicePixelRatio = 1;
|
||||
tester.view.physicalSize = const Size(960, 540);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: OAuthProxyDialog(start: start, serviceName: 'Example', onCancel: () {}),
|
||||
),
|
||||
);
|
||||
|
||||
// Wide layout: QR beside the instructions, dialog fully on screen.
|
||||
final dialogRect = tester.getRect(find.byType(AlertDialog));
|
||||
expect(dialogRect.height, lessThanOrEqualTo(540));
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user