Save standalone analysis session in memory (#2462)

* Save standalone analysis session in memory

Closes #2423

* Upgrade riverpod_lint
This commit is contained in:
Vincent Velociter
2025-12-27 11:36:26 +01:00
committed by GitHub
parent 23a71c771f
commit 96aef2d48e
6 changed files with 73 additions and 30 deletions
+1 -5
View File
@@ -25,7 +25,7 @@ analyzer:
errors:
invalid_annotation_target: ignore
plugins:
- custom_lint
- riverpod_lint
formatter:
page_width: 100
@@ -39,7 +39,3 @@ linter:
avoid_redundant_argument_values: false
sort_pub_dependencies: true
sort_unnamed_constructors_first: false
custom_lint:
rules:
- avoid_manual_providers_as_generated_provider_dependency: false
+36 -15
View File
@@ -110,6 +110,12 @@ final analysisControllerProvider = AsyncNotifierProvider.autoDispose
name: 'AnalysisControllerProvider',
);
Root? _savedStandaloneRoot;
void clearSavedStandaloneAnalysis() {
_savedStandaloneRoot = null;
}
class AnalysisController extends AsyncNotifier<AnalysisState>
with EngineEvaluationMixin
implements PgnTreeNotifier {
@@ -183,6 +189,13 @@ class AnalysisController extends AsyncNotifier<AnalysisState>
serverAnalysis = null;
division = null;
activeCorrespondenceGame = null;
// We want to keep the standalone analysis session alive even if the user navigates away
ref.onCancel(() {
if (_root.mainline.isNotEmpty) {
_savedStandaloneRoot = _root;
}
});
}
case ActiveCorrespondenceGame(:final gameFullId):
{
@@ -229,21 +242,24 @@ class AnalysisController extends AsyncNotifier<AnalysisState>
final List<Future<(UciPath, FullOpening)?>> openingFutures = [];
_root = Root.fromPgnGame(
game,
isLichessAnalysis: options.isLichessGameAnalysis,
onVisitNode: (root, branch, isMainline) {
if (isMainline &&
options.initialMoveCursor != null &&
branch.position.ply <= root.position.ply + options.initialMoveCursor!) {
path = path + branch.id;
lastMove = branch.sanMove.move;
}
if (isMainline && opening == null && branch.position.ply <= 10) {
openingFutures.add(_fetchOpening(branch.position.fen, path));
}
},
);
_root = switch (options) {
Standalone() when _savedStandaloneRoot != null => _savedStandaloneRoot!,
_ => Root.fromPgnGame(
game,
isLichessAnalysis: options.isLichessGameAnalysis,
onVisitNode: (root, branch, isMainline) {
if (isMainline &&
options.initialMoveCursor != null &&
branch.position.ply <= root.position.ply + options.initialMoveCursor!) {
path = path + branch.id;
lastMove = branch.sanMove.move;
}
if (isMainline && opening == null && branch.position.ply <= 10) {
openingFutures.add(_fetchOpening(branch.position.fen, path));
}
},
),
};
// wait for the opening to be fetched to recompute the branch opening
Future.wait(openingFutures)
@@ -345,6 +361,11 @@ class AnalysisController extends AsyncNotifier<AnalysisState>
return analysisState;
}
void clearSavedStandaloneAnalysis() {
_savedStandaloneRoot = null;
ref.invalidateSelf();
}
Future<void> onFocusRegained() async {
if (options case ActiveCorrespondenceGame(:final gameFullId)) {
final updatedGame = await _gameRepository.getActiveCorrespondenceGame(gameFullId);
@@ -553,6 +553,13 @@ class _BottomBar extends ConsumerWidget {
return showAdaptiveActionSheet(
context: context,
actions: [
if (options case Standalone())
BottomSheetAction(
makeLabel: (context) => Text(context.l10n.clearSavedMoves),
onPressed: () => ref
.read(analysisControllerProvider(options).notifier)
.clearSavedStandaloneAnalysis(),
),
if (analysisState.isEngineAvailable(evalPrefs))
BottomSheetAction(
makeLabel: (context) => Text(
+25 -9
View File
@@ -17,6 +17,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.65"
analysis_server_plugin:
dependency: transitive
description:
name: analysis_server_plugin
sha256: "26844e7f977087567135d62532b67d5639fe206c5194c3f410ba75e1a04a2747"
url: "https://pub.dev"
source: hosted
version: "0.3.3"
analyzer:
dependency: transitive
description:
@@ -314,7 +322,7 @@ packages:
source: hosted
version: "1.0.8"
custom_lint:
dependency: "direct dev"
dependency: transitive
description:
name: custom_lint
sha256: "751ee9440920f808266c3ec2553420dea56d3c7837dd2d62af76b11be3fcece5"
@@ -663,10 +671,10 @@ packages:
dependency: "direct main"
description:
name: flutter_riverpod
sha256: "9e2d6907f12cc7d23a846847615941bddee8709bf2bfd274acdf5e80bcf22fde"
sha256: "38ec6c303e2c83ee84512f5fc2a82ae311531021938e63d7137eccc107bf3c02"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "3.1.0"
flutter_secure_storage:
dependency: "direct main"
description:
@@ -1338,26 +1346,26 @@ packages:
dependency: transitive
description:
name: riverpod
sha256: c406de02bff19d920b832bddfb8283548bfa05ce41c59afba57ce643e116aa59
sha256: "16ff608d21e8ea64364f2b7c049c94a02ab81668f78845862b6e88b71dd4935a"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "3.1.0"
riverpod_analyzer_utils:
dependency: transitive
description:
name: riverpod_analyzer_utils
sha256: a0f68adb078b790faa3c655110a017f9a7b7b079a57bbd40f540e80dce5fcd29
sha256: "947b05d04c52a546a2ac6b19ef2a54b08520ff6bdf9f23d67957a4c8df1c3bc0"
url: "https://pub.dev"
source: hosted
version: "1.0.0-dev.7"
version: "1.0.0-dev.8"
riverpod_lint:
dependency: "direct dev"
description:
name: riverpod_lint
sha256: "7ef9c43469e9b5ac4e4c3b24d7c30642e47ce1b12cd7dcdd643534db0a72ed13"
sha256: "4d2eb0d19bbe7e3323bd0ce4553b2e6170d161a13914bfdd85a3612329edcb43"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "3.1.0"
rxdart:
dependency: transitive
description:
@@ -1883,6 +1891,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.3"
yaml_edit:
dependency: transitive
description:
name: yaml_edit
sha256: ec709065bb2c911b336853b67f3732dd13e0336bd065cc2f1061d7610ddf45e3
url: "https://pub.dev"
source: hosted
version: "2.2.3"
sdks:
dart: ">=3.10.0 <4.0.0"
flutter: ">=3.38.0 <4.0.0"
-1
View File
@@ -78,7 +78,6 @@ dependencies:
dev_dependencies:
build_runner: ^2.3.2
custom_lint: ^0.8.1
fake_async: ^1.3.1
flutter_test:
sdk: flutter
@@ -35,6 +35,10 @@ void main() {
// ignore: avoid_dynamic_calls
final sanMoves = jsonDecode(gameResponse)['moves'] as String;
tearDown(() {
clearSavedStandaloneAnalysis();
});
group('Analysis Screen', () {
testWidgets('displays correct move and position', (tester) async {
final app = await makeTestProviderScopeApp(