Skip to content

Commit

Permalink
refactor: make EngineLines independent of AnalysisController
Browse files Browse the repository at this point in the history
  • Loading branch information
tom-anders committed Oct 1, 2024
1 parent ca2d377 commit 55c6c23
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 159 deletions.
175 changes: 16 additions & 159 deletions lib/src/view/analysis/analysis_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import 'package:lichess_mobile/src/utils/string.dart';
import 'package:lichess_mobile/src/view/analysis/analysis_share_screen.dart';
import 'package:lichess_mobile/src/view/board_editor/board_editor_screen.dart';
import 'package:lichess_mobile/src/view/engine/engine_gauge.dart';
import 'package:lichess_mobile/src/view/engine/engine_lines.dart';
import 'package:lichess_mobile/src/view/opening_explorer/opening_explorer_screen.dart';
import 'package:lichess_mobile/src/widgets/adaptive_action_sheet.dart';
import 'package:lichess_mobile/src/widgets/adaptive_bottom_sheet.dart';
Expand Down Expand Up @@ -229,6 +230,10 @@ class _Body extends ConsumerWidget {
final displayMode =
ref.watch(ctrlProvider.select((value) => value.displayMode));

final currentNode = ref.watch(
ctrlProvider.select((value) => value.currentNode),
);

return Column(
children: [
Expanded(
Expand Down Expand Up @@ -293,8 +298,11 @@ class _Body extends ConsumerWidget {
mainAxisAlignment: MainAxisAlignment.start,
children: [
if (isEngineAvailable)
_EngineLines(
ctrlProvider,
EngineLines(
onTapMove:
ref.read(ctrlProvider.notifier).onUserMove,
clientEval: currentNode.eval,
isGameOver: currentNode.position.isGameOver,
isLandscape: true,
),
Expanded(
Expand Down Expand Up @@ -411,169 +419,18 @@ class _ColumnTopTable extends ConsumerWidget {
params: analysisState.engineGaugeParams,
),
if (analysisState.isEngineAvailable)
_EngineLines(ctrlProvider, isLandscape: false),
EngineLines(
clientEval: analysisState.currentNode.eval,
isGameOver: analysisState.currentNode.position.isGameOver,
onTapMove: ref.read(ctrlProvider.notifier).onUserMove,
isLandscape: false,
),
],
)
: kEmptyWidget;
}
}

class _EngineLines extends ConsumerWidget {
const _EngineLines(this.ctrlProvider, {required this.isLandscape});
final AnalysisControllerProvider ctrlProvider;
final bool isLandscape;

@override
Widget build(BuildContext context, WidgetRef ref) {
final analysisState = ref.watch(ctrlProvider);
final numEvalLines = ref.watch(
analysisPreferencesProvider.select(
(p) => p.numEvalLines,
),
);
final engineEval = ref.watch(engineEvaluationProvider).eval;
final eval = engineEval ?? analysisState.currentNode.eval;

final emptyLines = List.filled(
numEvalLines,
_Engineline.empty(ctrlProvider),
);

final content = !analysisState.position.isGameOver
? (eval != null
? eval.pvs
.take(numEvalLines)
.map(
(pv) => _Engineline(ctrlProvider, eval.position, pv),
)
.toList()
: emptyLines)
: emptyLines;

if (content.length < numEvalLines) {
final padding = List.filled(
numEvalLines - content.length,
_Engineline.empty(ctrlProvider),
);
content.addAll(padding);
}

return Padding(
padding: EdgeInsets.symmetric(
vertical: isLandscape ? kTabletBoardTableSidePadding : 0.0,
horizontal: isLandscape ? kTabletBoardTableSidePadding : 0.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: content,
),
);
}
}

class _Engineline extends ConsumerWidget {
const _Engineline(
this.ctrlProvider,
this.fromPosition,
this.pvData,
);

const _Engineline.empty(this.ctrlProvider)
: pvData = const PvData(moves: IListConst([])),
fromPosition = Chess.initial;

final AnalysisControllerProvider ctrlProvider;
final Position fromPosition;
final PvData pvData;

@override
Widget build(BuildContext context, WidgetRef ref) {
if (pvData.moves.isEmpty) {
return const SizedBox(
height: kEvalGaugeSize,
child: SizedBox.shrink(),
);
}

final pieceNotation = ref.watch(pieceNotationProvider).maybeWhen(
data: (value) => value,
orElse: () => defaultAccountPreferences.pieceNotation,
);

final lineBuffer = StringBuffer();
int ply = fromPosition.ply + 1;
pvData.sanMoves(fromPosition).forEachIndexed((i, s) {
lineBuffer.write(
ply.isOdd
? '${(ply / 2).ceil()}. $s '
: i == 0
? '${(ply / 2).ceil()}... $s '
: '$s ',
);
ply += 1;
});

final brightness = ref.watch(currentBrightnessProvider);

final evalString = pvData.evalString;
return AdaptiveInkWell(
onTap: () => ref
.read(ctrlProvider.notifier)
.onUserMove(NormalMove.fromUci(pvData.moves[0])),
child: SizedBox(
height: kEvalGaugeSize,
child: Padding(
padding: const EdgeInsets.all(2.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
decoration: BoxDecoration(
color: pvData.winningSide == Side.black
? EngineGauge.backgroundColor(context, brightness)
: EngineGauge.valueColor(context, brightness),
borderRadius: BorderRadius.circular(4.0),
),
padding: const EdgeInsets.symmetric(
horizontal: 4.0,
vertical: 2.0,
),
child: Text(
evalString,
style: TextStyle(
color: pvData.winningSide == Side.black
? Colors.white
: Colors.black,
fontSize: kEvalGaugeFontSize,
fontWeight: FontWeight.w600,
),
),
),
const SizedBox(width: 8.0),
Expanded(
child: Text(
lineBuffer.toString(),
maxLines: 1,
softWrap: false,
style: TextStyle(
fontFamily: pieceNotation == PieceNotation.symbol
? 'ChessFont'
: null,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
),
),
);
}
}

class _BottomBar extends ConsumerWidget {
const _BottomBar({
required this.pgn,
Expand Down
174 changes: 174 additions & 0 deletions lib/src/view/engine/engine_lines.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import 'package:collection/collection.dart';
import 'package:dartchess/dartchess.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:lichess_mobile/src/constants.dart';
import 'package:lichess_mobile/src/model/account/account_preferences.dart';
import 'package:lichess_mobile/src/model/analysis/analysis_preferences.dart';
import 'package:lichess_mobile/src/model/common/eval.dart';
import 'package:lichess_mobile/src/model/engine/evaluation_service.dart';
import 'package:lichess_mobile/src/model/settings/brightness.dart';
import 'package:lichess_mobile/src/view/engine/engine_gauge.dart';
import 'package:lichess_mobile/src/widgets/buttons.dart';

class EngineLines extends ConsumerWidget {
const EngineLines({
required this.onTapMove,
required this.clientEval,
required this.isGameOver,
required this.isLandscape,
});
final void Function(NormalMove move) onTapMove;
final ClientEval? clientEval;
final bool isGameOver;
final bool isLandscape;

@override
Widget build(BuildContext context, WidgetRef ref) {
final numEvalLines = ref.watch(
analysisPreferencesProvider.select(
(p) => p.numEvalLines,
),
);
final engineEval = ref.watch(engineEvaluationProvider).eval;
final eval = engineEval ?? clientEval;

final emptyLines = List.filled(
numEvalLines,
const Engineline.empty(),
);

final content = isGameOver
? emptyLines
: (eval != null
? eval.pvs
.take(numEvalLines)
.map(
(pv) => Engineline(onTapMove, eval.position, pv),
)
.toList()
: emptyLines);

if (content.length < numEvalLines) {
final padding = List.filled(
numEvalLines - content.length,
const Engineline.empty(),
);
content.addAll(padding);
}

return Padding(
padding: EdgeInsets.symmetric(
vertical: isLandscape ? kTabletBoardTableSidePadding : 0.0,
horizontal: isLandscape ? kTabletBoardTableSidePadding : 0.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: content,
),
);
}
}

class Engineline extends ConsumerWidget {
const Engineline(
this.onTapMove,
this.fromPosition,
this.pvData,
);

const Engineline.empty()
: onTapMove = null,
pvData = const PvData(moves: IListConst([])),
fromPosition = Chess.initial;

final void Function(NormalMove move)? onTapMove;
final Position fromPosition;
final PvData pvData;

@override
Widget build(BuildContext context, WidgetRef ref) {
if (pvData.moves.isEmpty) {
return const SizedBox(
height: kEvalGaugeSize,
child: SizedBox.shrink(),
);
}

final pieceNotation = ref.watch(pieceNotationProvider).maybeWhen(
data: (value) => value,
orElse: () => defaultAccountPreferences.pieceNotation,
);

final lineBuffer = StringBuffer();
int ply = fromPosition.ply + 1;
pvData.sanMoves(fromPosition).forEachIndexed((i, s) {
lineBuffer.write(
ply.isOdd
? '${(ply / 2).ceil()}. $s '
: i == 0
? '${(ply / 2).ceil()}... $s '
: '$s ',
);
ply += 1;
});

final brightness = ref.watch(currentBrightnessProvider);

final evalString = pvData.evalString;
return AdaptiveInkWell(
onTap: () => onTapMove?.call(NormalMove.fromUci(pvData.moves[0])),
child: SizedBox(
height: kEvalGaugeSize,
child: Padding(
padding: const EdgeInsets.all(2.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
decoration: BoxDecoration(
color: pvData.winningSide == Side.black
? EngineGauge.backgroundColor(context, brightness)
: EngineGauge.valueColor(context, brightness),
borderRadius: BorderRadius.circular(4.0),
),
padding: const EdgeInsets.symmetric(
horizontal: 4.0,
vertical: 2.0,
),
child: Text(
evalString,
style: TextStyle(
color: pvData.winningSide == Side.black
? Colors.white
: Colors.black,
fontSize: kEvalGaugeFontSize,
fontWeight: FontWeight.w600,
),
),
),
const SizedBox(width: 8.0),
Expanded(
child: Text(
lineBuffer.toString(),
maxLines: 1,
softWrap: false,
style: TextStyle(
fontFamily: pieceNotation == PieceNotation.symbol
? 'ChessFont'
: null,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
),
),
);
}
}

0 comments on commit 55c6c23

Please sign in to comment.