mirror of
https://github.com/lichess-org/mobile.git
synced 2026-05-26 13:50:52 +00:00
feat: add "small board" option (again), reduce "small screen" detection threshold (#3157)
This commit is contained in:
@@ -9,11 +9,14 @@ import 'package:home_widget/home_widget.dart';
|
||||
import 'package:l10n_esperanto/l10n_esperanto.dart';
|
||||
import 'package:lichess_mobile/l10n/l10n.dart';
|
||||
import 'package:lichess_mobile/src/app_links_service.dart';
|
||||
import 'package:lichess_mobile/src/binding.dart';
|
||||
import 'package:lichess_mobile/src/constants.dart';
|
||||
import 'package:lichess_mobile/src/model/account/account_repository.dart';
|
||||
import 'package:lichess_mobile/src/model/account/account_service.dart';
|
||||
import 'package:lichess_mobile/src/model/account/ongoing_game.dart';
|
||||
import 'package:lichess_mobile/src/model/analysis/analysis_preferences.dart';
|
||||
import 'package:lichess_mobile/src/model/announce/announce_service.dart';
|
||||
import 'package:lichess_mobile/src/model/broadcast/broadcast_preferences.dart';
|
||||
import 'package:lichess_mobile/src/model/challenge/challenge_service.dart';
|
||||
import 'package:lichess_mobile/src/model/common/preloaded_data.dart';
|
||||
import 'package:lichess_mobile/src/model/correspondence/correspondence_service.dart';
|
||||
@@ -22,6 +25,7 @@ import 'package:lichess_mobile/src/model/message/message_service.dart';
|
||||
import 'package:lichess_mobile/src/model/notifications/notification_service.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/board_preferences.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/general_preferences.dart';
|
||||
import 'package:lichess_mobile/src/model/study/study_preferences.dart';
|
||||
import 'package:lichess_mobile/src/network/connectivity.dart';
|
||||
import 'package:lichess_mobile/src/network/socket.dart';
|
||||
import 'package:lichess_mobile/src/quick_actions.dart';
|
||||
@@ -81,8 +85,59 @@ class _AppState extends ConsumerState<Application> {
|
||||
|
||||
StreamSubscription<List<SharedMediaFile>>? _intentSub;
|
||||
|
||||
// Adjusts some settings for small screens based on the MediaQuery data.
|
||||
Future<void> _screenSizeBasedInitialization(WidgetRef ref) async {
|
||||
// Bump version here in case we adjust the thresholds for screen size based initialization
|
||||
// and want it to run again for users who already launched the app with a previous version.
|
||||
const kDoneScreenSizeInitKey = 'done_screen_size_init_v1';
|
||||
|
||||
final prefs = LichessBinding.instance.sharedPreferences;
|
||||
if (prefs.getBool(kDoneScreenSizeInitKey) == true) {
|
||||
return;
|
||||
}
|
||||
|
||||
final mediaQueryData = MediaQueryData.fromView(
|
||||
WidgetsBinding.instance.platformDispatcher.views.first,
|
||||
);
|
||||
final isTablet = mediaQueryData.size.shortestSide > FormFactor.tablet;
|
||||
final isSmallScreen = estimateHeightMinusBoard(mediaQueryData) < kSmallHeightMinusBoard;
|
||||
final showEngineLines =
|
||||
isTablet || estimateHeightMinusBoard(mediaQueryData) > kSmallHeightMinusBoard - 30;
|
||||
|
||||
// For tablets in portrait mode using the full board size makes the bottom analysis tabs tiny,
|
||||
// see https://github.com/lichess-org/mobile/issues/3150,
|
||||
// so use a small board there by default as well.
|
||||
final smallBoard = isTablet || isSmallScreen;
|
||||
|
||||
await ref
|
||||
.read(analysisPreferencesProvider.notifier)
|
||||
.save(
|
||||
ref
|
||||
.read(analysisPreferencesProvider)
|
||||
.copyWith(smallBoard: smallBoard, showEngineLines: showEngineLines),
|
||||
);
|
||||
await ref
|
||||
.read(studyPreferencesProvider.notifier)
|
||||
.save(
|
||||
ref
|
||||
.read(studyPreferencesProvider)
|
||||
.copyWith(smallBoard: smallBoard, showEngineLines: showEngineLines),
|
||||
);
|
||||
await ref
|
||||
.read(broadcastPreferencesProvider.notifier)
|
||||
.save(
|
||||
ref
|
||||
.read(broadcastPreferencesProvider)
|
||||
.copyWith(smallBoard: smallBoard, showEngineLines: showEngineLines),
|
||||
);
|
||||
|
||||
await prefs.setBool(kDoneScreenSizeInitKey, true);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_screenSizeBasedInitialization(ref);
|
||||
|
||||
// Start services
|
||||
ref.read(appLogServiceProvider).start();
|
||||
ref.read(notificationServiceProvider).start();
|
||||
|
||||
@@ -69,7 +69,7 @@ const kBottomBarHeight = 56.0;
|
||||
const kMaterialPopupMenuMaxWidth = 500.0;
|
||||
|
||||
/// The threshold to detect screens with a small remaining height minus board.
|
||||
const kSmallHeightMinusBoard = 170;
|
||||
const kSmallHeightMinusBoard = 200;
|
||||
|
||||
// annotations
|
||||
class _AllowedWidgetReturn {
|
||||
|
||||
@@ -9,13 +9,10 @@ import 'package:lichess_mobile/l10n/l10n.dart';
|
||||
import 'package:lichess_mobile/src/binding.dart';
|
||||
import 'package:lichess_mobile/src/constants.dart';
|
||||
import 'package:lichess_mobile/src/db/secure_storage.dart';
|
||||
import 'package:lichess_mobile/src/model/analysis/analysis_preferences.dart';
|
||||
import 'package:lichess_mobile/src/model/broadcast/broadcast_preferences.dart';
|
||||
import 'package:lichess_mobile/src/model/notifications/notification_service.dart';
|
||||
import 'package:lichess_mobile/src/model/notifications/notifications.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/board_preferences.dart';
|
||||
import 'package:lichess_mobile/src/model/settings/preferences_storage.dart';
|
||||
import 'package:lichess_mobile/src/model/study/study_preferences.dart';
|
||||
import 'package:lichess_mobile/src/utils/chessboard.dart';
|
||||
import 'package:lichess_mobile/src/utils/color_palette.dart';
|
||||
import 'package:lichess_mobile/src/utils/screen.dart';
|
||||
@@ -51,8 +48,6 @@ Future<void> initializeApp() async {
|
||||
await prefs.setString(PrefCategory.board.storageKey, jsonEncode(boardPrefs.toJson()));
|
||||
}
|
||||
|
||||
_screenSizeBasedInitialization();
|
||||
|
||||
_logger.info('First run initialization completed.');
|
||||
}
|
||||
|
||||
@@ -162,19 +157,3 @@ Future<void> androidDisplayInitialization(WidgetsBinding widgetsBinding) async {
|
||||
// This setting is per session.
|
||||
await FlutterDisplayMode.setPreferredMode(mostOptimalMode);
|
||||
}
|
||||
|
||||
// Adjusts some settings for small screens based on the MediaQuery data.
|
||||
Future<void> _screenSizeBasedInitialization() async {
|
||||
final prefs = LichessBinding.instance.sharedPreferences;
|
||||
final mediaQueryData = MediaQueryData.fromView(
|
||||
WidgetsBinding.instance.platformDispatcher.views.first,
|
||||
);
|
||||
final isSmallScreen = estimateHeightMinusBoard(mediaQueryData) < kSmallHeightMinusBoard;
|
||||
|
||||
final analysisPrefs = AnalysisPrefs.defaults.copyWith(showEngineLines: !isSmallScreen);
|
||||
await prefs.setString(PrefCategory.analysis.storageKey, jsonEncode(analysisPrefs.toJson()));
|
||||
final studyPrefs = StudyPrefs.defaults.copyWith(showEngineLines: !isSmallScreen);
|
||||
await prefs.setString(PrefCategory.study.storageKey, jsonEncode(studyPrefs.toJson()));
|
||||
final broadcastPrefs = BroadcastPrefs.defaults.copyWith(showEngineLines: !isSmallScreen);
|
||||
await prefs.setString(PrefCategory.broadcast.storageKey, jsonEncode(broadcastPrefs.toJson()));
|
||||
}
|
||||
|
||||
@@ -55,6 +55,10 @@ class AnalysisPreferences extends Notifier<AnalysisPrefs> with PreferencesStorag
|
||||
Future<void> toggleInlineNotation() {
|
||||
return save(state.copyWith(inlineNotation: !state.inlineNotation));
|
||||
}
|
||||
|
||||
Future<void> toggleSmallBoard() {
|
||||
return save(state.copyWith(smallBoard: !state.smallBoard));
|
||||
}
|
||||
}
|
||||
|
||||
@Freezed(fromJson: true, toJson: true)
|
||||
@@ -69,6 +73,7 @@ sealed class AnalysisPrefs with _$AnalysisPrefs implements Serializable, CommonA
|
||||
required bool showAnnotations,
|
||||
required bool showPgnComments,
|
||||
@JsonKey(defaultValue: false) required bool inlineNotation,
|
||||
@JsonKey(defaultValue: false) required bool smallBoard,
|
||||
}) = _AnalysisPrefs;
|
||||
|
||||
static const defaults = AnalysisPrefs(
|
||||
@@ -79,6 +84,7 @@ sealed class AnalysisPrefs with _$AnalysisPrefs implements Serializable, CommonA
|
||||
showAnnotations: true,
|
||||
showPgnComments: true,
|
||||
inlineNotation: false,
|
||||
smallBoard: false,
|
||||
);
|
||||
|
||||
factory AnalysisPrefs.fromJson(Map<String, dynamic> json) {
|
||||
|
||||
@@ -61,6 +61,10 @@ class BroadcastPreferences extends Notifier<BroadcastPrefs>
|
||||
Future<void> toggleInlineNotation() {
|
||||
return save(state.copyWith(inlineNotation: !state.inlineNotation));
|
||||
}
|
||||
|
||||
Future<void> toggleSmallBoard() {
|
||||
return save(state.copyWith(smallBoard: !state.smallBoard));
|
||||
}
|
||||
}
|
||||
|
||||
@Freezed(fromJson: true, toJson: true)
|
||||
@@ -74,6 +78,7 @@ sealed class BroadcastPrefs with _$BroadcastPrefs implements Serializable, Commo
|
||||
@JsonKey(defaultValue: true) required bool showAnnotations,
|
||||
@JsonKey(defaultValue: true) required bool showPgnComments,
|
||||
@JsonKey(defaultValue: false) required bool inlineNotation,
|
||||
@JsonKey(defaultValue: false) required bool smallBoard,
|
||||
}) = _BroadcastPrefs;
|
||||
|
||||
static const defaults = BroadcastPrefs(
|
||||
@@ -85,6 +90,7 @@ sealed class BroadcastPrefs with _$BroadcastPrefs implements Serializable, Commo
|
||||
showAnnotations: true,
|
||||
showPgnComments: true,
|
||||
inlineNotation: false,
|
||||
smallBoard: false,
|
||||
);
|
||||
|
||||
factory BroadcastPrefs.fromJson(Map<String, dynamic> json) => _$BroadcastPrefsFromJson(json);
|
||||
|
||||
@@ -55,6 +55,10 @@ class StudyPreferencesNotifier extends Notifier<StudyPrefs> with PreferencesStor
|
||||
Future<void> toggleInlineNotation() {
|
||||
return save(state.copyWith(inlineNotation: !state.inlineNotation));
|
||||
}
|
||||
|
||||
Future<void> toggleSmallBoard() {
|
||||
return save(state.copyWith(smallBoard: !state.smallBoard));
|
||||
}
|
||||
}
|
||||
|
||||
@Freezed(fromJson: true, toJson: true)
|
||||
@@ -69,6 +73,7 @@ sealed class StudyPrefs with _$StudyPrefs implements Serializable, CommonAnalysi
|
||||
@JsonKey(defaultValue: true) required bool showAnnotations,
|
||||
@JsonKey(defaultValue: true) required bool showPgnComments,
|
||||
@JsonKey(defaultValue: false) required bool inlineNotation,
|
||||
@JsonKey(defaultValue: false) required bool smallBoard,
|
||||
}) = _StudyPrefs;
|
||||
|
||||
static const defaults = StudyPrefs(
|
||||
@@ -79,6 +84,7 @@ sealed class StudyPrefs with _$StudyPrefs implements Serializable, CommonAnalysi
|
||||
showAnnotations: true,
|
||||
showPgnComments: true,
|
||||
inlineNotation: false,
|
||||
smallBoard: false,
|
||||
);
|
||||
|
||||
factory StudyPrefs.fromJson(Map<String, dynamic> json) {
|
||||
|
||||
@@ -72,6 +72,7 @@ class AnalysisLayout extends ConsumerWidget {
|
||||
this.engineGaugeBuilder,
|
||||
this.engineLines,
|
||||
this.bottomBar,
|
||||
this.smallBoard = false,
|
||||
this.pockets,
|
||||
super.key,
|
||||
});
|
||||
@@ -118,6 +119,11 @@ class AnalysisLayout extends ConsumerWidget {
|
||||
/// A widget to show at the bottom of the screen.
|
||||
final Widget? bottomBar;
|
||||
|
||||
/// If true, the board is displayed in a small size on portrait orientation.
|
||||
///
|
||||
/// This is `false` by default.
|
||||
final bool smallBoard;
|
||||
|
||||
/// Current state of the pockets, in variants like crazyhouse.
|
||||
///
|
||||
/// If not null, will render a [PocketsMenu] for each player.
|
||||
@@ -278,18 +284,22 @@ class AnalysisLayout extends ConsumerWidget {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
final evalGaugeWidth = getEvalGaugeWidth(context);
|
||||
final defaultBoardSize = constraints.biggest.shortestSide;
|
||||
final evalGaugeSize = engineGaugeBuilder != null
|
||||
? getEvalGaugeWidth(context)
|
||||
: 0.0;
|
||||
|
||||
final defaultBoardSize =
|
||||
(smallBoard ? kSmallBoardScale : 1.0) *
|
||||
(constraints.biggest.shortestSide - evalGaugeSize);
|
||||
|
||||
final remainingHeight = constraints.maxHeight - defaultBoardSize;
|
||||
final isSmallScreen = remainingHeight < kSmallHeightMinusBoard;
|
||||
final evalGaugeSize = engineGaugeBuilder != null ? evalGaugeWidth : 0.0;
|
||||
final additionalBoardSidePaddingForPockets = isSmallScreen ? 70.0 : 16.0;
|
||||
final boardSize = isTablet || isSmallScreen || pockets != null
|
||||
? defaultBoardSize -
|
||||
evalGaugeSize -
|
||||
kTabletBoardTableSidePadding * 2 -
|
||||
(pockets != null ? additionalBoardSidePaddingForPockets : 0.0)
|
||||
: defaultBoardSize - evalGaugeSize;
|
||||
|
||||
final boardSize =
|
||||
defaultBoardSize -
|
||||
(isTablet ? kTabletBoardTableSidePadding * 2 : 0) -
|
||||
(pockets != null ? additionalBoardSidePaddingForPockets : 0.0);
|
||||
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
||||
@@ -302,6 +302,7 @@ class _Body extends ConsumerWidget {
|
||||
sideToMove: analysisState.currentPosition.turn,
|
||||
boardBuilder: (context, boardSize, borderRadius) =>
|
||||
GameAnalysisBoard(options: options, boardSize: boardSize, boardRadius: borderRadius),
|
||||
smallBoard: analysisPrefs.smallBoard,
|
||||
boardHeader: boardHeader,
|
||||
boardFooter: boardFooter,
|
||||
engineGaugeBuilder: showEvaluationGauge && analysisState.hasAvailableEval(enginePrefs)
|
||||
|
||||
@@ -39,6 +39,12 @@ class AnalysisSettingsScreen extends ConsumerWidget {
|
||||
onChanged: (value) =>
|
||||
ref.read(analysisPreferencesProvider.notifier).toggleInlineNotation(),
|
||||
),
|
||||
SwitchSettingTile(
|
||||
title: const Text('Small board'), // TODO l10n
|
||||
value: prefs.smallBoard,
|
||||
onChanged: (value) =>
|
||||
ref.read(analysisPreferencesProvider.notifier).toggleSmallBoard(),
|
||||
),
|
||||
ListTile(
|
||||
title: Text(context.l10n.openingExplorer),
|
||||
onTap: () => showModalBottomSheet<void>(
|
||||
|
||||
@@ -286,6 +286,7 @@ class _Body extends ConsumerWidget {
|
||||
boardSize: boardSize,
|
||||
boardRadius: borderRadius,
|
||||
),
|
||||
smallBoard: broadcastPrefs.smallBoard,
|
||||
boardHeader: _PlayerWidget(
|
||||
tournamentId: tournamentId,
|
||||
roundId: roundId,
|
||||
|
||||
@@ -41,6 +41,12 @@ class BroadcastGameSettingsScreen extends ConsumerWidget {
|
||||
onChanged: (value) =>
|
||||
ref.read(broadcastPreferencesProvider.notifier).toggleInlineNotation(),
|
||||
),
|
||||
SwitchSettingTile(
|
||||
title: const Text('Small board'), // TODO l10n
|
||||
value: broadcastPrefs.smallBoard,
|
||||
onChanged: (value) =>
|
||||
ref.read(broadcastPreferencesProvider.notifier).toggleSmallBoard(),
|
||||
),
|
||||
ListTile(
|
||||
title: Text(context.l10n.openingExplorer),
|
||||
onTap: () => showModalBottomSheet<void>(
|
||||
|
||||
@@ -69,6 +69,7 @@ class _StudyScreenLoader extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final boardPrefs = ref.watch(boardPreferencesProvider);
|
||||
final studyPrefs = ref.watch(studyPreferencesProvider);
|
||||
switch (ref.watch(studyControllerProvider(options))) {
|
||||
case AsyncData(:final value):
|
||||
return _StudyScreen(options: options, studyState: value);
|
||||
@@ -90,6 +91,7 @@ class _StudyScreenLoader extends ConsumerWidget {
|
||||
orientation: Side.white,
|
||||
fen: kEmptyFEN,
|
||||
),
|
||||
smallBoard: studyPrefs.smallBoard,
|
||||
children: const [Center(child: Text('Failed to load study.'))],
|
||||
),
|
||||
),
|
||||
@@ -127,6 +129,7 @@ class _StudyScreenLoader extends ConsumerWidget {
|
||||
orientation: Side.white,
|
||||
fen: kEmptyFEN,
|
||||
),
|
||||
smallBoard: studyPrefs.smallBoard,
|
||||
children: const [Center(child: CircularProgressIndicator.adaptive())],
|
||||
),
|
||||
),
|
||||
@@ -439,6 +442,7 @@ class _Body extends ConsumerWidget {
|
||||
dimension: boardSize,
|
||||
child: Center(child: Text('${variant.label} is not supported yet.')),
|
||||
),
|
||||
smallBoard: studyPrefs.smallBoard,
|
||||
children: const [SizedBox.shrink()],
|
||||
),
|
||||
);
|
||||
@@ -463,6 +467,7 @@ class _Body extends ConsumerWidget {
|
||||
sideToMove: studyState.currentPosition?.turn,
|
||||
boardBuilder: (context, boardSize, borderRadius) =>
|
||||
StudyAnalysisBoard(options: options, boardSize: boardSize, boardRadius: borderRadius),
|
||||
smallBoard: studyPrefs.smallBoard,
|
||||
engineGaugeBuilder:
|
||||
isComputerAnalysisAllowed && showEvaluationGauge && engineGaugeParams != null
|
||||
? (context) {
|
||||
|
||||
@@ -40,6 +40,12 @@ class StudySettingsScreen extends ConsumerWidget {
|
||||
onChanged: (value) =>
|
||||
ref.read(studyPreferencesProvider.notifier).toggleInlineNotation(),
|
||||
),
|
||||
SwitchSettingTile(
|
||||
title: const Text('Small board'), // TODO l10n
|
||||
value: studyPrefs.smallBoard,
|
||||
onChanged: (value) =>
|
||||
ref.read(studyPreferencesProvider.notifier).toggleSmallBoard(),
|
||||
),
|
||||
ListTile(
|
||||
title: Text(context.l10n.openingExplorer),
|
||||
onTap: () => showModalBottomSheet<void>(
|
||||
|
||||
Reference in New Issue
Block a user