mirror of
https://github.com/lichess-org/mobile.git
synced 2026-05-26 13:50:52 +00:00
Add alphabetical sort toggle to puzzle openings screen (#3045)
Fixes #2981 - Add sort toggle button in app bar (popular ↔ alphabetical) - Sort is done client-side as the server endpoint does not support order param - puzzleOpeningsProvider is now a family provider keyed by sort order - Both orders are cached separately for 1 day
This commit is contained in:
@@ -2,6 +2,8 @@ import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:lichess_mobile/src/model/puzzle/puzzle_providers.dart';
|
||||
|
||||
enum PuzzleOpeningSort { popular, alphabetical }
|
||||
|
||||
typedef PuzzleOpeningFamily = ({
|
||||
String key,
|
||||
String name,
|
||||
@@ -15,7 +17,7 @@ typedef PuzzleOpeningData = ({String key, String name, int count});
|
||||
final flatOpeningsListProvider = FutureProvider.autoDispose<IList<PuzzleOpeningData>>((
|
||||
Ref ref,
|
||||
) async {
|
||||
final families = await ref.watch(puzzleOpeningsProvider.future);
|
||||
final families = await ref.watch(puzzleOpeningsProvider(PuzzleOpeningSort.popular).future);
|
||||
return families
|
||||
.map(
|
||||
(f) => [
|
||||
|
||||
@@ -135,9 +135,12 @@ final puzzleThemesProvider = FutureProvider.autoDispose<IMap<PuzzleThemeKey, Puz
|
||||
}, name: 'PuzzleThemesProvider');
|
||||
|
||||
/// Fetches available puzzle openings.
|
||||
final puzzleOpeningsProvider = FutureProvider.autoDispose<IList<PuzzleOpeningFamily>>((Ref ref) {
|
||||
return ref.withClientCacheFor(
|
||||
(client) => PuzzleRepository(client).puzzleOpenings(),
|
||||
const Duration(days: 1),
|
||||
);
|
||||
}, name: 'PuzzleOpeningsProvider');
|
||||
final puzzleOpeningsProvider = FutureProvider.autoDispose
|
||||
.family<IList<PuzzleOpeningFamily>, PuzzleOpeningSort>((Ref ref, PuzzleOpeningSort sort) {
|
||||
return ref.withClientCacheFor(
|
||||
(client) => PuzzleRepository(
|
||||
client,
|
||||
).puzzleOpenings(alphabetical: sort == PuzzleOpeningSort.alphabetical),
|
||||
const Duration(days: 1),
|
||||
);
|
||||
}, name: 'PuzzleOpeningsProvider');
|
||||
|
||||
@@ -170,12 +170,14 @@ class PuzzleRepository {
|
||||
);
|
||||
}
|
||||
|
||||
Future<IList<PuzzleOpeningFamily>> puzzleOpenings() {
|
||||
return client.readJson(
|
||||
Future<IList<PuzzleOpeningFamily>> puzzleOpenings({bool alphabetical = false}) async {
|
||||
final result = await client.readJson(
|
||||
Uri(path: '/training/openings'),
|
||||
headers: {'Accept': 'application/json'},
|
||||
mapper: _puzzleOpeningFromJson,
|
||||
);
|
||||
if (!alphabetical) return result;
|
||||
return result.sort((a, b) => a.name.compareTo(b.name));
|
||||
}
|
||||
|
||||
Future<IList<PuzzleId>> puzzleReplay(int days, String theme) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_riverpod/legacy.dart';
|
||||
import 'package:lichess_mobile/src/model/puzzle/puzzle_angle.dart';
|
||||
import 'package:lichess_mobile/src/model/puzzle/puzzle_opening.dart';
|
||||
import 'package:lichess_mobile/src/model/puzzle/puzzle_providers.dart';
|
||||
@@ -11,21 +12,29 @@ import 'package:lichess_mobile/src/utils/navigation.dart';
|
||||
import 'package:lichess_mobile/src/view/puzzle/puzzle_screen.dart';
|
||||
import 'package:lichess_mobile/src/widgets/list.dart';
|
||||
import 'package:lichess_mobile/src/widgets/platform.dart';
|
||||
import 'package:lichess_mobile/src/widgets/platform_context_menu_button.dart';
|
||||
|
||||
final _openingsProvider =
|
||||
FutureProvider.autoDispose<(bool, IMap<String, int>, IList<PuzzleOpeningFamily>?)>((ref) async {
|
||||
final isOnline = await ref.watch(onlineStatusProvider.future);
|
||||
final _openingsSortProvider = StateProvider.autoDispose<PuzzleOpeningSort>(
|
||||
(ref) => PuzzleOpeningSort.popular,
|
||||
);
|
||||
|
||||
final _openingsProvider = FutureProvider.autoDispose
|
||||
.family<(bool, IMap<String, int>, IList<PuzzleOpeningFamily>?), PuzzleOpeningSort>((
|
||||
ref,
|
||||
sort,
|
||||
) async {
|
||||
final connectivityStatus = await ref.watch(connectivityChangesProvider.future);
|
||||
final savedOpenings = await ref.watch(savedOpeningBatchesProvider.future);
|
||||
IList<PuzzleOpeningFamily>? onlineOpenings;
|
||||
try {
|
||||
onlineOpenings = await ref.watch(puzzleOpeningsProvider.future);
|
||||
onlineOpenings = await ref.watch(puzzleOpeningsProvider(sort).future);
|
||||
} catch (e) {
|
||||
onlineOpenings = null;
|
||||
}
|
||||
return (isOnline, savedOpenings, onlineOpenings);
|
||||
return (connectivityStatus.isOnline, savedOpenings, onlineOpenings);
|
||||
});
|
||||
|
||||
class OpeningThemeScreen extends StatelessWidget {
|
||||
class OpeningThemeScreen extends ConsumerWidget {
|
||||
const OpeningThemeScreen({super.key});
|
||||
|
||||
static Route<dynamic> buildRoute() {
|
||||
@@ -33,9 +42,35 @@ class OpeningThemeScreen extends StatelessWidget {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final sort = ref.watch(_openingsSortProvider);
|
||||
return PlatformScaffold(
|
||||
appBar: PlatformAppBar(title: Text(context.l10n.puzzlePuzzlesByOpenings)),
|
||||
appBar: PlatformAppBar(
|
||||
title: Text(context.l10n.puzzlePuzzlesByOpenings),
|
||||
actions: [
|
||||
ContextMenuIconButton(
|
||||
consumeOutsideTap: true,
|
||||
icon: const Icon(Icons.sort_outlined),
|
||||
semanticsLabel: 'Sort openings',
|
||||
actions: [
|
||||
ContextMenuAction(
|
||||
icon: sort == PuzzleOpeningSort.popular ? Icons.check : null,
|
||||
label: context.l10n.popularOpenings,
|
||||
onPressed: () {
|
||||
ref.read(_openingsSortProvider.notifier).state = PuzzleOpeningSort.popular;
|
||||
},
|
||||
),
|
||||
ContextMenuAction(
|
||||
icon: sort == PuzzleOpeningSort.alphabetical ? Icons.check : null,
|
||||
label: 'Alphabetical',
|
||||
onPressed: () {
|
||||
ref.read(_openingsSortProvider.notifier).state = PuzzleOpeningSort.alphabetical;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
body: const _Body(),
|
||||
);
|
||||
}
|
||||
@@ -46,7 +81,8 @@ class _Body extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final openings = ref.watch(_openingsProvider);
|
||||
final sort = ref.watch(_openingsSortProvider);
|
||||
final openings = ref.watch(_openingsProvider(sort));
|
||||
return openings.when(
|
||||
data: (data) {
|
||||
final (isOnline, savedOpenings, onlineOpenings) = data;
|
||||
|
||||
Reference in New Issue
Block a user