Skip to content

Commit

Permalink
Merge pull request #1077 from tom-anders/studyObjects
Browse files Browse the repository at this point in the history
[Study] add models to get study (chapter) as JSON and PGN
  • Loading branch information
veloce authored Oct 27, 2024
2 parents 4c3a246 + a01ed8a commit 19b963a
Show file tree
Hide file tree
Showing 4 changed files with 645 additions and 0 deletions.
10 changes: 10 additions & 0 deletions lib/src/model/common/id.dart
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,14 @@ extension IDPick on Pick {
return null;
}
}

StudyId asStudyIdOrThrow() {
final value = required().value;
if (value is String) {
return StudyId(value);
}
throw PickException(
"value $value at $debugParsingExit can't be casted to StudyId",
);
}
}
141 changes: 141 additions & 0 deletions lib/src/model/study/study.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,152 @@
import 'package:collection/collection.dart';
import 'package:dartchess/dartchess.dart';
import 'package:deep_pick/deep_pick.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:lichess_mobile/src/model/common/chess.dart';
import 'package:lichess_mobile/src/model/common/id.dart';
import 'package:lichess_mobile/src/model/user/user.dart';

part 'study.freezed.dart';
part 'study.g.dart';

@freezed
class Study with _$Study {
const Study._();

const factory Study({
required StudyId id,
required String name,
required bool liked,
required int likes,
required UserId? ownerId,
required StudyFeatures features,
required IList<String> topics,
required IList<StudyChapterMeta> chapters,
required StudyChapter chapter,

/// Hints to display in "gamebook"/"interactive" mode
/// Index corresponds to the current ply.
required IList<String?> hints,

/// Comment to display when deviating from the mainline in "gamebook" mode
/// (i.e. when making a wrong move).
/// Index corresponds to the current ply.
required IList<String?> deviationComments,
}) = _Study;

StudyChapterMeta get currentChapterMeta =>
chapters.firstWhere((c) => c.id == chapter.id);

factory Study.fromServerJson(Map<String, Object?> json) =>
_studyFromPick(pick(json).required());
}

Study _studyFromPick(RequiredPick pick) {
final treeParts = pick('analysis', 'treeParts').asListOrThrow((part) => part);

final hints = <String?>[];
final deviationComments = <String?>[];

for (final part in treeParts) {
hints.add(part('gamebook', 'hint').asStringOrNull());
deviationComments.add(part('gamebook', 'deviation').asStringOrNull());
}

final study = pick('study');
return Study(
id: study('id').asStudyIdOrThrow(),
name: study('name').asStringOrThrow(),
liked: study('liked').asBoolOrThrow(),
likes: study('likes').asIntOrThrow(),
ownerId: study('ownerId').asUserIdOrNull(),
features: (
cloneable: study('features', 'cloneable').asBoolOrFalse(),
chat: study('features', 'chat').asBoolOrFalse(),
sticky: study('features', 'sticky').asBoolOrFalse(),
),
topics:
study('topics').asListOrThrow((pick) => pick.asStringOrThrow()).lock,
chapters: study('chapters')
.asListOrThrow((pick) => StudyChapterMeta.fromJson(pick.asMapOrThrow()))
.lock,
chapter: StudyChapter.fromJson(study('chapter').asMapOrThrow()),
hints: hints.lock,
deviationComments: deviationComments.lock,
);
}

typedef StudyFeatures = ({
bool cloneable,
bool chat,
bool sticky,
});

@Freezed(fromJson: true)
class StudyChapter with _$StudyChapter {
const StudyChapter._();

const factory StudyChapter({
required StudyChapterId id,
required StudyChapterSetup setup,
@JsonKey(defaultValue: false) required bool practise,
required int? conceal,
@JsonKey(defaultValue: false) required bool gamebook,
@JsonKey(fromJson: studyChapterFeaturesFromJson)
required StudyChapterFeatures features,
}) = _StudyChapter;

factory StudyChapter.fromJson(Map<String, Object?> json) =>
_$StudyChapterFromJson(json);
}

typedef StudyChapterFeatures = ({
bool computer,
bool explorer,
});

StudyChapterFeatures studyChapterFeaturesFromJson(Map<String, Object?> json) {
return (
computer: json['computer'] as bool? ?? false,
explorer: json['explorer'] as bool? ?? false,
);
}

@Freezed(fromJson: true)
class StudyChapterSetup with _$StudyChapterSetup {
const StudyChapterSetup._();

const factory StudyChapterSetup({
required GameId? id,
required Side orientation,
@JsonKey(fromJson: _variantFromJson) required Variant variant,
required bool? fromFen,
}) = _StudyChapterSetup;

factory StudyChapterSetup.fromJson(Map<String, Object?> json) =>
_$StudyChapterSetupFromJson(json);
}

Variant _variantFromJson(Map<String, Object?> json) {
return Variant.values.firstWhereOrNull(
(v) => v.name == json['key'],
)!;
}

@Freezed(fromJson: true)
class StudyChapterMeta with _$StudyChapterMeta {
const StudyChapterMeta._();

const factory StudyChapterMeta({
required StudyChapterId id,
required String name,
required String? fen,
}) = _StudyChapterMeta;

factory StudyChapterMeta.fromJson(Map<String, Object?> json) =>
_$StudyChapterMetaFromJson(json);
}

@Freezed(fromJson: true)
class StudyPageData with _$StudyPageData {
const StudyPageData._();
Expand Down
35 changes: 35 additions & 0 deletions lib/src/model/study/study_repository.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
import 'dart:convert';

import 'package:deep_pick/deep_pick.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart';
import 'package:lichess_mobile/src/model/common/id.dart';
import 'package:lichess_mobile/src/model/study/study.dart';
import 'package:lichess_mobile/src/model/study/study_filter.dart';
import 'package:lichess_mobile/src/network/http.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'study_repository.g.dart';

@Riverpod(keepAlive: true)
StudyRepository studyRepository(Ref ref) {
return StudyRepository(ref.read(lichessClientProvider));
}

class StudyRepository {
StudyRepository(this.client);
Expand Down Expand Up @@ -53,4 +65,27 @@ class StudyRepository {
},
);
}

Future<(Study study, String pgn)> getStudy({
required StudyId id,
StudyChapterId? chapterId,
}) async {
final study = await client.readJson(
Uri(
path: (chapterId != null) ? '/study/$id/$chapterId' : '/study/$id',
queryParameters: {
'chapters': '1',
},
),
headers: {'Accept': 'application/json'},
mapper: Study.fromServerJson,
);

final pgnBytes = await client.readBytes(
Uri(path: '/api/study/$id/${chapterId ?? study.chapter.id}.pgn'),
headers: {'Accept': 'application/x-chess-pgn'},
);

return (study, utf8.decode(pgnBytes));
}
}
Loading

0 comments on commit 19b963a

Please sign in to comment.