From e1f9ae889c014ba2ed330b13b4d324be373fc9e8 Mon Sep 17 00:00:00 2001 From: Jaime <52668514+jsgalarraga@users.noreply.github.com> Date: Fri, 7 Jun 2024 11:20:20 +0200 Subject: [PATCH] feat: update copy and end game page (#558) * feat: update copy * feat: update copy * feat: update copy * fix: move end game scrollbar to the side * feat: fit the welcome text nicer * feat: update end game --- lib/end_game/view/end_game_page.dart | 35 ++------ lib/end_game/view/end_game_view_content.dart | 32 +++++-- lib/l10n/arb/app_en.arb | 12 ++- .../link/project_details_links.dart | 1 + .../view/project_details_view.dart | 2 +- lib/welcome/view/welcome_page.dart | 2 +- lib/widget/how_made.dart | 62 +++++++++---- .../lib/src/theme/io_crossword_colors.dart | 3 + .../view/end_game_view_content_test.dart | 4 +- .../view/project_details_view_test.dart | 2 +- test/widget/how_made_test.dart | 90 +++++++++++++++++-- web/index.html | 4 +- 12 files changed, 180 insertions(+), 69 deletions(-) diff --git a/lib/end_game/view/end_game_page.dart b/lib/end_game/view/end_game_page.dart index 1bfc7a3f6..f8190a2d0 100644 --- a/lib/end_game/view/end_game_page.dart +++ b/lib/end_game/view/end_game_page.dart @@ -44,37 +44,20 @@ class EndGameLargeView extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - body: Center( - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 502), - child: const SingleChildScrollView( - child: Padding( - padding: EdgeInsets.symmetric( - vertical: 32, - ), + body: SingleChildScrollView( + child: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 512), + child: const Padding( + padding: EdgeInsets.all(32), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: EdgeInsets.symmetric( - horizontal: 32, - ), - child: LeaderboardButton(), - ), + LeaderboardButton(), SizedBox(height: 48), - Padding( - padding: EdgeInsets.symmetric( - horizontal: 80, - ), - child: EndGameContent(), - ), + EndGameContent(), SizedBox(height: 48), - Padding( - padding: EdgeInsets.symmetric( - horizontal: 56, - ), - child: ActionButtonsEndGame(), - ), + ActionButtonsEndGame(), ], ), ), diff --git a/lib/end_game/view/end_game_view_content.dart b/lib/end_game/view/end_game_view_content.dart index 585cb39f3..6fcfe69a5 100644 --- a/lib/end_game/view/end_game_view_content.dart +++ b/lib/end_game/view/end_game_view_content.dart @@ -23,7 +23,7 @@ class EndGameContent extends StatelessWidget { textAlign: TextAlign.center, ), const SizedBox(height: 24), - const HowMade(), + const HowMadeAndJoinCompetition(), const SizedBox(height: 24), const Center( child: PlayerInitials(), @@ -97,12 +97,23 @@ class ActionButtonsEndGame extends StatelessWidget { ), ), const SizedBox(height: 24), - FilledButton.tonalIcon( - onPressed: () { - context.launchUrl(ProjectDetailsLinks.claimBadge); - }, - label: Text(l10n.claimBadge), - icon: const Icon(IoIcons.google, size: 20), + SizedBox( + height: 56, + child: FilledButton.icon( + onPressed: () { + context.launchUrl(ProjectDetailsLinks.claimBadge); + }, + label: Text(l10n.claimBadge), + style: Theme.of(context).filledButtonTheme.style?.copyWith( + backgroundColor: const WidgetStatePropertyAll( + IoCrosswordColors.developerBlue, + ), + foregroundColor: const WidgetStatePropertyAll( + IoCrosswordColors.black, + ), + ), + icon: const Icon(IoIcons.google, size: 20), + ), ), ], ); @@ -126,8 +137,11 @@ class EndGameImage extends StatelessWidget { Mascot.sparky => Assets.images.endGameSparky, }; - return image.image( - fit: BoxFit.fitWidth, + return ClipRRect( + borderRadius: BorderRadius.circular(8), + child: image.image( + fit: BoxFit.fitWidth, + ), ); } } diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index a46aa2b86..13bc058d3 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -41,7 +41,7 @@ "@shareWordSubtitle": { "description": "Subtitle of the share word modal" }, - "welcomeSubtitle": "Help solve the 2024 I/O Crossword, built with Gemini API.", + "welcomeSubtitle": "Help solve the 2024 I/O Crossword, built with the Gemini API.", "@welcomeSubtitle": { "description": "Subtitle to be displayed in the welcome modal" }, @@ -61,7 +61,7 @@ "@chooseYourTeam": { "description": "The title for the team selection page" }, - "joinTeam": "Join {team} team", + "joinTeam": "Join team {team}", "@joinTeam": { "description": "Text for which team you are choosing", "placeholders": { @@ -215,6 +215,10 @@ "@openSourceCode": { "description": "Open source code text to open link" }, + "joinGeminiCompetition": "Join the Gemini API Developer Competition", + "@joinGeminiCompetition": { + "description": "Gemini API dev competition text to open link" + }, "otherLinks": "Other links", "@otherLinks": { "description": "Other links label" @@ -303,7 +307,7 @@ "@finishAndSubmitScore": { "description": "Finish & submit score for menu label" }, - "claimBadgeDescription": "Get a badge for your Developer Profile to show you helped solve the I/O Crossword built with Gemini API.", + "claimBadgeDescription": "Get a badge for your Developer Profile to show you helped solve the I/O Crossword built with the Gemini API.", "@claimBadgeDescription": { "description": "Developer Profile label" }, @@ -343,7 +347,7 @@ "@aboutHowToPlayFourthInstructionsTitle": { "description": "The fourth step instruction title." }, - "aboutHowToPlayFourthInstructions": "Stumped? Ask Gemini Yes or No questions to get hints in real time.", + "aboutHowToPlayFourthInstructions": "Stumped? Ask the Gemini API Yes or No questions to get hints in real time.", "@aboutHowToPlayFourthInstructions": { "description": "The Fourth step instruction on how to play." }, diff --git a/lib/project_details/link/project_details_links.dart b/lib/project_details/link/project_details_links.dart index 7ce360158..83d27423b 100644 --- a/lib/project_details/link/project_details_links.dart +++ b/lib/project_details/link/project_details_links.dart @@ -12,4 +12,5 @@ class ProjectDetailsLinks { defaultValue: 'https://crossword.withgoogle.com', ); static const blogPost = 'https://flutter.dev/crossword'; + static const geminiDeveloperCompetition = 'https://ai.google.dev/competition'; } diff --git a/lib/project_details/view/project_details_view.dart b/lib/project_details/view/project_details_view.dart index f3e69239b..ed7454e0e 100644 --- a/lib/project_details/view/project_details_view.dart +++ b/lib/project_details/view/project_details_view.dart @@ -93,7 +93,7 @@ class ProjectDetailsContent extends StatelessWidget { if (layout == IoLayoutData.small) Assets.images.hero.image(height: 200), const SizedBox(height: 60), - const HowMade(), + const HowMadeAndOpenSource(), ], ), ), diff --git a/lib/welcome/view/welcome_page.dart b/lib/welcome/view/welcome_page.dart index b5a741958..433f67868 100644 --- a/lib/welcome/view/welcome_page.dart +++ b/lib/welcome/view/welcome_page.dart @@ -123,7 +123,7 @@ class WelcomeBody extends StatelessWidget { return Theme( data: const IoCrosswordTheme().themeData, child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 294), + constraints: const BoxConstraints(maxWidth: 320), child: Column( mainAxisSize: MainAxisSize.min, children: [ diff --git a/lib/widget/how_made.dart b/lib/widget/how_made.dart index e8655c44f..4817f8e2c 100644 --- a/lib/widget/how_made.dart +++ b/lib/widget/how_made.dart @@ -5,8 +5,8 @@ import 'package:io_crossword/l10n/l10n.dart'; import 'package:io_crossword/project_details/link/project_details_links.dart'; import 'package:io_crossword_ui/io_crossword_ui.dart'; -class HowMade extends StatelessWidget { - const HowMade({super.key}); +class HowMadeAndOpenSource extends StatelessWidget { + const HowMadeAndOpenSource({super.key}); @override Widget build(BuildContext context) { @@ -19,35 +19,61 @@ class HowMade extends StatelessWidget { return RichText( textAlign: TextAlign.center, text: TextSpan( - text: '${l10n.learn} ', - style: textTheme.body.copyWith( - color: textColor, - ), + style: textTheme.body.copyWith(color: textColor), children: [ + TextSpan(text: '${l10n.learn} '), TextSpan( text: l10n.howMade, - style: textTheme.body.copyWith( - color: linkColor, - ), + style: textTheme.body.copyWith(color: linkColor), recognizer: TapGestureRecognizer() ..onTap = () => context.launchUrl(ProjectDetailsLinks.blogPost), ), - TextSpan( - text: ' ${l10n.and} ', - style: textTheme.body, - ), + TextSpan(text: ' ${l10n.and} '), TextSpan( text: l10n.openSourceCode, - style: textTheme.body.copyWith( - color: linkColor, - ), + style: textTheme.body.copyWith(color: linkColor), recognizer: TapGestureRecognizer() ..onTap = () => context.launchUrl(ProjectDetailsLinks.github), ), + const TextSpan(text: '.'), + ], + ), + ); + } +} + +class HowMadeAndJoinCompetition extends StatelessWidget { + const HowMadeAndJoinCompetition({super.key}); + + @override + Widget build(BuildContext context) { + final l10n = context.l10n; + + final textTheme = Theme.of(context).io.textStyles; + const linkColor = IoCrosswordColors.linkBlue; + const textColor = IoCrosswordColors.seedWhite; + + return RichText( + textAlign: TextAlign.center, + text: TextSpan( + style: textTheme.body.copyWith(color: textColor), + children: [ + TextSpan(text: '${l10n.learn} '), TextSpan( - text: '.', - style: textTheme.body, + text: l10n.howMade, + style: textTheme.body.copyWith(color: linkColor), + recognizer: TapGestureRecognizer() + ..onTap = () => context.launchUrl(ProjectDetailsLinks.blogPost), + ), + const TextSpan(text: '.\n'), + TextSpan( + text: l10n.joinGeminiCompetition, + style: textTheme.body.copyWith(color: linkColor), + recognizer: TapGestureRecognizer() + ..onTap = () => context + .launchUrl(ProjectDetailsLinks.geminiDeveloperCompetition), ), + const TextSpan(text: '.'), ], ), ); diff --git a/packages/io_crossword_ui/lib/src/theme/io_crossword_colors.dart b/packages/io_crossword_ui/lib/src/theme/io_crossword_colors.dart index c7a00bc35..2c9646040 100644 --- a/packages/io_crossword_ui/lib/src/theme/io_crossword_colors.dart +++ b/packages/io_crossword_ui/lib/src/theme/io_crossword_colors.dart @@ -29,6 +29,9 @@ abstract class IoCrosswordColors { /// light blue gradient static const darkGradientBlue = Color(0xFF337BFA); + /// developer blue + static const developerBlue = Color(0xFFB7D8FF); + /// googleBlue static const googleBlue = Color(0xFF4383F2); diff --git a/test/end_game/view/end_game_view_content_test.dart b/test/end_game/view/end_game_view_content_test.dart index 3c7b4a43a..5ae934978 100644 --- a/test/end_game/view/end_game_view_content_test.dart +++ b/test/end_game/view/end_game_view_content_test.dart @@ -50,10 +50,10 @@ void main() { expect(find.text(l10n.thanksForContributing), findsOneWidget); }); - testWidgets('displays HowMade', (tester) async { + testWidgets('displays HowMadeAndJoinCompetition', (tester) async { await tester.pumpApp(SingleChildScrollView(child: EndGameContent())); - expect(find.byType(HowMade), findsOneWidget); + expect(find.byType(HowMadeAndJoinCompetition), findsOneWidget); }); testWidgets('displays EndGameImage', (tester) async { diff --git a/test/project_details/view/project_details_view_test.dart b/test/project_details/view/project_details_view_test.dart index 5b96ad476..5d55822ee 100644 --- a/test/project_details/view/project_details_view_test.dart +++ b/test/project_details/view/project_details_view_test.dart @@ -60,7 +60,7 @@ void main() { (tester) async { await tester.pumpApp(const ProjectDetailsContent()); - expect(find.byType(HowMade), findsOneWidget); + expect(find.byType(HowMadeAndOpenSource), findsOneWidget); }, ); }); diff --git a/test/widget/how_made_test.dart b/test/widget/how_made_test.dart index 275775e6e..5a51ca99b 100644 --- a/test/widget/how_made_test.dart +++ b/test/widget/how_made_test.dart @@ -17,7 +17,7 @@ class _MockUrlLauncherPlatform extends Mock class _FakeLaunchOptions extends Fake implements LaunchOptions {} void main() { - group('$HowMade', () { + group('$HowMadeAndOpenSource', () { late UrlLauncherPlatform urlLauncher; late AppLocalizations l10n; @@ -40,7 +40,7 @@ void main() { testWidgets( 'displays information how made and open source', (tester) async { - await tester.pumpApp(HowMade()); + await tester.pumpApp(HowMadeAndOpenSource()); final text = '${l10n.learn} ${l10n.howMade} ${l10n.and} ${l10n.openSourceCode}.'; @@ -52,7 +52,7 @@ void main() { testWidgets( 'calls launchUrl when tapped on open source code', (tester) async { - await tester.pumpApp(HowMade()); + await tester.pumpApp(HowMadeAndOpenSource()); final finder = find.byWidgetPredicate( (widget) => @@ -79,7 +79,7 @@ void main() { testWidgets( 'calls launchUrl when tapped on "How crossword was made"', (tester) async { - await tester.pumpApp(HowMade()); + await tester.pumpApp(HowMadeAndOpenSource()); final finder = find.byWidgetPredicate( (widget) => @@ -103,7 +103,7 @@ void main() { (tester) async { when(() => urlLauncher.canLaunch(any())).thenAnswer((_) async => false); - await tester.pumpApp(HowMade()); + await tester.pumpApp(HowMadeAndOpenSource()); final finder = find.byWidgetPredicate( (widget) => @@ -118,4 +118,84 @@ void main() { }, ); }); + + group('$HowMadeAndJoinCompetition', () { + late UrlLauncherPlatform urlLauncher; + + late AppLocalizations l10n; + + setUpAll(() async { + l10n = await AppLocalizations.delegate.load(Locale('en')); + registerFallbackValue(_FakeLaunchOptions()); + }); + + setUp(() { + urlLauncher = _MockUrlLauncherPlatform(); + + UrlLauncherPlatform.instance = urlLauncher; + + when(() => urlLauncher.canLaunch(any())).thenAnswer((_) async => true); + when(() => urlLauncher.launchUrl(any(), any())) + .thenAnswer((_) async => true); + }); + + testWidgets( + 'displays information how made and the gemini dev competition', + (tester) async { + await tester.pumpApp(HowMadeAndJoinCompetition()); + + final text = + '${l10n.learn} ${l10n.howMade}.\n${l10n.joinGeminiCompetition}.'; + expect(find.text(text, findRichText: true), findsOneWidget); + }, + ); + + testWidgets( + 'calls launchUrl when tapped on join developer competition', + (tester) async { + await tester.pumpApp(HowMadeAndJoinCompetition()); + + final finder = find.byWidgetPredicate( + (widget) => + widget is RichText && + find.tapTextSpan( + widget, + l10n.joinGeminiCompetition, + ), + ); + + await tester.tap(finder); + await tester.pumpAndSettle(); + + verify( + () => urlLauncher.launchUrl( + 'https://ai.google.dev/competition', + any(), + ), + ); + }, + ); + + testWidgets( + 'calls launchUrl when tapped on "How crossword was made"', + (tester) async { + await tester.pumpApp(HowMadeAndJoinCompetition()); + + final finder = find.byWidgetPredicate( + (widget) => + widget is RichText && find.tapTextSpan(widget, l10n.howMade), + ); + + await tester.tap(finder); + await tester.pumpAndSettle(); + + verify( + () => urlLauncher.launchUrl( + 'https://flutter.dev/crossword', + any(), + ), + ); + }, + ); + }); } diff --git a/web/index.html b/web/index.html index 88f85cd7b..37f4d6232 100644 --- a/web/index.html +++ b/web/index.html @@ -6,7 +6,7 @@ - + @@ -194,7 +194,7 @@
Welcome!
- Come experience the I/O Crossword, built with Gemini API for Google I/O 2024 + Come experience the I/O Crossword, built with the Gemini API for Google I/O 2024