Try cupertino navigation bar bottom

Not ready yet, since the transitions are not handled.
This commit is contained in:
Vincent Velociter
2025-02-12 14:53:05 +01:00
parent e4f65028f8
commit 027371a65d
2 changed files with 225 additions and 394 deletions
@@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:ui';
import 'package:deep_pick/deep_pick.dart';
import 'package:flutter/cupertino.dart';
@@ -53,14 +52,7 @@ class CreateCustomGameScreen extends StatelessWidget {
}
Widget _buildIos(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
automaticBackgroundVisibility: false,
backgroundColor: CupertinoTheme.of(context).barBackgroundColor.withValues(alpha: 0.0),
border: null,
),
child: const _CupertinoBody(),
);
return const _CupertinoBody();
}
Widget _buildAndroid(BuildContext context) {
@@ -131,40 +123,13 @@ class _CupertinoBody extends StatefulWidget {
class _CupertinoBodyState extends State<_CupertinoBody> {
_ViewMode _selectedSegment = _ViewMode.create;
double headerOpacity = 0;
void setViewMode(_ViewMode mode) {
setState(() {
_selectedSegment = mode;
headerOpacity = 0.0;
});
}
bool handleScrollNotification(ScrollNotification notification) {
if (notification is ScrollUpdateNotification && notification.depth == 0) {
final ScrollMetrics metrics = notification.metrics;
double scrollExtent = 0.0;
switch (metrics.axisDirection) {
case AxisDirection.up:
scrollExtent = metrics.extentAfter;
case AxisDirection.down:
scrollExtent = metrics.extentBefore;
case AxisDirection.right:
case AxisDirection.left:
break;
}
final opacity = scrollExtent > 0.0 ? 1.0 : 0.0;
if (opacity != headerOpacity) {
setState(() {
headerOpacity = opacity;
});
}
}
return false;
}
@override
Widget build(BuildContext context) {
final tabSwitcher = CupertinoSlidingSegmentedControl<_ViewMode>(
@@ -181,79 +146,30 @@ class _CupertinoBodyState extends State<_CupertinoBody> {
}
},
);
return NotificationListener<ScrollNotification>(
onNotification: handleScrollNotification,
child:
_selectedSegment == _ViewMode.create
? _TabView(
cupertinoTabSwitcher: tabSwitcher,
cupertinoHeaderOpacity: headerOpacity,
sliver: _CreateGameBody(setViewMode: setViewMode),
)
: _TabView(
cupertinoTabSwitcher: tabSwitcher,
cupertinoHeaderOpacity: headerOpacity,
sliver: _ChallengesBody(setViewMode: setViewMode),
),
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(context.l10n.custom),
bottom: PreferredSize(preferredSize: const Size.fromHeight(44.0), child: tabSwitcher),
),
child: _TabView(
sliver:
_selectedSegment == _ViewMode.create
? _CreateGameBody(setViewMode: setViewMode)
: _ChallengesBody(setViewMode: setViewMode),
),
);
}
}
class _TabView extends StatelessWidget {
const _TabView({
required this.sliver,
this.cupertinoTabSwitcher,
this.cupertinoHeaderOpacity = 0.0,
});
const _TabView({required this.sliver});
final Widget sliver;
final Widget? cupertinoTabSwitcher;
final double cupertinoHeaderOpacity;
@override
Widget build(BuildContext context) {
final edgeInsets =
MediaQuery.paddingOf(context) -
(cupertinoTabSwitcher != null
? EdgeInsets.only(top: MediaQuery.paddingOf(context).top)
: EdgeInsets.zero) +
Styles.verticalBodyPadding;
final backgroundColor = CupertinoTheme.of(context).barBackgroundColor;
return CustomScrollView(
slivers: [
if (cupertinoTabSwitcher != null)
PinnedHeaderSliver(
child: ClipRect(
child: BackdropFilter(
enabled: backgroundColor.a != 1,
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
decoration: ShapeDecoration(
color:
cupertinoHeaderOpacity == 1.0
? backgroundColor
: backgroundColor.withAlpha(0),
shape: LinearBorder.bottom(
side: BorderSide(
color:
cupertinoHeaderOpacity == 1.0
? const Color(0x4D000000)
: Colors.transparent,
width: 0.0,
),
),
),
padding:
Styles.bodyPadding + EdgeInsets.only(top: MediaQuery.paddingOf(context).top),
child: cupertinoTabSwitcher,
),
),
),
),
SliverPadding(padding: edgeInsets, sliver: sliver),
],
);
final edgeInsets = MediaQuery.paddingOf(context) + Styles.verticalBodyPadding;
return CustomScrollView(slivers: [SliverPadding(padding: edgeInsets, sliver: sliver)]);
}
}
+210 -295
View File
@@ -1,4 +1,3 @@
import 'dart:ui' show ImageFilter;
import 'package:chessground/chessground.dart';
import 'package:dartchess/dartchess.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
@@ -22,7 +21,7 @@ import 'package:lichess_mobile/src/widgets/list.dart';
import 'package:lichess_mobile/src/widgets/platform.dart';
import 'package:lichess_mobile/src/widgets/settings.dart';
class ThemeSettingsScreen extends ConsumerWidget {
class ThemeSettingsScreen extends ConsumerStatefulWidget {
const ThemeSettingsScreen({super.key});
static Route<dynamic> buildRoute(BuildContext context) {
@@ -34,17 +33,220 @@ class ThemeSettingsScreen extends ConsumerWidget {
}
@override
Widget build(BuildContext context, WidgetRef ref) {
ConsumerState<ThemeSettingsScreen> createState() => _ThemeSettingsScreenState();
}
class _ThemeSettingsScreenState extends ConsumerState<ThemeSettingsScreen> {
late double brightness;
late double hue;
@override
void initState() {
super.initState();
final boardPrefs = ref.read(boardPreferencesProvider);
brightness = boardPrefs.brightness;
hue = boardPrefs.hue;
}
@override
Widget build(BuildContext context) {
final generalPrefs = ref.watch(generalPreferencesProvider);
final boardPrefs = ref.watch(boardPreferencesProvider);
final boardSize = isTabletOrLarger(context) ? 350.0 : 200.0;
final bool hasAjustedColors =
brightness != kBoardDefaultBrightnessFilter || hue != kBoardDefaultHueFilter;
final body = ListView(
padding:
Theme.of(context).platform == TargetPlatform.iOS
? (MediaQuery.paddingOf(context) +
EdgeInsets.only(top: boardSize + 32.0 + kMinInteractiveDimensionCupertino))
: null,
children: [
ListSection(
hasLeading: true,
children: [
if (getCorePalette() != null)
SwitchSettingTile(
leading: const Icon(Icons.colorize_outlined),
title: Text(context.l10n.mobileSystemColors),
value: generalPrefs.systemColors,
onChanged: (value) {
ref.read(generalPreferencesProvider.notifier).toggleSystemColors();
},
),
SettingsListTile(
icon: const Icon(Icons.wallpaper),
settingsLabel: Text(context.l10n.background),
settingsValue:
generalPrefs.backgroundTheme?.label(context.l10n) ??
(generalPrefs.backgroundImage != null ? 'Image' : 'Default'),
onTap: () {
Navigator.of(context).push(BackgroundChoiceScreen.buildRoute(context));
},
),
if (generalPrefs.backgroundTheme != null || generalPrefs.backgroundImage != null)
PlatformListTile(
leading: const Icon(Icons.cancel),
title: const Text('Reset background'),
onTap: () {
ref
.read(generalPreferencesProvider.notifier)
.setBackground(backgroundTheme: null, backgroundImage: null);
},
),
SettingsListTile(
icon: const Icon(LichessIcons.chess_board),
settingsLabel: Text(context.l10n.board),
settingsValue: boardPrefs.boardTheme.label,
onTap: () {
Navigator.of(context).push(BoardChoiceScreen.buildRoute(context));
},
),
SettingsListTile(
icon: const Icon(LichessIcons.chess_pawn),
settingsLabel: Text(context.l10n.pieceSet),
settingsValue: boardPrefs.pieceSet.label,
onTap: () {
Navigator.of(context).push(PieceSetScreen.buildRoute(context));
},
),
SettingsListTile(
icon: const Icon(LichessIcons.arrow_full_upperright),
settingsLabel: const Text('Shape color'),
settingsValue: shapeColorL10n(context, boardPrefs.shapeColor),
onTap: () {
showChoicePicker(
context,
choices: ShapeColor.values,
selectedItem: boardPrefs.shapeColor,
labelBuilder:
(t) => Text.rich(
TextSpan(
children: [
TextSpan(text: shapeColorL10n(context, t)),
const TextSpan(text: ' '),
WidgetSpan(child: Container(width: 15, height: 15, color: t.color)),
],
),
),
onSelectedItemChanged: (ShapeColor? value) {
ref
.read(boardPreferencesProvider.notifier)
.setShapeColor(value ?? ShapeColor.green);
},
);
},
),
SwitchSettingTile(
leading: const Icon(Icons.location_on),
title: Text(context.l10n.preferencesBoardCoordinates),
value: boardPrefs.coordinates,
onChanged: (value) {
ref.read(boardPreferencesProvider.notifier).toggleCoordinates();
},
),
SwitchSettingTile(
// TODO translate
leading: const Icon(Icons.border_outer),
title: const Text('Show border'),
value: boardPrefs.showBorder,
onChanged: (value) {
ref.read(boardPreferencesProvider.notifier).toggleBorder();
},
),
],
),
ListSection(
header: SettingsSectionTitle(context.l10n.advancedSettings),
hasLeading: true,
children: [
PlatformListTile(
leading: const Icon(Icons.brightness_6),
title: Slider.adaptive(
min: 0.2,
max: 1.4,
value: brightness,
onChanged: (value) {
setState(() {
brightness = value;
});
},
onChangeEnd: (value) {
ref.read(boardPreferencesProvider.notifier).adjustColors(brightness: brightness);
},
),
),
PlatformListTile(
leading: const Icon(Icons.invert_colors),
title: Slider.adaptive(
min: 0.0,
max: 360.0,
value: hue,
onChanged: (value) {
setState(() {
hue = value;
});
},
onChangeEnd: (value) {
ref.read(boardPreferencesProvider.notifier).adjustColors(hue: hue);
},
),
),
PlatformListTile(
leading: Opacity(
opacity: hasAjustedColors ? 1.0 : 0.5,
child: const Icon(Icons.cancel),
),
title: Opacity(
opacity: hasAjustedColors ? 1.0 : 0.5,
child: Text(context.l10n.boardReset),
),
onTap:
hasAjustedColors
? () {
setState(() {
brightness = kBoardDefaultBrightnessFilter;
hue = kBoardDefaultHueFilter;
});
ref
.read(boardPreferencesProvider.notifier)
.adjustColors(brightness: brightness, hue: hue);
}
: null,
),
],
),
],
);
final boardPreview = PreferredSize(
preferredSize: Size.fromHeight(boardSize + 32.0),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: _BoardPreview(
size: boardSize,
boardPrefs: boardPrefs,
brightness: brightness,
hue: hue,
),
),
);
return PlatformWidget(
androidBuilder: (context) => const Scaffold(body: _Body()),
androidBuilder:
(context) => Scaffold(
appBar: AppBar(title: Text(context.l10n.mobileTheme), bottom: boardPreview),
body: body,
),
iosBuilder:
(context) => CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
automaticBackgroundVisibility: false,
backgroundColor: CupertinoTheme.of(context).barBackgroundColor.withValues(alpha: 0.0),
border: null,
middle: Text(context.l10n.mobileTheme),
bottom: boardPreview,
),
child: const _Body(),
child: body,
),
);
}
@@ -59,293 +261,6 @@ switch (shapeColor) {
ShapeColor.yellow => 'Yellow',
};
class _Body extends ConsumerStatefulWidget {
const _Body();
@override
ConsumerState<_Body> createState() => _BodyState();
}
class _BodyState extends ConsumerState<_Body> {
late double brightness;
late double hue;
double headerOpacity = 0;
bool openAdjustColorSection = false;
@override
void initState() {
super.initState();
final boardPrefs = ref.read(boardPreferencesProvider);
brightness = boardPrefs.brightness;
hue = boardPrefs.hue;
}
bool handleScrollNotification(ScrollNotification notification) {
if (notification is ScrollUpdateNotification && notification.depth == 0) {
final ScrollMetrics metrics = notification.metrics;
double scrollExtent = 0.0;
switch (metrics.axisDirection) {
case AxisDirection.up:
scrollExtent = metrics.extentAfter;
case AxisDirection.down:
scrollExtent = metrics.extentBefore;
case AxisDirection.right:
case AxisDirection.left:
break;
}
final opacity = scrollExtent > 0.0 ? 1.0 : 0.0;
if (opacity != headerOpacity) {
setState(() {
headerOpacity = opacity;
});
}
}
return false;
}
@override
Widget build(BuildContext context) {
final generalPrefs = ref.watch(generalPreferencesProvider);
final boardPrefs = ref.watch(boardPreferencesProvider);
final bool hasAjustedColors =
brightness != kBoardDefaultBrightnessFilter || hue != kBoardDefaultHueFilter;
final boardSize = isTabletOrLarger(context) ? 350.0 : 200.0;
final backgroundColor = CupertinoTheme.of(context).barBackgroundColor;
return NotificationListener(
onNotification: handleScrollNotification,
child: CustomScrollView(
slivers: [
if (Theme.of(context).platform == TargetPlatform.iOS)
PinnedHeaderSliver(
child: ClipRect(
child: BackdropFilter(
enabled: backgroundColor.a != 1,
filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
decoration: ShapeDecoration(
color: headerOpacity == 1.0 ? backgroundColor : backgroundColor.withAlpha(0),
shape: LinearBorder.bottom(
side: BorderSide(
color:
headerOpacity == 1.0 ? const Color(0x4D000000) : Colors.transparent,
width: 0.0,
),
),
),
padding:
Styles.bodyPadding +
EdgeInsets.only(top: MediaQuery.paddingOf(context).top),
child: _BoardPreview(
size: boardSize,
boardPrefs: boardPrefs,
brightness: brightness,
hue: hue,
),
),
),
),
)
else
SliverAppBar(
backgroundColor: Theme.of(
context,
).appBarTheme.backgroundColor?.withValues(alpha: headerOpacity),
pinned: true,
title: Text(context.l10n.mobileTheme),
bottom: PreferredSize(
preferredSize: Size.fromHeight(boardSize + 16.0),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: _BoardPreview(
size: boardSize,
boardPrefs: boardPrefs,
brightness: brightness,
hue: hue,
),
),
),
),
SliverList.list(
children: [
ListSection(
hasLeading: true,
children: [
if (getCorePalette() != null)
SwitchSettingTile(
leading: const Icon(Icons.colorize_outlined),
title: Text(context.l10n.mobileSystemColors),
value: generalPrefs.systemColors,
onChanged: (value) {
ref.read(generalPreferencesProvider.notifier).toggleSystemColors();
},
),
SettingsListTile(
icon: const Icon(Icons.wallpaper),
settingsLabel: Text(context.l10n.background),
settingsValue:
generalPrefs.backgroundTheme?.label(context.l10n) ??
(generalPrefs.backgroundImage != null ? 'Image' : 'Default'),
onTap: () {
Navigator.of(context).push(BackgroundChoiceScreen.buildRoute(context));
},
),
if (generalPrefs.backgroundTheme != null || generalPrefs.backgroundImage != null)
PlatformListTile(
leading: const Icon(Icons.cancel),
title: const Text('Reset background'),
onTap: () {
ref
.read(generalPreferencesProvider.notifier)
.setBackground(backgroundTheme: null, backgroundImage: null);
},
),
SettingsListTile(
icon: const Icon(LichessIcons.chess_board),
settingsLabel: Text(context.l10n.board),
settingsValue: boardPrefs.boardTheme.label,
onTap: () {
Navigator.of(context).push(BoardChoiceScreen.buildRoute(context));
},
),
SettingsListTile(
icon: const Icon(LichessIcons.chess_pawn),
settingsLabel: Text(context.l10n.pieceSet),
settingsValue: boardPrefs.pieceSet.label,
onTap: () {
Navigator.of(context).push(PieceSetScreen.buildRoute(context));
},
),
SettingsListTile(
icon: const Icon(LichessIcons.arrow_full_upperright),
settingsLabel: const Text('Shape color'),
settingsValue: shapeColorL10n(context, boardPrefs.shapeColor),
onTap: () {
showChoicePicker(
context,
choices: ShapeColor.values,
selectedItem: boardPrefs.shapeColor,
labelBuilder:
(t) => Text.rich(
TextSpan(
children: [
TextSpan(text: shapeColorL10n(context, t)),
const TextSpan(text: ' '),
WidgetSpan(
child: Container(width: 15, height: 15, color: t.color),
),
],
),
),
onSelectedItemChanged: (ShapeColor? value) {
ref
.read(boardPreferencesProvider.notifier)
.setShapeColor(value ?? ShapeColor.green);
},
);
},
),
SwitchSettingTile(
leading: const Icon(Icons.location_on),
title: Text(context.l10n.preferencesBoardCoordinates),
value: boardPrefs.coordinates,
onChanged: (value) {
ref.read(boardPreferencesProvider.notifier).toggleCoordinates();
},
),
SwitchSettingTile(
// TODO translate
leading: const Icon(Icons.border_outer),
title: const Text('Show border'),
value: boardPrefs.showBorder,
onChanged: (value) {
ref.read(boardPreferencesProvider.notifier).toggleBorder();
},
),
],
),
ListSection(
header: SettingsSectionTitle(context.l10n.advancedSettings),
hasLeading: true,
children: [
PlatformListTile(
leading: const Icon(Icons.brightness_6),
title: Slider.adaptive(
min: 0.2,
max: 1.4,
value: brightness,
onChanged: (value) {
setState(() {
brightness = value;
});
},
onChangeEnd: (value) {
ref
.read(boardPreferencesProvider.notifier)
.adjustColors(brightness: brightness);
},
),
),
PlatformListTile(
leading: const Icon(Icons.invert_colors),
title: Slider.adaptive(
min: 0.0,
max: 360.0,
value: hue,
onChanged: (value) {
setState(() {
hue = value;
});
},
onChangeEnd: (value) {
ref.read(boardPreferencesProvider.notifier).adjustColors(hue: hue);
},
),
),
PlatformListTile(
leading: Opacity(
opacity: hasAjustedColors ? 1.0 : 0.5,
child: const Icon(Icons.cancel),
),
title: Opacity(
opacity: hasAjustedColors ? 1.0 : 0.5,
child: Text(context.l10n.boardReset),
),
onTap:
hasAjustedColors
? () {
setState(() {
brightness = kBoardDefaultBrightnessFilter;
hue = kBoardDefaultHueFilter;
});
ref
.read(boardPreferencesProvider.notifier)
.adjustColors(brightness: brightness, hue: hue);
}
: null,
),
],
),
],
),
const SliverSafeArea(
top: false,
sliver: SliverToBoxAdapter(child: SizedBox(height: 16.0)),
),
],
),
);
}
}
class _BoardPreview extends StatelessWidget {
const _BoardPreview({
required this.size,