diff --git a/gui/packages/ubuntupro/end_to_end/end_to_end_test.dart b/gui/packages/ubuntupro/end_to_end/end_to_end_test.dart index d314c9b30..100c01d13 100644 --- a/gui/packages/ubuntupro/end_to_end/end_to_end_test.dart +++ b/gui/packages/ubuntupro/end_to_end/end_to_end_test.dart @@ -7,7 +7,6 @@ import 'package:stack_trace/stack_trace.dart' as stack_trace; import 'package:ubuntupro/core/environment.dart'; import 'package:ubuntupro/main.dart' as app; import 'package:ubuntupro/pages/subscribe_now/subscribe_now_page.dart'; -import 'package:ubuntupro/pages/subscribe_now/subscribe_now_widgets.dart'; import 'package:ubuntupro/pages/subscription_status/subscription_status_page.dart'; import '../test/utils/l10n_tester.dart'; @@ -63,11 +62,6 @@ Future testManualTokenInput(WidgetTester tester) async { // The "subscribe now page" is only shown if the GUI communicates with the background agent. var l10n = tester.l10n(); - // expands the collapsed input field group - final toggle = find.byIcon(ProTokenInputField.expandIcon); - await tester.tap(toggle); - await tester.pumpAndSettle(); - // finds the pro token from the environment final goodToken = Environment()[proTokenEnv]; expect( @@ -82,7 +76,7 @@ Future testManualTokenInput(WidgetTester tester) async { await tester.pumpAndSettle(); // submits the input. - final button = find.text(l10n.confirm); + final button = find.text(l10n.attach); await tester.tap(button); await tester.pumpAndSettle(); diff --git a/gui/packages/ubuntupro/integration_test/ubuntu_pro_for_wsl_test.dart b/gui/packages/ubuntupro/integration_test/ubuntu_pro_for_wsl_test.dart index 946be14c8..60e1e98bb 100644 --- a/gui/packages/ubuntupro/integration_test/ubuntu_pro_for_wsl_test.dart +++ b/gui/packages/ubuntupro/integration_test/ubuntu_pro_for_wsl_test.dart @@ -15,7 +15,6 @@ import 'package:ubuntupro/main.dart' as app; import 'package:ubuntupro/pages/landscape/landscape_page.dart'; import 'package:ubuntupro/pages/startup/startup_page.dart'; import 'package:ubuntupro/pages/subscribe_now/subscribe_now_page.dart'; -import 'package:ubuntupro/pages/subscribe_now/subscribe_now_widgets.dart'; import 'package:ubuntupro/pages/subscription_status/subscription_status_page.dart'; import 'package:yaru/widgets.dart'; import 'package:yaru_test/yaru_test.dart'; @@ -118,10 +117,6 @@ void main() { // The "subscribe now page" is only shown if the GUI communicates with the background agent. var l10n = tester.l10n(); - // expands the collapsed input field group - final toggle = find.byIcon(ProTokenInputField.expandIcon); - await tester.tap(toggle); - await tester.pumpAndSettle(); // enters a good token value final inputField = find.byType(TextField); @@ -129,7 +124,7 @@ void main() { await tester.pump(); // submits the input. - final button = find.text(l10n.confirm); + final button = find.text(l10n.attach); await tester.tap(button); await tester.pumpAndSettle(); @@ -166,10 +161,6 @@ void main() { // The "subscribe now page" is only shown if the GUI communicates with the background agent. var l10n = tester.l10n(); - // expands the collapsed input field group - final toggle = find.byIcon(ProTokenInputField.expandIcon); - await tester.tap(toggle); - await tester.pumpAndSettle(); // enters a good token value final inputField = find.byType(TextField); @@ -177,7 +168,7 @@ void main() { await tester.pump(); // submits the input. - final button = find.text(l10n.confirm); + final button = find.text(l10n.attach); await tester.tap(button); await tester.pumpAndSettle(); diff --git a/gui/packages/ubuntupro/lib/l10n/app_en.arb b/gui/packages/ubuntupro/lib/l10n/app_en.arb index 9962322c8..3bb289a36 100644 --- a/gui/packages/ubuntupro/lib/l10n/app_en.arb +++ b/gui/packages/ubuntupro/lib/l10n/app_en.arb @@ -1,10 +1,11 @@ { "appTitle": "Ubuntu Pro for WSL", "tokenErrorEmpty": "Token cannot be empty", - "tokenErrorInvalid": "Token invalid", + "tokenErrorInvalid": "Invalid token", + "tokenValid": "Valid token", "tokenInputTitle": "Already have a token?", "tokenInputHint": "Paste your Ubuntu Pro token here", - "confirm": "Confirm", + "attach": "Attach", "applyProToken": "Apply Pro Token", "applyingProToken": "Applying token {token}", "@applyingProToken": { diff --git a/gui/packages/ubuntupro/lib/pages/subscribe_now/subscribe_now_widgets.dart b/gui/packages/ubuntupro/lib/pages/subscribe_now/subscribe_now_widgets.dart index d826ad76b..73aaa3293 100644 --- a/gui/packages/ubuntupro/lib/pages/subscribe_now/subscribe_now_widgets.dart +++ b/gui/packages/ubuntupro/lib/pages/subscribe_now/subscribe_now_widgets.dart @@ -61,58 +61,97 @@ class _ProTokenInputFieldState extends State { final lang = AppLocalizations.of(context); final theme = Theme.of(context); - return YaruExpandable( - header: Text( - lang.tokenInputTitle, - style: - theme.textTheme.bodyMedium!.copyWith(fontWeight: FontWeight.w100), - ), - expandIcon: Icon( - ProTokenInputField.expandIcon, - color: theme.textTheme.bodyMedium!.color, - ), - isExpanded: widget.isExpanded, - child: ValueListenableBuilder( - valueListenable: _token, - builder: (context, _, __) => Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: TextField( - inputFormatters: [ - // This ignores all sorts of (Unicode) whitespaces (not only at the ends). - FilteringTextInputFormatter.deny(RegExp(r'\s')), - ], - autofocus: false, - controller: _controller, - decoration: InputDecoration( - hintText: lang.tokenInputHint, - errorText: _token.errorOrNull?.localize(lang), - counterText: '', + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + lang.tokenInputTitle, + style: + theme.textTheme.bodyMedium!.copyWith(fontWeight: FontWeight.w100), + ), + const SizedBox( + height: 8, + ), + ValueListenableBuilder( + valueListenable: _token, + builder: (context, _, __) => Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: TextField( + inputFormatters: [ + // This ignores all sorts of (Unicode) whitespaces (not only at the ends). + FilteringTextInputFormatter.deny(RegExp(r'\s')), + ], + autofocus: false, + controller: _controller, + decoration: InputDecoration( + hintText: lang.tokenInputHint, + error: _token.errorOrNull?.localize(lang) != null + ? Padding( + padding: const EdgeInsets.only(top: 4), + child: Row( + children: [ + Icon( + Icons.cancel, + color: YaruColors.of(context).error, + size: 16.0, + ), + const SizedBox(width: 4), + Text( + _token.errorOrNull!.localize(lang)!, + style: theme.textTheme.bodySmall!.copyWith( + color: YaruColors.of(context).error, + ), + ), + ], + ), + ) + : null, + helper: _token.valueOrNull != null + ? Padding( + padding: const EdgeInsets.only(top: 4), + child: Row( + children: [ + Icon( + Icons.check_circle, + color: YaruColors.of(context).success, + size: 16.0, + ), + const SizedBox(width: 4), + Text( + lang.tokenValid, + style: theme.textTheme.bodySmall!.copyWith( + color: YaruColors.of(context).success, + ), + ), + ], + ), + ) + : null, + ), + onChanged: _token.update, + onSubmitted: _onSubmitted, ), - onChanged: _token.update, - onSubmitted: _onSubmitted, ), - ), - const SizedBox( - width: 8.0, - ), - ElevatedButton( - onPressed: canSubmit ? _handleApplyButton : null, - child: Text(lang.confirm), - ), - ], + const SizedBox( + width: 8.0, + ), + ElevatedButton( + onPressed: canSubmit ? _handleApplyButton : null, + child: Text(lang.attach), + ), + ], + ), ), - ), + ], ); } } /// A value-notifier for the [ProToken] with validation. -/// Since we don't want to start the UI with an error due the text field being -/// empty, this stores a nullable [ProToken] object class ProTokenValue extends EitherValueNotifier { - ProTokenValue() : super.ok(null); + ProTokenValue() : super.err(TokenError.empty); String? get token => valueOrNull?.value; diff --git a/gui/packages/ubuntupro/lib/pages/subscription_status/subscription_status_widgets.dart b/gui/packages/ubuntupro/lib/pages/subscription_status/subscription_status_widgets.dart index 1f83d02d7..cbe908113 100644 --- a/gui/packages/ubuntupro/lib/pages/subscription_status/subscription_status_widgets.dart +++ b/gui/packages/ubuntupro/lib/pages/subscription_status/subscription_status_widgets.dart @@ -47,17 +47,20 @@ class SubscriptionStatus extends StatelessWidget { Container( padding: const EdgeInsets.all(16.0), decoration: BoxDecoration( - border: Border.all(color: const Color(0xFF0E8420), width: 1.0), - color: const Color(0xFFE6F8E8), + border: + Border.all(color: YaruColors.of(context).success, width: 1.0), + color: YaruColors.of(context) + .success + .copyWith(lightness: 0.94, saturation: 0.56), borderRadius: const BorderRadius.all(Radius.circular(4.0)), ), child: Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Icon( + Icon( Icons.check_circle_outline_outlined, - color: Color(0xFF0E8420), + color: YaruColors.of(context).success, size: 24.0, ), const SizedBox(width: 8.0), diff --git a/gui/packages/ubuntupro/test/pages/subcribe_now/subscribe_now_widgets_test.dart b/gui/packages/ubuntupro/test/pages/subcribe_now/subscribe_now_widgets_test.dart index 404ab9071..e022e0bd8 100644 --- a/gui/packages/ubuntupro/test/pages/subcribe_now/subscribe_now_widgets_test.dart +++ b/gui/packages/ubuntupro/test/pages/subcribe_now/subscribe_now_widgets_test.dart @@ -46,25 +46,6 @@ void main() { }); group('pro token input', () { - testWidgets('collapsed by default', (tester) async { - final app = MaterialApp( - home: Scaffold( - body: ProTokenInputField( - onApply: (_) {}, - ), - ), - localizationsDelegates: AppLocalizations.localizationsDelegates, - ); - - await tester.pumpWidget(app); - expect(find.byType(TextField).hitTestable(), findsNothing); - - final toggle = find.byType(IconButton); - await tester.tap(toggle); - await tester.pumpAndSettle(); - expect(find.byType(TextField).hitTestable(), findsOneWidget); - }); - group('basic flow', () { final theApp = buildApp(onApply: (_) {}, isExpanded: true); testWidgets('starts with no error', (tester) async { @@ -93,8 +74,11 @@ void main() { await tester.enterText(inputField, token); await tester.pump(); - final input = tester.firstWidget(inputField); - expect(input.decoration!.errorText, equals(lang.tokenErrorInvalid)); + final errorText = find.descendant( + of: inputField, + matching: find.text(lang.tokenErrorInvalid), + ); + expect(errorText, findsOne); final button = tester.firstWidget(find.byType(ElevatedButton)); @@ -112,8 +96,11 @@ void main() { await tester.enterText(inputField, tks.invalidTokens[0]); await tester.pump(); - var input = tester.firstWidget(inputField); - expect(input.decoration!.errorText, equals(lang.tokenErrorInvalid)); + final errorText = find.descendant( + of: inputField, + matching: find.text(lang.tokenErrorInvalid), + ); + expect(errorText, findsOne); final button = tester.firstWidget(find.byType(ElevatedButton)); @@ -122,20 +109,27 @@ void main() { // ...except when we delete the content we should have no more errors await tester.enterText(inputField, ''); await tester.pump(); - input = tester.firstWidget(inputField); - expect(input.decoration!.errorText, isNull); + final input = tester.firstWidget(inputField); + expect(input.decoration!.error, isNull); expect(button.enabled, isFalse); }); testWidgets('good token', (tester) async { await tester.pumpWidget(theApp); final inputField = find.byType(TextField); + final context = tester.element(inputField); + final lang = AppLocalizations.of(context); await tester.enterText(inputField, tks.good); await tester.pump(); final input = tester.firstWidget(inputField); - expect(input.decoration!.errorText, isNull); + expect(input.decoration!.error, isNull); + final validText = find.descendant( + of: inputField, + matching: find.text(lang.tokenValid), + ); + expect(validText, findsOne); final button = tester.firstWidget(find.byType(ElevatedButton));