From 0073c50ff603e37a58eeb9d901fb72553e030359 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Thu, 11 Apr 2024 14:31:08 +0200 Subject: [PATCH 1/8] Expand puzzle buttons and add colors --- lib/src/styles/styles.dart | 14 +++++ lib/src/view/puzzle/puzzle_tab_screen.dart | 67 +++++++++++----------- 2 files changed, 48 insertions(+), 33 deletions(-) diff --git a/lib/src/styles/styles.dart b/lib/src/styles/styles.dart index 4dfa756221..be095693e5 100644 --- a/lib/src/styles/styles.dart +++ b/lib/src/styles/styles.dart @@ -111,12 +111,16 @@ class CustomColors extends ThemeExtension { required this.good, required this.error, required this.fancy, + required this.purple, + required this.primary, }); final Color brag; final Color good; final Color error; final Color fancy; + final Color purple; + final Color primary; @override CustomColors copyWith({ @@ -124,12 +128,16 @@ class CustomColors extends ThemeExtension { Color? good, Color? error, Color? fancy, + Color? purple, + Color? primary, }) { return CustomColors( brag: brag ?? this.brag, good: good ?? this.good, error: error ?? this.error, fancy: fancy ?? this.fancy, + purple: purple ?? this.purple, + primary: primary ?? this.primary, ); } @@ -143,6 +151,8 @@ class CustomColors extends ThemeExtension { good: Color.lerp(good, other.good, t) ?? good, error: Color.lerp(error, other.error, t) ?? error, fancy: Color.lerp(fancy, other.fancy, t) ?? fancy, + purple: Color.lerp(purple, other.purple, t) ?? purple, + primary: Color.lerp(primary, other.primary, t) ?? primary, ); } @@ -152,6 +162,8 @@ class CustomColors extends ThemeExtension { good: good.harmonizeWith(colorScheme.primary), error: error.harmonizeWith(colorScheme.primary), fancy: fancy.harmonizeWith(colorScheme.primary), + purple: purple.harmonizeWith(colorScheme.primary), + primary: primary.harmonizeWith(colorScheme.primary), ); } } @@ -161,6 +173,8 @@ const lichessCustomColors = CustomColors( good: LichessColors.good, error: LichessColors.error, fancy: LichessColors.fancy, + purple: LichessColors.purple, + primary: LichessColors.primary, ); extension CustomColorsBuildContext on BuildContext { diff --git a/lib/src/view/puzzle/puzzle_tab_screen.dart b/lib/src/view/puzzle/puzzle_tab_screen.dart index 371413ea29..452f5002f4 100644 --- a/lib/src/view/puzzle/puzzle_tab_screen.dart +++ b/lib/src/view/puzzle/puzzle_tab_screen.dart @@ -114,10 +114,12 @@ class _Body extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final connectivity = ref.watch(connectivityChangesProvider); - final expansionTileColor = Styles.expansionTileColor(context); - final isTablet = getScreenType(context) == ScreenType.tablet; + final separator = Theme.of(context).platform == TargetPlatform.iOS + ? const SizedBox(height: 14.0) + : const SizedBox(height: 8.0); + final handsetChildren = [ const SizedBox(height: 8.0), connectivity.when( @@ -128,25 +130,13 @@ class _Body extends ConsumerWidget { error: (_, __) => const SizedBox.shrink(), ), PuzzleButton(), - Theme( - data: Theme.of(context).copyWith(dividerColor: Colors.transparent), - child: ExpansionTile( - title: Text( - context.l10n.more, - ), - tilePadding: Styles.horizontalBodyPadding, - iconColor: expansionTileColor, - collapsedIconColor: expansionTileColor, - textColor: expansionTileColor, - collapsedTextColor: expansionTileColor, - controlAffinity: ListTileControlAffinity.leading, - children: [ - const PuzzleThemeButton(), - StreakButton(connectivity: connectivity), - StormButton(connectivity: connectivity), - ], - ), - ), + separator, + const PuzzleThemeButton(), + separator, + StreakButton(connectivity: connectivity), + separator, + StormButton(connectivity: connectivity), + separator, PuzzleDashboardWidget(), PuzzleHistoryWidget(), ]; @@ -220,6 +210,11 @@ class PuzzleButton extends ConsumerWidget { } } +const _subPuzzleButtonTitleStyle = TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.w500, +); + class StreakButton extends StatelessWidget { const StreakButton({required this.connectivity, super.key}); @@ -228,15 +223,16 @@ class StreakButton extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( - padding: Styles.bodySectionBottomPadding, + padding: Styles.horizontalBodyPadding, child: CardButton( - icon: const Icon( + icon: Icon( LichessIcons.streak, size: 44, + color: context.lichessColors.fancy, ), title: const Text( 'Puzzle Streak', - style: Styles.callout, + style: _subPuzzleButtonTitleStyle, ), subtitle: Text( context.l10n.puzzleStreakDescription.characters @@ -270,15 +266,16 @@ class StormButton extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( - padding: Styles.bodySectionBottomPadding, + padding: Styles.horizontalBodyPadding, child: CardButton( - icon: const Icon( + icon: Icon( LichessIcons.storm, size: 44, + color: context.lichessColors.purple, ), title: const Text( 'Puzzle Storm', - style: Styles.callout, + style: _subPuzzleButtonTitleStyle, ), subtitle: const Text( 'Solve as many puzzles as possible in 3 minutes.', @@ -307,15 +304,19 @@ class PuzzleThemeButton extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( - padding: Styles.bodySectionBottomPadding, + padding: Styles.horizontalBodyPadding, child: CardButton( - icon: const Icon(PuzzleIcons.mix, size: 44), + icon: Icon( + PuzzleIcons.opening, + size: 44, + color: context.lichessColors.primary, + ), title: Text( context.l10n.puzzlePuzzleThemes, - style: Styles.callout, + style: _subPuzzleButtonTitleStyle, ), subtitle: const Text( - 'Choose puzzles by theme or opening.', + 'Play puzzles from your favorite openings, or choose a theme.', ), onTap: () { pushPlatformRoute( @@ -400,13 +401,13 @@ class _PuzzleButton extends StatelessWidget { icon: Icon( PuzzleIcons.mix, size: 44, - color: context.lichessColors.brag, + color: context.lichessColors.good, ), title: Text( context.l10n.puzzles, style: Styles.sectionTitle, ), - subtitle: Text(context.l10n.puzzleDesc), + subtitle: Text('${context.l10n.puzzleDesc}.'), onTap: onTap, ); } From 84fa4d8dd6698e46ba056dc4f8bd4d4efdef63b5 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Thu, 11 Apr 2024 16:51:28 +0200 Subject: [PATCH 2/8] Move puzzle dashboard to its own screen --- lib/src/styles/styles.dart | 4 ++ ...oard_widget.dart => dashboard_screen.dart} | 48 +++++++++++++++-- lib/src/view/puzzle/puzzle_tab_screen.dart | 51 +++++++++++++++++-- lib/src/view/tools/tools_tab_screen.dart | 4 +- 4 files changed, 98 insertions(+), 9 deletions(-) rename lib/src/view/puzzle/{puzzle_dashboard_widget.dart => dashboard_screen.dart} (84%) diff --git a/lib/src/styles/styles.dart b/lib/src/styles/styles.dart index be095693e5..64eb331324 100644 --- a/lib/src/styles/styles.dart +++ b/lib/src/styles/styles.dart @@ -12,6 +12,10 @@ abstract class Styles { fontSize: 20.0, fontWeight: FontWeight.bold, ); + static const subtitle = TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ); static const callout = TextStyle( fontSize: 18.0, fontWeight: FontWeight.w600, diff --git a/lib/src/view/puzzle/puzzle_dashboard_widget.dart b/lib/src/view/puzzle/dashboard_screen.dart similarity index 84% rename from lib/src/view/puzzle/puzzle_dashboard_widget.dart rename to lib/src/view/puzzle/dashboard_screen.dart index 39e92868a0..24dd2b5aab 100644 --- a/lib/src/view/puzzle/puzzle_dashboard_widget.dart +++ b/lib/src/view/puzzle/dashboard_screen.dart @@ -1,11 +1,13 @@ import 'package:collection/collection.dart'; import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart' show ClientException; import 'package:lichess_mobile/src/model/puzzle/puzzle.dart'; import 'package:lichess_mobile/src/model/puzzle/puzzle_providers.dart'; import 'package:lichess_mobile/src/model/puzzle/puzzle_theme.dart'; +import 'package:lichess_mobile/src/model/user/user.dart'; import 'package:lichess_mobile/src/styles/styles.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/utils/string.dart'; @@ -13,6 +15,47 @@ import 'package:lichess_mobile/src/widgets/list.dart'; import 'package:lichess_mobile/src/widgets/shimmer.dart'; import 'package:lichess_mobile/src/widgets/stat_card.dart'; +class PuzzleDashboardScreen extends StatelessWidget { + const PuzzleDashboardScreen({super.key, required this.user}); + + final LightUser user; + + @override + Widget build(BuildContext context) { + return Theme.of(context).platform == TargetPlatform.iOS + ? CupertinoPageScaffold( + navigationBar: const CupertinoNavigationBar( + middle: SizedBox.shrink(), + ), + child: _Body(user: user), + ) + : Scaffold( + body: _Body(user: user), + appBar: AppBar( + title: Text(context.l10n.puzzlePuzzleDashboard), + ), + ); + } +} + +class _Body extends ConsumerWidget { + const _Body({required this.user}); + + final LightUser user; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + PuzzleDashboardWidget(), + ], + ), + ); + } +} + class PuzzleDashboardWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { @@ -31,9 +74,8 @@ class PuzzleDashboardWidget extends ConsumerWidget { children: [ Text(context.l10n.puzzlePuzzleDashboard), Text( - context.l10n.nbDays(30), - style: TextStyle( - fontSize: 14, + context.l10n.puzzlePuzzleDashboardDescription, + style: Styles.subtitle.copyWith( color: textShade(context, Styles.subtitleOpacity), ), ), diff --git a/lib/src/view/puzzle/puzzle_tab_screen.dart b/lib/src/view/puzzle/puzzle_tab_screen.dart index 452f5002f4..b84f95935b 100644 --- a/lib/src/view/puzzle/puzzle_tab_screen.dart +++ b/lib/src/view/puzzle/puzzle_tab_screen.dart @@ -20,8 +20,8 @@ import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/utils/layout.dart'; import 'package:lichess_mobile/src/utils/navigation.dart'; import 'package:lichess_mobile/src/utils/string.dart'; +import 'package:lichess_mobile/src/view/puzzle/dashboard_screen.dart'; import 'package:lichess_mobile/src/view/puzzle/history_boards.dart'; -import 'package:lichess_mobile/src/view/puzzle/puzzle_dashboard_widget.dart'; import 'package:lichess_mobile/src/view/puzzle/puzzle_history_screen.dart'; import 'package:lichess_mobile/src/widgets/board_preview.dart'; import 'package:lichess_mobile/src/widgets/buttons.dart'; @@ -64,7 +64,12 @@ class _PuzzleTabScreenState extends ConsumerState { ], ); return Scaffold( - appBar: AppBar(title: Text(context.l10n.puzzles)), + appBar: AppBar( + title: Text(context.l10n.puzzles), + actions: [ + _DashboardButton(), + ], + ), body: userSession != null ? RefreshIndicator( key: _androidRefreshKey, @@ -81,7 +86,12 @@ class _PuzzleTabScreenState extends ConsumerState { controller: puzzlesScrollController, slivers: [ CupertinoSliverNavigationBar( + padding: const EdgeInsetsDirectional.only( + start: 16.0, + end: 8.0, + ), largeTitle: Text(context.l10n.puzzles), + trailing: _DashboardButton(), ), if (userSession != null) CupertinoSliverRefreshControl( @@ -137,7 +147,6 @@ class _Body extends ConsumerWidget { separator, StormButton(connectivity: connectivity), separator, - PuzzleDashboardWidget(), PuzzleHistoryWidget(), ]; @@ -169,7 +178,6 @@ class _Body extends ConsumerWidget { Expanded( child: Column( children: [ - PuzzleDashboardWidget(), PuzzleHistoryWidget(), ], ), @@ -390,6 +398,41 @@ class PuzzleHistoryWidget extends ConsumerWidget { } } +class _DashboardButton extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final session = ref.watch(authSessionProvider); + if (session != null) { + switch (Theme.of(context).platform) { + case TargetPlatform.iOS: + return CupertinoIconButton( + padding: EdgeInsets.zero, + onPressed: () => _showDashboard(context, session), + semanticsLabel: context.l10n.puzzlePuzzleDashboard, + icon: const Icon(Icons.history), + ); + case TargetPlatform.android: + return IconButton( + tooltip: context.l10n.puzzlePuzzleDashboard, + onPressed: () => _showDashboard(context, session), + icon: const Icon(Icons.history), + ); + default: + assert(false, 'Unexpected platform $Theme.of(context).platform'); + return const SizedBox.shrink(); + } + } + return const SizedBox.shrink(); + } + + void _showDashboard(BuildContext context, AuthSessionState session) => + pushPlatformRoute( + context, + title: context.l10n.puzzlePuzzleDashboard, + builder: (_) => PuzzleDashboardScreen(user: session.user), + ); +} + class _PuzzleButton extends StatelessWidget { const _PuzzleButton({this.onTap}); diff --git a/lib/src/view/tools/tools_tab_screen.dart b/lib/src/view/tools/tools_tab_screen.dart index 3bcd74686d..96f3b4e777 100644 --- a/lib/src/view/tools/tools_tab_screen.dart +++ b/lib/src/view/tools/tools_tab_screen.dart @@ -58,7 +58,7 @@ class _Body extends StatelessWidget { icon: Icon( Icons.biotech, size: 44, - color: context.lichessColors.brag, + color: context.lichessColors.good, ), title: Text( context.l10n.analysis, @@ -76,7 +76,7 @@ class _Body extends StatelessWidget { icon: Icon( Icons.alarm, size: 44, - color: context.lichessColors.brag, + color: context.lichessColors.primary, ), title: Text( context.l10n.clock, From 6687320d505d042d7f2066b262d11400f2c74e58 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Thu, 11 Apr 2024 17:01:39 +0200 Subject: [PATCH 3/8] Add days selector to dashboard --- lib/src/model/puzzle/puzzle_providers.dart | 3 +- lib/src/model/puzzle/puzzle_repository.dart | 4 +- lib/src/view/puzzle/dashboard_screen.dart | 66 ++++++++++++++++++- lib/src/view/puzzle/puzzle_tab_screen.dart | 1 - test/model/puzzle/puzzle_repository_test.dart | 2 +- 5 files changed, 70 insertions(+), 6 deletions(-) diff --git a/lib/src/model/puzzle/puzzle_providers.dart b/lib/src/model/puzzle/puzzle_providers.dart index af57e223f5..13d71c7662 100644 --- a/lib/src/model/puzzle/puzzle_providers.dart +++ b/lib/src/model/puzzle/puzzle_providers.dart @@ -79,11 +79,12 @@ Future> savedOpeningBatches( @riverpod Future puzzleDashboard( PuzzleDashboardRef ref, + int days, ) async { final session = ref.watch(authSessionProvider); if (session == null) return null; return ref.withClientCacheFor( - (client) => PuzzleRepository(client).puzzleDashboard(), + (client) => PuzzleRepository(client).puzzleDashboard(days), const Duration(hours: 3), ); } diff --git a/lib/src/model/puzzle/puzzle_repository.dart b/lib/src/model/puzzle/puzzle_repository.dart index 2a10f89c91..de4395444b 100644 --- a/lib/src/model/puzzle/puzzle_repository.dart +++ b/lib/src/model/puzzle/puzzle_repository.dart @@ -157,9 +157,9 @@ class PuzzleRepository { ); } - Future puzzleDashboard() { + Future puzzleDashboard(int days) { return client.readJson( - Uri.parse('$kLichessHost/api/puzzle/dashboard/30'), + Uri.parse('$kLichessHost/api/puzzle/dashboard/$days'), mapper: _puzzleDashboardFromJson, ); } diff --git a/lib/src/view/puzzle/dashboard_screen.dart b/lib/src/view/puzzle/dashboard_screen.dart index 24dd2b5aab..6558614247 100644 --- a/lib/src/view/puzzle/dashboard_screen.dart +++ b/lib/src/view/puzzle/dashboard_screen.dart @@ -4,6 +4,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart' show ClientException; +import 'package:lichess_mobile/src/model/auth/auth_session.dart'; import 'package:lichess_mobile/src/model/puzzle/puzzle.dart'; import 'package:lichess_mobile/src/model/puzzle/puzzle_providers.dart'; import 'package:lichess_mobile/src/model/puzzle/puzzle_theme.dart'; @@ -11,10 +12,14 @@ import 'package:lichess_mobile/src/model/user/user.dart'; import 'package:lichess_mobile/src/styles/styles.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/utils/string.dart'; +import 'package:lichess_mobile/src/widgets/adaptive_choice_picker.dart'; +import 'package:lichess_mobile/src/widgets/buttons.dart'; import 'package:lichess_mobile/src/widgets/list.dart'; import 'package:lichess_mobile/src/widgets/shimmer.dart'; import 'package:lichess_mobile/src/widgets/stat_card.dart'; +final daysProvider = StateProvider((ref) => Days.month); + class PuzzleDashboardScreen extends StatelessWidget { const PuzzleDashboardScreen({super.key, required this.user}); @@ -26,6 +31,7 @@ class PuzzleDashboardScreen extends StatelessWidget { ? CupertinoPageScaffold( navigationBar: const CupertinoNavigationBar( middle: SizedBox.shrink(), + trailing: DaysSelector(), ), child: _Body(user: user), ) @@ -33,6 +39,7 @@ class PuzzleDashboardScreen extends StatelessWidget { body: _Body(user: user), appBar: AppBar( title: Text(context.l10n.puzzlePuzzleDashboard), + actions: const [DaysSelector()], ), ); } @@ -59,7 +66,8 @@ class _Body extends ConsumerWidget { class PuzzleDashboardWidget extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final puzzleDashboard = ref.watch(puzzleDashboardProvider); + final puzzleDashboard = + ref.watch(puzzleDashboardProvider(ref.read(daysProvider).days)); return puzzleDashboard.when( data: (dashboard) { @@ -222,3 +230,59 @@ class PuzzleChart extends StatelessWidget { ); } } + +class DaysSelector extends ConsumerWidget { + const DaysSelector(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final session = ref.watch(authSessionProvider); + final day = ref.watch(daysProvider); + return session != null + ? AppBarTextButton( + onPressed: () => showChoicePicker( + context, + choices: Days.values, + selectedItem: day, + labelBuilder: (t) => Text(_daysL10n(context, t)), + onSelectedItemChanged: (newDay) { + ref.read(daysProvider.notifier).state = newDay; + }, + ), + child: Text(_daysL10n(context, day)), + ) + : const SizedBox.shrink(); + } +} + +enum Days { + oneday(1), + twodays(2), + week(7), + twoweeks(14), + month(30), + twomonths(60), + threemonths(90); + + const Days(this.days); + final int days; +} + +String _daysL10n(BuildContext context, Days day) { + switch (day) { + case Days.oneday: + return context.l10n.nbDays(1); + case Days.twodays: + return context.l10n.nbDays(2); + case Days.week: + return context.l10n.nbDays(7); + case Days.twoweeks: + return context.l10n.nbDays(14); + case Days.month: + return context.l10n.nbDays(30); + case Days.twomonths: + return context.l10n.nbDays(60); + case Days.threemonths: + return context.l10n.nbDays(90); + } +} diff --git a/lib/src/view/puzzle/puzzle_tab_screen.dart b/lib/src/view/puzzle/puzzle_tab_screen.dart index b84f95935b..92e1abba63 100644 --- a/lib/src/view/puzzle/puzzle_tab_screen.dart +++ b/lib/src/view/puzzle/puzzle_tab_screen.dart @@ -109,7 +109,6 @@ class _PuzzleTabScreenState extends ConsumerState { Future _refreshData() { return Future.wait([ - ref.refresh(puzzleDashboardProvider.future), ref.refresh(puzzleRecentActivityProvider.future), ]); } diff --git a/test/model/puzzle/puzzle_repository_test.dart b/test/model/puzzle/puzzle_repository_test.dart index c2f161040c..5169b539b1 100644 --- a/test/model/puzzle/puzzle_repository_test.dart +++ b/test/model/puzzle/puzzle_repository_test.dart @@ -139,7 +139,7 @@ void main() { final container = await makeTestContainer(mockClient); final client = container.read(lichessClientFactoryProvider)(); final repo = PuzzleRepository(client); - final result = await repo.puzzleDashboard(); + final result = await repo.puzzleDashboard(30); expect(result, isA()); }); From b6b248a477b043e69d30035d58aa1ff6fa088001 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Thu, 11 Apr 2024 17:26:53 +0200 Subject: [PATCH 4/8] Don't show puzzle rating in history Closes #614 --- lib/src/view/puzzle/history_boards.dart | 107 -------------- .../view/puzzle/puzzle_history_screen.dart | 132 +++++++++++++++--- lib/src/view/puzzle/puzzle_tab_screen.dart | 3 +- lib/src/view/puzzle/storm_screen.dart | 5 +- 4 files changed, 118 insertions(+), 129 deletions(-) delete mode 100644 lib/src/view/puzzle/history_boards.dart diff --git a/lib/src/view/puzzle/history_boards.dart b/lib/src/view/puzzle/history_boards.dart deleted file mode 100644 index 4de7f90f85..0000000000 --- a/lib/src/view/puzzle/history_boards.dart +++ /dev/null @@ -1,107 +0,0 @@ -import 'package:fast_immutable_collections/fast_immutable_collections.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_layout_grid/flutter_layout_grid.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:lichess_mobile/src/model/puzzle/puzzle.dart'; -import 'package:lichess_mobile/src/model/puzzle/puzzle_angle.dart'; -import 'package:lichess_mobile/src/model/puzzle/puzzle_theme.dart'; -import 'package:lichess_mobile/src/styles/styles.dart'; -import 'package:lichess_mobile/src/utils/chessground_compat.dart'; -import 'package:lichess_mobile/src/utils/layout.dart'; -import 'package:lichess_mobile/src/utils/navigation.dart'; -import 'package:lichess_mobile/src/view/account/rating_pref_aware.dart'; -import 'package:lichess_mobile/src/view/puzzle/puzzle_screen.dart'; -import 'package:lichess_mobile/src/widgets/board_thumbnail.dart'; - -class PuzzleHistoryBoards extends ConsumerWidget { - const PuzzleHistoryBoards(this.history, {this.maxRows, super.key}); - - final IList history; - final int? maxRows; - - @override - Widget build(BuildContext context, WidgetRef ref) { - return LayoutBuilder( - builder: (context, constraints) { - final crossAxisCount = constraints.maxWidth > FormFactor.tablet ? 4 : 2; - const columnGap = 12.0; - final boardWidth = - (constraints.maxWidth - (columnGap * crossAxisCount - columnGap)) / - crossAxisCount; - return LayoutGrid( - columnSizes: List.generate(crossAxisCount, (_) => 1.fr), - rowSizes: List.generate( - (history.length / crossAxisCount).ceil(), - (_) => auto, - ), - rowGap: 16.0, - columnGap: columnGap, - children: history.map((e) { - final (fen, side, lastMove) = e.preview; - return BoardThumbnail( - size: boardWidth, - onTap: () { - pushPlatformRoute( - context, - rootNavigator: true, - builder: (_) => PuzzleScreen( - angle: const PuzzleTheme(PuzzleThemeKey.mix), - puzzleId: e.id, - ), - ); - }, - orientation: side.cg, - fen: fen, - lastMove: lastMove.cg, - footer: Padding( - padding: const EdgeInsets.only(top: 4.0), - child: Row( - mainAxisSize: MainAxisSize.max, - children: [ - ColoredBox( - color: e.win - ? context.lichessColors.good - : context.lichessColors.error, - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 1, - horizontal: 3, - ), - child: Row( - children: [ - if (e.win) - const Icon( - color: Colors.white, - Icons.done, - size: 20, - ) - else - const Icon( - Icons.close, - color: Colors.white, - size: 20, - ), - if (e.solvingTime != null) - Text( - '${e.solvingTime!.inSeconds}s', - overflow: TextOverflow.fade, - style: const TextStyle( - color: Colors.white, - ), - ), - ], - ), - ), - ), - const SizedBox(width: 6), - RatingPrefAware(child: Text(e.rating.toString())), - ], - ), - ), - ); - }).toList(), - ); - }, - ); - } -} diff --git a/lib/src/view/puzzle/puzzle_history_screen.dart b/lib/src/view/puzzle/puzzle_history_screen.dart index 425e3468bf..320ead086d 100644 --- a/lib/src/view/puzzle/puzzle_history_screen.dart +++ b/lib/src/view/puzzle/puzzle_history_screen.dart @@ -1,19 +1,20 @@ import 'package:collection/collection.dart'; +import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_layout_grid/flutter_layout_grid.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; import 'package:lichess_mobile/src/model/puzzle/puzzle.dart'; import 'package:lichess_mobile/src/model/puzzle/puzzle_activity.dart'; import 'package:lichess_mobile/src/model/puzzle/puzzle_angle.dart'; import 'package:lichess_mobile/src/model/puzzle/puzzle_theme.dart'; -import 'package:lichess_mobile/src/styles/lichess_colors.dart'; import 'package:lichess_mobile/src/styles/styles.dart'; import 'package:lichess_mobile/src/utils/chessground_compat.dart' as cg; +import 'package:lichess_mobile/src/utils/chessground_compat.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/utils/layout.dart'; import 'package:lichess_mobile/src/utils/navigation.dart'; -import 'package:lichess_mobile/src/view/account/rating_pref_aware.dart'; import 'package:lichess_mobile/src/view/puzzle/puzzle_screen.dart'; import 'package:lichess_mobile/src/widgets/board_thumbnail.dart'; import 'package:lichess_mobile/src/widgets/feedback.dart'; @@ -45,6 +46,62 @@ class PuzzleHistoryScreen extends StatelessWidget { } } +class PuzzleHistoryPreview extends ConsumerWidget { + const PuzzleHistoryPreview(this.history, {this.maxRows, super.key}); + + final IList history; + final int? maxRows; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return LayoutBuilder( + builder: (context, constraints) { + final crossAxisCount = constraints.maxWidth > FormFactor.tablet ? 4 : 2; + const columnGap = 12.0; + final boardWidth = + (constraints.maxWidth - (columnGap * crossAxisCount - columnGap)) / + crossAxisCount; + return LayoutGrid( + columnSizes: List.generate(crossAxisCount, (_) => 1.fr), + rowSizes: List.generate( + (history.length / crossAxisCount).ceil(), + (_) => auto, + ), + rowGap: 16.0, + columnGap: columnGap, + children: history.map((e) { + final (fen, side, lastMove) = e.preview; + return BoardThumbnail( + size: boardWidth, + onTap: () { + pushPlatformRoute( + context, + rootNavigator: true, + builder: (_) => PuzzleScreen( + angle: const PuzzleTheme(PuzzleThemeKey.mix), + puzzleId: e.id, + ), + ); + }, + orientation: side.cg, + fen: fen, + lastMove: lastMove.cg, + footer: Padding( + padding: const EdgeInsets.only(top: 2.0), + child: Row( + children: [ + _PuzzleResult(e), + ], + ), + ), + ); + }).toList(), + ); + }, + ); + } +} + class _Body extends ConsumerStatefulWidget { @override ConsumerState<_Body> createState() => _BodyState(); @@ -172,7 +229,6 @@ class _HistoryBoard extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final (fen, turn, lastMove) = puzzle.preview; - final customColors = Theme.of(context).extension(); return Padding( padding: const EdgeInsets.only( left: _kPuzzlePadding, @@ -195,23 +251,65 @@ class _HistoryBoard extends ConsumerWidget { fen: fen, lastMove: lastMove.cg, footer: Padding( - padding: const EdgeInsets.only(top: 8), - child: Row( - children: [ - ColoredBox( - color: puzzle.win - ? customColors?.good ?? LichessColors.green - : customColors?.error ?? LichessColors.red, - child: Icon( - size: 20, + padding: const EdgeInsets.only(top: 2), + child: _PuzzleResult(puzzle), + ), + ), + ); + } +} + +class _PuzzleResult extends StatelessWidget { + const _PuzzleResult(this.entry); + + final PuzzleHistoryEntry entry; + + @override + Widget build(BuildContext context) { + return ColoredBox( + color: entry.win + ? context.lichessColors.good.withOpacity(0.7) + : context.lichessColors.error.withOpacity(0.7), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 1, + horizontal: 3, + ), + child: Row( + children: [ + Text( + entry.win + ? String.fromCharCode(Icons.done.codePoint) + : String.fromCharCode(Icons.close.codePoint), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + fontFamily: 'MaterialIcons', + color: Colors.white, + ), + ), + const SizedBox(width: 2), + if (entry.solvingTime != null) + Text( + '${entry.solvingTime!.inSeconds}s', + overflow: TextOverflow.fade, + style: const TextStyle( + color: Colors.white, + ), + ) + else + Text( + (entry.win + ? context.l10n.puzzleSolved + : context.l10n.puzzleFailed) + .toUpperCase(), + overflow: TextOverflow.fade, + style: const TextStyle( + fontSize: 10, color: Colors.white, - (puzzle.win) ? Icons.done : Icons.close, ), ), - const SizedBox(width: 8), - RatingPrefAware(child: Text(puzzle.rating.toString())), - ], - ), + ], ), ), ); diff --git a/lib/src/view/puzzle/puzzle_tab_screen.dart b/lib/src/view/puzzle/puzzle_tab_screen.dart index 92e1abba63..f2e9082bca 100644 --- a/lib/src/view/puzzle/puzzle_tab_screen.dart +++ b/lib/src/view/puzzle/puzzle_tab_screen.dart @@ -21,7 +21,6 @@ import 'package:lichess_mobile/src/utils/layout.dart'; import 'package:lichess_mobile/src/utils/navigation.dart'; import 'package:lichess_mobile/src/utils/string.dart'; import 'package:lichess_mobile/src/view/puzzle/dashboard_screen.dart'; -import 'package:lichess_mobile/src/view/puzzle/history_boards.dart'; import 'package:lichess_mobile/src/view/puzzle/puzzle_history_screen.dart'; import 'package:lichess_mobile/src/widgets/board_preview.dart'; import 'package:lichess_mobile/src/widgets/buttons.dart'; @@ -370,7 +369,7 @@ class PuzzleHistoryWidget extends ConsumerWidget { children: [ Padding( padding: Styles.bodySectionPadding, - child: PuzzleHistoryBoards(recentActivity.take(8).toIList()), + child: PuzzleHistoryPreview(recentActivity.take(8).toIList()), ), ], ); diff --git a/lib/src/view/puzzle/storm_screen.dart b/lib/src/view/puzzle/storm_screen.dart index 725d6159cb..3bcb93f4ee 100644 --- a/lib/src/view/puzzle/storm_screen.dart +++ b/lib/src/view/puzzle/storm_screen.dart @@ -20,6 +20,7 @@ import 'package:lichess_mobile/src/utils/gestures_exclusion.dart'; import 'package:lichess_mobile/src/utils/immersive_mode.dart'; import 'package:lichess_mobile/src/utils/l10n_context.dart'; import 'package:lichess_mobile/src/utils/navigation.dart'; +import 'package:lichess_mobile/src/view/puzzle/puzzle_history_screen.dart'; import 'package:lichess_mobile/src/view/puzzle/storm_clock.dart'; import 'package:lichess_mobile/src/view/puzzle/storm_dashboard.dart'; import 'package:lichess_mobile/src/view/settings/toggle_sound_button.dart'; @@ -31,8 +32,6 @@ import 'package:lichess_mobile/src/widgets/list.dart'; import 'package:lichess_mobile/src/widgets/platform.dart'; import 'package:lichess_mobile/src/widgets/yes_no_dialog.dart'; -import 'history_boards.dart'; - class StormScreen extends StatefulWidget { const StormScreen({super.key}); @@ -820,7 +819,7 @@ class _RunStatsPopupState extends ConsumerState<_RunStatsPopup> { ), const SizedBox(height: 3.0), if (puzzleList.isNotEmpty) - PuzzleHistoryBoards(puzzleList) + PuzzleHistoryPreview(puzzleList) else const Center( child: Text('Nothing to show, go change the filters'), From dee4cec1f3cae4ee5f59f64545023e1b62ea8adf Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Thu, 11 Apr 2024 17:38:08 +0200 Subject: [PATCH 5/8] Invalidate puzzle dashboard before entering screen --- lib/src/view/puzzle/puzzle_screen.dart | 1 - lib/src/view/puzzle/puzzle_tab_screen.dart | 10 ++++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/src/view/puzzle/puzzle_screen.dart b/lib/src/view/puzzle/puzzle_screen.dart index d9083b184f..feb40cee96 100644 --- a/lib/src/view/puzzle/puzzle_screen.dart +++ b/lib/src/view/puzzle/puzzle_screen.dart @@ -75,7 +75,6 @@ class _PuzzleScreenState extends ConsumerState with RouteAware { super.didPop(); if (mounted) { ref.invalidate(nextPuzzleProvider(widget.angle)); - ref.invalidate(puzzleDashboardProvider); ref.invalidate(puzzleRecentActivityProvider); } } diff --git a/lib/src/view/puzzle/puzzle_tab_screen.dart b/lib/src/view/puzzle/puzzle_tab_screen.dart index f2e9082bca..0d214208c4 100644 --- a/lib/src/view/puzzle/puzzle_tab_screen.dart +++ b/lib/src/view/puzzle/puzzle_tab_screen.dart @@ -405,14 +405,20 @@ class _DashboardButton extends ConsumerWidget { case TargetPlatform.iOS: return CupertinoIconButton( padding: EdgeInsets.zero, - onPressed: () => _showDashboard(context, session), + onPressed: () { + ref.invalidate(puzzleDashboardProvider); + _showDashboard(context, session); + }, semanticsLabel: context.l10n.puzzlePuzzleDashboard, icon: const Icon(Icons.history), ); case TargetPlatform.android: return IconButton( tooltip: context.l10n.puzzlePuzzleDashboard, - onPressed: () => _showDashboard(context, session), + onPressed: () { + ref.invalidate(puzzleDashboardProvider); + _showDashboard(context, session); + }, icon: const Icon(Icons.history), ); default: From 9a9ae027e8a48b30158cc350487a0a99f120b12e Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Thu, 11 Apr 2024 17:44:37 +0200 Subject: [PATCH 6/8] Tweak --- lib/src/view/puzzle/puzzle_history_screen.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/src/view/puzzle/puzzle_history_screen.dart b/lib/src/view/puzzle/puzzle_history_screen.dart index 320ead086d..b4a0717d03 100644 --- a/lib/src/view/puzzle/puzzle_history_screen.dart +++ b/lib/src/view/puzzle/puzzle_history_screen.dart @@ -286,6 +286,7 @@ class _PuzzleResult extends StatelessWidget { fontWeight: FontWeight.bold, fontFamily: 'MaterialIcons', color: Colors.white, + height: 1.0, ), ), const SizedBox(width: 2), @@ -295,6 +296,8 @@ class _PuzzleResult extends StatelessWidget { overflow: TextOverflow.fade, style: const TextStyle( color: Colors.white, + fontSize: 10, + height: 1.0, ), ) else @@ -307,6 +310,7 @@ class _PuzzleResult extends StatelessWidget { style: const TextStyle( fontSize: 10, color: Colors.white, + height: 1.0, ), ), ], From 8344eded720437bf1fc6600ccf2e2a76dc63aa0f Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Thu, 11 Apr 2024 17:47:32 +0200 Subject: [PATCH 7/8] Restore default ios bar background --- lib/src/app.dart | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index 252bcb5e43..a87f09d812 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -166,11 +166,6 @@ class _AppState extends ConsumerState { ? LichessColors.primary : const Color(0xFF3692E7), brightness: brightness, - barBackgroundColor: - const CupertinoDynamicColor.withBrightness( - color: Color(0xC8F9F9F9), - darkColor: Color(0xC81D1D1D), - ), scaffoldBackgroundColor: brightness == Brightness.light ? CupertinoColors.systemGroupedBackground : null, From 69ca6e681b5f90e1a3f184ab1e62fec52c410d30 Mon Sep 17 00:00:00 2001 From: Vincent Velociter Date: Thu, 11 Apr 2024 18:02:40 +0200 Subject: [PATCH 8/8] Remove double dashboard title on android --- lib/src/view/puzzle/dashboard_screen.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/view/puzzle/dashboard_screen.dart b/lib/src/view/puzzle/dashboard_screen.dart index 6558614247..5005b0be2f 100644 --- a/lib/src/view/puzzle/dashboard_screen.dart +++ b/lib/src/view/puzzle/dashboard_screen.dart @@ -38,7 +38,7 @@ class PuzzleDashboardScreen extends StatelessWidget { : Scaffold( body: _Body(user: user), appBar: AppBar( - title: Text(context.l10n.puzzlePuzzleDashboard), + title: const SizedBox.shrink(), actions: const [DaysSelector()], ), );