Format code with new formatting rules

This commit is contained in:
Vincent Velociter
2025-05-21 07:45:17 +02:00
parent f5524d3240
commit 754678ef54
218 changed files with 6146 additions and 6732 deletions
+3 -7
View File
@@ -1,8 +1,7 @@
// File generated by FlutterFire CLI.
// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart'
show defaultTargetPlatform, kIsWeb, TargetPlatform;
import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform;
/// Default [FirebaseOptions] for use with your Firebase apps.
///
@@ -43,9 +42,7 @@ class DefaultFirebaseOptions {
'you can reconfigure this by running the FlutterFire CLI again.',
);
default:
throw UnsupportedError(
'DefaultFirebaseOptions are not supported for this platform.',
);
throw UnsupportedError('DefaultFirebaseOptions are not supported for this platform.');
}
}
@@ -63,8 +60,7 @@ class DefaultFirebaseOptions {
messagingSenderId: '974101866555',
projectId: 'lichessv2',
storageBucket: 'lichessv2.appspot.com',
iosClientId:
'974101866555-8ag66ua0p0pn1ab7u982i58a9iubhbod.apps.googleusercontent.com',
iosClientId: '974101866555-8ag66ua0p0pn1ab7u982i58a9iubhbod.apps.googleusercontent.com',
iosBundleId: 'org.lichess.mobileV2',
);
}
+7 -9
View File
@@ -120,16 +120,14 @@ class _AppState extends ConsumerState<Application> {
title: 'lichess.org',
locale: generalPrefs.locale,
theme: theme.copyWith(
navigationBarTheme:
isIOS
? null
: NavigationBarTheme.of(context).copyWith(
height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null,
),
navigationBarTheme: isIOS
? null
: NavigationBarTheme.of(context).copyWith(
height: remainingHeight < kSmallRemainingHeightLeftBoardThreshold ? 60 : null,
),
),
onGenerateRoute:
(settings) =>
settings.name != null ? resolveAppLinkUri(context, Uri.parse(settings.name!)) : null,
onGenerateRoute: (settings) =>
settings.name != null ? resolveAppLinkUri(context, Uri.parse(settings.name!)) : null,
onGenerateInitialRoutes: (initialRoute) {
final homeRoute = buildScreenRoute<void>(context, screen: const MainTabScaffold());
return <Route<dynamic>?>[
+3 -2
View File
@@ -48,8 +48,9 @@ const kFlexGoldenRatioBase = 100000000000;
const kFlexGoldenRatio = 161803398875;
/// Use same box shadows as material widgets with elevation 1.
final List<BoxShadow> boardShadows =
defaultTargetPlatform == TargetPlatform.iOS ? <BoxShadow>[] : kElevationToShadow[1]!;
final List<BoxShadow> boardShadows = defaultTargetPlatform == TargetPlatform.iOS
? <BoxShadow>[]
: kElevationToShadow[1]!;
const kMaxClockTextScaleFactor = 1.94;
const kEmptyWidget = SizedBox.shrink();
+4 -5
View File
@@ -129,11 +129,10 @@ Future<void> androidDisplayInitialization(WidgetsBinding widgetsBinding) async {
) {
final CorePalette? palette = value[0] as CorePalette?;
final schemes = value[1] as dynamic;
final ColorSchemes? colorSchemes =
schemes != null
// ignore: avoid_dynamic_calls
? (light: schemes.light as ColorScheme, dark: schemes.dark as ColorScheme)
: null;
final ColorSchemes? colorSchemes = schemes != null
// ignore: avoid_dynamic_calls
? (light: schemes.light as ColorScheme, dark: schemes.dark as ColorScheme)
: null;
setSystemColors(palette, colorSchemes);
});
+3 -4
View File
@@ -12,10 +12,9 @@ Future<Locale> setupIntl(WidgetsBinding widgetsBinding) async {
// Get locale from shared preferences, if any
final json = LichessBinding.instance.sharedPreferences.getString(PrefCategory.general.storageKey);
final generalPref =
json != null
? GeneralPrefs.fromJson(jsonDecode(json) as Map<String, dynamic>)
: GeneralPrefs.defaults;
final generalPref = json != null
? GeneralPrefs.fromJson(jsonDecode(json) as Map<String, dynamic>)
: GeneralPrefs.defaults;
final prefsLocale = generalPref.locale;
final locale = prefsLocale ?? systemLocale;
+19 -20
View File
@@ -9,26 +9,25 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'account_preferences.g.dart';
typedef AccountPrefState =
({
// game display
Zen zenMode,
PieceNotation pieceNotation,
ShowRatings showRatings,
// game behavior
BooleanPref premove,
AutoQueen autoQueen,
AutoThreefold autoThreefold,
Takeback takeback,
BooleanPref confirmResign,
SubmitMove submitMove,
// clock
Moretime moretime,
BooleanPref clockSound,
// privacy
BooleanPref follow,
Challenge challenge,
});
typedef AccountPrefState = ({
// game display
Zen zenMode,
PieceNotation pieceNotation,
ShowRatings showRatings,
// game behavior
BooleanPref premove,
AutoQueen autoQueen,
AutoThreefold autoThreefold,
Takeback takeback,
BooleanPref confirmResign,
SubmitMove submitMove,
// clock
Moretime moretime,
BooleanPref clockSound,
// privacy
BooleanPref follow,
Challenge challenge,
});
/// A provider that tells if the user wants to see ratings in the app.
@Riverpod(keepAlive: true)
+7 -8
View File
@@ -42,14 +42,13 @@ Map<String, dynamic> _makeEmojiData(String list) {
}
final data = {
'categories':
categories.map((category) {
return {
'id': category[0],
'name': category[1],
'emojis': lines.where((line) => line.startsWith(category[0])).toList(),
};
}).toList(),
'categories': categories.map((category) {
return {
'id': category[0],
'name': category[1],
'emojis': lines.where((line) => line.startsWith(category[0])).toList(),
};
}).toList(),
'emojis': <String, dynamic>{for (final line in lines) line: emojiData(line)},
};
+41 -46
View File
@@ -128,30 +128,27 @@ class AnalysisController extends _$AnalysisController
final game = PgnGame.parsePgn(
pgn,
initHeaders:
() =>
options.isLichessGameAnalysis
? {}
: {
'Event': '?',
'Site': '?',
'Date': _dateFormat.format(DateTime.now()),
'Round': '?',
'White': '?',
'Black': '?',
'Result': '*',
'WhiteElo': '?',
'BlackElo': '?',
},
initHeaders: () => options.isLichessGameAnalysis
? {}
: {
'Event': '?',
'Site': '?',
'Date': _dateFormat.format(DateTime.now()),
'Round': '?',
'White': '?',
'Black': '?',
'Result': '*',
'WhiteElo': '?',
'BlackElo': '?',
},
);
final pgnHeaders = IMap(game.headers);
final rootComments = IList(game.comments.map((c) => PgnComment.fromPgn(c)));
final isComputerAnalysisAllowed =
options.isLichessGameAnalysis
? pgnHeaders['Result'] != '*'
: options.standalone!.isComputerAnalysisAllowed;
final isComputerAnalysisAllowed = options.isLichessGameAnalysis
? pgnHeaders['Result'] != '*'
: options.standalone!.isComputerAnalysisAllowed;
final List<Future<(UciPath, FullOpening)?>> openingFutures = [];
@@ -451,8 +448,9 @@ class AnalysisController extends _$AnalysisController
// root view is only used to display move list, so we need to
// recompute the root view only when the nodelist length changes
// or a variation is hidden/shown
final rootView =
shouldForceShowVariation || shouldRecomputeRootView ? _root.view : curState.root;
final rootView = shouldForceShowVariation || shouldRecomputeRootView
? _root.view
: curState.root;
final isForward = path.size > curState.currentPath.size;
if (currentNode is Branch) {
@@ -538,10 +536,9 @@ class AnalysisController extends _$AnalysisController
state = AsyncData(
state.requireValue.copyWith(
acplChartData: _makeAcplChartData(),
playersAnalysis:
event.$2.analysis != null
? (white: event.$2.analysis!.white, black: event.$2.analysis!.black)
: null,
playersAnalysis: event.$2.analysis != null
? (white: event.$2.analysis!.white, black: event.$2.analysis!.black)
: null,
root: _root.view,
),
);
@@ -552,12 +549,11 @@ class AnalysisController extends _$AnalysisController
final eval = n2['eval'] as Map<String, dynamic>?;
final cp = eval?['cp'] as int?;
final mate = eval?['mate'] as int?;
final pgnEval =
cp != null
? PgnEvaluation.pawns(pawns: cpToPawns(cp))
: mate != null
? PgnEvaluation.mate(mate: mate)
: null;
final pgnEval = cp != null
? PgnEvaluation.pawns(pawns: cpToPawns(cp))
: mate != null
? PgnEvaluation.mate(mate: mate)
: null;
final glyphs = n2['glyphs'] as List<dynamic>?;
final glyph = glyphs?.first as Map<String, dynamic>?;
final comments = n2['comments'] as List<dynamic>?;
@@ -614,20 +610,19 @@ class AnalysisController extends _$AnalysisController
final (isCheckmate, side, eval) = el;
return eval != null
? ExternalEval(
cp: eval.pawns != null ? cpFromPawns(eval.pawns!) : null,
mate: eval.mate,
depth: eval.depth,
)
cp: eval.pawns != null ? cpFromPawns(eval.pawns!) : null,
mate: eval.mate,
depth: eval.depth,
)
: ExternalEval(
cp: null,
// hack to display checkmate as the max eval
mate:
isCheckmate
? side == Side.white
cp: null,
// hack to display checkmate as the max eval
mate: isCheckmate
? side == Side.white
? -1
: 1
: null,
);
: null,
);
})
.toList(growable: false);
return list.isEmpty ? null : IList(list);
@@ -806,10 +801,10 @@ sealed class AnalysisCurrentNode with _$AnalysisCurrentNode {
final pgnEval = lichessAnalysisComments?.firstWhereOrNull((c) => c.eval != null)?.eval;
return pgnEval != null
? ExternalEval(
cp: pgnEval.pawns != null ? cpFromPawns(pgnEval.pawns!) : null,
mate: pgnEval.mate,
depth: pgnEval.depth,
)
cp: pgnEval.pawns != null ? cpFromPawns(pgnEval.pawns!) : null,
mate: pgnEval.mate,
depth: pgnEval.depth,
)
: null;
}
}
@@ -193,10 +193,9 @@ sealed class BoardEditorState with _$BoardEditorState {
final rooksAndKings =
(board.bySide(side) & SquareSet.backrankOf(side)) & (board.rooks | board.kings);
for (final castlingSide in CastlingSide.values) {
final candidate =
castlingSide == CastlingSide.king
? rooksAndKings.squares.lastOrNull
: rooksAndKings.squares.firstOrNull;
final candidate = castlingSide == CastlingSide.king
? rooksAndKings.squares.lastOrNull
: rooksAndKings.squares.firstOrNull;
final isCastlingPossible =
candidate != null && board.rooks.has(candidate) && backrankKing.singleSquare != null;
switch ((side, castlingSide)) {
+30 -33
View File
@@ -21,15 +21,15 @@ enum BroadcastResult {
return (result == null)
? BroadcastResult.noResultPgnTag
: switch (result) {
'½-½' => BroadcastResult.draw,
'1-0' => BroadcastResult.whiteWins,
'0-1' => BroadcastResult.blackWins,
'0-0' => BroadcastResult.canceled,
'½-0' => BroadcastResult.whiteHalfWins,
'0-½' => BroadcastResult.blackHalfWins,
'*' => BroadcastResult.newOrOngoing,
_ => throw FormatException("value $result can't be interpreted as a broadcast result"),
};
'½-½' => BroadcastResult.draw,
'1-0' => BroadcastResult.whiteWins,
'0-1' => BroadcastResult.blackWins,
'0-0' => BroadcastResult.canceled,
'½-0' => BroadcastResult.whiteHalfWins,
'0-½' => BroadcastResult.blackHalfWins,
'*' => BroadcastResult.newOrOngoing,
_ => throw FormatException("value $result can't be interpreted as a broadcast result"),
};
}
String resultToString(Side side) {
@@ -95,16 +95,15 @@ sealed class BroadcastTournamentData with _$BroadcastTournamentData {
}) = _BroadcastTournamentData;
}
typedef BroadcastTournamentInformation =
({
String? format,
String? timeControl,
String? players,
String? location,
BroadcastTournamentDates? dates,
Uri? website,
Uri? standings,
});
typedef BroadcastTournamentInformation = ({
String? format,
String? timeControl,
String? players,
String? location,
BroadcastTournamentDates? dates,
Uri? website,
Uri? standings,
});
typedef BroadcastTournamentDates = ({DateTime startsAt, DateTime? endsAt});
@@ -123,14 +122,13 @@ sealed class BroadcastRound with _$BroadcastRound {
}) = _BroadcastRound;
}
typedef BroadcastRoundResponse =
({
String? groupName,
IList<BroadcastTournamentGroup>? group,
BroadcastTournamentData tournament,
BroadcastRound round,
BroadcastRoundGames games,
});
typedef BroadcastRoundResponse = ({
String? groupName,
IList<BroadcastTournamentGroup>? group,
BroadcastTournamentData tournament,
BroadcastRound round,
BroadcastRoundGames games,
});
typedef BroadcastRoundGames = IMap<BroadcastGameId, BroadcastGame>;
@@ -193,12 +191,11 @@ sealed class BroadcastPlayerWithOverallResult with _$BroadcastPlayerWithOverallR
typedef BroadcastFideData = ({({int? standard, int? rapid, int? blitz}) ratings, int? birthYear});
typedef BroadcastPlayerWithGameResults =
({
BroadcastPlayerWithOverallResult playerWithOverallResult,
BroadcastFideData fideData,
IList<BroadcastPlayerGameResult> games,
});
typedef BroadcastPlayerWithGameResults = ({
BroadcastPlayerWithOverallResult playerWithOverallResult,
BroadcastFideData fideData,
IList<BroadcastPlayerGameResult> games,
});
enum BroadcastPoints { one, half, zero }
@@ -180,18 +180,18 @@ class BroadcastAnalysisController extends _$BroadcastAnalysisController
final newRoot = Root.fromPgnGame(game, isLichessAnalysis: true);
final broadcastPath = newRoot.mainlinePath;
final lastMove =
wasOnLivePath ? newRoot.branchAt(newRoot.mainlinePath)?.sanMove.move : curState.lastMove;
final lastMove = wasOnLivePath
? newRoot.branchAt(newRoot.mainlinePath)?.sanMove.move
: curState.lastMove;
newRoot.merge(_root);
_root = newRoot;
final newCurrentPath = wasOnLivePath ? broadcastPath : curState.currentPath;
final newCurrentNode =
wasOnLivePath
? AnalysisCurrentNode.fromNode(_root.nodeAt(newCurrentPath))
: curState.currentNode;
final newCurrentNode = wasOnLivePath
? AnalysisCurrentNode.fromNode(_root.nodeAt(newCurrentPath))
: curState.currentNode;
state = AsyncData(
state.requireValue.copyWith(
@@ -459,8 +459,9 @@ class BroadcastAnalysisController extends _$BroadcastAnalysisController
// root view is only used to display move list, so we need to
// recompute the root view only when the nodelist length changes
// or a variation is hidden/shown
final rootView =
shouldForceShowVariation || shouldRecomputeRootView ? _root.view : state.requireValue.root;
final rootView = shouldForceShowVariation || shouldRecomputeRootView
? _root.view
: state.requireValue.root;
final isForward = path.size > state.requireValue.currentPath.size;
if (currentNode is Branch) {
@@ -119,12 +119,11 @@ BroadcastTournamentGroup _tournamentGroupFromPick(RequiredPick pick) {
BroadcastRound _roundFromPick(RequiredPick pick) {
final live = pick('ongoing').asBoolOrFalse();
final finished = pick('finished').asBoolOrFalse();
final status =
live
? RoundStatus.live
: finished
? RoundStatus.finished
: RoundStatus.upcoming;
final status = live
? RoundStatus.live
: finished
? RoundStatus.finished
: RoundStatus.upcoming;
return BroadcastRound(
id: pick('id').asBroadcastRoundIdOrThrow(),
+13 -17
View File
@@ -88,19 +88,17 @@ sealed class Challenge with _$Challenge, BaseChallenge implements BaseChallenge
final variantStr = variant == Variant.standard ? '' : '${variant.label}';
final sidePiece =
sideChoice == SideChoice.black
? ''
: sideChoice == SideChoice.white
? ''
: '';
final sidePiece = sideChoice == SideChoice.black
? ''
: sideChoice == SideChoice.white
? ''
: '';
final side =
sideChoice == SideChoice.black
? l10n.white
: sideChoice == SideChoice.white
? l10n.black
: l10n.randomColor;
final side = sideChoice == SideChoice.black
? l10n.white
: sideChoice == SideChoice.white
? l10n.black
: l10n.randomColor;
final mode = rated ? l10n.rated : l10n.casual;
@@ -319,11 +317,9 @@ extension ChallengeExtension on Pick {
if (value is String) {
return ChallengeDeclineReason.values.firstWhere(
(element) => element.name.toLowerCase() == value,
orElse:
() =>
throw PickException(
"value $value at $debugParsingExit can't be casted to ChallengeDeclineReason: invalid string.",
),
orElse: () => throw PickException(
"value $value at $debugParsingExit can't be casted to ChallengeDeclineReason: invalid string.",
),
);
}
throw PickException(
@@ -76,10 +76,9 @@ sealed class ChallengePrefs with _$ChallengePrefs implements Serializable {
sideChoice: SideChoice.random,
);
Speed get speed =>
timeControl == ChallengeTimeControlType.clock
? Speed.fromTimeIncrement(TimeIncrement(clock.time.inSeconds, clock.increment.inSeconds))
: Speed.correspondence;
Speed get speed => timeControl == ChallengeTimeControlType.clock
? Speed.fromTimeIncrement(TimeIncrement(clock.time.inSeconds, clock.increment.inSeconds))
: Speed.correspondence;
ChallengeRequest makeRequest(LightUser destUser, [String? initialFen]) {
return ChallengeRequest(
+18 -20
View File
@@ -42,14 +42,13 @@ class ChallengeService {
StreamSubscription<(NotificationResponse, LocalNotification)>? _notificationResponseSubscription;
/// The stream of challenge events that are received from the server.
static Stream<ChallengesList> get stream =>
socketGlobalStream.map((event) {
if (event.topic != 'challenges') return null;
final listPick = pick(event.data).required();
final inward = listPick('in').asListOrEmpty(Challenge.fromPick);
final outward = listPick('out').asListOrEmpty(Challenge.fromPick);
return (inward: inward.lock, outward: outward.lock);
}).whereNotNull();
static Stream<ChallengesList> get stream => socketGlobalStream.map((event) {
if (event.topic != 'challenges') return null;
final listPick = pick(event.data).required();
final inward = listPick('in').asListOrEmpty(Challenge.fromPick);
final outward = listPick('out').asListOrEmpty(Challenge.fromPick);
return (inward: inward.lock, outward: outward.lock);
}).whereNotNull();
/// Start listening to events.
void start() {
@@ -134,18 +133,17 @@ class ChallengeService {
if (context == null || !context.mounted) break;
showAdaptiveActionSheet<void>(
context: context,
actions:
ChallengeDeclineReason.values
.map(
(reason) => BottomSheetAction(
makeLabel: (context) => Text(reason.label(context.l10n)),
onPressed: () {
final repo = ref.read(challengeRepositoryProvider);
repo.decline(challengeId, reason: reason);
},
),
)
.toList(),
actions: ChallengeDeclineReason.values
.map(
(reason) => BottomSheetAction(
makeLabel: (context) => Text(reason.label(context.l10n)),
onPressed: () {
final repo = ref.read(challengeRepositoryProvider);
repo.decline(challengeId, reason: reason);
},
),
)
.toList(),
);
case null:
+9 -10
View File
@@ -29,16 +29,15 @@ sealed class ChatMessage with _$ChatMessage {
String? title,
}) = _ChatMessage;
LightUser? get user =>
username != null
? LightUser(
id: UserId.fromUserName(username!),
name: username!,
title: title,
flair: flair,
isPatron: patron,
)
: null;
LightUser? get user => username != null
? LightUser(
id: UserId.fromUserName(username!),
name: username!,
title: title,
flair: flair,
isPatron: patron,
)
: null;
factory ChatMessage.fromJson(Map<String, dynamic> json) =>
ChatMessage.fromPick(pick(json).required());
@@ -56,8 +56,9 @@ class ClockToolController extends _$ClockToolController {
void onClockEmergency() {
final activeSide = state.activeSide;
if (_hasPlayedLowTimeSound[activeSide]!) return;
final activeSideTime =
activeSide == Side.white ? _clock.whiteTime.value : _clock.blackTime.value;
final activeSideTime = activeSide == Side.white
? _clock.whiteTime.value
: _clock.blackTime.value;
if (activeSideTime <= _emergencyThreshold) {
ref.read(soundServiceProvider).play(Sound.lowTime);
_hasPlayedLowTimeSound[activeSide] = true;
@@ -120,10 +121,12 @@ class ClockToolController extends _$ClockToolController {
final options = ClockOptions(
whiteTime: player == Side.white ? Duration(seconds: clock.time) : state.options.whiteTime,
blackTime: player == Side.black ? Duration(seconds: clock.time) : state.options.blackTime,
whiteIncrement:
player == Side.white ? Duration(seconds: clock.increment) : state.options.whiteIncrement,
blackIncrement:
player == Side.black ? Duration(seconds: clock.increment) : state.options.blackIncrement,
whiteIncrement: player == Side.white
? Duration(seconds: clock.increment)
: state.options.whiteIncrement,
blackIncrement: player == Side.black
? Duration(seconds: clock.increment)
: state.options.blackIncrement,
);
_clock.setTimes(whiteTime: options.whiteTime, blackTime: options.blackTime);
state = ClockState(
+2 -2
View File
@@ -247,8 +247,8 @@ extension ChessExtension on Pick {
: value == 'black'
? Side.black
: throw PickException(
"value $value at $debugParsingExit can't be casted to Side: invalid string.",
);
"value $value at $debugParsingExit can't be casted to Side: invalid string.",
);
}
throw PickException("value $value at $debugParsingExit can't be casted to Side");
}
+26 -29
View File
@@ -310,23 +310,21 @@ abstract class Node {
PgnNodeData(
san: childFrom.sanMove.san,
startingComments: childFrom.startingComments?.map((c) => c.makeComment()).toList(),
comments:
(childFrom.lichessAnalysisComments ?? childFrom.comments)?.map((c) {
final eval = childFrom.eval;
final pgnEval =
eval?.cp != null
? PgnEvaluation.pawns(pawns: cpToPawns(eval!.cp!), depth: eval.depth)
: eval?.mate != null
? PgnEvaluation.mate(mate: eval!.mate, depth: eval.depth)
: c.eval;
return PgnComment(
text: c.text,
shapes: c.shapes,
clock: c.clock,
emt: c.emt,
eval: pgnEval,
).makeComment();
}).toList(),
comments: (childFrom.lichessAnalysisComments ?? childFrom.comments)?.map((c) {
final eval = childFrom.eval;
final pgnEval = eval?.cp != null
? PgnEvaluation.pawns(pawns: cpToPawns(eval!.cp!), depth: eval.depth)
: eval?.mate != null
? PgnEvaluation.mate(mate: eval!.mate, depth: eval.depth)
: c.eval;
return PgnComment(
text: c.text,
shapes: c.shapes,
clock: c.clock,
emt: c.emt,
eval: pgnEval,
).makeComment();
}).toList(),
nags: childFrom.nags,
),
);
@@ -531,10 +529,9 @@ class Root extends Node {
isCollapsed: frame.nesting > 2 || hideVariations && childIdx > 0,
isComputerVariation: isLichessAnalysis && childIdx > 0,
lichessAnalysisComments: isLichessAnalysis ? comments?.toList() : null,
startingComments:
isLichessAnalysis
? null
: childFrom.data.startingComments?.map(PgnComment.fromPgn).toList(),
startingComments: isLichessAnalysis
? null
: childFrom.data.startingComments?.map(PgnComment.fromPgn).toList(),
comments: isLichessAnalysis ? null : comments?.toList(),
nags: childFrom.data.nags,
);
@@ -545,10 +542,10 @@ class Root extends Node {
to: branch,
nesting:
frame.from.children.length == 1 ||
// mainline continuation
(childIdx == 0 && frame.nesting == 1)
? frame.nesting
: frame.nesting + 1,
// mainline continuation
(childIdx == 0 && frame.nesting == 1)
? frame.nesting
: frame.nesting + 1,
));
onVisitNode?.call(root, branch, isMainline);
@@ -673,10 +670,10 @@ sealed class ViewBranch extends ViewNode with _$ViewBranch {
final pgnEval = lichessAnalysisComments?.firstWhereOrNull((c) => c.eval != null)?.eval;
return pgnEval != null
? ExternalEval(
cp: pgnEval.pawns != null ? cpFromPawns(pgnEval.pawns!) : null,
mate: pgnEval.mate,
depth: pgnEval.depth,
)
cp: pgnEval.pawns != null ? cpFromPawns(pgnEval.pawns!) : null,
mate: pgnEval.mate,
depth: pgnEval.depth,
)
: null;
}
+9 -10
View File
@@ -16,16 +16,15 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'preloaded_data.g.dart';
typedef PreloadedData =
({
PackageInfo packageInfo,
BaseDeviceInfo deviceInfo,
AuthSessionState? userSession,
String sri,
int engineMaxMemoryInMb,
Directory? appDocumentsDirectory,
Directory? appSupportDirectory,
});
typedef PreloadedData = ({
PackageInfo packageInfo,
BaseDeviceInfo deviceInfo,
AuthSessionState? userSession,
String sri,
int engineMaxMemoryInMb,
Directory? appDocumentsDirectory,
Directory? appSupportDirectory,
});
@Riverpod(keepAlive: true)
Future<PreloadedData> preloadedData(Ref ref) async {
@@ -122,8 +122,7 @@ sealed class CoordinateTrainingState with _$CoordinateTrainingState {
bool get trainingActive => elapsed != null;
double? get timeFractionElapsed =>
(elapsed != null && timeLimit != null)
? elapsed!.inMilliseconds / timeLimit!.inMilliseconds
: null;
double? get timeFractionElapsed => (elapsed != null && timeLimit != null)
? elapsed!.inMilliseconds / timeLimit!.inMilliseconds
: null;
}
@@ -144,8 +144,9 @@ class CorrespondenceService {
.then((games) => games.map((e) => e.$2).toList());
WebSocket.userAgent = ref.read(userAgentProvider);
final Map<String, String> wsHeaders =
_session != null ? {'Authorization': 'Bearer ${signBearerToken(_session!.token)}'} : {};
final Map<String, String> wsHeaders = _session != null
? {'Authorization': 'Bearer ${signBearerToken(_session!.token)}'}
: {};
int movesPlayed = 0;
+9 -10
View File
@@ -208,16 +208,15 @@ mixin EngineEvaluationMixin {
final nodes = pick(event.data, 'knodes').asIntOrThrow() * 1000;
final depth = pick(event.data, 'depth').asIntOrThrow();
final pvs =
pick(event.data, 'pvs')
.asListOrThrow(
(pv) => PvData(
moves: pv('moves').asStringOrThrow().split(' ').toIList(),
cp: pv('cp').asIntOrNull(),
mate: pv('mate').asIntOrNull(),
),
)
.toIList();
final pvs = pick(event.data, 'pvs')
.asListOrThrow(
(pv) => PvData(
moves: pv('moves').asStringOrThrow().split(' ').toIList(),
cp: pv('cp').asIntOrNull(),
mate: pv('mate').asIntOrNull(),
),
)
.toIList();
bool isSameEvalString = true;
positionTree.updateAt(path, (node) {
+6 -2
View File
@@ -342,8 +342,12 @@ class EvaluationService {
}
}
typedef EngineEvaluationState =
({String engineName, EngineState state, Work? currentWork, LocalEval? eval});
typedef EngineEvaluationState = ({
String engineName,
EngineState state,
Work? currentWork,
LocalEval? eval,
});
/// A provider that holds the state of the engine and the current evaluation.
@riverpod
+16 -19
View File
@@ -163,15 +163,14 @@ ExportedGame _archivedGameFromPick(RequiredPick pick, {bool withBookmarked = fal
speed: data.speed,
perf: data.perf,
rated: data.rated,
clock:
data.clock != null
? (
initial: data.clock!.initial,
increment: data.clock!.increment,
emergency: null,
moreTime: null,
)
: null,
clock: data.clock != null
? (
initial: data.clock!.initial,
increment: data.clock!.increment,
emergency: null,
moreTime: null,
)
: null,
opening: data.opening,
division: division,
),
@@ -190,10 +189,9 @@ ExportedGame _archivedGameFromPick(RequiredPick pick, {bool withBookmarked = fal
final movesList = moves.isEmpty ? <String>[] : moves.split(' ');
// assume lichess always send initialFen with fromPosition and chess960
Position position =
(data.variant == Variant.fromPosition || data.variant == Variant.chess960)
? Chess.fromSetup(Setup.parseFen(initialFen!))
: data.variant.initialPosition;
Position position = (data.variant == Variant.fromPosition || data.variant == Variant.chess960)
? Chess.fromSetup(Setup.parseFen(initialFen!))
: data.variant.initialPosition;
int index = 0;
final List<GameStep> steps = [GameStep(position: position)];
Duration? clock = data.clock?.initial;
@@ -247,12 +245,11 @@ LightExportedGame _lightExportedGameFromPick(
lastMove: pick('lastMove').asUciMoveOrNull(),
clock: pick('clock').letOrNull(_clockDataFromPick),
opening: pick('opening').letOrNull(_openingFromPick),
bookmarked:
isBookmarked
? true
: withBookmarked
? pick('bookmarked').asBoolOrFalse()
: null,
bookmarked: isBookmarked
? true
: withBookmarked
? pick('bookmarked').asBoolOrFalse()
: null,
);
}
+27 -32
View File
@@ -58,20 +58,18 @@ abstract mixin class BaseGame {
Player get black;
/// Player of the playing point of view. Null if spectating.
Player? get me =>
youAre == null
? null
: youAre == Side.white
? white
: black;
Player? get me => youAre == null
? null
: youAre == Side.white
? white
: black;
/// Opponent from the playing point of view. Null if spectating.
Player? get opponent =>
youAre == null
? null
: youAre == Side.white
? black
: white;
Player? get opponent => youAre == null
? null
: youAre == Side.white
? black
: white;
Position get lastPosition;
@@ -94,8 +92,8 @@ abstract mixin class BaseGame {
({PlayerAnalysis white, PlayerAnalysis black})? get serverAnalysis =>
white.analysis != null && black.analysis != null
? (white: white.analysis!, black: black.analysis!)
: null;
? (white: white.analysis!, black: black.analysis!)
: null;
/// Converts the game to a tree representation
Root makeTree() {
@@ -108,12 +106,11 @@ abstract mixin class BaseGame {
for (var i = 1; i < steps.length; i++) {
final step = steps[i];
final eval = evals?.elementAtOrNull(i - 1);
final pgnEval =
eval?.cp != null
? PgnEvaluation.pawns(pawns: cpToPawns(eval!.cp!), depth: eval.depth)
: eval?.mate != null
? PgnEvaluation.mate(mate: eval!.mate, depth: eval.depth)
: null;
final pgnEval = eval?.cp != null
? PgnEvaluation.pawns(pawns: cpToPawns(eval!.cp!), depth: eval.depth)
: eval?.mate != null
? PgnEvaluation.mate(mate: eval!.mate, depth: eval.depth)
: null;
final clock = clocks?.elementAtOrNull(i - 1);
Duration? emt;
if (clock != null) {
@@ -131,10 +128,9 @@ abstract mixin class BaseGame {
}
}
final comment =
eval != null || clock != null
? PgnComment(text: eval?.judgment?.comment, clock: clock, emt: emt, eval: pgnEval)
: null;
final comment = eval != null || clock != null
? PgnComment(text: eval?.judgment?.comment, clock: clock, emt: emt, eval: pgnEval)
: null;
final nag = eval?.judgment != null ? _judgmentNameToNag(eval!.judgment!.name) : null;
final nextNode = Branch(
sanMove: step.sanMove!,
@@ -186,14 +182,13 @@ abstract mixin class BaseGame {
black.user?.name ??
black.name ??
(black.aiLevel != null ? 'Stockfish level ${black.aiLevel}' : 'Anonymous'),
'Result':
status.value >= GameStatus.mate.value
? winner == null
? '½-½'
: winner == Side.white
? '1-0'
: '0-1'
: '*',
'Result': status.value >= GameStatus.mate.value
? winner == null
? '½-½'
: winner == Side.white
? '1-0'
: '0-1'
: '*',
if (white.rating != null) 'WhiteElo': white.rating!.toString(),
if (black.rating != null) 'BlackElo': black.rating!.toString(),
if (white.ratingDiff != null)
+52 -59
View File
@@ -427,12 +427,11 @@ class GameController extends _$GameController {
void _sendMoveToSocket(Move move, {required bool isPremove, required bool withLag}) {
final thinkTime = _clock?.stop();
final moveTime =
_clock != null
? isPremove == true
? Duration.zero
: thinkTime
: null;
final moveTime = _clock != null
? isPremove == true
? Duration.zero
: thinkTime
: null;
_socketClient.send(
'move',
{
@@ -521,8 +520,9 @@ class GameController extends _$GameController {
premove: hasSameNumberOfSteps ? curState.premove : null,
promotionMove: hasSameNumberOfSteps ? curState.promotionMove : null,
moveToConfirm: hasSameNumberOfSteps ? curState.moveToConfirm : null,
opponentLeftCountdown:
isOpponentOnGame ? null : state.requireValue.opponentLeftCountdown,
opponentLeftCountdown: isOpponentOnGame
? null
: state.requireValue.opponentLeftCountdown,
),
);
@@ -596,13 +596,12 @@ class GameController extends _$GameController {
}
if (data.clock != null) {
final lag =
newState.game.playable && newState.game.isMyTurn
// my own clock doesn't need to be compensated for
? Duration.zero
// server will send the lag only if it's more than 10ms
// default lag of 10ms is also used by web client
: data.clock?.lag ?? const Duration(milliseconds: 10);
final lag = newState.game.playable && newState.game.isMyTurn
// my own clock doesn't need to be compensated for
? Duration.zero
// server will send the lag only if it's more than 10ms
// default lag of 10ms is also used by web client
: data.clock?.lag ?? const Duration(milliseconds: 10);
_updateClock(
white: data.clock!.white,
@@ -721,10 +720,9 @@ class GameController extends _$GameController {
_clock?.setTime(side, newClock);
// sync game clock object even if it's not used to display the clock
final newState =
side == Side.white
? curState.copyWith.game.clock!(white: newClock)
: curState.copyWith.game.clock!(black: newClock);
final newState = side == Side.white
? curState.copyWith.game.clock!(white: newClock)
: curState.copyWith.game.clock!(black: newClock);
state = AsyncValue.data(newState);
}
@@ -776,8 +774,9 @@ class GameController extends _$GameController {
final curState = state.requireValue;
state = AsyncValue.data(
curState.copyWith(
lastDrawOfferAtPly:
side != null && side == curState.game.youAre ? curState.game.lastPly : null,
lastDrawOfferAtPly: side != null && side == curState.game.youAre
? curState.game.lastPly
: null,
game: curState.game.copyWith(
white: curState.game.white.copyWith(
offeringDraw: side == null ? null : side == Side.white,
@@ -907,21 +906,17 @@ class GameController extends _$GameController {
IList<GameStep> newSteps = game.steps;
if (rewriteSteps && game.meta.clock != null && data.clocks != null) {
final initialTime = game.meta.clock!.initial;
newSteps =
game.steps.mapIndexed((index, element) {
if (index == 0) {
return element.copyWith(
archivedWhiteClock: initialTime,
archivedBlackClock: initialTime,
);
}
final prevClock = index > 1 ? data.clocks![index - 2] : initialTime;
final stepClock = data.clocks![index - 1];
return element.copyWith(
archivedWhiteClock: index.isOdd ? stepClock : prevClock,
archivedBlackClock: index.isEven ? stepClock : prevClock,
);
}).toIList();
newSteps = game.steps.mapIndexed((index, element) {
if (index == 0) {
return element.copyWith(archivedWhiteClock: initialTime, archivedBlackClock: initialTime);
}
final prevClock = index > 1 ? data.clocks![index - 2] : initialTime;
final stepClock = data.clocks![index - 1];
return element.copyWith(
archivedWhiteClock: index.isOdd ? stepClock : prevClock,
archivedBlackClock: index.isEven ? stepClock : prevClock,
);
}).toIList();
}
return game.copyWith(
@@ -1054,28 +1049,26 @@ sealed class GameState with _$GameState {
String get analysisPgn => game.makePgn();
AnalysisOptions get analysisOptions =>
game.finished
? AnalysisOptions(
orientation: game.youAre ?? Side.white,
initialMoveCursor: stepCursor,
gameId: gameFullId.gameId,
)
: AnalysisOptions(
orientation: game.youAre ?? Side.white,
initialMoveCursor: stepCursor,
standalone: (
pgn: game.makePgn(),
variant: game.meta.variant,
isComputerAnalysisAllowed: false,
),
);
AnalysisOptions get analysisOptions => game.finished
? AnalysisOptions(
orientation: game.youAre ?? Side.white,
initialMoveCursor: stepCursor,
gameId: gameFullId.gameId,
)
: AnalysisOptions(
orientation: game.youAre ?? Side.white,
initialMoveCursor: stepCursor,
standalone: (
pgn: game.makePgn(),
variant: game.meta.variant,
isComputerAnalysisAllowed: false,
),
);
GameChatOptions? get chatOptions =>
isZenModeActive || game.meta.tournament != null
? null
: GameChatOptions(
id: gameFullId,
opponent: game.youAre != null ? game.playerOf(game.youAre!.opposite).user : null,
);
GameChatOptions? get chatOptions => isZenModeActive || game.meta.tournament != null
? null
: GameChatOptions(
id: gameFullId,
opponent: game.youAre != null ? game.playerOf(game.youAre!.opposite).user : null,
);
}
+12 -14
View File
@@ -29,20 +29,18 @@ sealed class GameFilterState with _$GameFilterState {
/// Returns a translated label of the selected filters.
String selectionLabel(AppLocalizations l10n) {
final fields = [side, perfs];
final labels =
fields
.map(
(field) =>
field is ISet<Perf>
? field.map((e) => e.shortTitle).join(', ')
: (field as Side?) != null
? field == Side.white
? l10n.white
: l10n.black
: null,
)
.where((label) => label != null && label.isNotEmpty)
.toList();
final labels = fields
.map(
(field) => field is ISet<Perf>
? field.map((e) => e.shortTitle).join(', ')
: (field as Side?) != null
? field == Side.white
? l10n.white
: l10n.black
: null,
)
.where((label) => label != null && label.isNotEmpty)
.toList();
return labels.isEmpty ? 'All' : labels.join(', ');
}
+54 -59
View File
@@ -45,12 +45,11 @@ Future<IList<LightExportedGameWithPov>> myRecentGames(Ref ref) async {
return storage
.page(userId: session?.user.id, max: kNumberOfRecentGames)
.then(
(value) =>
value
// we can assume that `youAre` is not null either for logged
// in users or for anonymous users
.map((e) => (game: e.game.data, pov: e.game.youAre ?? Side.white))
.toIList(),
(value) => value
// we can assume that `youAre` is not null either for logged
// in users or for anonymous users
.map((e) => (game: e.game.data, pov: e.game.youAre ?? Side.white))
.toIList(),
);
}
}
@@ -111,26 +110,24 @@ class UserGameHistory extends _$UserGameHistory {
final storage = await ref.watch(gameStorageProvider.future);
final id = userId ?? session?.user.id;
final recentGames =
id != null && online
? ref.withClient(
(client) => GameRepository(client).getUserGames(
id,
filter: filter,
withBookmarked: true,
withMoves: prefs.displayMode == GameHistoryDisplayMode.detail,
),
)
: storage
.page(userId: id, filter: filter)
.then(
(value) =>
value
// we can assume that `youAre` is not null either for logged
// in users or for anonymous users
.map((e) => (game: e.game.data, pov: e.game.youAre ?? Side.white))
.toIList(),
);
final recentGames = id != null && online
? ref.withClient(
(client) => GameRepository(client).getUserGames(
id,
filter: filter,
withBookmarked: true,
withMoves: prefs.displayMode == GameHistoryDisplayMode.detail,
),
)
: storage
.page(userId: id, filter: filter)
.then(
(value) => value
// we can assume that `youAre` is not null either for logged
// in users or for anonymous users
.map((e) => (game: e.game.data, pov: e.game.youAre ?? Side.white))
.toIList(),
);
_list.addAll(await recentGames);
@@ -153,39 +150,37 @@ class UserGameHistory extends _$UserGameHistory {
final currentVal = state.requireValue;
state = AsyncData(currentVal.copyWith(isLoading: true));
try {
final value =
await (userId != null
? ref.withClient(
(client) => GameRepository(client).getUserGames(
userId!,
max: _nbPerPage,
until: _list.last.game.createdAt,
filter: currentVal.filter,
withBookmarked: true,
withMoves: prefs.displayMode == GameHistoryDisplayMode.detail,
),
)
: currentVal.online && currentVal.session != null
? ref.withClient(
(client) => GameRepository(client).getUserGames(
currentVal.session!.user.id,
max: _nbPerPage,
until: _list.last.game.createdAt,
filter: currentVal.filter,
withBookmarked: true,
withMoves: prefs.displayMode == GameHistoryDisplayMode.detail,
),
)
: (await ref.watch(gameStorageProvider.future))
.page(max: _nbPerPage, until: _list.last.game.createdAt)
.then(
(value) =>
value
// we can assume that `youAre` is not null either for logged
// in users or for anonymous users
.map((e) => (game: e.game.data, pov: e.game.youAre ?? Side.white))
.toIList(),
));
final value = await (userId != null
? ref.withClient(
(client) => GameRepository(client).getUserGames(
userId!,
max: _nbPerPage,
until: _list.last.game.createdAt,
filter: currentVal.filter,
withBookmarked: true,
withMoves: prefs.displayMode == GameHistoryDisplayMode.detail,
),
)
: currentVal.online && currentVal.session != null
? ref.withClient(
(client) => GameRepository(client).getUserGames(
currentVal.session!.user.id,
max: _nbPerPage,
until: _list.last.game.createdAt,
filter: currentVal.filter,
withBookmarked: true,
withMoves: prefs.displayMode == GameHistoryDisplayMode.detail,
),
)
: (await ref.watch(gameStorageProvider.future))
.page(max: _nbPerPage, until: _list.last.game.createdAt)
.then(
(value) => value
// we can assume that `youAre` is not null either for logged
// in users or for anonymous users
.map((e) => (game: e.game.data, pov: e.game.youAre ?? Side.white))
.toIList(),
));
if (value.isEmpty) {
state = AsyncData(currentVal.copyWith(hasMore: false, isLoading: false));
+18 -20
View File
@@ -70,16 +70,15 @@ class GameRepository {
mapper: (json) => LightExportedGame.fromServerJson(json, withBookmarked: withBookmarked),
)
.then(
(value) =>
value
.map(
(e) => (
game: e,
// we know here user is not null for at least one of the players
pov: e.white.user?.id == userId ? Side.white : Side.black,
),
)
.toIList(),
(value) => value
.map(
(e) => (
game: e,
// we know here user is not null for at least one of the players
pov: e.white.user?.id == userId ? Side.white : Side.black,
),
)
.toIList(),
);
}
@@ -105,16 +104,15 @@ class GameRepository {
mapper: (json) => LightExportedGame.fromServerJson(json, isBookmarked: true),
)
.then(
(value) =>
value
.map(
(e) => (
game: e,
// we know here user is not null for at least one of the players
pov: e.white.user?.id == session.user.id ? Side.white : Side.black,
),
)
.toIList(),
(value) => value
.map(
(e) => (
game: e,
// we know here user is not null for at least one of the players
pov: e.white.user?.id == session.user.id ? Side.white : Side.black,
),
)
.toIList(),
);
}
+6 -8
View File
@@ -71,10 +71,9 @@ class GameShareService {
/// Fetches the GIF animation of a game.
Future<(XFile, ExportedGame)> gameGif(GameId id, Side orientation) async {
final boardPreferences = _ref.read(boardPreferencesProvider);
final boardTheme =
boardPreferences.boardTheme == BoardTheme.system
? BoardTheme.brown
: boardPreferences.boardTheme;
final boardTheme = boardPreferences.boardTheme == BoardTheme.system
? BoardTheme.brown
: boardPreferences.boardTheme;
final pieceTheme = boardPreferences.pieceSet;
final resp = await Future.wait([
_ref
@@ -99,10 +98,9 @@ class GameShareService {
/// Fetches the GIF animation of a study chapter.
Future<XFile> chapterGif(StringId id, StringId chapterId) async {
final boardPreferences = _ref.read(boardPreferencesProvider);
final boardTheme =
boardPreferences.boardTheme == BoardTheme.system
? BoardTheme.brown
: boardPreferences.boardTheme;
final boardTheme = boardPreferences.boardTheme == BoardTheme.system
? BoardTheme.brown
: boardPreferences.boardTheme;
final pieceTheme = boardPreferences.pieceSet;
final resp = await _ref
+3 -4
View File
@@ -159,10 +159,9 @@ ServerEvalEvent _serverEvalEventFromPick(RequiredPick pick) {
final glyph = glyphs?.first as Map<String, dynamic>?;
final comments = node['comments'] as List<dynamic>?;
final comment = comments?.first as Map<String, dynamic>?;
final judgment =
glyph != null && comment != null
? (name: _nagToJugdmentName(glyph['id'] as int), comment: comment['text'] as String)
: null;
final judgment = glyph != null && comment != null
? (name: _nagToJugdmentName(glyph['id'] as int), comment: comment['text'] as String)
: null;
final variation = nextVariation;
+8 -10
View File
@@ -80,8 +80,8 @@ sealed class PlayableGame with _$PlayableGame, BaseGame, IndexableSteps implemen
Duration? clockOf(Side side) {
return clock != null
? side == Side.white
? clock!.white
: clock!.black
? clock!.white
: clock!.black
: null;
}
@@ -137,10 +137,9 @@ sealed class PlayableGame with _$PlayableGame, BaseGame, IndexableSteps implemen
status: status,
white: white,
black: black,
clock:
meta.clock != null
? (initial: meta.clock!.initial, increment: meta.clock!.increment)
: null,
clock: meta.clock != null
? (initial: meta.clock!.initial, increment: meta.clock!.increment)
: null,
opening: meta.opening,
),
initialFen: initialFen,
@@ -184,10 +183,9 @@ PlayableGame _playableGameFromPick(RequiredPick pick) {
final initialFen = requiredGamePick('initialFen').asStringOrNull();
// assume lichess always send initialFen with fromPosition and chess960
Position position =
(meta.variant == Variant.fromPosition || meta.variant == Variant.chess960)
? Chess.fromSetup(Setup.parseFen(initialFen!))
: meta.variant.initialPosition;
Position position = (meta.variant == Variant.fromPosition || meta.variant == Variant.chess960)
? Chess.fromSetup(Setup.parseFen(initialFen!))
: meta.variant.initialPosition;
final steps = [GameStep(position: position)];
final pgn = pick('game', 'pgn').asStringOrNull();
+5 -2
View File
@@ -17,8 +17,11 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'create_game_service.g.dart';
typedef ChallengeResponse =
({GameFullId? gameFullId, Challenge? challenge, ChallengeDeclineReason? declineReason});
typedef ChallengeResponse = ({
GameFullId? gameFullId,
Challenge? challenge,
ChallengeDeclineReason? declineReason,
});
/// A provider for the [CreateGameService].
@riverpod
+3 -2
View File
@@ -75,8 +75,9 @@ sealed class GameSeek with _$GameSeek {
/// the same time control, variant and rated status.
factory GameSeek.newOpponentFromGame(PlayableGame game, GameSetupPrefs setup) {
return GameSeek(
clock:
game.meta.clock != null ? (game.meta.clock!.initial, game.meta.clock!.increment) : null,
clock: game.meta.clock != null
? (game.meta.clock!.initial, game.meta.clock!.increment)
: null,
rated: game.meta.rated,
variant: game.meta.variant,
ratingDelta: game.source == GameSource.lobby ? setup.customRatingDelta : null,
@@ -141,8 +141,8 @@ class NotificationService {
// Get any messages which caused the application to open from
// a terminated state.
final RemoteMessage? initialMessage =
await LichessBinding.instance.firebaseMessaging.getInitialMessage();
final RemoteMessage? initialMessage = await LichessBinding.instance.firebaseMessaging
.getInitialMessage();
if (initialMessage != null) {
_handleFcmMessageOpenedApp(initialMessage);
@@ -51,10 +51,9 @@ sealed class FcmMessage {
final round = message.data['lichess.round'] as String?;
if (gameFullId != null) {
final fullId = GameFullId(gameFullId);
final game =
round != null
? PlayableGame.fromServerJson(jsonDecode(round) as Map<String, dynamic>)
: null;
final game = round != null
? PlayableGame.fromServerJson(jsonDecode(round) as Map<String, dynamic>)
: null;
return CorresGameUpdateFcmMessage(
fullId,
game: game,
@@ -326,10 +325,9 @@ class ChallengeNotification extends LocalNotification {
),
iOS: DarwinNotificationDetails(
threadIdentifier: channelId,
categoryIdentifier:
challenge.variant.isPlaySupported
? darwinPlayableVariantCategoryId
: darwinUnplayableVariantCategoryId,
categoryIdentifier: challenge.variant.isPlaySupported
? darwinPlayableVariantCategoryId
: darwinUnplayableVariantCategoryId,
),
);
@@ -36,10 +36,9 @@ class OpeningExplorerPreferences extends _$OpeningExplorerPreferences
Future<void> toggleLichessDbSpeed(Speed speed) => save(
state.copyWith(
lichessDb: state.lichessDb.copyWith(
speeds:
state.lichessDb.speeds.contains(speed)
? state.lichessDb.speeds.remove(speed)
: state.lichessDb.speeds.add(speed),
speeds: state.lichessDb.speeds.contains(speed)
? state.lichessDb.speeds.remove(speed)
: state.lichessDb.speeds.add(speed),
),
),
);
@@ -47,10 +46,9 @@ class OpeningExplorerPreferences extends _$OpeningExplorerPreferences
Future<void> toggleLichessDbRating(int rating) => save(
state.copyWith(
lichessDb: state.lichessDb.copyWith(
ratings:
state.lichessDb.ratings.contains(rating)
? state.lichessDb.ratings.remove(rating)
: state.lichessDb.ratings.add(rating),
ratings: state.lichessDb.ratings.contains(rating)
? state.lichessDb.ratings.remove(rating)
: state.lichessDb.ratings.add(rating),
),
),
);
@@ -67,10 +65,9 @@ class OpeningExplorerPreferences extends _$OpeningExplorerPreferences
Future<void> togglePlayerDbSpeed(Speed speed) => save(
state.copyWith(
playerDb: state.playerDb.copyWith(
speeds:
state.playerDb.speeds.contains(speed)
? state.playerDb.speeds.remove(speed)
: state.playerDb.speeds.add(speed),
speeds: state.playerDb.speeds.contains(speed)
? state.playerDb.speeds.remove(speed)
: state.playerDb.speeds.add(speed),
),
),
);
@@ -78,10 +75,9 @@ class OpeningExplorerPreferences extends _$OpeningExplorerPreferences
Future<void> togglePlayerDbGameMode(GameMode gameMode) => save(
state.copyWith(
playerDb: state.playerDb.copyWith(
gameModes:
state.playerDb.gameModes.contains(gameMode)
? state.playerDb.gameModes.remove(gameMode)
: state.playerDb.gameModes.add(gameMode),
gameModes: state.playerDb.gameModes.contains(gameMode)
? state.playerDb.gameModes.remove(gameMode)
: state.playerDb.gameModes.add(gameMode),
),
),
);
@@ -54,14 +54,12 @@ class OpeningExplorer extends _$OpeningExplorer {
_openingExplorerSubscription = openingExplorerStream.listen(
(openingExplorer) => state = AsyncValue.data((entry: openingExplorer, isIndexing: true)),
onDone:
() =>
state.value != null
? state = AsyncValue.data((entry: state.value!.entry, isIndexing: false))
: state = AsyncValue.error(
'No opening explorer data returned for player ${prefs.playerDb.username}',
StackTrace.current,
),
onDone: () => state.value != null
? state = AsyncValue.data((entry: state.value!.entry, isIndexing: false))
: state = AsyncValue.error(
'No opening explorer data returned for player ${prefs.playerDb.username}',
StackTrace.current,
),
);
return null;
}
@@ -108,10 +108,9 @@ sealed class OverTheBoardClockState with _$OverTheBoardClockState {
}) = _OverTheBoardClockState;
factory OverTheBoardClockState.fromTimeIncrement(TimeIncrement timeIncrement) {
final initialTime =
timeIncrement.isInfinite
? null
: Duration(seconds: max(timeIncrement.time, timeIncrement.increment));
final initialTime = timeIncrement.isInfinite
? null
: Duration(seconds: max(timeIncrement.time, timeIncrement.increment));
return OverTheBoardClockState(
timeIncrement: timeIncrement,
@@ -138,8 +138,9 @@ sealed class OverTheBoardGameState with _$OverTheBoardGameState {
}) = _OverTheBoardGameState;
factory OverTheBoardGameState.fromVariant(Variant variant, Speed speed) {
final position =
variant == Variant.chess960 ? randomChess960Position() : variant.initialPosition;
final position = variant == Variant.chess960
? randomChess960Position()
: variant.initialPosition;
return OverTheBoardGameState(
game: OverTheBoardGame(
steps: [GameStep(position: position)].lock,
@@ -162,13 +162,12 @@ class PuzzleBatchStorage {
final angle = map['angle'] as String?;
final raw = map['data'] as String?;
final openingKey =
angle != null
? switch (PuzzleAngle.fromKey(angle)) {
PuzzleTheme(themeKey: _) => null,
PuzzleOpening(key: final key) => key,
}
: null;
final openingKey = angle != null
? switch (PuzzleAngle.fromKey(angle)) {
PuzzleTheme(themeKey: _) => null,
PuzzleOpening(key: final key) => key,
}
: null;
if (openingKey != null) {
int? count;
+6 -2
View File
@@ -5,8 +5,12 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'puzzle_opening.g.dart';
typedef PuzzleOpeningFamily =
({String key, String name, int count, IList<PuzzleOpeningData> openings});
typedef PuzzleOpeningFamily = ({
String key,
String name,
int count,
IList<PuzzleOpeningData> openings,
});
typedef PuzzleOpeningData = ({String key, String name, int count});
+5 -8
View File
@@ -371,14 +371,11 @@ PuzzleDashboard _puzzleDashboardFromPick(RequiredPick pick) => PuzzleDashboard(
performance: pick('global')('performance').asIntOrThrow(),
theme: PuzzleThemeKey.mix,
),
themes:
pick('themes')
.asMapOrThrow<String, Map<String, dynamic>>()
.keys
.map(
(key) => _puzzleDashboardDataFromPick(pick('themes')(key)('results').required(), key),
)
.toIList(),
themes: pick('themes')
.asMapOrThrow<String, Map<String, dynamic>>()
.keys
.map((key) => _puzzleDashboardDataFromPick(pick('themes')(key)('results').required(), key))
.toIList(),
);
PuzzleDashboardData _puzzleDashboardDataFromPick(RequiredPick results, String themeKey) =>
+13 -14
View File
@@ -87,16 +87,15 @@ class PuzzleService {
}) {
return Result.release(
_syncAndLoadData(userId, angle).map(
(data) =>
data.$1 != null && data.$1!.unsolved.isNotEmpty
? PuzzleContext(
puzzle: data.$1!.unsolved[0],
angle: angle,
userId: userId,
glicko: data.$2,
rounds: data.$3,
)
: null,
(data) => data.$1 != null && data.$1!.unsolved.isNotEmpty
? PuzzleContext(
puzzle: data.$1!.unsolved[0],
angle: angle,
userId: userId,
glicko: data.$2,
rounds: data.$3,
)
: null,
),
);
}
@@ -170,11 +169,11 @@ class PuzzleService {
(client) => Result.capture(
solved.isNotEmpty && userId != null
? PuzzleRepository(
client,
).solveBatch(nb: deficit, solved: solved, angle: angle, difficulty: difficulty)
client,
).solveBatch(nb: deficit, solved: solved, angle: angle, difficulty: difficulty)
: PuzzleRepository(
client,
).selectBatch(nb: deficit, angle: angle, difficulty: difficulty),
client,
).selectBatch(nb: deficit, angle: angle, difficulty: difficulty),
),
);
+4 -5
View File
@@ -49,11 +49,10 @@ class PuzzleSession extends _$PuzzleSession {
Future<void> setRatingDiffs(Iterable<PuzzleRound> rounds) async {
await _update((d) {
final newState = d.copyWith(
attempts:
d.attempts.map((a) {
final round = rounds.firstWhereOrNull((r) => r.id == a.id);
return round != null ? a.copyWith(ratingDiff: round.ratingDiff) : a;
}).toIList(),
attempts: d.attempts.map((a) {
final round = rounds.firstWhereOrNull((r) => r.id == a.id);
return round != null ? a.copyWith(ratingDiff: round.ratingDiff) : a;
}).toIList(),
);
state = newState;
return newState;
+3 -4
View File
@@ -46,10 +46,9 @@ class PuzzleStreakController extends _$PuzzleStreakController {
final activeStreak = await streakStorage.loadActiveStreak();
if (activeStreak != null) {
final puzzle = await ref.read(puzzleProvider(activeStreak.streak[activeStreak.index]).future);
final nextPuzzle =
activeStreak.nextId != null
? await ref.read(puzzleProvider(activeStreak.nextId!).future)
: null;
final nextPuzzle = activeStreak.nextId != null
? await ref.read(puzzleProvider(activeStreak.nextId!).future)
: null;
return (streak: activeStreak, puzzle: puzzle, nextPuzzle: nextPuzzle);
}
+3 -6
View File
@@ -35,12 +35,9 @@ sealed class StormRunStats with _$StormRunStats {
IList<PuzzleHistoryEntry> historyFilter(StormFilter filter) {
return history
.where(
(e) =>
(filter.slow && filter.failed)
? (!e.win && slowPuzzleIds.any((id) => id == e.id))
: (filter.slow
? slowPuzzleIds.any((id) => id == e.id)
: (!filter.failed || !e.win)),
(e) => (filter.slow && filter.failed)
? (!e.win && slowPuzzleIds.any((id) => id == e.id))
: (filter.slow ? slowPuzzleIds.any((id) => id == e.id) : (!filter.failed || !e.win)),
)
.toIList();
}
+12 -15
View File
@@ -243,26 +243,23 @@ class StormController extends _$StormController {
comboBest: state.combo.best,
time: state.clock.endAt!,
timePerMove: mean,
highest:
wins.isNotEmpty
? wins
.map((e) => e.rating)
.reduce((maxRating, rating) => rating > maxRating ? rating : maxRating)
: 0,
highest: wins.isNotEmpty
? wins
.map((e) => e.rating)
.reduce((maxRating, rating) => rating > maxRating ? rating : maxRating)
: 0,
history: state.history,
slowPuzzleIds:
state.history
.where((e) => e.solvingTime!.inSeconds > threshold)
.map((e) => e.id)
.toIList(),
slowPuzzleIds: state.history
.where((e) => e.solvingTime!.inSeconds > threshold)
.map((e) => e.id)
.toIList(),
);
}
void _pushToHistory({required bool success}) {
final timeTaken =
state.lastSolvedTime != null
? DateTime.now().difference(state.lastSolvedTime!)
: DateTime.now().difference(state.clock.startAt!);
final timeTaken = state.lastSolvedTime != null
? DateTime.now().difference(state.lastSolvedTime!)
: DateTime.now().difference(state.clock.startAt!);
state = state.copyWith(
history: state.history.add(
PuzzleHistoryEntry.fromLitePuzzle(state.puzzle, success, timeTaken),
@@ -183,10 +183,9 @@ sealed class BoardPrefs with _$BoardPrefs implements Serializable {
colorScheme: boardTheme.colors,
brightness: brightness,
hue: hue,
border:
showBorder
? BoardBorder(color: darken(boardTheme.colors.darkSquare, 0.2), width: 16.0)
: null,
border: showBorder
? BoardBorder(color: darken(boardTheme.colors.darkSquare, 0.2), width: 16.0)
: null,
showValidMoves: showLegalMoves,
showLastMove: boardHighlights,
enableCoordinates: coordinates,
@@ -374,10 +373,9 @@ enum BoardTheme {
for (final c in const [1, 2, 3, 4, 5, 6])
Container(
width: 44,
color:
c.isEven
? BoardTheme.system.colors.darkSquare
: BoardTheme.system.colors.lightSquare,
color: c.isEven
? BoardTheme.system.colors.darkSquare
: BoardTheme.system.colors.lightSquare,
),
],
),
+3 -4
View File
@@ -30,10 +30,9 @@ class HomePreferences extends _$HomePreferences with PreferencesStorage<HomePref
return Future.value();
}
final newState = state.copyWith(
disabledWidgets:
state.disabledWidgets.contains(widget)
? state.disabledWidgets.remove(widget)
: state.disabledWidgets.add(widget),
disabledWidgets: state.disabledWidgets.contains(widget)
? state.disabledWidgets.remove(widget)
: state.disabledWidgets.add(widget),
);
return save(newState);
}
+3 -4
View File
@@ -79,10 +79,9 @@ Study _studyFromPick(RequiredPick pick) {
sticky: study('features', 'sticky').asBoolOrFalse(),
),
topics: study('topics').asListOrThrow((pick) => pick.asStringOrThrow()).lock,
chapters:
study(
'chapters',
).asListOrThrow((pick) => StudyChapterMeta.fromJson(pick.asMapOrThrow())).lock,
chapters: study(
'chapters',
).asListOrThrow((pick) => StudyChapterMeta.fromJson(pick.asMapOrThrow())).lock,
chapter: StudyChapter.fromJson(study('chapter').asMapOrThrow()),
hints: hints.lock,
deviationComments: deviationComments.lock,
+9 -10
View File
@@ -518,16 +518,15 @@ sealed class StudyState with _$StudyState implements EvaluationMixinState {
bool get isOpeningExplorerAvailable => !gamebookActive && study.chapter.features.explorer;
EngineGaugeParams? engineGaugeParams(EngineEvaluationPrefState prefs) =>
isEngineAvailable(prefs)
? (
isLocalEngineAvailable: isEngineAvailable(prefs),
orientation: pov,
position: currentPosition!,
savedEval: currentNode.eval,
serverEval: null,
)
: null;
EngineGaugeParams? engineGaugeParams(EngineEvaluationPrefState prefs) => isEngineAvailable(prefs)
? (
isLocalEngineAvailable: isEngineAvailable(prefs),
orientation: pov,
position: currentPosition!,
savedEval: currentNode.eval,
serverEval: null,
)
: null;
@override
Position? get currentPosition => currentNode.position;
+4 -5
View File
@@ -50,11 +50,10 @@ class StudyRepository {
final paginator = pick(json, 'paginator').asMapOrThrow<String, dynamic>();
return (
studies:
pick(
paginator,
'currentPageResults',
).asListOrThrow((pick) => StudyPageData.fromJson(pick.asMapOrThrow())).toIList(),
studies: pick(
paginator,
'currentPageResults',
).asListOrThrow((pick) => StudyPageData.fromJson(pick.asMapOrThrow())).toIList(),
nextPage: pick(paginator, 'nextPage').asIntOrNull(),
);
},
+23 -25
View File
@@ -33,12 +33,11 @@ enum TournamentFreq implements Comparable<TournamentFreq> {
}
}
typedef TournamentLists =
({
IList<LightTournament> started,
IList<LightTournament> created,
IList<LightTournament> finished,
});
typedef TournamentLists = ({
IList<LightTournament> started,
IList<LightTournament> created,
IList<LightTournament> finished,
});
typedef TournamentMe = ({int rank, GameFullId? gameId, bool? withdraw, Duration? pauseDelay});
@@ -193,10 +192,9 @@ Tournament _updateTournamentFromPartialPick(Tournament tournament, RequiredPick
// Sometimes a new FEN comes in via the websocket, but the API reload response
// still has the FEN of the previous move, leading to sync issues.
// So only copy the whole game here if it's a new ID.
featuredGame:
tournament.featuredGame?.id != newFeaturedGameId
? pick('featured').asFeaturedGameOrNull()
: tournament.featuredGame,
featuredGame: tournament.featuredGame?.id != newFeaturedGameId
? pick('featured').asFeaturedGameOrNull()
: tournament.featuredGame,
isFinished: pick('isFinished').asBoolOrNull(),
isStarted: pick('isStarted').asBoolOrNull(),
timeToStart: pick(
@@ -243,8 +241,10 @@ StandingPlayer _standingPlayerFromPick(RequiredPick pick) {
rank: pick('rank').asIntOrThrow(),
sheet: (
fire: pick('sheet', 'fire').asBoolOrFalse(),
scores:
pick('sheet', 'scores').asStringOrThrow().characters.map((e) => int.parse(e)).toIList(),
scores: pick(
'sheet',
'scores',
).asStringOrThrow().characters.map((e) => int.parse(e)).toIList(),
),
withdraw: pick('withdraw').asBoolOrFalse(),
);
@@ -388,15 +388,14 @@ extension TournamentExtension on Pick {
Verdicts asVerdictsOrThrow() {
final requiredPick = this.required();
return (
list:
requiredPick('list')
.asListOrThrow((pick) => pick.asVerdictOrThrow())
.where(
// we don't want to show the condition specific to the bot players
// since it makes no sense a a bot player use the app
(v) => v.condition != 'Bot players are not allowed',
)
.toIList(),
list: requiredPick('list')
.asListOrThrow((pick) => pick.asVerdictOrThrow())
.where(
// we don't want to show the condition specific to the bot players
// since it makes no sense a a bot player use the app
(v) => v.condition != 'Bot players are not allowed',
)
.toIList(),
accepted: requiredPick('accepted').asBoolOrThrow(),
);
}
@@ -422,10 +421,9 @@ extension TournamentExtension on Pick {
final requiredPick = this.required();
return (
page: requiredPick('page').asIntOrThrow(),
players:
requiredPick(
'players',
).asListOrThrow((pick) => _standingPlayerFromPick(pick.required())).toIList(),
players: requiredPick(
'players',
).asListOrThrow((pick) => _standingPlayerFromPick(pick.required())).toIList(),
);
} catch (_) {
return null;
@@ -227,8 +227,7 @@ sealed class TournamentState with _$TournamentState {
/// True if the user has joined the tournament and is not withdrawn.
bool get joined => tournament.me != null && tournament.me!.withdraw != true;
ChatOptions? get chatOptions =>
tournament.chat != null
? TournamentChatOptions(id: tournament.id, writeable: tournament.chat!.writeable)
: null;
ChatOptions? get chatOptions => tournament.chat != null
? TournamentChatOptions(id: tournament.id, writeable: tournament.chat!.writeable)
: null;
}
+3 -4
View File
@@ -104,10 +104,9 @@ class LiveTvChannels extends _$LiveTvChannels {
if (snapshots.isNotEmpty) {
state = AsyncValue.data(
state.requireValue.updateAll(
(key, value) =>
value.id == fenEvent.id
? value.copyWith(fen: fenEvent.fen, lastMove: fenEvent.lastMove)
: value,
(key, value) => value.id == fenEvent.id
? value.copyWith(fen: fenEvent.fen, lastMove: fenEvent.lastMove)
: value,
),
);
}
+5 -6
View File
@@ -46,12 +46,11 @@ FinishSocketEvent _finishEventFromPick(RequiredPick pick) {
final winner = pick('win').asStringOrNull();
return FinishSocketEvent(
id: pick('id').asGameIdOrThrow(),
winner:
winner == 'w'
? Side.white
: winner == 'b'
? Side.black
: null,
winner: winner == 'w'
? Side.white
: winner == 'b'
? Side.black
: null,
);
}
+18 -18
View File
@@ -38,22 +38,21 @@ sealed class Profile with _$Profile {
fideRating: pick('fideRating').asIntOrNull(),
uscfRating: pick('uscfRating').asIntOrNull(),
ecfRating: pick('ecfRating').asIntOrNull(),
links:
rawLinks
?.where((e) => e.trim().isNotEmpty)
.map((e) {
final link = SocialLink.fromUrl(e);
if (link == null) {
final uri = Uri.tryParse(e);
if (uri != null) {
return SocialLink(site: null, url: uri);
}
return null;
}
return link;
})
.nonNulls
.toIList(),
links: rawLinks
?.where((e) => e.trim().isNotEmpty)
.map((e) {
final link = SocialLink.fromUrl(e);
if (link == null) {
final uri = Uri.tryParse(e);
if (uri != null) {
return SocialLink(site: null, url: uri);
}
return null;
}
return link;
})
.nonNulls
.toIList(),
);
}
}
@@ -65,8 +64,9 @@ sealed class SocialLink with _$SocialLink {
const SocialLink._();
static SocialLink? fromUrl(String url) {
final updatedUrl =
url.startsWith('http://') || url.startsWith('https://') ? url : 'https://$url';
final updatedUrl = url.startsWith('http://') || url.startsWith('https://')
? url
: 'https://$url';
final uri = Uri.tryParse(updatedUrl);
if (uri == null) return null;
final host = uri.host.replaceAll(RegExp(r'www\.'), '');
+6 -8
View File
@@ -114,10 +114,9 @@ sealed class User with _$User {
perfs: IMap({
for (final entry in receivedPerfsMap.entries)
if (Perf.nameMap.containsKey(entry.key))
Perf.nameMap.get(entry.key)!:
(['storm', 'streak'].contains(entry.key))
? UserPerf.fromJsonStreak(entry.value)
: UserPerf.fromJson(entry.value),
Perf.nameMap.get(entry.key)!: (['storm', 'streak'].contains(entry.key))
? UserPerf.fromJsonStreak(entry.value)
: UserPerf.fromJson(entry.value),
}),
followable: pick('followable').asBoolOrNull(),
following: pick('following').asBoolOrNull(),
@@ -405,10 +404,9 @@ sealed class UserPerfGame with _$UserPerfGame {
String? opponentTitle,
}) = _UserPerfGame;
LightUser? get opponent =>
opponentId != null && opponentName != null
? LightUser(id: UserId(opponentId!), name: opponentName!, title: opponentTitle)
: null;
LightUser? get opponent => opponentId != null && opponentName != null
? LightUser(id: UserId(opponentId!), name: opponentName!, title: opponentTitle)
: null;
}
@immutable
+17 -18
View File
@@ -95,14 +95,13 @@ UserRatingHistoryPerf? _ratingHistoryFromPick(RequiredPick pick) {
return UserRatingHistoryPerf(
perf: perf,
points:
pick('points').asListOrThrow((point) {
final values = point.asListOrThrow((point) => point.asIntOrThrow());
return UserRatingHistoryPoint(
date: DateTime.utc(values[0], values[1] + 1, values[2]),
elo: values[3],
);
}).toIList(),
points: pick('points').asListOrThrow((point) {
final values = point.asListOrThrow((point) => point.asIntOrThrow());
return UserRatingHistoryPoint(
date: DateTime.utc(values[0], values[1] + 1, values[2]),
elo: values[3],
);
}).toIList(),
);
}
@@ -144,8 +143,10 @@ UserActivity _userActivityFromPick(RequiredPick pick) {
storm: pick('storm').letOrNull(UserActivityStreak.fromPick),
correspondenceEnds: pick('correspondenceEnds', 'score').letOrNull(UserActivityScore.fromPick),
correspondenceMovesNb: pick('correspondenceMoves', 'nb').asIntOrNull(),
correspondenceGamesNb:
pick('correspondenceMoves', 'games').asListOrNull((p) => p('id').asStringOrThrow())?.length,
correspondenceGamesNb: pick(
'correspondenceMoves',
'games',
).asListOrNull((p) => p('id').asStringOrThrow())?.length,
);
}
@@ -299,14 +300,12 @@ LeaderboardUser _leaderboardUserFromPick(RequiredPick pick) {
flair: pick('flair').asStringOrNull(),
patron: pick('patron').asBoolOrNull(),
online: pick('online').asBoolOrNull(),
rating:
pick(
'perfs',
).letOrThrow((perfsPick) => perfsPick(prefMap.keys.first, 'rating')).asIntOrThrow(),
progress:
pick(
'perfs',
).letOrThrow((prefsPick) => prefsPick(prefMap.keys.first, 'progress')).asIntOrThrow(),
rating: pick(
'perfs',
).letOrThrow((perfsPick) => perfsPick(prefMap.keys.first, 'rating')).asIntOrThrow(),
progress: pick(
'perfs',
).letOrThrow((prefsPick) => prefsPick(prefMap.keys.first, 'progress')).asIntOrThrow(),
);
}
+44 -49
View File
@@ -40,10 +40,10 @@ const _maxCacheSize = 2 * 1024 * 1024;
/// Creates a Uri pointing to lichess server with the given unencoded path and query parameters.
Uri lichessUri(String unencodedPath, [Map<String, dynamic>? queryParameters]) =>
kLichessHost.startsWith('localhost') ||
kLichessHost.startsWith('10.') ||
kLichessHost.startsWith('192.168.')
? Uri.http(kLichessHost, unencodedPath, queryParameters)
: Uri.https(kLichessHost, unencodedPath, queryParameters);
kLichessHost.startsWith('10.') ||
kLichessHost.startsWith('192.168.')
? Uri.http(kLichessHost, unencodedPath, queryParameters)
: Uri.https(kLichessHost, unencodedPath, queryParameters);
/// Creates the appropriate http client for the platform.
///
@@ -63,10 +63,9 @@ class HttpClientFactory {
);
return CronetClient.fromCronetEngine(engine);
} else if (Platform.isIOS || Platform.isMacOS) {
final config =
URLSessionConfiguration.ephemeralSessionConfiguration()
..cache = URLCache.withCapacity(memoryCapacity: _maxCacheSize)
..httpAdditionalHeaders = {'User-Agent': userAgent};
final config = URLSessionConfiguration.ephemeralSessionConfiguration()
..cache = URLCache.withCapacity(memoryCapacity: _maxCacheSize)
..httpAdditionalHeaders = {'User-Agent': userAgent};
return CupertinoClient.fromSessionConfiguration(config);
} else {
return IOClient(HttpClient()..userAgent = userAgent);
@@ -85,47 +84,43 @@ class HttpClientFactory {
@Riverpod(keepAlive: true)
HttpClientFactory httpClientFactory(Ref ref) {
return HttpClientFactory(
wrapper:
(client) => _RegisterCallbackClient(
client,
onRequest: (request) async {
if (request.method == 'HEAD') return;
final httpLogStorage = await ref.read(httpLogStorageProvider.future);
httpLogStorage.save(
HttpLogEntry(
httpLogId: request.hashCode.toString(),
requestDateTime: DateTime.now(),
requestMethod: request.method,
requestUrl: request.url,
),
);
},
onResponse: (response) async {
if (response.request != null) {
final httpLogStorage = await ref.read(httpLogStorageProvider.future);
httpLogStorage.updateWithResponse(
response.request!.hashCode.toString(),
responseCode: response.statusCode,
responseDateTime: DateTime.now(),
);
}
},
onError: (request, error, [st]) async {
if (request.method == 'HEAD') return;
final httpLogStorage = await ref.read(httpLogStorageProvider.future);
if (error is ClientException) {
httpLogStorage.updateWithError(
request.hashCode.toString(),
errorMessage: error.message,
);
} else {
httpLogStorage.updateWithError(
request.hashCode.toString(),
errorMessage: error.toString(),
);
}
},
),
wrapper: (client) => _RegisterCallbackClient(
client,
onRequest: (request) async {
if (request.method == 'HEAD') return;
final httpLogStorage = await ref.read(httpLogStorageProvider.future);
httpLogStorage.save(
HttpLogEntry(
httpLogId: request.hashCode.toString(),
requestDateTime: DateTime.now(),
requestMethod: request.method,
requestUrl: request.url,
),
);
},
onResponse: (response) async {
if (response.request != null) {
final httpLogStorage = await ref.read(httpLogStorageProvider.future);
httpLogStorage.updateWithResponse(
response.request!.hashCode.toString(),
responseCode: response.statusCode,
responseDateTime: DateTime.now(),
);
}
},
onError: (request, error, [st]) async {
if (request.method == 'HEAD') return;
final httpLogStorage = await ref.read(httpLogStorageProvider.future);
if (error is ClientException) {
httpLogStorage.updateWithError(request.hashCode.toString(), errorMessage: error.message);
} else {
httpLogStorage.updateWithError(
request.hashCode.toString(),
errorMessage: error.toString(),
);
}
},
),
);
}
+29 -29
View File
@@ -52,21 +52,21 @@ final socketGlobalStream = _globalStreamController.stream;
/// Creates a WebSocket URI for the lichess server.
Uri lichessWSUri(String unencodedPath, [Map<String, String>? queryParameters]) =>
kLichessWSHost.startsWith('localhost') ||
kLichessWSHost.startsWith('10.') ||
kLichessWSHost.startsWith('192.168.')
? Uri(
scheme: 'ws',
host: kLichessWSHost.split(':')[0],
port: int.parse(kLichessWSHost.split(':')[1]),
path: unencodedPath,
queryParameters: queryParameters,
)
: Uri(
scheme: 'wss',
host: kLichessWSHost,
path: unencodedPath,
queryParameters: queryParameters,
);
kLichessWSHost.startsWith('10.') ||
kLichessWSHost.startsWith('192.168.')
? Uri(
scheme: 'ws',
host: kLichessWSHost.split(':')[0],
port: int.parse(kLichessWSHost.split(':')[1]),
path: unencodedPath,
queryParameters: queryParameters,
)
: Uri(
scheme: 'wss',
host: kLichessWSHost,
path: unencodedPath,
queryParameters: queryParameters,
);
/// A lichess WebSocket client.
///
@@ -210,8 +210,9 @@ class SocketClient {
final session = getSession();
final uri = lichessWSUri(route.path, version != null ? {'v': version.toString()} : null);
final Map<String, String> headers =
session != null ? {'Authorization': 'Bearer ${signBearerToken(session.token)}'} : {};
final Map<String, String> headers = session != null
? {'Authorization': 'Bearer ${signBearerToken(session.token)}'}
: {};
WebSocket.userAgent = makeUserAgent(packageInfo, deviceInfo, sri, session?.user);
_logger.info('Creating WebSocket connection to $route');
@@ -672,23 +673,22 @@ class SocketPing extends _$SocketPing {
final pool = ref.read(socketPoolProvider);
return route != null
? route == pool.currentClient.route
? pool.averageLag.value
: Duration.zero
? pool.averageLag.value
: Duration.zero
: pool.averageLag.value;
}
SocketPingState _getPing(Duration lag) => (
averageLag: lag,
rating:
lag.inMicroseconds == 0
? 0
: lag.inMicroseconds < 150000
? 4
: lag.inMicroseconds < 300000
? 3
: lag.inMicroseconds < 500000
? 2
: 1,
rating: lag.inMicroseconds == 0
? 0
: lag.inMicroseconds < 150000
? 4
: lag.inMicroseconds < 300000
? 3
: lag.inMicroseconds < 500000
? 2
: 1,
);
void _listener() {
+32 -34
View File
@@ -124,34 +124,33 @@ class MainTabScaffold extends ConsumerWidget {
child: Scaffold(
body: _TabSwitchingView(currentTab: currentTab, tabBuilder: _tabBuilder),
extendBody: extendBody,
bottomNavigationBar:
Theme.of(context).platform == TargetPlatform.iOS
? CupertinoTabBar(
height: 50,
backgroundColor: NavigationBarTheme.of(context).backgroundColor,
border: const Border(top: BorderSide(color: Colors.transparent)),
activeColor: ColorScheme.of(context).onSurface,
currentIndex: currentTab.index,
items: [
for (final tab in BottomTab.values)
BottomNavigationBarItem(
icon: Icon(tab.icon, fill: tab == currentTab ? 1 : 0),
label: tab.label(context.l10n),
),
],
onTap: (i) => _onItemTapped(ref, i),
)
: NavigationBar(
selectedIndex: currentTab.index,
destinations: [
for (final tab in BottomTab.values)
NavigationDestination(
icon: Icon(tab.icon, fill: tab == currentTab ? 1 : 0),
label: tab.label(context.l10n),
),
],
onDestinationSelected: (i) => _onItemTapped(ref, i),
),
bottomNavigationBar: Theme.of(context).platform == TargetPlatform.iOS
? CupertinoTabBar(
height: 50,
backgroundColor: NavigationBarTheme.of(context).backgroundColor,
border: const Border(top: BorderSide(color: Colors.transparent)),
activeColor: ColorScheme.of(context).onSurface,
currentIndex: currentTab.index,
items: [
for (final tab in BottomTab.values)
BottomNavigationBarItem(
icon: Icon(tab.icon, fill: tab == currentTab ? 1 : 0),
label: tab.label(context.l10n),
),
],
onTap: (i) => _onItemTapped(ref, i),
)
: NavigationBar(
selectedIndex: currentTab.index,
destinations: [
for (final tab in BottomTab.values)
NavigationDestination(
icon: Icon(tab.icon, fill: tab == currentTab ? 1 : 0),
label: tab.label(context.l10n),
),
],
onDestinationSelected: (i) => _onItemTapped(ref, i),
),
),
),
);
@@ -438,12 +437,11 @@ class _MaterialTabViewState extends ConsumerState<_MaterialTabView> {
final currentTab = ref.watch(currentBottomTabProvider);
final enablePopHandler = currentTab == widget.tab;
return NavigatorPopHandler(
onPopWithResult:
enablePopHandler
? (_) {
widget.navigatorKey?.currentState?.maybePop();
}
: null,
onPopWithResult: enablePopHandler
? (_) {
widget.navigatorKey?.currentState?.maybePop();
}
: null,
enabled: enablePopHandler,
child: Navigator(
key: widget.navigatorKey,
+62 -75
View File
@@ -12,14 +12,13 @@ const kSliderTheme = SliderThemeData(
ThemeData makeAppTheme(BuildContext context, GeneralPrefs generalPrefs, BoardPrefs boardPrefs) {
final isIOS = Theme.of(context).platform == TargetPlatform.iOS;
final brightness =
generalPrefs.isForcedDarkMode
? Brightness.dark
: switch (generalPrefs.themeMode) {
BackgroundThemeMode.light => Brightness.light,
BackgroundThemeMode.dark => Brightness.dark,
BackgroundThemeMode.system => MediaQuery.platformBrightnessOf(context),
};
final brightness = generalPrefs.isForcedDarkMode
? Brightness.dark
: switch (generalPrefs.themeMode) {
BackgroundThemeMode.light => Brightness.light,
BackgroundThemeMode.dark => Brightness.dark,
BackgroundThemeMode.system => MediaQuery.platformBrightnessOf(context),
};
if (generalPrefs.backgroundColor == null && generalPrefs.backgroundImage == null) {
return _makeDefaultTheme(brightness, generalPrefs, boardPrefs, isIOS);
@@ -97,10 +96,9 @@ ThemeData _makeDefaultTheme(
final textTheme = isIOS ? kCupertinoDefaultTextTheme : null;
final theme =
hasSystemColors
? ThemeData.from(colorScheme: systemScheme, textTheme: textTheme)
: ThemeData.from(colorScheme: defaultScheme, textTheme: textTheme);
final theme = hasSystemColors
? ThemeData.from(colorScheme: systemScheme, textTheme: textTheme)
: ThemeData.from(colorScheme: defaultScheme, textTheme: textTheme);
return theme.copyWith(
cupertinoOverrideTheme: _makeCupertinoThemeData(theme.colorScheme, brightness),
@@ -108,35 +106,31 @@ ThemeData _makeDefaultTheme(
appBarTheme: _appBarTheme.copyWith(
backgroundColor: isIOS ? theme.colorScheme.surface.withValues(alpha: 0.9) : null,
scrolledUnderElevation: isIOS ? 0 : null,
titleTextStyle:
isIOS
? const CupertinoTextThemeData().navTitleTextStyle.copyWith(
color: theme.colorScheme.onSurface,
)
: null,
),
navigationBarTheme:
isIOS
? NavigationBarThemeData(
backgroundColor: theme.colorScheme.surface.withValues(alpha: 0.9),
titleTextStyle: isIOS
? const CupertinoTextThemeData().navTitleTextStyle.copyWith(
color: theme.colorScheme.onSurface,
)
: null,
: null,
),
navigationBarTheme: isIOS
? NavigationBarThemeData(backgroundColor: theme.colorScheme.surface.withValues(alpha: 0.9))
: null,
bottomAppBarTheme: BottomAppBarTheme(
color: theme.colorScheme.surface,
elevation: isIOS ? 0 : null,
),
iconTheme: IconThemeData(color: theme.colorScheme.onSurface.withValues(alpha: 0.7)),
listTileTheme: _makeListTileTheme(theme.colorScheme, isIOS),
cardTheme:
isIOS ? _kCupertinoCardTheme.copyWith(color: theme.colorScheme.surfaceContainerHigh) : null,
cardTheme: isIOS
? _kCupertinoCardTheme.copyWith(color: theme.colorScheme.surfaceContainerHigh)
: null,
inputDecorationTheme: isIOS ? _makeCupertinoInputDecorationTheme(theme.colorScheme) : null,
floatingActionButtonTheme:
isIOS
? FloatingActionButtonThemeData(
backgroundColor: theme.colorScheme.secondaryFixedDim,
foregroundColor: theme.colorScheme.onSecondaryFixedVariant,
)
: null,
floatingActionButtonTheme: isIOS
? FloatingActionButtonThemeData(
backgroundColor: theme.colorScheme.secondaryFixedDim,
foregroundColor: theme.colorScheme.onSecondaryFixedVariant,
)
: null,
dialogTheme: isIOS ? _kCupertinoDialogTheme : null,
filledButtonTheme: isIOS ? _kCupertinoFilledButtonTheme : null,
outlinedButtonTheme: isIOS ? _kCupertinoOutlinedButtonTheme : null,
@@ -179,10 +173,9 @@ ThemeData _makeBackgroundImageTheme({
cardTheme: isIOS ? _kCupertinoCardTheme : null,
inputDecorationTheme: isIOS ? _makeCupertinoInputDecorationTheme(baseTheme.colorScheme) : null,
bottomSheetTheme: (isIOS ? _kCupertinoBottomSheetTheme : const BottomSheetThemeData()).copyWith(
backgroundColor:
isIOS
? lighten(baseTheme.colorScheme.surface, 0.1).withValues(alpha: 0.9)
: baseTheme.colorScheme.surface.withValues(alpha: 0.9),
backgroundColor: isIOS
? lighten(baseTheme.colorScheme.surface, 0.1).withValues(alpha: 0.9)
: baseTheme.colorScheme.surface.withValues(alpha: 0.9),
),
floatingActionButtonTheme: FloatingActionButtonThemeData(
backgroundColor: baseTheme.colorScheme.secondaryFixedDim,
@@ -193,48 +186,43 @@ ThemeData _makeBackgroundImageTheme({
),
filledButtonTheme: isIOS ? _kCupertinoFilledButtonTheme : null,
outlinedButtonTheme: isIOS ? _kCupertinoOutlinedButtonTheme : null,
menuTheme:
isIOS
? MenuThemeData(
style: MenuStyle(
elevation: const WidgetStatePropertyAll(0),
backgroundColor: WidgetStatePropertyAll(
baseTheme.colorScheme.surfaceContainer.withValues(alpha: 0.8),
),
),
)
: MenuThemeData(
style: MenuStyle(
backgroundColor: WidgetStatePropertyAll(
baseTheme.colorScheme.surfaceContainerLow.withValues(alpha: 0.8),
),
menuTheme: isIOS
? MenuThemeData(
style: MenuStyle(
elevation: const WidgetStatePropertyAll(0),
backgroundColor: WidgetStatePropertyAll(
baseTheme.colorScheme.surfaceContainer.withValues(alpha: 0.8),
),
),
)
: MenuThemeData(
style: MenuStyle(
backgroundColor: WidgetStatePropertyAll(
baseTheme.colorScheme.surfaceContainerLow.withValues(alpha: 0.8),
),
),
),
scaffoldBackgroundColor: seedColor.withValues(alpha: 0),
appBarTheme: _appBarTheme.copyWith(
backgroundColor: isBackgroundImage ? null : seedColor.withValues(alpha: 0.9),
scrolledUnderElevation: isIOS ? 0 : null,
titleTextStyle:
isIOS
? const CupertinoTextThemeData().navTitleTextStyle.copyWith(
color: baseTheme.colorScheme.onSurface,
)
: null,
),
navigationBarTheme:
isIOS
? NavigationBarThemeData(
backgroundColor:
isBackgroundImage
? baseTheme.colorScheme.surface.withValues(alpha: baseSurfaceAlpha)
: seedColor.withValues(alpha: 0.9),
titleTextStyle: isIOS
? const CupertinoTextThemeData().navTitleTextStyle.copyWith(
color: baseTheme.colorScheme.onSurface,
)
: null,
: null,
),
navigationBarTheme: isIOS
? NavigationBarThemeData(
backgroundColor: isBackgroundImage
? baseTheme.colorScheme.surface.withValues(alpha: baseSurfaceAlpha)
: seedColor.withValues(alpha: 0.9),
)
: null,
bottomAppBarTheme: BottomAppBarTheme(
color:
isBackgroundImage
? baseTheme.colorScheme.surface.withValues(alpha: baseSurfaceAlpha)
: seedColor,
color: isBackgroundImage
? baseTheme.colorScheme.surface.withValues(alpha: baseSurfaceAlpha)
: seedColor,
elevation: isIOS ? 0 : null,
),
splashFactory: isIOS ? NoSplash.splashFactory : null,
@@ -275,10 +263,9 @@ const MenuThemeData _kCupertinoMenuThemeData = MenuThemeData(
ListTileThemeData _makeListTileTheme(ColorScheme colorScheme, bool isIOS) {
return ListTileThemeData(
iconColor: colorScheme.onSurface.withValues(alpha: 0.7),
titleTextStyle:
isIOS
? TextStyle(color: colorScheme.onSurface, fontWeight: FontWeight.w500, fontSize: 16)
: null,
titleTextStyle: isIOS
? TextStyle(color: colorScheme.onSurface, fontWeight: FontWeight.w500, fontSize: 16)
: null,
subtitleTextStyle: TextStyle(
color: colorScheme.onSurface.withValues(alpha: Styles.subtitleOpacity),
),
+12 -14
View File
@@ -134,20 +134,18 @@ class GesturesExclusion {
return;
}
final rectsAsMaps =
rects
.map(
(r) =>
r.hasNaN || r.isInfinite
? null
: {
'left': r.left.floor(),
'top': r.top.floor(),
'right': r.right.floor(),
'bottom': r.bottom.floor(),
},
)
.toList();
final rectsAsMaps = rects
.map(
(r) => r.hasNaN || r.isInfinite
? null
: {
'left': r.left.floor(),
'top': r.top.floor(),
'right': r.right.floor(),
'bottom': r.bottom.floor(),
},
)
.toList();
try {
await _channel.invokeMethod<void>('setSystemGestureExclusionRects', rectsAsMaps);
+6 -7
View File
@@ -98,13 +98,12 @@ class ImageColorWorker {
(int previousValue, int element) => previousValue + element,
);
final int scoredResult =
Score.score(
colorToCount,
desired: 1,
fallbackColorARGB: 0xFFFFFFFF,
filter: false,
).first;
final int scoredResult = Score.score(
colorToCount,
desired: 1,
fallbackColorARGB: 0xFFFFFFFF,
filter: false,
).first;
final Hct sourceColor = Hct.fromInt(scoredResult);
if ((meanTone - sourceColor.tone).abs() > 20) {
sourceColor.tone = meanTone;
+9 -11
View File
@@ -83,18 +83,16 @@ class ImmersiveMode {
Future<void> disable() async {
final wakeFuture = WakelockPlus.disable();
final androidInfo =
defaultTargetPlatform == TargetPlatform.android
? await DeviceInfoPlugin().androidInfo
: null;
final androidInfo = defaultTargetPlatform == TargetPlatform.android
? await DeviceInfoPlugin().androidInfo
: null;
final setUiModeFuture =
androidInfo == null || androidInfo.version.sdkInt >= 29
? SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge)
: SystemChrome.setEnabledSystemUIMode(
SystemUiMode.manual,
overlays: SystemUiOverlay.values,
);
final setUiModeFuture = androidInfo == null || androidInfo.version.sdkInt >= 29
? SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge)
: SystemChrome.setEnabledSystemUIMode(
SystemUiMode.manual,
overlays: SystemUiOverlay.values,
);
return Future.wait([wakeFuture, setUiModeFuture]).then((_) {});
}
+9 -9
View File
@@ -23,10 +23,10 @@ class LocaleConverter implements JsonConverter<Locale?, Map<String, dynamic>?> {
Map<String, dynamic>? toJson(Locale? locale) {
return locale != null
? {
'languageCode': locale.languageCode,
'countryCode': locale.countryCode,
'scriptCode': locale.scriptCode,
}
'languageCode': locale.languageCode,
'countryCode': locale.countryCode,
'scriptCode': locale.scriptCode,
}
: null;
}
}
@@ -38,11 +38,11 @@ class ColorConverter implements JsonConverter<Color?, Map<String, dynamic>?> {
Color? fromJson(Map<String, dynamic>? json) {
return json != null
? Color.from(
alpha: json['a'] as double,
red: json['r'] as double,
green: json['g'] as double,
blue: json['b'] as double,
)
alpha: json['a'] as double,
red: json['r'] as double,
green: json['g'] as double,
blue: json['b'] as double,
)
: null;
}
+4 -4
View File
@@ -53,8 +53,8 @@ String relativeDate(AppLocalizations l10n, DateTime date, {bool shortDate = true
if (diff.isNegative) {
return diff.inDays == 0
? diff.inHours == 0
? l10n.timeagoNbMinutesAgo(diff.inMinutes.abs())
: l10n.timeagoNbHoursAgo(diff.inHours.abs())
? l10n.timeagoNbMinutesAgo(diff.inMinutes.abs())
: l10n.timeagoNbHoursAgo(diff.inHours.abs())
: diff.inDays == 1
? l10n.yesterday
: diff.inDays.abs() <= 7
@@ -67,8 +67,8 @@ String relativeDate(AppLocalizations l10n, DateTime date, {bool shortDate = true
}
return diff.inDays == 0
? diff.inHours == 0
? l10n.timeagoInNbMinutes(diff.inMinutes)
: l10n.timeagoInNbHours(diff.inHours)
? l10n.timeagoInNbMinutes(diff.inMinutes)
: l10n.timeagoInNbHours(diff.inHours)
: diff.inDays.abs() <= 7
? l10n.timeagoInNbDays(diff.inDays)
: diff.inDays.abs() <= 30
+71 -86
View File
@@ -51,29 +51,25 @@ class _AccountIconButtonState extends ConsumerState<AccountIconButton> {
return switch (account) {
AsyncData(:final value) => IconButton(
tooltip: value == null ? context.l10n.signIn : value.username,
icon:
value == null
? const Icon(Icons.account_circle_outlined, size: 30)
: CircleAvatar(
radius: 16,
foregroundImage:
value.flair != null
? CachedNetworkImageProvider(lichessFlairSrc(value.flair!))
: null,
onForegroundImageError:
value.flair != null
? (error, _) {
setState(() {
errorLoadingFlair = true;
});
}
: null,
backgroundColor:
value.flair == null || errorLoadingFlair
? null
: ColorScheme.of(context).surfaceContainer,
child: value.flair == null || errorLoadingFlair ? Text(value.initials) : null,
),
icon: value == null
? const Icon(Icons.account_circle_outlined, size: 30)
: CircleAvatar(
radius: 16,
foregroundImage: value.flair != null
? CachedNetworkImageProvider(lichessFlairSrc(value.flair!))
: null,
onForegroundImageError: value.flair != null
? (error, _) {
setState(() {
errorLoadingFlair = true;
});
}
: null,
backgroundColor: value.flair == null || errorLoadingFlair
? null
: ColorScheme.of(context).surfaceContainer,
child: value.flair == null || errorLoadingFlair ? Text(value.initials) : null,
),
onPressed: () {
Navigator.of(context, rootNavigator: true).push(AccountScreen.buildRoute(context));
},
@@ -106,27 +102,25 @@ class AccountScreen extends ConsumerWidget {
final packageInfo = ref.read(preloadedDataProvider).requireValue.packageInfo;
final dbSize = ref.watch(getDbSizeInBytesProvider);
final Widget? donateButton =
userSession == null || userSession.user.isPatron != true
? ListTile(
leading: Icon(
LichessIcons.patron,
semanticLabel: context.l10n.patronLichessPatron,
color: context.lichessColors.brag,
),
title: Text(
context.l10n.patronDonate,
style: TextStyle(color: context.lichessColors.brag),
),
trailing:
Theme.of(context).platform == TargetPlatform.iOS
? const Icon(Icons.chevron_right)
: null,
onTap: () {
launchUrl(Uri.parse('https://lichess.org/patron'));
},
)
: null;
final Widget? donateButton = userSession == null || userSession.user.isPatron != true
? ListTile(
leading: Icon(
LichessIcons.patron,
semanticLabel: context.l10n.patronLichessPatron,
color: context.lichessColors.brag,
),
title: Text(
context.l10n.patronDonate,
style: TextStyle(color: context.lichessColors.brag),
),
trailing: Theme.of(context).platform == TargetPlatform.iOS
? const Icon(Icons.chevron_right)
: null,
onTap: () {
launchUrl(Uri.parse('https://lichess.org/patron'));
},
)
: null;
return PlatformScaffold(
appBar: PlatformAppBar(
@@ -142,10 +136,9 @@ class AccountScreen extends ConsumerWidget {
ListTile(
leading: const Icon(Icons.person_outline),
title: Text(context.l10n.profile),
trailing:
Theme.of(context).platform == TargetPlatform.iOS
? const Icon(Icons.chevron_right)
: null,
trailing: Theme.of(context).platform == TargetPlatform.iOS
? const Icon(Icons.chevron_right)
: null,
onTap: () {
ref.invalidate(accountActivityProvider);
Navigator.of(context).push(ProfileScreen.buildRoute(context));
@@ -154,10 +147,9 @@ class AccountScreen extends ConsumerWidget {
ListTile(
leading: const Icon(Icons.manage_accounts_outlined),
title: Text(context.l10n.preferencesPreferences),
trailing:
Theme.of(context).platform == TargetPlatform.iOS
? const Icon(Icons.chevron_right)
: null,
trailing: Theme.of(context).platform == TargetPlatform.iOS
? const Icon(Icons.chevron_right)
: null,
onTap: () {
Navigator.of(context).push(AccountPreferencesScreen.buildRoute(context));
},
@@ -211,34 +203,30 @@ class AccountScreen extends ConsumerWidget {
child: SettingsListTile(
icon: const Icon(Icons.brightness_medium_outlined),
settingsLabel: Text(context.l10n.background),
settingsValue:
generalPrefs.isForcedDarkMode
? BackgroundThemeMode.dark.title(context.l10n)
: generalPrefs.themeMode.title(context.l10n),
onTap:
generalPrefs.isForcedDarkMode
? null
: () {
showChoicePicker(
context,
choices: BackgroundThemeMode.values,
selectedItem: generalPrefs.themeMode,
labelBuilder: (t) => Text(t.title(context.l10n)),
onSelectedItemChanged:
(BackgroundThemeMode? value) => ref
.read(generalPreferencesProvider.notifier)
.setBackgroundThemeMode(value ?? BackgroundThemeMode.system),
);
},
settingsValue: generalPrefs.isForcedDarkMode
? BackgroundThemeMode.dark.title(context.l10n)
: generalPrefs.themeMode.title(context.l10n),
onTap: generalPrefs.isForcedDarkMode
? null
: () {
showChoicePicker(
context,
choices: BackgroundThemeMode.values,
selectedItem: generalPrefs.themeMode,
labelBuilder: (t) => Text(t.title(context.l10n)),
onSelectedItemChanged: (BackgroundThemeMode? value) => ref
.read(generalPreferencesProvider.notifier)
.setBackgroundThemeMode(value ?? BackgroundThemeMode.system),
);
},
),
),
ListTile(
leading: const Icon(Icons.palette_outlined),
title: Text(context.l10n.mobileTheme),
trailing:
Theme.of(context).platform == TargetPlatform.iOS
? const Icon(Icons.chevron_right)
: null,
trailing: Theme.of(context).platform == TargetPlatform.iOS
? const Icon(Icons.chevron_right)
: null,
onTap: () {
Navigator.of(context).push(ThemeSettingsScreen.buildRoute(context));
},
@@ -246,10 +234,9 @@ class AccountScreen extends ConsumerWidget {
ListTile(
leading: const Icon(Symbols.chess_pawn),
title: Text(context.l10n.preferencesGameBehavior, overflow: TextOverflow.ellipsis),
trailing:
Theme.of(context).platform == TargetPlatform.iOS
? const Icon(Icons.chevron_right)
: null,
trailing: Theme.of(context).platform == TargetPlatform.iOS
? const Icon(Icons.chevron_right)
: null,
onTap: () {
Navigator.of(context).push(BoardSettingsScreen.buildRoute(context));
},
@@ -257,10 +244,9 @@ class AccountScreen extends ConsumerWidget {
ListTile(
leading: const Icon(Icons.memory_outlined),
title: const Text('Chess engine'),
trailing:
Theme.of(context).platform == TargetPlatform.iOS
? const Icon(Icons.chevron_right)
: null,
trailing: Theme.of(context).platform == TargetPlatform.iOS
? const Icon(Icons.chevron_right)
: null,
onTap: () {
Navigator.of(context).push(EngineSettingsScreen.buildRoute(context));
},
@@ -278,9 +264,8 @@ class AccountScreen extends ConsumerWidget {
choices: AppLocalizations.supportedLocales,
selectedItem: generalPrefs.locale ?? Localizations.localeOf(context),
labelBuilder: (t) => Text(localeToLocalizedName(t)),
onSelectedItemChanged:
(Locale? locale) =>
ref.read(generalPreferencesProvider.notifier).setLocale(locale),
onSelectedItemChanged: (Locale? locale) =>
ref.read(generalPreferencesProvider.notifier).setLocale(locale),
);
} else {
AppSettings.openAppSettings();
+88 -95
View File
@@ -84,25 +84,24 @@ class _EditProfileScreenState extends ConsumerState<EditProfileScreen> {
navigator.pop();
}
},
child:
value == null
? Center(child: Text(context.l10n.mobileMustBeLoggedIn))
: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Padding(
padding: Styles.bodyPadding.copyWith(top: 0, bottom: 0),
child: ListView(
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
children: [
SizedBox(height: Styles.bodyPadding.top),
Text(context.l10n.allInformationIsPublicAndOptional),
const SizedBox(height: 16),
_EditProfileForm(value, _formKey, _formData),
SizedBox(height: Styles.bodyPadding.bottom),
],
),
child: value == null
? Center(child: Text(context.l10n.mobileMustBeLoggedIn))
: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Padding(
padding: Styles.bodyPadding.copyWith(top: 0, bottom: 0),
child: ListView(
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
children: [
SizedBox(height: Styles.bodyPadding.top),
Text(context.l10n.allInformationIsPublicAndOptional),
const SizedBox(height: 16),
_EditProfileForm(value, _formKey, _formData),
SizedBox(height: Styles.bodyPadding.bottom),
],
),
),
),
),
);
@@ -216,19 +215,15 @@ class _EditProfileFormState extends ConsumerState<_EditProfileForm> {
),
child: EmojiPicker(
emojiData: value,
itemBuilder: (
context,
emojiId,
emoji,
callback,
) {
return EmojiItem(
onTap: () {
callback(emojiId, emoji);
itemBuilder:
(context, emojiId, emoji, callback) {
return EmojiItem(
onTap: () {
callback(emojiId, emoji);
},
emoji: emoji,
);
},
emoji: emoji,
);
},
onEmojiSelected: (emojiId, emoji) {
Navigator.of(context).pop(emojiId);
},
@@ -290,10 +285,9 @@ class _EditProfileFormState extends ConsumerState<_EditProfileForm> {
border: Border.all(color: Theme.of(context).dividerColor),
borderRadius: BorderRadius.circular(4.0),
),
child:
field.value != null
? CachedNetworkImage(imageUrl: lichessFlairSrc(field.value!))
: Text(context.l10n.setFlair),
child: field.value != null
? CachedNetworkImage(imageUrl: lichessFlairSrc(field.value!))
: Text(context.l10n.setFlair),
),
),
],
@@ -315,10 +309,9 @@ class _EditProfileFormState extends ConsumerState<_EditProfileForm> {
Text(context.l10n.countryRegion, style: Styles.formLabel),
const SizedBox(height: 6.0),
Autocomplete<String>(
initialValue:
field.value != null
? TextEditingValue(text: countries[field.value]!)
: null,
initialValue: field.value != null
? TextEditingValue(text: countries[field.value]!)
: null,
optionsBuilder: (TextEditingValue value) {
if (value.text.isEmpty) {
return const Iterable<String>.empty();
@@ -333,21 +326,22 @@ class _EditProfileFormState extends ConsumerState<_EditProfileForm> {
);
field.didChange(country.key);
},
fieldViewBuilder: (
BuildContext context,
TextEditingController textEditingController,
FocusNode focusNode,
VoidCallback onFieldSubmitted,
) {
return TextField(
controller: textEditingController,
textInputAction: TextInputAction.next,
focusNode: focusNode,
onSubmitted: (String value) {
onFieldSubmitted();
fieldViewBuilder:
(
BuildContext context,
TextEditingController textEditingController,
FocusNode focusNode,
VoidCallback onFieldSubmitted,
) {
return TextField(
controller: textEditingController,
textInputAction: TextInputAction.next,
focusNode: focusNode,
onSubmitted: (String value) {
onFieldSubmitted();
},
);
},
);
},
),
],
);
@@ -422,55 +416,54 @@ class _EditProfileFormState extends ConsumerState<_EditProfileForm> {
future: _pendingSaveProfile,
builder: (context, snapshot) {
return FilledButton(
onPressed:
snapshot.connectionState == ConnectionState.waiting
? null
: () async {
if (widget.formKey.currentState!.validate()) {
widget.formKey.currentState!.save();
widget.formData.removeWhere((key, value) {
return value == null;
});
final future = Result.capture(
ref.withClient(
(client) => AccountRepository(client).saveProfile(
widget.formData.map(
(key, value) => MapEntry(key, value.toString()),
),
onPressed: snapshot.connectionState == ConnectionState.waiting
? null
: () async {
if (widget.formKey.currentState!.validate()) {
widget.formKey.currentState!.save();
widget.formData.removeWhere((key, value) {
return value == null;
});
final future = Result.capture(
ref.withClient(
(client) => AccountRepository(client).saveProfile(
widget.formData.map(
(key, value) => MapEntry(key, value.toString()),
),
),
);
),
);
setState(() {
_pendingSaveProfile = future;
});
setState(() {
_pendingSaveProfile = future;
});
final result = await future;
final result = await future;
result.match(
onError: (err, _) {
if (context.mounted) {
showSnackBar(
context,
'Something went wrong',
type: SnackBarType.error,
);
}
},
onSuccess: (_) {
if (context.mounted) {
ref.invalidate(accountProvider);
showSnackBar(
context,
context.l10n.success,
type: SnackBarType.success,
);
Navigator.of(context).pop();
}
},
);
}
},
result.match(
onError: (err, _) {
if (context.mounted) {
showSnackBar(
context,
'Something went wrong',
type: SnackBarType.error,
);
}
},
onSuccess: (_) {
if (context.mounted) {
ref.invalidate(accountProvider);
showSnackBar(
context,
context.l10n.success,
type: SnackBarType.success,
);
Navigator.of(context).pop();
}
},
);
}
},
child: Text(context.l10n.apply),
);
},
+68 -70
View File
@@ -80,62 +80,60 @@ class _BodyState extends ConsumerState<_Body> {
return list.isEmpty
? const Padding(
padding: EdgeInsets.symmetric(vertical: 32.0),
child: Center(child: Text('No games found')),
)
padding: EdgeInsets.symmetric(vertical: 32.0),
child: Center(child: Text('No games found')),
)
: ListView.separated(
controller: _scrollController,
separatorBuilder:
(context, index) =>
Theme.of(context).platform == TargetPlatform.iOS
? const PlatformDivider(height: 1, cupertinoHasLeading: true)
: const SizedBox.shrink(),
itemCount: list.length + (state.isLoading ? 1 : 0),
itemBuilder: (context, index) {
if (state.isLoading && index == list.length) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 32.0),
child: CenterLoadingIndicator(),
);
} else if (state.hasError && state.hasMore && index == list.length) {
// TODO: add a retry button
return const Padding(
padding: EdgeInsets.symmetric(vertical: 32.0),
child: Center(child: Text('Could not load more games')),
);
}
controller: _scrollController,
separatorBuilder: (context, index) =>
Theme.of(context).platform == TargetPlatform.iOS
? const PlatformDivider(height: 1, cupertinoHasLeading: true)
: const SizedBox.shrink(),
itemCount: list.length + (state.isLoading ? 1 : 0),
itemBuilder: (context, index) {
if (state.isLoading && index == list.length) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 32.0),
child: CenterLoadingIndicator(),
);
} else if (state.hasError && state.hasMore && index == list.length) {
// TODO: add a retry button
return const Padding(
padding: EdgeInsets.symmetric(vertical: 32.0),
child: Center(child: Text('Could not load more games')),
);
}
final game = list[index].game;
final pov = list[index].pov;
final game = list[index].game;
final pov = list[index].pov;
Future<void> onRemoveBookmark(BuildContext context) async {
try {
await ref
.read(accountServiceProvider)
.setGameBookmark(game.id, bookmark: false);
ref.read(gameBookmarksPaginatorProvider.notifier).removeBookmark(game.id);
} on Exception catch (_) {
if (context.mounted) {
showSnackBar(context, 'Bookmark action failed', type: SnackBarType.error);
Future<void> onRemoveBookmark(BuildContext context) async {
try {
await ref
.read(accountServiceProvider)
.setGameBookmark(game.id, bookmark: false);
ref.read(gameBookmarksPaginatorProvider.notifier).removeBookmark(game.id);
} on Exception catch (_) {
if (context.mounted) {
showSnackBar(context, 'Bookmark action failed', type: SnackBarType.error);
}
}
}
}
final gameTile = GameListTile(
item: list[index],
onPressedBookmark: onRemoveBookmark,
);
final gameTile = GameListTile(
item: list[index],
onPressedBookmark: onRemoveBookmark,
);
return Slidable(
startActionPane: ActionPane(
motion: const StretchMotion(),
children: [
SlidableAction(
backgroundColor: ColorScheme.of(context).tertiaryContainer,
foregroundColor: ColorScheme.of(context).onTertiaryContainer,
onPressed:
game.variant.isReadSupported
? (_) {
return Slidable(
startActionPane: ActionPane(
motion: const StretchMotion(),
children: [
SlidableAction(
backgroundColor: ColorScheme.of(context).tertiaryContainer,
foregroundColor: ColorScheme.of(context).onTertiaryContainer,
onPressed: game.variant.isReadSupported
? (_) {
Navigator.of(context, rootNavigator: true).push(
AnalysisScreen.buildRoute(
context,
@@ -143,33 +141,33 @@ class _BodyState extends ConsumerState<_Body> {
),
);
}
: (_) {
: (_) {
showSnackBar(
context,
'This variant is not supported yet.',
type: SnackBarType.info,
);
},
icon: Icons.biotech,
label: context.l10n.analysis,
),
],
),
endActionPane: ActionPane(
motion: const ScrollMotion(),
children: [
SlidableAction(
backgroundColor: context.lichessColors.error,
onPressed: onRemoveBookmark,
icon: Icons.bookmark_remove_outlined,
label: 'Unbookmark',
),
],
),
child: gameTile,
);
},
);
icon: Icons.biotech,
label: context.l10n.analysis,
),
],
),
endActionPane: ActionPane(
motion: const ScrollMotion(),
children: [
SlidableAction(
backgroundColor: context.lichessColors.error,
onPressed: onRemoveBookmark,
icon: Icons.bookmark_remove_outlined,
label: 'Unbookmark',
),
],
),
child: gameTile,
);
},
);
},
error: (e, s) {
debugPrint('SEVERE: [GameBookmarksScreen] could not load bookmarks');
+26 -30
View File
@@ -38,9 +38,8 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
return PlatformScaffold(
appBar: PlatformAppBar(
title: account.when(
data:
(user) =>
user == null ? const SizedBox.shrink() : UserFullNameWidget(user: user.lightUser),
data: (user) =>
user == null ? const SizedBox.shrink() : UserFullNameWidget(user: user.lightUser),
loading: () => const SizedBox.shrink(),
error: (error, _) => const SizedBox.shrink(),
),
@@ -60,10 +59,9 @@ class _ProfileScreenState extends ConsumerState<ProfileScreen> {
final recentGames = ref.watch(myRecentGamesProvider);
final nbOfGames = ref.watch(userNumberOfGamesProvider(null)).valueOrNull ?? 0;
return RefreshIndicator.adaptive(
edgeOffset:
Theme.of(context).platform == TargetPlatform.iOS
? MediaQuery.paddingOf(context).top + kToolbarHeight
: 0.0,
edgeOffset: Theme.of(context).platform == TargetPlatform.iOS
? MediaQuery.paddingOf(context).top + kToolbarHeight
: 0.0,
key: _refreshIndicatorKey,
onRefresh: () async => ref.refresh(accountProvider),
child: ListView(
@@ -119,33 +117,31 @@ class AccountPerfCards extends ConsumerWidget {
return const SizedBox.shrink();
}
},
loading:
() => Shimmer(
child: Padding(
padding: padding ?? Styles.bodySectionPadding,
child: SizedBox(
height: 106,
child: ListView.separated(
padding: const EdgeInsets.symmetric(vertical: 3.0),
scrollDirection: Axis.horizontal,
itemCount: 5,
separatorBuilder: (context, index) => const SizedBox(width: 10),
itemBuilder:
(context, index) => ShimmerLoading(
isLoading: true,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(10.0),
),
),
),
loading: () => Shimmer(
child: Padding(
padding: padding ?? Styles.bodySectionPadding,
child: SizedBox(
height: 106,
child: ListView.separated(
padding: const EdgeInsets.symmetric(vertical: 3.0),
scrollDirection: Axis.horizontal,
itemCount: 5,
separatorBuilder: (context, index) => const SizedBox(width: 10),
itemBuilder: (context, index) => ShimmerLoading(
isLoading: true,
child: Container(
width: 100,
height: 100,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(10.0),
),
),
),
),
),
),
),
error: (error, stack) => const SizedBox.shrink(),
);
}
+22 -31
View File
@@ -50,21 +50,15 @@ class AnalysisBoardState extends ConsumerState<AnalysisBoard> {
analysisState.isComputerAnalysisAllowedAndEnabled && analysisPrefs.showAnnotations;
final currentNode = analysisState.currentNode;
final bestMoves =
showBestMoveArrow
? pickBestClientEval(
localEval: ref.watch(engineEvaluationProvider.select((value) => value.eval)),
savedEval: currentNode.eval,
)?.bestMoves
: null;
final ISet<Shape> bestMoveShapes =
bestMoves != null
? computeBestMoveShapes(
bestMoves,
currentNode.position.turn,
boardPrefs.pieceSet.assets,
)
: ISet();
final bestMoves = showBestMoveArrow
? pickBestClientEval(
localEval: ref.watch(engineEvaluationProvider.select((value) => value.eval)),
savedEval: currentNode.eval,
)?.bestMoves
: null;
final ISet<Shape> bestMoveShapes = bestMoves != null
? computeBestMoveShapes(bestMoves, currentNode.position.turn, boardPrefs.pieceSet.assets)
: ISet();
final annotation = showAnnotations ? makeAnnotation(currentNode.nags) : null;
final sanMove = currentNode.sanMove;
@@ -77,26 +71,23 @@ class AnalysisBoardState extends ConsumerState<AnalysisBoard> {
game: boardPrefs.toGameData(
variant: analysisState.variant,
position: analysisState.currentPosition,
playerSide:
analysisState.currentPosition.isGameOver
? PlayerSide.none
: analysisState.currentPosition.turn == Side.white
? PlayerSide.white
: PlayerSide.black,
playerSide: analysisState.currentPosition.isGameOver
? PlayerSide.none
: analysisState.currentPosition.turn == Side.white
? PlayerSide.white
: PlayerSide.black,
promotionMove: analysisState.promotionMove,
onMove:
(move, {isDrop, captured}) => ref
.read(ctrlProvider.notifier)
.onUserMove(move, shouldReplace: widget.shouldReplaceChildOnUserMove),
onMove: (move, {isDrop, captured}) => ref
.read(ctrlProvider.notifier)
.onUserMove(move, shouldReplace: widget.shouldReplaceChildOnUserMove),
onPromotionSelection: (role) => ref.read(ctrlProvider.notifier).onPromotionSelection(role),
),
shapes: userShapes.union(bestMoveShapes),
annotations:
sanMove != null && annotation != null
? altCastles.containsKey(sanMove.move.uci)
? IMap({Move.parse(altCastles[sanMove.move.uci]!)!.to: annotation})
: IMap({sanMove.move.to: annotation})
: null,
annotations: sanMove != null && annotation != null
? altCastles.containsKey(sanMove.move.uci)
? IMap({Move.parse(altCastles[sanMove.move.uci]!)!.to: annotation})
: IMap({sanMove.move.to: annotation})
: null,
settings: boardPrefs.toBoardSettings().copyWith(
borderRadius: widget.borderRadius,
boxShadow: widget.borderRadius != null ? boardShadows : const <BoxShadow>[],
+46 -57
View File
@@ -94,16 +94,15 @@ class _AppBarAnalysisTabIndicatorState extends State<AppBarAnalysisTabIndicator>
onPressed: () {
showAdaptiveActionSheet<void>(
context: context,
actions:
widget.tabs.map((tab) {
return BottomSheetAction(
leading: Icon(tab.icon),
makeLabel: (context) => Text(tab.l10n(context.l10n)),
onPressed: () {
widget.controller.animateTo(widget.tabs.indexOf(tab));
},
);
}).toList(),
actions: widget.tabs.map((tab) {
return BottomSheetAction(
leading: Icon(tab.icon),
makeLabel: (context) => Text(tab.l10n(context.l10n)),
onPressed: () {
widget.controller.animateTo(widget.tabs.indexOf(tab));
},
);
}).toList(),
);
},
);
@@ -184,10 +183,9 @@ class AnalysisLayout extends StatelessWidget {
bottom: false,
child: LayoutBuilder(
builder: (context, constraints) {
final orientation =
constraints.maxWidth > constraints.maxHeight
? Orientation.landscape
: Orientation.portrait;
final orientation = constraints.maxWidth > constraints.maxHeight
? Orientation.landscape
: Orientation.portrait;
final isTablet = isTabletOrLarger(context);
const tabletBoardRadius = Styles.boardBorderRadius;
@@ -203,7 +201,7 @@ class AnalysisLayout extends StatelessWidget {
(sideWidth >= 250
? defaultBoardSize
: constraints.biggest.longestSide / kGoldenRatio -
(kTabletBoardTableSidePadding * 2)) -
(kTabletBoardTableSidePadding * 2)) -
headerAndFooterHeight;
return Padding(
padding: const EdgeInsets.all(kTabletBoardTableSidePadding),
@@ -217,13 +215,12 @@ class AnalysisLayout extends StatelessWidget {
// This key is used to preserve the state of the board header when the pov changes
key: ValueKey(pov.opposite),
decoration: BoxDecoration(
borderRadius:
isTablet
? tabletBoardRadius.copyWith(
bottomLeft: Radius.zero,
bottomRight: Radius.zero,
)
: null,
borderRadius: isTablet
? tabletBoardRadius.copyWith(
bottomLeft: Radius.zero,
bottomRight: Radius.zero,
)
: null,
),
clipBehavior: isTablet ? Clip.hardEdge : Clip.none,
child: SizedBox(
@@ -244,13 +241,12 @@ class AnalysisLayout extends StatelessWidget {
// This key is used to preserve the state of the board footer when the pov changes
key: ValueKey(pov),
decoration: BoxDecoration(
borderRadius:
isTablet
? tabletBoardRadius.copyWith(
topLeft: Radius.zero,
topRight: Radius.zero,
)
: null,
borderRadius: isTablet
? tabletBoardRadius.copyWith(
topLeft: Radius.zero,
topRight: Radius.zero,
)
: null,
),
clipBehavior: isTablet ? Clip.hardEdge : Clip.none,
height: kAnalysisBoardHeaderOrFooterHeight,
@@ -287,10 +283,9 @@ class AnalysisLayout extends StatelessWidget {
final defaultBoardSize = constraints.biggest.shortestSide;
final remainingHeight = constraints.maxHeight - defaultBoardSize;
final isSmallScreen = remainingHeight < kSmallRemainingHeightLeftBoardThreshold;
final boardSize =
isTablet || isSmallScreen
? defaultBoardSize - kTabletBoardTableSidePadding * 2
: defaultBoardSize;
final boardSize = isTablet || isSmallScreen
? defaultBoardSize - kTabletBoardTableSidePadding * 2
: defaultBoardSize;
return Column(
mainAxisAlignment: MainAxisAlignment.center,
@@ -301,10 +296,9 @@ class AnalysisLayout extends StatelessWidget {
engineGaugeBuilder!(context, Orientation.portrait),
if (engineLines != null) engineLines!,
Padding(
padding:
isTablet
? const EdgeInsets.all(kTabletBoardTableSidePadding)
: EdgeInsets.zero,
padding: isTablet
? const EdgeInsets.all(kTabletBoardTableSidePadding)
: EdgeInsets.zero,
child: Column(
children: [
if (boardHeader != null)
@@ -312,13 +306,12 @@ class AnalysisLayout extends StatelessWidget {
Container(
key: ValueKey(pov.opposite),
decoration: BoxDecoration(
borderRadius:
isTablet
? tabletBoardRadius.copyWith(
bottomLeft: Radius.zero,
bottomRight: Radius.zero,
)
: null,
borderRadius: isTablet
? tabletBoardRadius.copyWith(
bottomLeft: Radius.zero,
bottomRight: Radius.zero,
)
: null,
),
clipBehavior: isTablet ? Clip.hardEdge : Clip.none,
height: kAnalysisBoardHeaderOrFooterHeight,
@@ -336,13 +329,12 @@ class AnalysisLayout extends StatelessWidget {
// This key is used to preserve the state of the board footer when the pov changes
key: ValueKey(pov),
decoration: BoxDecoration(
borderRadius:
isTablet
? tabletBoardRadius.copyWith(
topLeft: Radius.zero,
topRight: Radius.zero,
)
: null,
borderRadius: isTablet
? tabletBoardRadius.copyWith(
topLeft: Radius.zero,
topRight: Radius.zero,
)
: null,
),
clipBehavior: isTablet ? Clip.hardEdge : Clip.none,
height: kAnalysisBoardHeaderOrFooterHeight,
@@ -353,12 +345,9 @@ class AnalysisLayout extends StatelessWidget {
),
Expanded(
child: Padding(
padding:
isTablet
? const EdgeInsets.symmetric(
horizontal: kTabletBoardTableSidePadding,
)
: EdgeInsets.zero,
padding: isTablet
? const EdgeInsets.symmetric(horizontal: kTabletBoardTableSidePadding)
: EdgeInsets.zero,
child: Container(
decoration: BoxDecoration(
color: ColorScheme.of(context).surfaceContainerLowest,
+42 -40
View File
@@ -102,7 +102,10 @@ class _AnalysisScreenState extends ConsumerState<_AnalysisScreen>
case AsyncData(:final value):
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(title: _Title(variant: value.variant), actions: appBarActions),
appBar: AppBar(
title: _Title(variant: value.variant),
actions: appBarActions,
),
body: _Body(
options: widget.options,
controller: _tabController,
@@ -119,7 +122,10 @@ class _AnalysisScreenState extends ConsumerState<_AnalysisScreen>
case _:
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(title: const _Title(variant: Variant.standard), actions: appBarActions),
appBar: AppBar(
title: const _Title(variant: Variant.standard),
actions: appBarActions,
),
body: const Center(child: CircularProgressIndicator.adaptive()),
);
}
@@ -170,22 +176,20 @@ class _Body extends ConsumerWidget {
smallBoard: analysisPrefs.smallBoard,
tabController: controller,
pov: pov,
boardBuilder:
(context, boardSize, borderRadius) => AnalysisBoard(
options,
boardSize,
borderRadius: borderRadius,
enableDrawingShapes: enableDrawingShapes,
),
engineGaugeBuilder:
analysisState.hasAvailableEval(enginePrefs) && showEvaluationGauge
? (context, orientation) {
return orientation == Orientation.portrait
? EngineGauge(
boardBuilder: (context, boardSize, borderRadius) => AnalysisBoard(
options,
boardSize,
borderRadius: borderRadius,
enableDrawingShapes: enableDrawingShapes,
),
engineGaugeBuilder: analysisState.hasAvailableEval(enginePrefs) && showEvaluationGauge
? (context, orientation) {
return orientation == Orientation.portrait
? EngineGauge(
displayMode: EngineGaugeDisplayMode.horizontal,
params: analysisState.engineGaugeParams(enginePrefs),
)
: Container(
: Container(
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(4.0)),
child: EngineGauge(
@@ -193,27 +197,25 @@ class _Body extends ConsumerWidget {
params: analysisState.engineGaugeParams(enginePrefs),
),
);
}
: null,
engineLines:
isEngineAvailable && numEvalLines > 0
? EngineLines(
onTapMove: ref.read(ctrlProvider.notifier).onUserMove,
savedEval: currentNode.eval,
isGameOver: currentNode.position.isGameOver,
)
: null,
}
: null,
engineLines: isEngineAvailable && numEvalLines > 0
? EngineLines(
onTapMove: ref.read(ctrlProvider.notifier).onUserMove,
savedEval: currentNode.eval,
isGameOver: currentNode.position.isGameOver,
)
: null,
bottomBar: _BottomBar(options: options),
children: [
OpeningExplorerView(
shouldDisplayGames: analysisState.isComputerAnalysisAllowed,
position: currentNode.position,
opening:
kOpeningAllowedVariants.contains(analysisState.variant)
? analysisState.currentNode.isRoot
? LightOpening(eco: '', name: context.l10n.startPosition)
: analysisState.currentNode.opening ?? analysisState.currentBranchOpening
: null,
opening: kOpeningAllowedVariants.contains(analysisState.variant)
? analysisState.currentNode.isRoot
? LightOpening(eco: '', name: context.l10n.startPosition)
: analysisState.currentNode.opening ?? analysisState.currentBranchOpening
: null,
onMoveSelected: (move) {
ref.read(ctrlProvider.notifier).onUserMove(move);
},
@@ -256,16 +258,16 @@ class _BottomBar extends ConsumerWidget {
label: context.l10n.toggleLocalEvaluation,
onTap:
analysisState.isEngineAllowed &&
snapshot.connectionState != ConnectionState.waiting
? () async {
toggleFuture = ref.read(ctrlProvider.notifier).toggleEngine();
try {
await toggleFuture;
} finally {
toggleFuture = null;
}
snapshot.connectionState != ConnectionState.waiting
? () async {
toggleFuture = ref.read(ctrlProvider.notifier).toggleEngine();
try {
await toggleFuture;
} finally {
toggleFuture = null;
}
: null,
}
: null,
icon: CupertinoIcons.gauge,
highlighted: analysisState.isEngineAvailable(evalPrefs),
);
@@ -36,17 +36,15 @@ class AnalysisSettingsScreen extends ConsumerWidget {
SwitchSettingTile(
title: Text(context.l10n.inlineNotation),
value: prefs.inlineNotation,
onChanged:
(value) =>
ref.read(analysisPreferencesProvider.notifier).toggleInlineNotation(),
onChanged: (value) =>
ref.read(analysisPreferencesProvider.notifier).toggleInlineNotation(),
),
SwitchSettingTile(
// TODO: translate
title: const Text('Small board'),
value: prefs.smallBoard,
onChanged:
(value) =>
ref.read(analysisPreferencesProvider.notifier).toggleSmallBoard(),
onChanged: (value) =>
ref.read(analysisPreferencesProvider.notifier).toggleSmallBoard(),
),
],
),
@@ -63,48 +61,37 @@ class AnalysisSettingsScreen extends ConsumerWidget {
),
AnimatedCrossFade(
duration: const Duration(milliseconds: 300),
crossFadeState:
value.isComputerAnalysisAllowedAndEnabled
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
crossFadeState: value.isComputerAnalysisAllowedAndEnabled
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
firstChild: const SizedBox.shrink(),
secondChild: Column(
children: [
SwitchSettingTile(
title: Text(context.l10n.evaluationGauge),
value: prefs.showEvaluationGauge,
onChanged:
(value) =>
ref
.read(analysisPreferencesProvider.notifier)
.toggleShowEvaluationGauge(),
onChanged: (value) => ref
.read(analysisPreferencesProvider.notifier)
.toggleShowEvaluationGauge(),
),
SwitchSettingTile(
title: Text(context.l10n.toggleGlyphAnnotations),
value: prefs.showAnnotations,
onChanged:
(_) =>
ref
.read(analysisPreferencesProvider.notifier)
.toggleAnnotations(),
onChanged: (_) =>
ref.read(analysisPreferencesProvider.notifier).toggleAnnotations(),
),
SwitchSettingTile(
title: Text(context.l10n.mobileShowComments),
value: prefs.showPgnComments,
onChanged:
(_) =>
ref
.read(analysisPreferencesProvider.notifier)
.togglePgnComments(),
onChanged: (_) =>
ref.read(analysisPreferencesProvider.notifier).togglePgnComments(),
),
SwitchSettingTile(
title: Text(context.l10n.bestMoveArrow),
value: prefs.showBestMoveArrow,
onChanged:
(value) =>
ref
.read(analysisPreferencesProvider.notifier)
.toggleShowBestMoveArrow(),
onChanged: (value) => ref
.read(analysisPreferencesProvider.notifier)
.toggleShowBestMoveArrow(),
),
],
),
@@ -113,10 +100,9 @@ class AnalysisSettingsScreen extends ConsumerWidget {
),
AnimatedCrossFade(
duration: const Duration(milliseconds: 300),
crossFadeState:
value.isComputerAnalysisAllowedAndEnabled
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
crossFadeState: value.isComputerAnalysisAllowedAndEnabled
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
firstChild: const SizedBox.shrink(),
secondChild: EngineSettingsWidget(
onSetEngineSearchTime: (value) {
@@ -134,14 +120,13 @@ class AnalysisSettingsScreen extends ConsumerWidget {
children: [
ListTile(
title: Text(context.l10n.openingExplorer),
onTap:
() => showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
showDragHandle: true,
isDismissible: true,
builder: (_) => const OpeningExplorerSettings(),
),
onTap: () => showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
showDragHandle: true,
isDismissible: true,
builder: (_) => const OpeningExplorerSettings(),
),
),
],
),
@@ -118,44 +118,43 @@ class _EditPgnTagsFormState extends ConsumerState<_EditPgnTagsForm> {
child: ListView(
children: [
Column(
children:
pgnHeaders.entries
.where((e) => value != ShowRatings.no || !_ratingHeaders.contains(e.key))
.mapIndexed((index, e) {
return _EditablePgnField(
entry: e,
controller: _controllers[e.key]!,
focusNode: _focusNodes[e.key]!,
onTap: () {
_controllers[e.key]!.selection = TextSelection(
baseOffset: 0,
extentOffset: _controllers[e.key]!.text.length,
);
if (e.key == 'Result') {
_showResultChoicePicker(
e,
context: context,
onEntryChanged: () {
focusAndSelectNextField(index, pgnHeaders);
},
);
} else if (e.key == 'Date') {
_showDatePicker(
e,
context: context,
onEntryChanged: () {
focusAndSelectNextField(index, pgnHeaders);
},
);
}
},
onSubmitted: (value) {
ref.read(ctrlProvider.notifier).updatePgnHeader(e.key, value);
focusAndSelectNextField(index, pgnHeaders);
},
children: pgnHeaders.entries
.where((e) => value != ShowRatings.no || !_ratingHeaders.contains(e.key))
.mapIndexed((index, e) {
return _EditablePgnField(
entry: e,
controller: _controllers[e.key]!,
focusNode: _focusNodes[e.key]!,
onTap: () {
_controllers[e.key]!.selection = TextSelection(
baseOffset: 0,
extentOffset: _controllers[e.key]!.text.length,
);
})
.toList(),
if (e.key == 'Result') {
_showResultChoicePicker(
e,
context: context,
onEntryChanged: () {
focusAndSelectNextField(index, pgnHeaders);
},
);
} else if (e.key == 'Date') {
_showDatePicker(
e,
context: context,
onEntryChanged: () {
focusAndSelectNextField(index, pgnHeaders);
},
);
}
},
onSubmitted: (value) {
ref.read(ctrlProvider.notifier).updatePgnHeader(e.key, value);
focusAndSelectNextField(index, pgnHeaders);
},
);
})
.toList(),
),
Padding(
padding: const EdgeInsets.all(24.0),
@@ -166,10 +165,9 @@ class _EditPgnTagsFormState extends ConsumerState<_EditPgnTagsForm> {
launchShareDialog(
context,
ShareParams(
text:
ref
.read(analysisControllerProvider(widget.options).notifier)
.makeExportPgn(),
text: ref
.read(analysisControllerProvider(widget.options).notifier)
.makeExportPgn(),
),
);
},
@@ -195,34 +193,35 @@ class _EditPgnTagsFormState extends ConsumerState<_EditPgnTagsForm> {
if (Theme.of(context).platform == TargetPlatform.iOS) {
return showCupertinoModalPopup<void>(
context: context,
builder:
(BuildContext context) => Container(
height: 216,
padding: const EdgeInsets.only(top: 6.0),
margin: EdgeInsets.only(bottom: MediaQuery.viewInsetsOf(context).bottom),
color: CupertinoColors.systemBackground.resolveFrom(context),
child: SafeArea(
top: false,
child: CupertinoDatePicker(
mode: CupertinoDatePickerMode.date,
initialDateTime:
entry.value.isNotEmpty ? _dateFormatter.parse(entry.value) : DateTime.now(),
onDateTimeChanged: (DateTime newDateTime) {
final newDate = _dateFormatter.format(newDateTime);
ref.read(ctrlProvider.notifier).updatePgnHeader(entry.key, newDate);
_controllers[entry.key]!.text = newDate;
},
),
),
builder: (BuildContext context) => Container(
height: 216,
padding: const EdgeInsets.only(top: 6.0),
margin: EdgeInsets.only(bottom: MediaQuery.viewInsetsOf(context).bottom),
color: CupertinoColors.systemBackground.resolveFrom(context),
child: SafeArea(
top: false,
child: CupertinoDatePicker(
mode: CupertinoDatePickerMode.date,
initialDateTime: entry.value.isNotEmpty
? _dateFormatter.parse(entry.value)
: DateTime.now(),
onDateTimeChanged: (DateTime newDateTime) {
final newDate = _dateFormatter.format(newDateTime);
ref.read(ctrlProvider.notifier).updatePgnHeader(entry.key, newDate);
_controllers[entry.key]!.text = newDate;
},
),
),
),
).then((_) {
onEntryChanged();
});
} else {
return showDatePicker(
context: context,
initialDate:
entry.value.isNotEmpty ? _dateFormatter.parse(entry.value) : DateTime.now(),
initialDate: entry.value.isNotEmpty
? _dateFormatter.parse(entry.value)
: DateTime.now(),
firstDate: DateTime(1900),
lastDate: DateTime(2100),
)
@@ -295,10 +294,9 @@ class _EditablePgnField extends StatelessWidget {
focusNode: focusNode,
controller: controller,
textInputAction: TextInputAction.next,
keyboardType:
entry.key == 'WhiteElo' || entry.key == 'BlackElo'
? TextInputType.number
: TextInputType.text,
keyboardType: entry.key == 'WhiteElo' || entry.key == 'BlackElo'
? TextInputType.number
: TextInputType.text,
onTap: onTap,
onSubmitted: onSubmitted,
),
@@ -51,10 +51,9 @@ class EngineSettingsWidget extends ConsumerWidget {
children: [
TextSpan(
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18),
text:
prefs.engineSearchTime.inSeconds == 3600
? ''
: '${prefs.engineSearchTime.inSeconds}s',
text: prefs.engineSearchTime.inSeconds == 3600
? ''
: '${prefs.engineSearchTime.inSeconds}s',
),
],
),
+131 -128
View File
@@ -58,132 +58,135 @@ class ServerAnalysisSummary extends ConsumerWidget {
return playersAnalysis != null
? ListView(
children: [
if (currentGameAnalysis == options.gameId)
const Padding(padding: EdgeInsets.only(top: 16.0), child: WaitingForServerAnalysis()),
AcplChart(options),
Center(
child: SizedBox(
width: math.min(MediaQuery.sizeOf(context).width, 500),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Table(
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
columnWidths: const {
0: FlexColumnWidth(1),
1: FlexColumnWidth(1),
2: FlexColumnWidth(1),
},
children: [
TableRow(
decoration: const BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.grey)),
),
children: [
_SummaryPlayerName(Side.white, pgnHeaders),
Center(
child: Text(
pgnHeaders.get('Result') ?? '',
style: const TextStyle(fontWeight: FontWeight.bold),
),
children: [
if (currentGameAnalysis == options.gameId)
const Padding(
padding: EdgeInsets.only(top: 16.0),
child: WaitingForServerAnalysis(),
),
AcplChart(options),
Center(
child: SizedBox(
width: math.min(MediaQuery.sizeOf(context).width, 500),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Table(
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
columnWidths: const {
0: FlexColumnWidth(1),
1: FlexColumnWidth(1),
2: FlexColumnWidth(1),
},
children: [
TableRow(
decoration: const BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.grey)),
),
_SummaryPlayerName(Side.black, pgnHeaders),
],
),
if (playersAnalysis.white.accuracy != null &&
playersAnalysis.black.accuracy != null)
TableRow(
children: [
_SummaryNumber('${playersAnalysis.white.accuracy}%'),
_SummaryPlayerName(Side.white, pgnHeaders),
Center(
heightFactor: 1.8,
child: Text(context.l10n.accuracy, softWrap: true),
),
_SummaryNumber('${playersAnalysis.black.accuracy}%'),
],
),
for (final item in [
(
playersAnalysis.white.inaccuracies.toString(),
context.l10n.nbInaccuracies(2).replaceAll('2', '').trim().capitalize(),
playersAnalysis.black.inaccuracies.toString(),
),
(
playersAnalysis.white.mistakes.toString(),
context.l10n.nbMistakes(2).replaceAll('2', '').trim().capitalize(),
playersAnalysis.black.mistakes.toString(),
),
(
playersAnalysis.white.blunders.toString(),
context.l10n.nbBlunders(2).replaceAll('2', '').trim().capitalize(),
playersAnalysis.black.blunders.toString(),
),
])
TableRow(
children: [
_SummaryNumber(item.$1),
Center(heightFactor: 1.2, child: Text(item.$2, softWrap: true)),
_SummaryNumber(item.$3),
],
),
if (playersAnalysis.white.acpl != null && playersAnalysis.black.acpl != null)
TableRow(
children: [
_SummaryNumber(playersAnalysis.white.acpl.toString()),
Center(
heightFactor: 1.5,
child: Text(
context.l10n.averageCentipawnLoss,
softWrap: true,
textAlign: TextAlign.center,
pgnHeaders.get('Result') ?? '',
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
_SummaryNumber(playersAnalysis.black.acpl.toString()),
_SummaryPlayerName(Side.black, pgnHeaders),
],
),
],
if (playersAnalysis.white.accuracy != null &&
playersAnalysis.black.accuracy != null)
TableRow(
children: [
_SummaryNumber('${playersAnalysis.white.accuracy}%'),
Center(
heightFactor: 1.8,
child: Text(context.l10n.accuracy, softWrap: true),
),
_SummaryNumber('${playersAnalysis.black.accuracy}%'),
],
),
for (final item in [
(
playersAnalysis.white.inaccuracies.toString(),
context.l10n.nbInaccuracies(2).replaceAll('2', '').trim().capitalize(),
playersAnalysis.black.inaccuracies.toString(),
),
(
playersAnalysis.white.mistakes.toString(),
context.l10n.nbMistakes(2).replaceAll('2', '').trim().capitalize(),
playersAnalysis.black.mistakes.toString(),
),
(
playersAnalysis.white.blunders.toString(),
context.l10n.nbBlunders(2).replaceAll('2', '').trim().capitalize(),
playersAnalysis.black.blunders.toString(),
),
])
TableRow(
children: [
_SummaryNumber(item.$1),
Center(heightFactor: 1.2, child: Text(item.$2, softWrap: true)),
_SummaryNumber(item.$3),
],
),
if (playersAnalysis.white.acpl != null &&
playersAnalysis.black.acpl != null)
TableRow(
children: [
_SummaryNumber(playersAnalysis.white.acpl.toString()),
Center(
heightFactor: 1.5,
child: Text(
context.l10n.averageCentipawnLoss,
softWrap: true,
textAlign: TextAlign.center,
),
),
_SummaryNumber(playersAnalysis.black.acpl.toString()),
],
),
],
),
),
),
),
),
],
)
],
)
: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Spacer(),
if (currentGameAnalysis == options.gameId)
const Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 16.0),
child: WaitingForServerAnalysis(),
),
)
else
Center(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Builder(
builder: (context) {
Future<void>? pendingRequest;
return StatefulBuilder(
builder: (context, setState) {
return FutureBuilder<void>(
future: pendingRequest,
builder: (context, snapshot) {
return FilledButton.tonal(
onPressed:
ref.watch(authSessionProvider) == null
? () {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Spacer(),
if (currentGameAnalysis == options.gameId)
const Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 16.0),
child: WaitingForServerAnalysis(),
),
)
else
Center(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Builder(
builder: (context) {
Future<void>? pendingRequest;
return StatefulBuilder(
builder: (context, setState) {
return FutureBuilder<void>(
future: pendingRequest,
builder: (context, snapshot) {
return FilledButton.tonal(
onPressed: ref.watch(authSessionProvider) == null
? () {
showSnackBar(
context,
context.l10n.youNeedAnAccountToDoThat,
);
}
: snapshot.connectionState == ConnectionState.waiting
? null
: () {
: snapshot.connectionState == ConnectionState.waiting
? null
: () {
setState(() {
pendingRequest = ref
.read(ctrlProvider.notifier)
@@ -199,19 +202,19 @@ class ServerAnalysisSummary extends ConsumerWidget {
});
});
},
child: Text(context.l10n.requestAComputerAnalysis),
);
},
);
},
);
},
child: Text(context.l10n.requestAComputerAnalysis),
);
},
);
},
);
},
),
),
),
),
const Spacer(),
],
);
const Spacer(),
],
);
}
}
@@ -251,12 +254,12 @@ class _SummaryPlayerName extends StatelessWidget {
@override
Widget build(BuildContext context) {
final playerTitle =
side == Side.white ? pgnHeaders.get('WhiteTitle') : pgnHeaders.get('BlackTitle');
final playerName =
side == Side.white
? pgnHeaders.get('White') ?? context.l10n.white
: pgnHeaders.get('Black') ?? context.l10n.black;
final playerTitle = side == Side.white
? pgnHeaders.get('WhiteTitle')
: pgnHeaders.get('BlackTitle');
final playerName = side == Side.white
? pgnHeaders.get('White') ?? context.l10n.white
: pgnHeaders.get('Black') ?? context.l10n.black;
final brightness = Theme.of(context).brightness;
@@ -270,8 +273,8 @@ class _SummaryPlayerName extends StatelessWidget {
Icon(
side == Side.white
? brightness == Brightness.light
? CupertinoIcons.circle
: CupertinoIcons.circle_filled
? CupertinoIcons.circle
: CupertinoIcons.circle_filled
: brightness == Brightness.light
? CupertinoIcons.circle_filled
: CupertinoIcons.circle,
+3 -4
View File
@@ -33,10 +33,9 @@ class AnalysisTreeView extends ConsumerWidget {
shouldShowComputerAnalysis: enableComputerAnalysis,
shouldShowComments: enableComputerAnalysis && prefs.showPgnComments,
shouldShowAnnotations: enableComputerAnalysis && prefs.showAnnotations,
displayMode:
prefs.inlineNotation
? PgnTreeDisplayMode.inlineNotation
: PgnTreeDisplayMode.twoColumn,
displayMode: prefs.inlineNotation
? PgnTreeDisplayMode.inlineNotation
: PgnTreeDisplayMode.twoColumn,
),
if (analysisState.archivedGame != null)
Padding(
@@ -27,20 +27,17 @@ class BoardEditorFilters extends ConsumerWidget {
padding: Styles.horizontalBodyPadding,
child: Wrap(
spacing: 8.0,
children:
Side.values.map((side) {
return ChoiceChip(
label: Text(
side == Side.white ? context.l10n.whitePlays : context.l10n.blackPlays,
),
selected: editorState.sideToPlay == side,
onSelected: (selected) {
if (selected) {
ref.read(editorController.notifier).setSideToPlay(side);
}
},
);
}).toList(),
children: Side.values.map((side) {
return ChoiceChip(
label: Text(side == Side.white ? context.l10n.whitePlays : context.l10n.blackPlays),
selected: editorState.sideToPlay == side,
onSelected: (selected) {
if (selected) {
ref.read(editorController.notifier).setSideToPlay(side);
}
},
);
}).toList(),
),
),
Padding(
@@ -81,16 +78,15 @@ class BoardEditorFilters extends ConsumerWidget {
),
Wrap(
spacing: 8.0,
children:
editorState.enPassantOptions.squares.map((square) {
return ChoiceChip(
label: Text(square.name),
selected: editorState.enPassantSquare == square,
onSelected: (selected) {
ref.read(editorController.notifier).toggleEnPassantSquare(square);
},
);
}).toList(),
children: editorState.enPassantOptions.squares.map((square) {
return ChoiceChip(
label: Text(square.name),
selected: editorState.enPassantSquare == square,
onSelected: (selected) {
ref.read(editorController.notifier).toggleEnPassantSquare(square);
},
);
}).toList(),
),
],
],
@@ -116,7 +112,10 @@ class SearchPositionScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final tabBar = TabBar(
tabs: [Tab(text: context.l10n.openings), Tab(text: context.l10n.endgamePositions)],
tabs: [
Tab(text: context.l10n.openings),
Tab(text: context.l10n.endgamePositions),
],
);
final tabBarView = TabBarView(
children: [
@@ -53,10 +53,9 @@ class BoardEditorScreen extends ConsumerWidget {
final isTablet = isTabletOrLarger(context);
final remainingHeight = constraints.maxHeight - defaultBoardSize;
final isSmallScreen = remainingHeight < kSmallRemainingHeightLeftBoardThreshold;
final boardSize =
isTablet || isSmallScreen
? defaultBoardSize - kTabletBoardTableSidePadding * 2
: defaultBoardSize;
final boardSize = isTablet || isSmallScreen
? defaultBoardSize - kTabletBoardTableSidePadding * 2
: defaultBoardSize;
final direction = aspectRatio > 1 ? Axis.horizontal : Axis.vertical;
@@ -126,16 +125,13 @@ class _BoardEditor extends ConsumerWidget {
boxShadow: isTablet ? boardShadows : const <BoxShadow>[],
),
pointerMode: editorState.editorPointerMode,
onDiscardedPiece:
(Square square) =>
ref.read(boardEditorControllerProvider(initialFen).notifier).discardPiece(square),
onDroppedPiece:
(Square? origin, Square dest, Piece piece) => ref
.read(boardEditorControllerProvider(initialFen).notifier)
.movePiece(origin, dest, piece),
onEditedSquare:
(Square square) =>
ref.read(boardEditorControllerProvider(initialFen).notifier).editSquare(square),
onDiscardedPiece: (Square square) =>
ref.read(boardEditorControllerProvider(initialFen).notifier).discardPiece(square),
onDroppedPiece: (Square? origin, Square dest, Piece piece) => ref
.read(boardEditorControllerProvider(initialFen).notifier)
.movePiece(origin, dest, piece),
onEditedSquare: (Square square) =>
ref.read(boardEditorControllerProvider(initialFen).notifier).editSquare(square),
);
}
}
@@ -190,13 +186,12 @@ class _PieceMenuState extends ConsumerState<_PieceMenu> {
height: squareSize,
child: ColoredBox(
key: Key('drag-button-${widget.side.name}'),
color:
editorState.editorPointerMode == EditorPointerMode.drag
? context.lichessColors.good
: Colors.transparent,
color: editorState.editorPointerMode == EditorPointerMode.drag
? context.lichessColors.good
: Colors.transparent,
child: GestureDetector(
onTap:
() => ref.read(editorController.notifier).updateMode(EditorPointerMode.drag),
onTap: () =>
ref.read(editorController.notifier).updateMode(EditorPointerMode.drag),
child: Icon(CupertinoIcons.hand_draw, size: 0.9 * squareSize),
),
),
@@ -213,9 +208,9 @@ class _PieceMenuState extends ConsumerState<_PieceMenu> {
key: Key('piece-button-${piece.color.name}-${piece.role.name}'),
color:
ref.read(boardEditorControllerProvider(widget.initialFen)).activePieceOnEdit ==
piece
? ColorScheme.of(context).primary
: Colors.transparent,
piece
? ColorScheme.of(context).primary
: Colors.transparent,
child: GestureDetector(
child: Draggable(
data: Piece(role: role, color: widget.side),
@@ -225,14 +220,11 @@ class _PieceMenuState extends ConsumerState<_PieceMenu> {
pieceAssets: boardPrefs.pieceSet.assets,
),
child: pieceWidget,
onDragEnd:
(_) =>
ref.read(editorController.notifier).updateMode(EditorPointerMode.drag),
onDragEnd: (_) =>
ref.read(editorController.notifier).updateMode(EditorPointerMode.drag),
),
onTap:
() => ref
.read(editorController.notifier)
.updateMode(EditorPointerMode.edit, piece),
onTap: () =>
ref.read(editorController.notifier).updateMode(EditorPointerMode.edit, piece),
),
);
}),
@@ -241,17 +233,13 @@ class _PieceMenuState extends ConsumerState<_PieceMenu> {
width: squareSize,
height: squareSize,
child: ColoredBox(
color:
editorState.deletePiecesActive
? context.lichessColors.error
: Colors.transparent,
color: editorState.deletePiecesActive
? context.lichessColors.error
: Colors.transparent,
child: GestureDetector(
onTap:
() => {
ref
.read(editorController.notifier)
.updateMode(EditorPointerMode.edit, null),
},
onTap: () => {
ref.read(editorController.notifier).updateMode(EditorPointerMode.edit, null),
},
child: Icon(CupertinoIcons.delete, size: 0.8 * squareSize),
),
),
@@ -279,40 +267,38 @@ class _BottomBar extends ConsumerWidget {
BottomBarButton(
icon: Icons.menu,
label: context.l10n.menu,
onTap:
() => showAdaptiveActionSheet<void>(
context: context,
actions: [
BottomSheetAction(
makeLabel: (context) => Text(context.l10n.startPosition),
onPressed: () {
ref.read(editorController.notifier).loadFen(kInitialEPD);
},
),
BottomSheetAction(
makeLabel: (context) => Text(context.l10n.loadPosition),
onPressed: () {
final notifier = ref.read(editorController.notifier);
Navigator.of(context).push(
SearchPositionScreen.buildRoute(
context,
onPositionSelected:
(position) => {
notifier.loadFen(position.fen),
Navigator.of(context).pop(),
},
),
);
},
),
BottomSheetAction(
makeLabel: (context) => Text(context.l10n.clearBoard),
onPressed: () {
ref.read(editorController.notifier).loadFen(kEmptyFen);
},
),
],
onTap: () => showAdaptiveActionSheet<void>(
context: context,
actions: [
BottomSheetAction(
makeLabel: (context) => Text(context.l10n.startPosition),
onPressed: () {
ref.read(editorController.notifier).loadFen(kInitialEPD);
},
),
BottomSheetAction(
makeLabel: (context) => Text(context.l10n.loadPosition),
onPressed: () {
final notifier = ref.read(editorController.notifier);
Navigator.of(context).push(
SearchPositionScreen.buildRoute(
context,
onPositionSelected: (position) => {
notifier.loadFen(position.fen),
Navigator.of(context).pop(),
},
),
);
},
),
BottomSheetAction(
makeLabel: (context) => Text(context.l10n.clearBoard),
onPressed: () {
ref.read(editorController.notifier).loadFen(kEmptyFen);
},
),
],
),
),
BottomBarButton(
key: const Key('flip-button'),
@@ -325,36 +311,35 @@ class _BottomBar extends ConsumerWidget {
label: context.l10n.analysis,
onTap:
editorState.pgn != null &&
// 1 condition (of many) where stockfish segfaults
pieceCount > 0 &&
pieceCount <= 32
? () {
Navigator.of(context).push(
AnalysisScreen.buildRoute(
context,
AnalysisOptions(
orientation: editorState.orientation,
standalone: (
pgn: editorState.pgn!,
isComputerAnalysisAllowed: true,
variant: Variant.fromPosition,
),
// 1 condition (of many) where stockfish segfaults
pieceCount > 0 &&
pieceCount <= 32
? () {
Navigator.of(context).push(
AnalysisScreen.buildRoute(
context,
AnalysisOptions(
orientation: editorState.orientation,
standalone: (
pgn: editorState.pgn!,
isComputerAnalysisAllowed: true,
variant: Variant.fromPosition,
),
),
);
}
: null,
),
);
}
: null,
icon: Icons.biotech,
),
BottomBarButton(
label: 'Filters',
onTap:
() => showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) => BoardEditorFilters(initialFen: initialFen),
showDragHandle: true,
constraints: BoxConstraints(minHeight: MediaQuery.sizeOf(context).height * 0.5),
),
onTap: () => showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) => BoardEditorFilters(initialFen: initialFen),
showDragHandle: true,
constraints: BoxConstraints(minHeight: MediaQuery.sizeOf(context).height * 0.5),
),
icon: Icons.tune,
),
],
@@ -45,28 +45,27 @@ class BroadcastBoardsTab extends ConsumerWidget {
AsyncData(:final value) =>
value.games.isEmpty
? Padding(
padding: Styles.bodyPadding,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(Icons.info, size: 30),
const SizedBox(height: 8.0),
Text(context.l10n.broadcastNoBoardsYet, textAlign: TextAlign.center),
],
),
)
padding: Styles.bodyPadding,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(Icons.info, size: 30),
const SizedBox(height: 8.0),
Text(context.l10n.broadcastNoBoardsYet, textAlign: TextAlign.center),
],
),
)
: BroadcastPreview(
games:
showOnlyOngoingGames
? value.games.values.where((game) => game.isOngoing).toIList()
: value.games.values.toIList(),
tournamentId: tournamentId,
roundId: roundId,
title: value.round.name,
tournamentSlug: tournamentSlug,
roundSlug: value.round.slug,
),
games: showOnlyOngoingGames
? value.games.values.where((game) => game.isOngoing).toIList()
: value.games.values.toIList(),
tournamentId: tournamentId,
roundId: roundId,
title: value.round.name,
tournamentSlug: tournamentSlug,
roundSlug: value.round.slug,
),
AsyncError(:final error) => Center(
child: Center(child: Text('Could not load broadcast: $error')),
),
@@ -241,13 +240,9 @@ class _ObservedBoardThumbnailState extends ConsumerState<ObservedBoardThumbnail>
orientation: Side.white,
fen: widget.game.fen,
showEvaluationBar: widget.showEvaluationBar,
whiteWinningChances:
(widget.game.cp != null || widget.game.mate != null)
? ExternalEval(
cp: widget.game.cp,
mate: widget.game.mate,
).winningChances(Side.white)
: null,
whiteWinningChances: (widget.game.cp != null || widget.game.mate != null)
? ExternalEval(cp: widget.game.cp, mate: widget.game.mate).winningChances(Side.white)
: null,
lastMove: widget.game.lastMove,
size: widget.boardSize,
header: _PlayerWidget(
@@ -328,14 +323,13 @@ class _PlayerWidget extends StatelessWidget {
CountdownClockBuilder(
timeLeft: clock,
active: isClockActive,
builder:
(context, timeLeft) => Text(
timeLeft.toHoursMinutesSeconds(),
style: TextStyle(
color: isClockActive ? Colors.orange[900] : null,
fontFeatures: const [FontFeature.tabularFigures()],
),
),
builder: (context, timeLeft) => Text(
timeLeft.toHoursMinutesSeconds(),
style: TextStyle(
color: isClockActive ? Colors.orange[900] : null,
fontFeatures: const [FontFeature.tabularFigures()],
),
),
tickInterval: const Duration(seconds: 1),
clockUpdatedAt: game.updatedClockAt,
),
+9 -10
View File
@@ -268,19 +268,18 @@ class _BroadcastCarouselItemState extends State<BroadcastCarouselItem> {
frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {
return AnimatedOpacity(
duration: const Duration(milliseconds: 500),
opacity:
wasSynchronouslyLoaded
? _tapDown
? 1.0
: kDefaultCardOpacity
: frame == null
? 0
: 1,
opacity: wasSynchronouslyLoaded
? _tapDown
? 1.0
: kDefaultCardOpacity
: frame == null
? 0
: 1,
child: child,
);
},
errorBuilder:
(context, error, stackTrace) => const Image(image: kDefaultBroadcastImage),
errorBuilder: (context, error, stackTrace) =>
const Image(image: kDefaultBroadcastImage),
),
Expanded(
child: _BroadcastCardContent(broadcast: widget.broadcast, cardColors: _cardColors),
+87 -104
View File
@@ -102,17 +102,16 @@ class _BroadcastGameScreenState extends ConsumerState<BroadcastGameScreen>
@override
Widget build(BuildContext context) {
final title =
(widget.title != null)
? Text(widget.title!, overflow: TextOverflow.ellipsis, maxLines: 1)
: switch (ref.watch(broadcastGameScreenTitleProvider(widget.roundId))) {
AsyncData(value: final title) => Text(
title,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
_ => const SizedBox.shrink(),
};
final title = (widget.title != null)
? Text(widget.title!, overflow: TextOverflow.ellipsis, maxLines: 1)
: switch (ref.watch(broadcastGameScreenTitleProvider(widget.roundId))) {
AsyncData(value: final title) => Text(
title,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
_ => const SizedBox.shrink(),
};
final asyncEval = ref.watch(broadcastGameEvalProvider(widget.roundId, widget.gameId));
final asyncIsEngineAvailable = ref.watch(
isBroadcastEngineAvailableProvider(widget.roundId, widget.gameId),
@@ -174,9 +173,8 @@ class _Body extends ConsumerWidget {
smallBoard: analysisPrefs.smallBoard,
pov: pov,
tabController: tabController,
boardBuilder:
(context, boardSize, borderRadius) =>
_BroadcastBoard(roundId, gameId, boardSize, borderRadius),
boardBuilder: (context, boardSize, borderRadius) =>
_BroadcastBoard(roundId, gameId, boardSize, borderRadius),
boardHeader: _PlayerWidget(
tournamentId: tournamentId,
roundId: roundId,
@@ -189,15 +187,14 @@ class _Body extends ConsumerWidget {
gameId: gameId,
widgetPosition: _PlayerWidgetPosition.bottom,
),
engineGaugeBuilder:
state.hasAvailableEval(enginePrefs) && showEvaluationGauge
? (context, orientation) {
return orientation == Orientation.portrait
? EngineGauge(
engineGaugeBuilder: state.hasAvailableEval(enginePrefs) && showEvaluationGauge
? (context, orientation) {
return orientation == Orientation.portrait
? EngineGauge(
displayMode: EngineGaugeDisplayMode.horizontal,
params: engineGaugeParams,
)
: Container(
: Container(
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(4.0)),
child: EngineGauge(
@@ -205,19 +202,17 @@ class _Body extends ConsumerWidget {
params: engineGaugeParams,
),
);
}
: null,
engineLines:
isLocalEvaluationEnabled && numEvalLines > 0
? EngineLines(
savedEval: currentNode.eval,
isGameOver: currentNode.position.isGameOver,
onTapMove:
ref
.read(broadcastAnalysisControllerProvider(roundId, gameId).notifier)
.onUserMove,
)
: null,
}
: null,
engineLines: isLocalEvaluationEnabled && numEvalLines > 0
? EngineLines(
savedEval: currentNode.eval,
isGameOver: currentNode.position.isGameOver,
onTapMove: ref
.read(broadcastAnalysisControllerProvider(roundId, gameId).notifier)
.onUserMove,
)
: null,
bottomBar: _BroadcastGameBottomBar(
roundId: roundId,
gameId: gameId,
@@ -258,10 +253,9 @@ class _BroadcastGameTreeView extends ConsumerWidget {
shouldShowAnnotations:
analysisPrefs.enableComputerAnalysis && analysisPrefs.showAnnotations,
notifier: ref.read(ctrlProvider.notifier),
displayMode:
analysisPrefs.inlineNotation
? PgnTreeDisplayMode.inlineNotation
: PgnTreeDisplayMode.twoColumn,
displayMode: analysisPrefs.inlineNotation
? PgnTreeDisplayMode.inlineNotation
: PgnTreeDisplayMode.twoColumn,
),
);
}
@@ -314,21 +308,15 @@ class _BroadcastBoardState extends ConsumerState<_BroadcastBoard> {
broadcastAnalysisState.isComputerAnalysisEnabled && analysisPrefs.showAnnotations;
final currentNode = broadcastAnalysisState.currentNode;
final bestMoves =
showBestMoveArrow
? pickBestClientEval(
localEval: ref.watch(engineEvaluationProvider.select((value) => value.eval)),
savedEval: currentNode.eval,
)?.bestMoves
: null;
final ISet<Shape> bestMoveShapes =
bestMoves != null
? computeBestMoveShapes(
bestMoves,
currentNode.position.turn,
boardPrefs.pieceSet.assets,
)
: ISet();
final bestMoves = showBestMoveArrow
? pickBestClientEval(
localEval: ref.watch(engineEvaluationProvider.select((value) => value.eval)),
savedEval: currentNode.eval,
)?.bestMoves
: null;
final ISet<Shape> bestMoveShapes = bestMoves != null
? computeBestMoveShapes(bestMoves, currentNode.position.turn, boardPrefs.pieceSet.assets)
: ISet();
final annotation = showAnnotations ? makeAnnotation(currentNode.nags) : null;
final sanMove = currentNode.sanMove;
@@ -341,23 +329,21 @@ class _BroadcastBoardState extends ConsumerState<_BroadcastBoard> {
game: boardPrefs.toGameData(
variant: Variant.standard,
position: broadcastAnalysisState.position,
playerSide:
broadcastAnalysisState.position.isGameOver
? PlayerSide.none
: broadcastAnalysisState.position.turn == Side.white
? PlayerSide.white
: PlayerSide.black,
playerSide: broadcastAnalysisState.position.isGameOver
? PlayerSide.none
: broadcastAnalysisState.position.turn == Side.white
? PlayerSide.white
: PlayerSide.black,
promotionMove: broadcastAnalysisState.promotionMove,
onMove: (move, {isDrop, captured}) => ref.read(ctrlProvider.notifier).onUserMove(move),
onPromotionSelection: (role) => ref.read(ctrlProvider.notifier).onPromotionSelection(role),
),
shapes: userShapes.union(bestMoveShapes),
annotations:
sanMove != null && annotation != null
? altCastles.containsKey(sanMove.move.uci)
? IMap({Move.parse(altCastles[sanMove.move.uci]!)!.to: annotation})
: IMap({sanMove.move.to: annotation})
: null,
annotations: sanMove != null && annotation != null
? altCastles.containsKey(sanMove.move.uci)
? IMap({Move.parse(altCastles[sanMove.move.uci]!)!.to: annotation})
: IMap({sanMove.move.to: annotation})
: null,
settings: boardPrefs.toBoardSettings().copyWith(
borderRadius: widget.borderRadius,
boxShadow: widget.borderRadius != null ? boardShadows : const <BoxShadow>[],
@@ -409,8 +395,9 @@ class _PlayerWidget extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
switch (ref.watch(broadcastRoundGameProvider(roundId, gameId))) {
case AsyncValue(value: final game?, hasValue: true):
final broadcastAnalysisState =
ref.watch(broadcastAnalysisControllerProvider(roundId, gameId)).requireValue;
final broadcastAnalysisState = ref
.watch(broadcastAnalysisControllerProvider(roundId, gameId))
.requireValue;
final isCursorOnLiveMove =
broadcastAnalysisState.currentPath == broadcastAnalysisState.broadcastLivePath;
@@ -457,34 +444,31 @@ class _PlayerWidget extends ConsumerWidget {
if (liveClock != null || pastClock != null)
Container(
height: kAnalysisBoardHeaderOrFooterHeight,
color:
isClockActive
? ColorScheme.of(context).tertiaryContainer
: (side == sideToMove)
? ColorScheme.of(context).secondaryContainer
: Colors.transparent,
color: isClockActive
? ColorScheme.of(context).tertiaryContainer
: (side == sideToMove)
? ColorScheme.of(context).secondaryContainer
: Colors.transparent,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 6.0),
child: Center(
child:
liveClock != null
? CountdownClockBuilder(
timeLeft: liveClock,
active: isClockActive,
builder:
(context, timeLeft) => _Clock(
timeLeft: timeLeft,
isSideToMove: side == sideToMove,
isClockActive: isClockActive,
),
tickInterval: const Duration(seconds: 1),
clockUpdatedAt: game.updatedClockAt,
)
: _Clock(
timeLeft: pastClock!,
child: liveClock != null
? CountdownClockBuilder(
timeLeft: liveClock,
active: isClockActive,
builder: (context, timeLeft) => _Clock(
timeLeft: timeLeft,
isSideToMove: side == sideToMove,
isClockActive: false,
isClockActive: isClockActive,
),
tickInterval: const Duration(seconds: 1),
clockUpdatedAt: game.updatedClockAt,
)
: _Clock(
timeLeft: pastClock!,
isSideToMove: side == sideToMove,
isClockActive: false,
),
),
),
),
@@ -512,12 +496,11 @@ class _Clock extends StatelessWidget {
return Text(
timeLeft.toHoursMinutesSeconds(),
style: TextStyle(
color:
isClockActive
? ColorScheme.of(context).onTertiaryContainer
: isSideToMove
? ColorScheme.of(context).onSecondaryContainer
: null,
color: isClockActive
? ColorScheme.of(context).onTertiaryContainer
: isSideToMove
? ColorScheme.of(context).onSecondaryContainer
: null,
fontFeatures: const [FontFeature.tabularFigures()],
),
);
@@ -629,16 +612,16 @@ class _BroadcastGameBottomBar extends ConsumerWidget {
label: context.l10n.toggleLocalEvaluation,
onTap:
analysisPrefs.enableComputerAnalysis &&
snapshot.connectionState != ConnectionState.waiting
? () async {
toggleFuture = ref.read(ctrlProvider.notifier).toggleEngine();
try {
await toggleFuture;
} finally {
toggleFuture = null;
}
snapshot.connectionState != ConnectionState.waiting
? () async {
toggleFuture = ref.read(ctrlProvider.notifier).toggleEngine();
try {
await toggleFuture;
} finally {
toggleFuture = null;
}
: null,
}
: null,
icon: CupertinoIcons.gauge,
highlighted: broadcastAnalysisState.isEngineAvailable(enginePrefs),
);
@@ -39,16 +39,15 @@ class BroadcastGameSettingsScreen extends ConsumerWidget {
SwitchSettingTile(
title: Text(context.l10n.inlineNotation),
value: analysisPrefs.inlineNotation,
onChanged:
(value) =>
ref.read(analysisPreferencesProvider.notifier).toggleInlineNotation(),
onChanged: (value) =>
ref.read(analysisPreferencesProvider.notifier).toggleInlineNotation(),
),
SwitchSettingTile(
// TODO: translate
title: const Text('Small board'),
value: analysisPrefs.smallBoard,
onChanged:
(value) => ref.read(analysisPreferencesProvider.notifier).toggleSmallBoard(),
onChanged: (value) =>
ref.read(analysisPreferencesProvider.notifier).toggleSmallBoard(),
),
],
),
@@ -64,42 +63,36 @@ class BroadcastGameSettingsScreen extends ConsumerWidget {
),
AnimatedCrossFade(
duration: const Duration(milliseconds: 300),
crossFadeState:
analysisPrefs.enableComputerAnalysis
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
crossFadeState: analysisPrefs.enableComputerAnalysis
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
firstChild: const SizedBox.shrink(),
secondChild: Column(
children: [
SwitchSettingTile(
title: Text(context.l10n.evaluationGauge),
value: analysisPrefs.showEvaluationGauge,
onChanged:
(value) =>
ref
.read(analysisPreferencesProvider.notifier)
.toggleShowEvaluationGauge(),
onChanged: (value) => ref
.read(analysisPreferencesProvider.notifier)
.toggleShowEvaluationGauge(),
),
SwitchSettingTile(
title: Text(context.l10n.toggleGlyphAnnotations),
value: analysisPrefs.showAnnotations,
onChanged:
(_) => ref.read(analysisPreferencesProvider.notifier).toggleAnnotations(),
onChanged: (_) =>
ref.read(analysisPreferencesProvider.notifier).toggleAnnotations(),
),
SwitchSettingTile(
title: Text(context.l10n.mobileShowComments),
value: analysisPrefs.showPgnComments,
onChanged:
(_) => ref.read(analysisPreferencesProvider.notifier).togglePgnComments(),
onChanged: (_) =>
ref.read(analysisPreferencesProvider.notifier).togglePgnComments(),
),
SwitchSettingTile(
title: Text(context.l10n.bestMoveArrow),
value: analysisPrefs.showBestMoveArrow,
onChanged:
(value) =>
ref
.read(analysisPreferencesProvider.notifier)
.toggleShowBestMoveArrow(),
onChanged: (value) =>
ref.read(analysisPreferencesProvider.notifier).toggleShowBestMoveArrow(),
),
],
),
@@ -108,14 +101,13 @@ class BroadcastGameSettingsScreen extends ConsumerWidget {
),
AnimatedCrossFade(
duration: const Duration(milliseconds: 300),
crossFadeState:
analysisPrefs.enableComputerAnalysis
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
crossFadeState: analysisPrefs.enableComputerAnalysis
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
firstChild: const SizedBox.shrink(),
secondChild: EngineSettingsWidget(
onSetEngineSearchTime:
(value) => ref.read(controller.notifier).setEngineSearchTime(value),
onSetEngineSearchTime: (value) =>
ref.read(controller.notifier).setEngineSearchTime(value),
onSetNumEvalLines: (value) => ref.read(controller.notifier).setNumEvalLines(value),
onSetEngineCores: (value) => ref.read(controller.notifier).setEngineCores(value),
),
@@ -124,14 +116,13 @@ class BroadcastGameSettingsScreen extends ConsumerWidget {
children: [
ListTile(
title: Text(context.l10n.openingExplorer),
onTap:
() => showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
showDragHandle: true,
isDismissible: true,
builder: (_) => const OpeningExplorerSettings(),
),
onTap: () => showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
showDragHandle: true,
isDismissible: true,
builder: (_) => const OpeningExplorerSettings(),
),
),
],
),
@@ -62,37 +62,38 @@ class _BroadcastListScreenState extends State<BroadcastListScreen> {
icon: const Icon(Icons.filter_list),
// TODO: translate
semanticsLabel: 'Filter broadcasts',
onPressed:
() => showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
constraints: BoxConstraints(minHeight: MediaQuery.sizeOf(context).height * 0.4),
builder:
(_) => StatefulBuilder(
builder: (context, setLocalState) {
return BottomSheetScrollableContainer(
padding: const EdgeInsets.all(16.0),
children: [
const SizedBox(height: 16.0),
Filter<_BroadcastFilter>(
filterType: FilterType.singleChoice,
choices: _BroadcastFilter.values,
choiceSelected: (choice) => filter == choice,
choiceLabel: (category) => Text(category.l10n(context.l10n)),
onSelected: (value, selected) {
setLocalState(() => filter = value);
setState(() => filter = value);
},
),
const SizedBox(height: 16.0),
],
);
onPressed: () => showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
constraints: BoxConstraints(minHeight: MediaQuery.sizeOf(context).height * 0.4),
builder: (_) => StatefulBuilder(
builder: (context, setLocalState) {
return BottomSheetScrollableContainer(
padding: const EdgeInsets.all(16.0),
children: [
const SizedBox(height: 16.0),
Filter<_BroadcastFilter>(
filterType: FilterType.singleChoice,
choices: _BroadcastFilter.values,
choiceSelected: (choice) => filter == choice,
choiceLabel: (category) => Text(category.l10n(context.l10n)),
onSelected: (value, selected) {
setLocalState(() => filter = value);
setState(() => filter = value);
},
),
),
const SizedBox(height: 16.0),
],
);
},
),
),
);
return Scaffold(body: _Body(filter), appBar: AppBar(title: title, actions: [filterButton]));
return Scaffold(
body: _Body(filter),
appBar: AppBar(title: title, actions: [filterButton]),
);
}
}
@@ -179,24 +180,22 @@ class _BodyState extends ConsumerState<_Body> {
SliverPadding(
padding: Styles.sectionBottomPadding,
sliver: SliverList.separated(
separatorBuilder:
(context, index) => PlatformDivider(
height: 1,
indent: BroadcastListScreen._thumbnailSize(context) + 16.0 + 10.0,
),
separatorBuilder: (context, index) => PlatformDivider(
height: 1,
indent: BroadcastListScreen._thumbnailSize(context) + 16.0 + 10.0,
),
itemCount: section.$3.length,
itemBuilder:
(context, index) =>
(section.$1 == 'past' &&
broadcasts.isLoading &&
index >= section.$3.length - 1)
? const Shimmer(
child: ShimmerLoading(
isLoading: true,
child: BroadcastListTile.loading(),
),
)
: BroadcastListTile(broadcast: section.$3[index]),
itemBuilder: (context, index) =>
(section.$1 == 'past' &&
broadcasts.isLoading &&
index >= section.$3.length - 1)
? const Shimmer(
child: ShimmerLoading(
isLoading: true,
child: BroadcastListTile.loading(),
),
)
: BroadcastListTile(broadcast: section.$3[index]),
),
),
],
@@ -322,16 +321,15 @@ class BroadcastListTile extends StatelessWidget {
final devicePixelRatio = MediaQuery.devicePixelRatioOf(context);
final leading =
broadcast.tour.imageUrl != null
? Image.network(
broadcast.tour.imageUrl!,
width: thumbnailSize,
cacheWidth: (thumbnailSize * devicePixelRatio).toInt(),
fit: BoxFit.cover,
errorBuilder: (context, _, _) => const Icon(LichessIcons.radio_tower_lichess),
)
: Image(image: kDefaultBroadcastImage, width: thumbnailSize);
final leading = broadcast.tour.imageUrl != null
? Image.network(
broadcast.tour.imageUrl!,
width: thumbnailSize,
cacheWidth: (thumbnailSize * devicePixelRatio).toInt(),
fit: BoxFit.cover,
errorBuilder: (context, _, _) => const Icon(LichessIcons.radio_tower_lichess),
)
: Image(image: kDefaultBroadcastImage, width: thumbnailSize);
final title = Text(broadcast.title, maxLines: 2, overflow: TextOverflow.ellipsis);
@@ -209,7 +209,10 @@ class _OverallStatPlayer extends StatelessWidget {
),
),
if (fideId != null)
SizedBox(width: statWidth, child: _StatCard('FIDE ID', value: fideId.toString())),
SizedBox(
width: statWidth,
child: _StatCard('FIDE ID', value: fideId.toString()),
),
],
),
if (score != null || performance != null || ratingDiff != null)
@@ -297,10 +300,9 @@ class _GameResultRow extends StatelessWidget {
Expanded(flex: 5, child: BroadcastPlayerWidget(player: opponent, showRating: false)),
Expanded(
flex: 3,
child:
(opponentRating != null)
? Center(child: Text(opponentRating.toString()))
: const SizedBox.shrink(),
child: (opponentRating != null)
? Center(child: Text(opponentRating.toString()))
: const SizedBox.shrink(),
),
SizedBox(
width: 30,
@@ -311,11 +313,11 @@ class _GameResultRow extends StatelessWidget {
decoration: BoxDecoration(
border:
(Theme.of(context).brightness == Brightness.light &&
color == Side.white ||
Theme.of(context).brightness == Brightness.dark &&
color == Side.black)
? Border.all(width: 2.0, color: ColorScheme.of(context).outline)
: null,
color == Side.white ||
Theme.of(context).brightness == Brightness.dark &&
color == Side.black)
? Border.all(width: 2.0, color: ColorScheme.of(context).outline)
: null,
shape: BoxShape.circle,
color: switch (color) {
Side.white => Colors.white.withValues(alpha: 0.9),
@@ -350,10 +352,9 @@ class _GameResultRow extends StatelessWidget {
if (showRatingDiff)
SizedBox(
width: 38,
child:
(playerGameResult.ratingDiff != null)
? ProgressionWidget(playerGameResult.ratingDiff!, fontSize: 14)
: null,
child: (playerGameResult.ratingDiff != null)
? ProgressionWidget(playerGameResult.ratingDiff!, fontSize: 14)
: null,
),
],
),
@@ -36,7 +36,9 @@ class BroadcastPlayerWidget extends ConsumerWidget {
),
const SizedBox(width: 5),
],
Flexible(child: Text(name, style: textStyle, overflow: TextOverflow.ellipsis)),
Flexible(
child: Text(name, style: textStyle, overflow: TextOverflow.ellipsis),
),
if (rating != null && showRating) ...[
const SizedBox(width: 5),
Text(rating.toString(), overflow: TextOverflow.ellipsis),
@@ -284,15 +284,14 @@ class BroadcastPlayerRow extends StatelessWidget {
width: scoreWidth,
child: Padding(
padding: _kTableRowPadding,
child:
(score != null)
? Align(
alignment: Alignment.centerRight,
child: Text(
'${score.toStringAsFixed((score == score.roundToDouble()) ? 0 : 1)} / $played',
),
)
: Align(alignment: Alignment.centerRight, child: Text(played.toString())),
child: (score != null)
? Align(
alignment: Alignment.centerRight,
child: Text(
'${score.toStringAsFixed((score == score.roundToDouble()) ? 0 : 1)} / $played',
),
)
: Align(alignment: Alignment.centerRight, child: Text(played.toString())),
),
),
],
@@ -187,18 +187,14 @@ class _BroadcastRoundScreenState extends ConsumerState<BroadcastRoundScreen>
actions: [
SemanticIconButton(
icon: const Icon(Icons.settings),
onPressed:
() => showModalBottomSheet<void>(
context: context,
isDismissible: true,
isScrollControlled: true,
showDragHandle: true,
builder:
(_) => _BroadcastSettingsBottomSheet(
filter,
onGameFilterChange: setGameFilter,
),
),
onPressed: () => showModalBottomSheet<void>(
context: context,
isDismissible: true,
isScrollControlled: true,
showDragHandle: true,
builder: (_) =>
_BroadcastSettingsBottomSheet(filter, onGameFilterChange: setGameFilter),
),
semanticsLabel: context.l10n.settingsSettings,
),
SemanticIconButton(
@@ -295,29 +291,27 @@ class _BottomBar extends ConsumerWidget {
children: [
if (tournament.group != null)
TextButton(
onPressed:
() => showModalBottomSheet<void>(
context: context,
showDragHandle: true,
isScrollControlled: true,
isDismissible: true,
builder:
(_) => DraggableScrollableSheet(
initialChildSize: 0.4,
maxChildSize: 0.4,
minChildSize: 0.1,
snap: true,
expand: false,
builder: (context, scrollController) {
return _TournamentSelectorMenu(
tournament: tournament,
group: tournament.group!,
scrollController: scrollController,
setTournamentId: setTournamentId,
);
},
),
),
onPressed: () => showModalBottomSheet<void>(
context: context,
showDragHandle: true,
isScrollControlled: true,
isDismissible: true,
builder: (_) => DraggableScrollableSheet(
initialChildSize: 0.4,
maxChildSize: 0.4,
minChildSize: 0.1,
snap: true,
expand: false,
builder: (context, scrollController) {
return _TournamentSelectorMenu(
tournament: tournament,
group: tournament.group!,
scrollController: scrollController,
setTournamentId: setTournamentId,
);
},
),
),
child: Text(
tournament.group!.firstWhere((g) => g.id == tournament.data.id).name,
maxLines: 1,
@@ -325,28 +319,26 @@ class _BottomBar extends ConsumerWidget {
),
),
TextButton(
onPressed:
() => showModalBottomSheet<void>(
context: context,
showDragHandle: true,
isScrollControlled: true,
isDismissible: true,
builder:
(_) => DraggableScrollableSheet(
initialChildSize: 0.6,
maxChildSize: 0.6,
snap: true,
expand: false,
builder: (context, scrollController) {
return _RoundSelectorMenu(
selectedRoundId: roundId,
rounds: tournament.rounds,
scrollController: scrollController,
setRoundId: setRoundId,
);
},
),
),
onPressed: () => showModalBottomSheet<void>(
context: context,
showDragHandle: true,
isScrollControlled: true,
isDismissible: true,
builder: (_) => DraggableScrollableSheet(
initialChildSize: 0.6,
maxChildSize: 0.6,
snap: true,
expand: false,
builder: (context, scrollController) {
return _RoundSelectorMenu(
selectedRoundId: roundId,
rounds: tournament.rounds,
scrollController: scrollController,
setRoundId: setRoundId,
);
},
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
@@ -411,17 +403,16 @@ class _RoundSelectorState extends ConsumerState<_RoundSelectorMenu> {
key: round.id == widget.selectedRoundId ? currentRoundKey : null,
selected: round.id == widget.selectedRoundId,
title: Text(round.name, overflow: TextOverflow.ellipsis, maxLines: 2),
subtitle:
(round.startsAt != null || round.startsAfterPrevious)
? Text(
round.startsAt != null
? round.startsAt!.difference(DateTime.now()).inDays.abs() < 30
subtitle: (round.startsAt != null || round.startsAfterPrevious)
? Text(
round.startsAt != null
? round.startsAt!.difference(DateTime.now()).inDays.abs() < 30
? _dateFormatMonth.format(round.startsAt!)
: _dateFormatYearMonth.format(round.startsAt!)
: context.l10n.broadcastStartsAfter(widget.rounds[index - 1].name),
overflow: TextOverflow.ellipsis,
)
: null,
: context.l10n.broadcastStartsAfter(widget.rounds[index - 1].name),
overflow: TextOverflow.ellipsis,
)
: null,
trailing: switch (round.status) {
RoundStatus.finished => Icon(Icons.check, color: context.lichessColors.good),
RoundStatus.live => Icon(Icons.circle, color: context.lichessColors.error),
@@ -517,46 +508,45 @@ class _BroadcastSettingsBottomSheetState extends ConsumerState<_BroadcastSetting
return DraggableScrollableSheet(
initialChildSize: .6,
expand: false,
builder:
(context, scrollController) => ListView(
controller: scrollController,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
SettingsSectionTitle(context.l10n.filterGames),
const SizedBox(height: 6),
Filter<_BroadcastGameFilter>(
filterType: FilterType.singleChoice,
choices: _BroadcastGameFilter.values,
choiceSelected: (choice) => filter == choice,
choiceLabel: (category) => Text(category.l10n(context.l10n)),
onSelected: (value, selected) {
setState(() => filter = value);
widget.onGameFilterChange.call(value);
},
),
],
builder: (context, scrollController) => ListView(
controller: scrollController,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
SettingsSectionTitle(context.l10n.filterGames),
const SizedBox(height: 6),
Filter<_BroadcastGameFilter>(
filterType: FilterType.singleChoice,
choices: _BroadcastGameFilter.values,
choiceSelected: (choice) => filter == choice,
choiceLabel: (category) => Text(category.l10n(context.l10n)),
onSelected: (value, selected) {
setState(() => filter = value);
widget.onGameFilterChange.call(value);
},
),
),
ListSection(
header: SettingsSectionTitle(context.l10n.preferencesDisplay),
materialFilledCard: true,
children: [
SwitchSettingTile(
title: Text(context.l10n.evaluationGauge),
value: broadcastPreferences.showEvaluationBar,
onChanged: (value) {
ref.read(broadcastPreferencesProvider.notifier).toggleEvaluationBar();
},
),
],
],
),
),
ListSection(
header: SettingsSectionTitle(context.l10n.preferencesDisplay),
materialFilledCard: true,
children: [
SwitchSettingTile(
title: Text(context.l10n.evaluationGauge),
value: broadcastPreferences.showEvaluationBar,
onChanged: (value) {
ref.read(broadcastPreferencesProvider.notifier).toggleEvaluationBar();
},
),
],
),
],
),
);
}
}
+24 -27
View File
@@ -29,8 +29,8 @@ class ChatBottomBarButton extends ConsumerWidget {
AsyncData(:final value) =>
value > 0
? value < 10
? value.toString()
: '9+'
? value.toString()
: '9+'
: null,
_ => null,
},
@@ -80,12 +80,11 @@ class _ChatScreenState extends ConsumerState<ChatScreen> with RouteAware {
case AsyncData(:final value):
return Scaffold(
appBar: AppBar(
title:
widget.options.isPublic
? Text(context.l10n.chatRoom)
: widget.options.opponent == null
? Text(context.l10n.chatRoom)
: UserFullNameWidget(user: widget.options.opponent),
title: widget.options.isPublic
? Text(context.l10n.chatRoom)
: widget.options.opponent == null
? Text(context.l10n.chatRoom)
: UserFullNameWidget(user: widget.options.opponent),
centerTitle: true,
),
body: Column(
@@ -106,10 +105,10 @@ class _ChatScreenState extends ConsumerState<ChatScreen> with RouteAware {
: (message.username == session?.user.name)
? _MessageBubble(you: true, message: message)
: _MessageBubble(
you: false,
message: message,
showUsername: widget.options.isPublic,
);
you: false,
message: message,
showUsername: widget.options.isPublic,
);
},
),
),
@@ -221,21 +220,19 @@ class _ChatBottomBarState extends ConsumerState<_ChatBottomBar> {
final session = ref.watch(authSessionProvider);
final sendButton = ValueListenableBuilder<TextEditingValue>(
valueListenable: _textController,
builder:
(context, value, child) => SemanticIconButton(
onPressed:
session != null && value.text.isNotEmpty
? () {
ref
.read(chatControllerProvider(widget.options).notifier)
.postMessage(_textController.text);
_textController.clear();
}
: null,
icon: const Icon(Icons.send),
padding: EdgeInsets.zero,
semanticsLabel: context.l10n.send,
),
builder: (context, value, child) => SemanticIconButton(
onPressed: session != null && value.text.isNotEmpty
? () {
ref
.read(chatControllerProvider(widget.options).notifier)
.postMessage(_textController.text);
_textController.clear();
}
: null,
icon: const Icon(Icons.send),
padding: EdgeInsets.zero,
semanticsLabel: context.l10n.send,
),
);
final placeholder = session != null ? context.l10n.talkInChat : context.l10n.loginToChat;
return SafeArea(
+64 -69
View File
@@ -42,12 +42,11 @@ class ClockSettings extends ConsumerWidget {
padding: _kIconPadding,
tooltip: context.l10n.reset,
iconSize: _iconSize,
onPressed:
buttonsEnabled
? () {
ref.read(clockToolControllerProvider.notifier).reset();
}
: null,
onPressed: buttonsEnabled
? () {
ref.read(clockToolControllerProvider.notifier).reset();
}
: null,
icon: const Icon(Icons.refresh),
),
),
@@ -59,36 +58,35 @@ class ClockSettings extends ConsumerWidget {
padding: _kIconPadding,
tooltip: context.l10n.settingsSettings,
iconSize: _iconSize,
onPressed:
buttonsEnabled
? () {
final double screenHeight = MediaQuery.sizeOf(context).height;
showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
constraints: BoxConstraints(
maxHeight: screenHeight - (screenHeight / 10),
),
builder: (BuildContext context) {
final options = ref.watch(
clockToolControllerProvider.select((value) => value.options),
);
return TimeControlModal(
excludeUltraBullet: true,
timeIncrement: TimeIncrement(
options.whiteTime.inSeconds,
options.whiteIncrement.inSeconds,
),
onSelected: (choice) {
ref
.read(clockToolControllerProvider.notifier)
.updateOptions(choice);
},
);
},
);
}
: null,
onPressed: buttonsEnabled
? () {
final double screenHeight = MediaQuery.sizeOf(context).height;
showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
constraints: BoxConstraints(
maxHeight: screenHeight - (screenHeight / 10),
),
builder: (BuildContext context) {
final options = ref.watch(
clockToolControllerProvider.select((value) => value.options),
);
return TimeControlModal(
excludeUltraBullet: true,
timeIncrement: TimeIncrement(
options.whiteTime.inSeconds,
options.whiteIncrement.inSeconds,
),
onSelected: (choice) {
ref
.read(clockToolControllerProvider.notifier)
.updateOptions(choice);
},
);
},
);
}
: null,
icon: const Icon(Icons.settings),
),
),
@@ -114,10 +112,9 @@ class ClockSettings extends ConsumerWidget {
iconSize: _iconSize,
// TODO: translate
tooltip: 'Flip clock',
onPressed:
() => ref
.read(clockToolControllerProvider.notifier)
.toggleOrientation(clockOrientation.toggle),
onPressed: () => ref
.read(clockToolControllerProvider.notifier)
.toggleOrientation(clockOrientation.toggle),
icon: const Icon(Icons.screen_rotation),
),
),
@@ -151,35 +148,33 @@ class _PlayResumeButton extends ConsumerWidget {
final state = ref.watch(clockToolControllerProvider);
return ColoredBox(
color:
!state.started
? Theme.of(context).colorScheme.surfaceContainerHighest
: Colors.transparent,
child:
!state.started
? IconButton(
padding: _kIconPadding,
color: Theme.of(context).colorScheme.primary,
tooltip: context.l10n.play,
iconSize: iconSize,
onPressed: () => controller.start(),
icon: const Icon(Icons.play_arrow),
)
: state.paused
? IconButton(
padding: _kIconPadding,
tooltip: context.l10n.resume,
iconSize: iconSize,
onPressed: () => controller.resume(),
icon: const Icon(Icons.play_arrow),
)
: IconButton(
padding: _kIconPadding,
tooltip: context.l10n.pause,
iconSize: iconSize,
onPressed: () => controller.pause(),
icon: const Icon(Icons.pause),
),
color: !state.started
? Theme.of(context).colorScheme.surfaceContainerHighest
: Colors.transparent,
child: !state.started
? IconButton(
padding: _kIconPadding,
color: Theme.of(context).colorScheme.primary,
tooltip: context.l10n.play,
iconSize: iconSize,
onPressed: () => controller.start(),
icon: const Icon(Icons.play_arrow),
)
: state.paused
? IconButton(
padding: _kIconPadding,
tooltip: context.l10n.resume,
iconSize: iconSize,
onPressed: () => controller.resume(),
icon: const Icon(Icons.play_arrow),
)
: IconButton(
padding: _kIconPadding,
tooltip: context.l10n.pause,
iconSize: iconSize,
onPressed: () => controller.pause(),
icon: const Icon(Icons.pause),
),
);
}
}
+54 -63
View File
@@ -112,14 +112,13 @@ class ClockTile extends ConsumerWidget {
final activeColor = colorScheme.primaryFixedDim;
final activeTextColor = colorScheme.onPrimaryFixed;
final pausedColor = activeColor.withValues(alpha: 0.5);
final backgroundColor =
clockState.isFlagged(playerType)
? colorScheme.error
: !clockState.paused && clockState.isPlayersTurn(playerType)
? activeColor
: clockState.activeSide == playerType
? pausedColor
: colorScheme.surface;
final backgroundColor = clockState.isFlagged(playerType)
? colorScheme.error
: !clockState.paused && clockState.isPlayersTurn(playerType)
? activeColor
: clockState.activeSide == playerType
? pausedColor
: colorScheme.surface;
final clockStyle = ClockStyle(
textColor: clockState.activeSide == playerType ? activeTextColor : colorScheme.onSurface,
@@ -133,12 +132,11 @@ class ClockTile extends ConsumerWidget {
final clockOrientation = ref.watch(clockToolControllerProvider).clockOrientation;
return RotatedBox(
quarterTurns:
clockOrientation.isPortrait
? (position == TilePosition.top
? clockOrientation.oppositeQuarterTurns
: clockOrientation.quarterTurns)
: clockOrientation.quarterTurns,
quarterTurns: clockOrientation.isPortrait
? (position == TilePosition.top
? clockOrientation.oppositeQuarterTurns
: clockOrientation.quarterTurns)
: clockOrientation.quarterTurns,
child: Stack(
alignment: Alignment.center,
fit: StackFit.expand,
@@ -147,22 +145,20 @@ class ClockTile extends ConsumerWidget {
color: backgroundColor,
child: InkWell(
splashFactory: NoSplash.splashFactory,
onTap:
!clockState.started
? () {
ref
.read(clockToolControllerProvider.notifier)
.setBottomPlayer(
position == TilePosition.bottom ? Side.white : Side.black,
);
}
: null,
onTapDown:
clockState.started && clockState.isPlayersMoveAllowed(playerType)
? (_) {
ref.read(clockToolControllerProvider.notifier).onTap(playerType);
}
: null,
onTap: !clockState.started
? () {
ref
.read(clockToolControllerProvider.notifier)
.setBottomPlayer(
position == TilePosition.bottom ? Side.white : Side.black,
);
}
: null,
onTapDown: clockState.started && clockState.isPlayersMoveAllowed(playerType)
? (_) {
ref.read(clockToolControllerProvider.notifier).onTap(playerType);
}
: null,
child: Padding(
padding: const EdgeInsets.all(48),
child: Column(
@@ -180,10 +176,9 @@ class ClockTile extends ConsumerWidget {
clockStyle: clockStyle,
),
secondChild: const Icon(Icons.flag),
crossFadeState:
clockState.isFlagged(playerType)
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
crossFadeState: clockState.isFlagged(playerType)
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
),
),
),
@@ -199,10 +194,9 @@ class ClockTile extends ConsumerWidget {
'${context.l10n.stormMoves}: ${clockState.getMovesCount(playerType)}',
style: TextStyle(
fontSize: 13,
color:
!clockState.paused && clockState.isPlayersTurn(playerType)
? clockStyle.activeTextColor
: clockStyle.textColor,
color: !clockState.paused && clockState.isPlayersTurn(playerType)
? clockStyle.activeTextColor
: clockStyle.textColor,
),
),
),
@@ -229,32 +223,29 @@ class ClockTile extends ConsumerWidget {
iconSize: 32,
icon: const Icon(Icons.tune),
color: clockStyle.textColor,
onPressed:
clockState.started
? null
: () => showModalBottomSheet<void>(
context: context,
builder:
(BuildContext context) => CustomClockSettings(
player: playerType,
clock:
playerType == Side.white
? TimeIncrement.fromDurations(
clockState.options.whiteTime,
clockState.options.whiteIncrement,
)
: TimeIncrement.fromDurations(
clockState.options.blackTime,
clockState.options.blackIncrement,
),
onSubmit: (Side player, TimeIncrement clock) {
Navigator.of(context).pop();
ref
.read(clockToolControllerProvider.notifier)
.updateOptionsCustom(clock, player);
},
),
onPressed: clockState.started
? null
: () => showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) => CustomClockSettings(
player: playerType,
clock: playerType == Side.white
? TimeIncrement.fromDurations(
clockState.options.whiteTime,
clockState.options.whiteIncrement,
)
: TimeIncrement.fromDurations(
clockState.options.blackTime,
clockState.options.blackIncrement,
),
onSubmit: (Side player, TimeIncrement clock) {
Navigator.of(context).pop();
ref
.read(clockToolControllerProvider.notifier)
.updateOptionsCustom(clock, player);
},
),
),
),
),
),
@@ -41,12 +41,11 @@ class CoordinateTrainingScreen extends StatelessWidget {
return SemanticIconButton(
icon: const Icon(Icons.settings),
semanticsLabel: context.l10n.settingsSettings,
onPressed:
() => showModalBottomSheet<void>(
context: context,
showDragHandle: true,
builder: (BuildContext context) => const _CoordinateTrainingMenu(),
),
onPressed: () => showModalBottomSheet<void>(
context: context,
showDragHandle: true,
builder: (BuildContext context) => const _CoordinateTrainingMenu(),
),
);
},
),
@@ -80,28 +79,28 @@ class _BodyState extends ConsumerState<_Body> {
final trainingState = ref.watch(coordinateTrainingControllerProvider);
final trainingPrefs = ref.watch(coordinateTrainingPreferencesProvider);
final IMap<Square, SquareHighlight> squareHighlights =
<Square, SquareHighlight>{
if (trainingState.trainingActive)
if (trainingPrefs.mode == TrainingMode.findSquare) ...{
if (highlightLastGuess != null) ...{
highlightLastGuess!: SquareHighlight(
details: HighlightDetails(
solidColor: (trainingState.lastGuess == Guess.correct
final IMap<Square, SquareHighlight> squareHighlights = <Square, SquareHighlight>{
if (trainingState.trainingActive)
if (trainingPrefs.mode == TrainingMode.findSquare) ...{
if (highlightLastGuess != null) ...{
highlightLastGuess!: SquareHighlight(
details: HighlightDetails(
solidColor:
(trainingState.lastGuess == Guess.correct
? context.lichessColors.good
: context.lichessColors.error)
.withValues(alpha: 0.5),
),
),
},
} else ...{
trainingState.currentCoord!: SquareHighlight(
details: HighlightDetails(
solidColor: context.lichessColors.good.withValues(alpha: 0.5),
),
),
},
}.lock;
),
},
} else ...{
trainingState.currentCoord!: SquareHighlight(
details: HighlightDetails(
solidColor: context.lichessColors.good.withValues(alpha: 0.5),
),
),
},
}.lock;
return SafeArea(
bottom: false,
@@ -116,10 +115,9 @@ class _BodyState extends ConsumerState<_Body> {
final isTablet = isTabletOrLarger(context);
final remainingHeight = constraints.maxHeight - defaultBoardSize;
final isSmallScreen = remainingHeight < kSmallRemainingHeightLeftBoardThreshold;
final boardSize =
isTablet || isSmallScreen
? defaultBoardSize - kTabletBoardTableSidePadding * 2
: defaultBoardSize;
final boardSize = isTablet || isSmallScreen
? defaultBoardSize - kTabletBoardTableSidePadding * 2
: defaultBoardSize;
final direction = aspectRatio > 1 ? Axis.horizontal : Axis.vertical;
@@ -134,10 +132,9 @@ class _BodyState extends ConsumerState<_Body> {
_TimeBar(
maxWidth: boardSize,
timeFractionElapsed: trainingState.timeFractionElapsed,
color:
trainingState.lastGuess == Guess.incorrect
? context.lichessColors.error
: context.lichessColors.good,
color: trainingState.lastGuess == Guess.incorrect
? context.lichessColors.error
: context.lichessColors.good,
),
_TrainingBoard(
boardSize: boardSize,
@@ -152,8 +149,9 @@ class _BodyState extends ConsumerState<_Body> {
_ScoreAndTrainingButton(
scoreSize: boardSize / 8,
score: trainingState.score,
onPressed:
ref.read(coordinateTrainingControllerProvider.notifier).abortTraining,
onPressed: ref
.read(coordinateTrainingControllerProvider.notifier)
.abortTraining,
label: 'Abort Training',
)
else if (trainingState.lastScore != null)
@@ -257,8 +255,9 @@ class _CoordinateTrainingMenu extends ConsumerWidget {
SwitchSettingTile(
title: const Text('Show Coordinates'),
value: trainingPrefs.showCoordinates,
onChanged:
ref.read(coordinateTrainingPreferencesProvider.notifier).setShowCoordinates,
onChanged: ref
.read(coordinateTrainingPreferencesProvider.notifier)
.setShowCoordinates,
),
SwitchSettingTile(
title: const Text('Show Pieces'),
@@ -296,10 +295,9 @@ class _ScoreAndTrainingButton extends ConsumerWidget {
_Score(
score: score,
size: scoreSize,
color:
trainingState.lastGuess == Guess.incorrect
? context.lichessColors.error
: context.lichessColors.good,
color: trainingState.lastGuess == Guess.incorrect
? context.lichessColors.error
: context.lichessColors.good,
),
_Button(label: label, onPressed: onPressed),
],
@@ -347,7 +345,10 @@ class _Button extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FilledButton(onPressed: onPressed, child: Text(label, style: Styles.bold));
return FilledButton(
onPressed: onPressed,
child: Text(label, style: Styles.bold),
);
}
}
@@ -437,10 +438,9 @@ class _TrainingBoardState extends ConsumerState<_TrainingBoard> {
orientation: widget.orientation,
settings: boardPrefs.toBoardSettings().copyWith(
enableCoordinates: trainingPrefs.showCoordinates,
borderRadius:
widget.isTablet
? const BorderRadius.all(Radius.circular(4.0))
: BorderRadius.zero,
borderRadius: widget.isTablet
? const BorderRadius.all(Radius.circular(4.0))
: BorderRadius.zero,
boxShadow: widget.isTablet ? boardShadows : const <BoxShadow>[],
),
onTouchedSquare: (square) {
@@ -495,7 +495,10 @@ Future<void> _coordinateTrainingInfoDialogBuilder(BuildContext context) {
' • You can analyse a game more effectively if you can quickly recognise coordinates.\n',
),
TextSpan(text: '\n'),
TextSpan(text: 'Find Square\n', style: TextStyle(fontWeight: FontWeight.bold)),
TextSpan(
text: 'Find Square\n',
style: TextStyle(fontWeight: FontWeight.bold),
),
TextSpan(
text:
'A coordinate appears on the board and you must click on the corresponding square.\n',
@@ -147,17 +147,15 @@ class _BodyState extends ConsumerState<_Body> {
materialDifferenceFormat: materialDifference,
shouldLinkToUserProfile: false,
mePlaying: youAre == Side.black,
confirmMoveCallbacks:
youAre == Side.black && moveToConfirm != null
? (confirm: confirmMove, cancel: cancelMove)
: null,
clock:
youAre == Side.black && game.estimatedTimeLeft(Side.black, widget.lastModified) != null
? CorrespondenceClock(
duration: game.estimatedTimeLeft(Side.black, widget.lastModified)!,
active: activeClockSide == Side.black,
)
: null,
confirmMoveCallbacks: youAre == Side.black && moveToConfirm != null
? (confirm: confirmMove, cancel: cancelMove)
: null,
clock: youAre == Side.black && game.estimatedTimeLeft(Side.black, widget.lastModified) != null
? CorrespondenceClock(
duration: game.estimatedTimeLeft(Side.black, widget.lastModified)!,
active: activeClockSide == Side.black,
)
: null,
);
final white = GamePlayer(
game: game,
@@ -166,17 +164,15 @@ class _BodyState extends ConsumerState<_Body> {
materialDifferenceFormat: materialDifference,
shouldLinkToUserProfile: false,
mePlaying: youAre == Side.white,
confirmMoveCallbacks:
youAre == Side.white && moveToConfirm != null
? (confirm: confirmMove, cancel: cancelMove)
: null,
clock:
game.estimatedTimeLeft(Side.white, widget.lastModified) != null
? CorrespondenceClock(
duration: game.estimatedTimeLeft(Side.white, widget.lastModified)!,
active: activeClockSide == Side.white,
)
: null,
confirmMoveCallbacks: youAre == Side.white && moveToConfirm != null
? (confirm: confirmMove, cancel: cancelMove)
: null,
clock: game.estimatedTimeLeft(Side.white, widget.lastModified) != null
? CorrespondenceClock(
duration: game.estimatedTimeLeft(Side.white, widget.lastModified)!,
active: activeClockSide == Side.white,
)
: null,
);
final topPlayer = youAre == Side.white ? black : white;
@@ -193,12 +189,11 @@ class _BodyState extends ConsumerState<_Body> {
interactiveBoardParams: (
variant: game.meta.variant,
position: position,
playerSide:
game.playable && !isReplaying
? youAre == Side.white
? PlayerSide.white
: PlayerSide.black
: PlayerSide.none,
playerSide: game.playable && !isReplaying
? youAre == Side.white
? PlayerSide.white
: PlayerSide.black
: PlayerSide.none,
promotionMove: promotionMove,
onMove: (move, {isDrop}) {
onUserMove(move);
@@ -257,8 +252,8 @@ class _BodyState extends ConsumerState<_Body> {
.firstWhereOrNull((g) => g.$2.isMyTurn);
return nextTurn != null
? () {
widget.onGameChanged(nextTurn);
}
widget.onGameChanged(nextTurn);
}
: null;
},
orElse: () => null,
@@ -266,17 +261,16 @@ class _BodyState extends ConsumerState<_Body> {
),
BottomBarButton(
label: context.l10n.mobileCorrespondenceClearSavedMove,
onTap:
game.registeredMoveAtPgn != null
? () {
showConfirmDialog<void>(
context,
title: Text(context.l10n.mobileCorrespondenceClearSavedMove),
isDestructiveAction: true,
onConfirm: () => deleteRegisteredMove(),
);
}
: null,
onTap: game.registeredMoveAtPgn != null
? () {
showConfirmDialog<void>(
context,
title: Text(context.l10n.mobileCorrespondenceClearSavedMove),
isDestructiveAction: true,
onConfirm: () => deleteRegisteredMove(),
);
}
: null,
icon: Icons.save,
),
RepeatButton(
+67 -72
View File
@@ -24,9 +24,9 @@ class EngineDepth extends ConsumerWidget {
final color =
engineName ==
'Stockfish' // while loading name is 'Stockfish'
? Colors.grey
: ColorScheme.of(context).secondary;
'Stockfish' // while loading name is 'Stockfish'
? Colors.grey
: ColorScheme.of(context).secondary;
final textColor = ColorScheme.of(context).onSecondary;
final loadingIndicator = SpinKitFadingFour(color: textColor.withValues(alpha: 0.7), size: 10);
@@ -45,24 +45,23 @@ class EngineDepth extends ConsumerWidget {
'${context.l10n.cloudAnalysis}, ${context.l10n.depthX('$depth')}',
_ => context.l10n.loadingEngine,
},
onPressed:
eval != null
? () {
showPopover(
context: context,
bodyBuilder: (_) {
return _EnginePopup(eval: eval, goDeeper: goDeeper);
},
direction: PopoverDirection.top,
width: 250,
backgroundColor:
DialogTheme.of(context).backgroundColor ??
ColorScheme.of(context).surfaceContainerHigh,
transitionDuration: Duration.zero,
popoverTransitionBuilder: (_, child) => child,
);
}
: null,
onPressed: eval != null
? () {
showPopover(
context: context,
bodyBuilder: (_) {
return _EnginePopup(eval: eval, goDeeper: goDeeper);
},
direction: PopoverDirection.top,
width: 250,
backgroundColor:
DialogTheme.of(context).backgroundColor ??
ColorScheme.of(context).surfaceContainerHigh,
transitionDuration: Duration.zero,
popoverTransitionBuilder: (_, child) => child,
);
}
: null,
icon: Badge(
offset: const Offset(4, -7),
backgroundColor: ColorScheme.of(context).tertiaryContainer,
@@ -85,10 +84,9 @@ class EngineDepth extends ConsumerWidget {
height: microChipSize,
child: RepaintBoundary(
child: Center(
child:
eval?.depth != null
? Text('${math.min(99, eval!.depth)}', style: iconTextStyle)
: loadingIndicator,
child: eval?.depth != null
? Text('${math.min(99, eval!.depth)}', style: iconTextStyle)
: loadingIndicator,
),
),
),
@@ -112,40 +110,38 @@ class MicroChipPainter extends CustomPainter {
const innerRimWidth = 1.0;
const outerRimWidth = 2.5;
final fillPaint =
Paint()
..color = color
..style = PaintingStyle.fill;
final fillPaint = Paint()
..color = color
..style = PaintingStyle.fill;
final strokePaint =
Paint()
..color = color
..style = PaintingStyle.stroke
..strokeWidth = outerRimWidth;
final strokePaint = Paint()
..color = color
..style = PaintingStyle.stroke
..strokeWidth = outerRimWidth;
final innerSquareSize = size.width - pinLength - innerRimWidth - outerRimWidth / 2;
final innerSquarePath =
Path()..addRRect(
RRect.fromLTRBR(
pinLength + innerRimWidth + outerRimWidth / 2,
pinLength + innerRimWidth + outerRimWidth / 2,
innerSquareSize,
innerSquareSize,
const Radius.circular(2),
),
);
final innerSquarePath = Path()
..addRRect(
RRect.fromLTRBR(
pinLength + innerRimWidth + outerRimWidth / 2,
pinLength + innerRimWidth + outerRimWidth / 2,
innerSquareSize,
innerSquareSize,
const Radius.circular(2),
),
);
final outerRimPath =
Path()..addRRect(
RRect.fromLTRBR(
pinLength,
pinLength,
size.width - pinLength,
size.height - pinLength,
const Radius.circular(4),
),
);
final outerRimPath = Path()
..addRRect(
RRect.fromLTRBR(
pinLength,
pinLength,
size.width - pinLength,
size.height - pinLength,
const Radius.circular(4),
),
);
final pinsPath = Path();
final chipSide = size.width - pinLength * 2;
@@ -245,14 +241,13 @@ class _EnginePopup extends ConsumerWidget {
contentPadding: const EdgeInsets.only(left: 16.0),
title: Text(context.l10n.cloudAnalysis),
subtitle: Text(context.l10n.depthX('${eval.depth}')),
trailing:
canGoDeeper
? IconButton(
icon: const Icon(Icons.add_circle_outlined),
onPressed: goDeeper,
tooltip: context.l10n.goDeeper,
)
: null,
trailing: canGoDeeper
? IconButton(
icon: const Icon(Icons.add_circle_outlined),
onPressed: goDeeper,
tooltip: context.l10n.goDeeper,
)
: null,
);
}
@@ -260,22 +255,22 @@ class _EnginePopup extends ConsumerWidget {
final depth = currentEval.depth;
// remove Fairy-Stockfish version from engine name
final fixedEngineName =
engineName.startsWith('Fairy-Stockfish') ? 'Fairy-Stockfish' : engineName;
final fixedEngineName = engineName.startsWith('Fairy-Stockfish')
? 'Fairy-Stockfish'
: engineName;
return ListTile(
contentPadding: const EdgeInsets.only(left: 16.0),
leading: Image.asset('assets/images/stockfish/icon.png', width: 44, height: 44),
title: Text(fixedEngineName),
subtitle: Text(context.l10n.depthX('$depth$knps')),
trailing:
canGoDeeper
? IconButton(
icon: const Icon(Icons.add_circle_outlined),
onPressed: goDeeper,
tooltip: context.l10n.goDeeper,
)
: null,
trailing: canGoDeeper
? IconButton(
icon: const Icon(Icons.add_circle_outlined),
onPressed: goDeeper,
tooltip: context.l10n.goDeeper,
)
: null,
);
}
}
+70 -78
View File
@@ -12,22 +12,21 @@ const double kEvalGaugeFontSize = 11.0;
enum EngineGaugeDisplayMode { vertical, horizontal }
typedef EngineGaugeParams =
({
bool isLocalEngineAvailable,
typedef EngineGaugeParams = ({
bool isLocalEngineAvailable,
/// Only used for vertical display mode.
Side orientation,
/// Only used for vertical display mode.
Side orientation,
/// Position to evaluate.
Position position,
/// Position to evaluate.
Position position,
/// Cached evaluation to display when the current evaluation is not available.
ClientEval? savedEval,
/// Cached evaluation to display when the current evaluation is not available.
ClientEval? savedEval,
/// Server evaluation to display when the current evaluation and the cached evaluation is not available.
ExternalEval? serverEval,
});
/// Server evaluation to display when the current evaluation and the cached evaluation is not available.
ExternalEval? serverEval,
});
class EngineGauge extends ConsumerWidget {
const EngineGauge({required this.displayMode, required this.params});
@@ -38,18 +37,18 @@ class EngineGauge extends ConsumerWidget {
static Color backgroundColor(BuildContext context) =>
Theme.of(context).brightness == Brightness.dark
? lighten(ColorScheme.of(context).surface, .07)
: lighten(ColorScheme.of(context).onSurface, .17);
? lighten(ColorScheme.of(context).surface, .07)
: lighten(ColorScheme.of(context).onSurface, .17);
static Color valueColor(BuildContext context) =>
Theme.of(context).brightness == Brightness.dark
? darken(ColorScheme.of(context).onSurface, .1)
: darken(ColorScheme.of(context).surface, .01);
static Color valueColor(BuildContext context) => Theme.of(context).brightness == Brightness.dark
? darken(ColorScheme.of(context).onSurface, .1)
: darken(ColorScheme.of(context).surface, .01);
@override
Widget build(BuildContext context, WidgetRef ref) {
final localEval =
params.isLocalEngineAvailable ? ref.watch(engineEvaluationProvider).eval : null;
final localEval = params.isLocalEngineAvailable
? ref.watch(engineEvaluationProvider).eval
: null;
final eval = pickBestEval(
localEval: localEval,
savedEval: params.savedEval,
@@ -79,16 +78,15 @@ class _EvalGauge extends StatefulWidget {
final Side orientation;
double? get whiteWinningChances => eval?.winningChances(Side.white);
double? get animationValue =>
position.outcome != null
? position.outcome!.winner == null
? 0.5
: position.outcome!.winner == Side.white
? 1.0
: 0.0
: whiteWinningChances != null
? (((whiteWinningChances! + 1) * 0.5).abs() * 100).roundToDouble() / 100
: null;
double? get animationValue => position.outcome != null
? position.outcome!.winner == null
? 0.5
: position.outcome!.winner == Side.white
? 1.0
: 0.0
: whiteWinningChances != null
? (((whiteWinningChances! + 1) * 0.5).abs() * 100).roundToDouble() / 100
: null;
@override
State<_EvalGauge> createState() => _EvalGaugeState();
@@ -114,16 +112,15 @@ class _EvalGaugeState extends State<_EvalGauge> {
Widget build(BuildContext context) {
final TextDirection textDirection = Directionality.of(context);
final evalDisplay =
widget.position.outcome != null
? widget.position.outcome!.winner == null
? widget.position.isStalemate
final evalDisplay = widget.position.outcome != null
? widget.position.outcome!.winner == null
? widget.position.isStalemate
? context.l10n.stalemate
: context.l10n.insufficientMaterial
: widget.position.isCheckmate
? context.l10n.checkmate
: context.l10n.variantEnding
: widget.eval?.evalString ?? oldEval?.evalString;
: widget.position.isCheckmate
? context.l10n.checkmate
: context.l10n.variantEnding
: widget.eval?.evalString ?? oldEval?.evalString;
return TweenAnimationBuilder<double>(
tween: Tween<double>(begin: fromValue, end: toValue),
@@ -135,44 +132,41 @@ class _EvalGaugeState extends State<_EvalGauge> {
value: evalDisplay ?? context.l10n.loadingEngine,
child: RepaintBoundary(
child: Container(
constraints:
widget.displayMode == EngineGaugeDisplayMode.vertical
? const BoxConstraints(minWidth: kEvalGaugeSize, minHeight: double.infinity)
: const BoxConstraints(minWidth: double.infinity, minHeight: kEvalGaugeSize),
constraints: widget.displayMode == EngineGaugeDisplayMode.vertical
? const BoxConstraints(minWidth: kEvalGaugeSize, minHeight: double.infinity)
: const BoxConstraints(minWidth: double.infinity, minHeight: kEvalGaugeSize),
width: widget.displayMode == EngineGaugeDisplayMode.vertical ? kEvalGaugeSize : null,
height: widget.displayMode == EngineGaugeDisplayMode.vertical ? null : kEvalGaugeSize,
child: CustomPaint(
painter:
widget.displayMode == EngineGaugeDisplayMode.vertical
? _EvalGaugeVerticalPainter(
orientation: widget.orientation,
backgroundColor: EngineGauge.backgroundColor(context),
valueColor: EngineGauge.valueColor(context),
value: value,
)
: _EvalGaugeHorizontalPainter(
backgroundColor: EngineGauge.backgroundColor(context),
valueColor: EngineGauge.valueColor(context),
value: value,
textDirection: textDirection,
),
child:
widget.displayMode == EngineGaugeDisplayMode.vertical
? const SizedBox.shrink()
: Align(
alignment: toValue >= 0.5 ? Alignment.centerLeft : Alignment.centerRight,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: Text(
evalDisplay ?? '',
style: TextStyle(
color: toValue >= 0.5 ? Colors.black : Colors.white,
fontSize: kEvalGaugeFontSize,
fontWeight: FontWeight.bold,
),
painter: widget.displayMode == EngineGaugeDisplayMode.vertical
? _EvalGaugeVerticalPainter(
orientation: widget.orientation,
backgroundColor: EngineGauge.backgroundColor(context),
valueColor: EngineGauge.valueColor(context),
value: value,
)
: _EvalGaugeHorizontalPainter(
backgroundColor: EngineGauge.backgroundColor(context),
valueColor: EngineGauge.valueColor(context),
value: value,
textDirection: textDirection,
),
child: widget.displayMode == EngineGaugeDisplayMode.vertical
? const SizedBox.shrink()
: Align(
alignment: toValue >= 0.5 ? Alignment.centerLeft : Alignment.centerRight,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: Text(
evalDisplay ?? '',
style: TextStyle(
color: toValue >= 0.5 ? Colors.black : Colors.white,
fontSize: kEvalGaugeFontSize,
fontWeight: FontWeight.bold,
),
),
),
),
),
),
),
@@ -197,10 +191,9 @@ class _EvalGaugeHorizontalPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final Paint paint =
Paint()
..color = backgroundColor
..style = PaintingStyle.fill;
final Paint paint = Paint()
..color = backgroundColor
..style = PaintingStyle.fill;
canvas.drawRect(Offset.zero & size, paint);
paint.color = valueColor;
@@ -247,10 +240,9 @@ class _EvalGaugeVerticalPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final Paint paint =
Paint()
..color = backgroundColor
..style = PaintingStyle.fill;
final Paint paint = Paint()
..color = backgroundColor
..style = PaintingStyle.fill;
canvas.drawRect(Offset.zero & size, paint);
paint.color = valueColor;

Some files were not shown because too many files have changed in this diff Show More