diff --git a/lib/src/model/common/http.dart b/lib/src/model/common/http.dart index 46bf77f76d..3a48845859 100644 --- a/lib/src/model/common/http.dart +++ b/lib/src/model/common/http.dart @@ -230,7 +230,7 @@ extension ClientExtension on Client { Future> readJsonList( Uri url, { Map? headers, - required T Function(Map) mapper, + required T? Function(Map) mapper, }) async { final response = await get(url, headers: headers); _checkResponseSuccess(url, response); @@ -243,20 +243,26 @@ extension ClientExtension on Client { ); } - return IList( - json.map((e) { - if (e is! Map) { - _logger.severe('Could not read json object as $T'); - throw ClientException('Could not read json object as $T', url); - } - try { - return mapper(e); - } catch (e, st) { - _logger.severe('Could not read json object as $T: $e', e, st); - throw ClientException('Could not read json object as $T: $e', url); + final List list = []; + for (final e in json) { + if (e is! Map) { + _logger.severe('Could not read json object as $T: expected an object.'); + throw ClientException( + 'Could not read json object as $T: expected an object.', + url, + ); + } + try { + final mapped = mapper(e); + if (mapped != null) { + list.add(mapped); } - }), - ); + } catch (e, st) { + _logger.severe('Could not read json object as $T: $e', e, st); + throw ClientException('Could not read json object as $T: $e', url); + } + } + return IList(list); } /// Sends an HTTP GET request with the given headers to the given URL and diff --git a/lib/src/model/common/perf.dart b/lib/src/model/common/perf.dart index 2f484f815c..ee8bd57122 100644 --- a/lib/src/model/common/perf.dart +++ b/lib/src/model/common/perf.dart @@ -14,9 +14,9 @@ enum Perf { classical('Classical', 'Classical', LichessIcons.classical), correspondence('Correspondence', 'Corresp.', LichessIcons.correspondence), fromPosition('From Position', 'From Pos.', LichessIcons.feather), - chess960('Chess 960', '960', LichessIcons.die_six), + chess960('Chess960', '960', LichessIcons.die_six), antichess('Antichess', 'Antichess', LichessIcons.antichess), - kingOfTheHill('King Of The Hill', 'KotH', LichessIcons.flag), + kingOfTheHill('King of the Hill', 'KotH', LichessIcons.flag), threeCheck('Three-check', '3check', LichessIcons.three_check), atomic('Atomic', 'Atomic', LichessIcons.atom), horde('Horde', 'Horde', LichessIcons.horde), @@ -70,9 +70,15 @@ enum Perf { } } - static IMap nameMap = IMap(Perf.values.asNameMap()); + static final IMap nameMap = IMap(Perf.values.asNameMap()); } +String _titleKey(String title) => + title.toLowerCase().replaceAll(RegExp('[ -_]'), ''); + +final IMap _lowerCaseTitleMap = + Perf.nameMap.map((key, value) => MapEntry(_titleKey(value.title), value)); + extension PerfExtension on Pick { Perf asPerfOrThrow() { final value = this.required().value; @@ -83,6 +89,11 @@ extension PerfExtension on Pick { if (Perf.nameMap.containsKey(value)) { return Perf.nameMap[value]!; } + // handle lichess api inconsistencies + final valueKey = _titleKey(value); + if (_lowerCaseTitleMap.containsKey(valueKey)) { + return _lowerCaseTitleMap[valueKey]!; + } } else if (value is Map) { final perf = Perf.nameMap[value['key'] as String]; if (perf != null) { diff --git a/lib/src/model/user/user.dart b/lib/src/model/user/user.dart index eb92682a66..c207bd3277 100644 --- a/lib/src/model/user/user.dart +++ b/lib/src/model/user/user.dart @@ -361,7 +361,7 @@ class UserPerfGame with _$UserPerfGame { @immutable class UserRatingHistoryPerf { - final String perf; + final Perf perf; final IList points; const UserRatingHistoryPerf({ diff --git a/lib/src/model/user/user_repository.dart b/lib/src/model/user/user_repository.dart index bb8b38119d..47690fd9b5 100644 --- a/lib/src/model/user/user_repository.dart +++ b/lib/src/model/user/user_repository.dart @@ -83,17 +83,23 @@ class UserRepository { } } -UserRatingHistoryPerf _ratingHistoryFromJson( +UserRatingHistoryPerf? _ratingHistoryFromJson( Map json, ) => _ratingHistoryFromPick(pick(json).required()); -UserRatingHistoryPerf _ratingHistoryFromPick( - RequiredPick perf, +UserRatingHistoryPerf? _ratingHistoryFromPick( + RequiredPick pick, ) { + final perf = pick('name').asPerfOrNull(); + + if (perf == null) { + return null; + } + return UserRatingHistoryPerf( - perf: perf('name').asStringOrThrow(), - points: perf('points').asListOrThrow((point) { + perf: perf, + points: pick('points').asListOrThrow((point) { final values = point.asListOrThrow((point) => point.asIntOrThrow()); return UserRatingHistoryPoint( date: DateTime.utc( diff --git a/lib/src/model/user/user_repository_providers.dart b/lib/src/model/user/user_repository_providers.dart index 0bf13afa7e..eb226d0e45 100644 --- a/lib/src/model/user/user_repository_providers.dart +++ b/lib/src/model/user/user_repository_providers.dart @@ -109,7 +109,8 @@ Future> userRatingHistory( UserRatingHistoryRef ref, { required UserId id, }) async { - return ref.withClient( + return ref.withClientCacheFor( (client) => UserRepository(client).getRatingHistory(id), + const Duration(minutes: 1), ); } diff --git a/lib/src/view/account/profile_screen.dart b/lib/src/view/account/profile_screen.dart index ec04c82abc..47f47f85ae 100644 --- a/lib/src/view/account/profile_screen.dart +++ b/lib/src/view/account/profile_screen.dart @@ -136,7 +136,7 @@ class _PerfCards extends ConsumerWidget { return account.when( data: (user) { if (user != null) { - return PerfCards(user: user); + return PerfCards(user: user, isMe: true); } else { return const SizedBox.shrink(); } diff --git a/lib/src/view/puzzle/dashboard_screen.dart b/lib/src/view/puzzle/dashboard_screen.dart index 5005b0be2f..bbb89d2975 100644 --- a/lib/src/view/puzzle/dashboard_screen.dart +++ b/lib/src/view/puzzle/dashboard_screen.dart @@ -8,7 +8,6 @@ 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'; -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'; @@ -21,22 +20,20 @@ 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}); - - final LightUser user; + const PuzzleDashboardScreen({super.key}); @override Widget build(BuildContext context) { return Theme.of(context).platform == TargetPlatform.iOS - ? CupertinoPageScaffold( - navigationBar: const CupertinoNavigationBar( + ? const CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( middle: SizedBox.shrink(), trailing: DaysSelector(), ), - child: _Body(user: user), + child: _Body(), ) : Scaffold( - body: _Body(user: user), + body: const _Body(), appBar: AppBar( title: const SizedBox.shrink(), actions: const [DaysSelector()], @@ -46,9 +43,7 @@ class PuzzleDashboardScreen extends StatelessWidget { } class _Body extends ConsumerWidget { - const _Body({required this.user}); - - final LightUser user; + const _Body(); @override Widget build(BuildContext context, WidgetRef ref) { diff --git a/lib/src/view/puzzle/puzzle_tab_screen.dart b/lib/src/view/puzzle/puzzle_tab_screen.dart index e9114ca81a..a0f2790174 100644 --- a/lib/src/view/puzzle/puzzle_tab_screen.dart +++ b/lib/src/view/puzzle/puzzle_tab_screen.dart @@ -435,7 +435,7 @@ class _DashboardButton extends ConsumerWidget { pushPlatformRoute( context, title: context.l10n.puzzlePuzzleDashboard, - builder: (_) => PuzzleDashboardScreen(user: session.user), + builder: (_) => const PuzzleDashboardScreen(), ); } diff --git a/lib/src/view/user/perf_cards.dart b/lib/src/view/user/perf_cards.dart index bd3425516d..37d614241c 100644 --- a/lib/src/view/user/perf_cards.dart +++ b/lib/src/view/user/perf_cards.dart @@ -15,10 +15,12 @@ import 'package:lichess_mobile/src/widgets/platform.dart'; import 'package:lichess_mobile/src/widgets/rating.dart'; class PerfCards extends StatelessWidget { - const PerfCards({required this.user, super.key}); + const PerfCards({required this.user, required this.isMe, super.key}); final User user; + final bool isMe; + @override Widget build(BuildContext context) { List userPerfs = Perf.values.where((element) { @@ -50,7 +52,8 @@ class PerfCards extends StatelessWidget { itemBuilder: (context, index) { final perf = userPerfs[index]; final userPerf = user.perfs[perf]!; - final bool isPerfWithoutStats = Perf.streak == perf; + final bool isPerfWithoutStats = + Perf.streak == perf || (!isMe && Perf.puzzle == perf); return SizedBox( height: 100, width: 100, @@ -126,7 +129,7 @@ class PerfCards extends StatelessWidget { case Perf.storm: return StormDashboardModal(user: user.lightUser); case Perf.puzzle: - return PuzzleDashboardScreen(user: user.lightUser); + return const PuzzleDashboardScreen(); default: return PerfStatsScreen( user: user, diff --git a/lib/src/view/user/perf_stats_screen.dart b/lib/src/view/user/perf_stats_screen.dart index 5c761649ef..f782247d90 100644 --- a/lib/src/view/user/perf_stats_screen.dart +++ b/lib/src/view/user/perf_stats_screen.dart @@ -166,10 +166,10 @@ class _Body extends ConsumerWidget { ratingHistory.when( data: (ratingHistoryData) { final ratingHistoryPerfData = ratingHistoryData - .where((element) => element.perf == perf.title) - .first; + .firstWhereOrNull((element) => element.perf == perf); - if (ratingHistoryPerfData.points.isEmpty) { + if (ratingHistoryPerfData == null || + ratingHistoryPerfData.points.isEmpty) { return const SizedBox.shrink(); } return _EloChart(ratingHistoryPerfData); diff --git a/lib/src/view/user/user_screen.dart b/lib/src/view/user/user_screen.dart index 6a0ad27a92..a5bd77e130 100644 --- a/lib/src/view/user/user_screen.dart +++ b/lib/src/view/user/user_screen.dart @@ -117,7 +117,7 @@ class _UserProfileListView extends StatelessWidget { return ListView( children: [ UserProfile(user: user), - PerfCards(user: user), + PerfCards(user: user, isMe: false), UserActivityWidget(user: user), RecentGames(user: user.lightUser), ],