Skip to content

Commit

Permalink
Syntax fix(?)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jimima committed Nov 20, 2024
1 parent b8e7a04 commit a4716f7
Show file tree
Hide file tree
Showing 56 changed files with 3,113 additions and 783 deletions.
16 changes: 10 additions & 6 deletions lib/src/model/account/account_preferences.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:lichess_mobile/src/model/auth/auth_session.dart';
import 'package:lichess_mobile/src/network/http.dart';
import 'package:lichess_mobile/src/utils/l10n_context.dart';
Expand Down Expand Up @@ -29,28 +30,31 @@ typedef AccountPrefState = ({
});

/// A provider that tells if the user wants to see ratings in the app.
final showRatingsPrefProvider = FutureProvider<bool>((ref) async {
@Riverpod(keepAlive: true)
Future<bool> showRatingsPref(Ref ref) async {
return ref.watch(
accountPreferencesProvider
.selectAsync((state) => state?.showRatings.value ?? true),
);
});
}

final clockSoundProvider = FutureProvider<bool>((ref) async {
@Riverpod(keepAlive: true)
Future<bool> clockSound(Ref ref) async {
return ref.watch(
accountPreferencesProvider
.selectAsync((state) => state?.clockSound.value ?? true),
);
});
}

final pieceNotationProvider = FutureProvider<PieceNotation>((ref) async {
@Riverpod(keepAlive: true)
Future<PieceNotation> pieceNotation(Ref ref) async {
return ref.watch(
accountPreferencesProvider.selectAsync(
(state) =>
state?.pieceNotation ?? defaultAccountPreferences.pieceNotation,
),
);
});
}

final defaultAccountPreferences = (
zenMode: Zen.no,
Expand Down
184 changes: 184 additions & 0 deletions lib/src/model/clock/chess_clock.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import 'dart:async';

import 'package:clock/clock.dart';
import 'package:dartchess/dartchess.dart';
import 'package:flutter/foundation.dart';

const _emergencyDelay = Duration(seconds: 20);
const _tickDelay = Duration(milliseconds: 100);

/// A chess clock.
class ChessClock {
ChessClock({
required Duration whiteTime,
required Duration blackTime,
this.emergencyThreshold,
this.onFlag,
this.onEmergency,
}) : _whiteTime = ValueNotifier(whiteTime),
_blackTime = ValueNotifier(blackTime),
_activeSide = Side.white;

/// The threshold at which the clock will call [onEmergency] if provided.
final Duration? emergencyThreshold;

/// Callback when the clock reaches zero.
VoidCallback? onFlag;

/// Called when one clock timers reaches the emergency threshold.
final void Function(Side activeSide)? onEmergency;

Timer? _timer;
Timer? _startDelayTimer;
DateTime? _lastStarted;
final _stopwatch = clock.stopwatch();
bool _shouldPlayEmergencyFeedback = true;
DateTime? _nextEmergency;

final ValueNotifier<Duration> _whiteTime;
final ValueNotifier<Duration> _blackTime;
Side _activeSide;

bool get isRunning {
return _lastStarted != null;
}

/// Returns the current white time.
ValueListenable<Duration> get whiteTime => _whiteTime;

/// Returns the current black time.
ValueListenable<Duration> get blackTime => _blackTime;

/// Returns the current active time.
ValueListenable<Duration> get activeTime => _activeTime;

/// Returns the current active side.
Side get activeSide => _activeSide;

/// Sets the time for either side.
void setTimes({Duration? whiteTime, Duration? blackTime}) {
if (whiteTime != null) {
_whiteTime.value = whiteTime;
}
if (blackTime != null) {
_blackTime.value = blackTime;
}
}

/// Sets the time for the given side.
void setTime(Side side, Duration time) {
if (side == Side.white) {
_whiteTime.value = time;
} else {
_blackTime.value = time;
}
}

/// Increments the time for either side.
void incTimes({Duration? whiteInc, Duration? blackInc}) {
if (whiteInc != null) {
_whiteTime.value += whiteInc;
}
if (blackInc != null) {
_blackTime.value += blackInc;
}
}

/// Increments the time for the given side.
void incTime(Side side, Duration increment) {
if (side == Side.white) {
_whiteTime.value += increment;
} else {
_blackTime.value += increment;
}
}

/// Starts the clock and switch to the given side.
///
/// Trying to start an already running clock on the same side is a no-op.
///
/// The [delay] parameter can be used to add a delay before the clock starts counting down. This is useful for lag compensation.
///
/// Returns the think time of the active side before switching or `null` if the clock is not running.
Duration? startSide(Side side, {Duration? delay}) {
if (isRunning && _activeSide == side) {
return _thinkTime;
}
_activeSide = side;
final thinkTime = _thinkTime;
start(delay: delay);
return thinkTime;
}

/// Starts the clock.
///
/// The [delay] parameter can be used to add a delay before the clock starts counting down. This is useful for lag compensation.
void start({Duration? delay}) {
_lastStarted = clock.now().add(delay ?? Duration.zero);
_startDelayTimer?.cancel();
_startDelayTimer = Timer(delay ?? Duration.zero, _scheduleTick);
}

/// Pauses the clock.
///
/// Returns the current think time for the active side.
Duration stop() {
_stopwatch.stop();
_startDelayTimer?.cancel();
_timer?.cancel();
final thinkTime = _thinkTime ?? Duration.zero;
_lastStarted = null;
return thinkTime;
}

void dispose() {
_timer?.cancel();
_startDelayTimer?.cancel();
_whiteTime.dispose();
_blackTime.dispose();
}

/// Returns the current think time for the active side.
Duration? get _thinkTime {
if (_lastStarted == null) {
return null;
}
return clock.now().difference(_lastStarted!);
}

ValueNotifier<Duration> get _activeTime {
return activeSide == Side.white ? _whiteTime : _blackTime;
}

void _scheduleTick() {
_stopwatch.reset();
_stopwatch.start();
_timer?.cancel();
_timer = Timer(_tickDelay, _tick);
}

void _tick() {
final newTime = _activeTime.value - _stopwatch.elapsed;
_activeTime.value = newTime < Duration.zero ? Duration.zero : newTime;
_checkEmergency();
if (_activeTime.value == Duration.zero) {
onFlag?.call();
}
_scheduleTick();
}

void _checkEmergency() {
final timeLeft = _activeTime.value;
if (emergencyThreshold != null &&
timeLeft <= emergencyThreshold! &&
_shouldPlayEmergencyFeedback &&
(_nextEmergency == null || _nextEmergency!.isBefore(clock.now()))) {
_shouldPlayEmergencyFeedback = false;
_nextEmergency = clock.now().add(_emergencyDelay);
onEmergency?.call(_activeSide);
} else if (emergencyThreshold != null &&
timeLeft > emergencyThreshold! * 1.5) {
_shouldPlayEmergencyFeedback = true;
}
}
}
Loading

0 comments on commit a4716f7

Please sign in to comment.