mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-22 07:20:39 +00:00
ML-18 Implement primary color picker (#19)
* wip * hide `DynamicColorListTile` if unavailable * added color animation for `AnimatedDialog` * adjusted some colors * sync `AnimatedDialog` insets with material * scroll to selected color
This commit is contained in:
parent
cb4e91cb0a
commit
9cfffc3377
19 changed files with 369 additions and 138 deletions
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -9,8 +9,6 @@
|
||||||
},
|
},
|
||||||
"dart.lineLength": 100,
|
"dart.lineLength": 100,
|
||||||
"[dart]": {
|
"[dart]": {
|
||||||
"editor.formatOnSave": true,
|
|
||||||
"editor.formatOnType": true,
|
|
||||||
"editor.rulers": [
|
"editor.rulers": [
|
||||||
100,
|
100,
|
||||||
120,
|
120,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
import 'models/ev_source_type.dart';
|
import 'models/ev_source_type.dart';
|
||||||
|
@ -15,6 +16,7 @@ class UserPreferencesService {
|
||||||
|
|
||||||
static const _hapticsKey = "haptics";
|
static const _hapticsKey = "haptics";
|
||||||
static const _themeTypeKey = "themeType";
|
static const _themeTypeKey = "themeType";
|
||||||
|
static const _primaryColorKey = "primaryColor";
|
||||||
static const _dynamicColorKey = "dynamicColor";
|
static const _dynamicColorKey = "dynamicColor";
|
||||||
|
|
||||||
final SharedPreferences _sharedPreferences;
|
final SharedPreferences _sharedPreferences;
|
||||||
|
@ -42,6 +44,9 @@ class UserPreferencesService {
|
||||||
ThemeType get themeType => ThemeType.values[_sharedPreferences.getInt(_themeTypeKey) ?? 0];
|
ThemeType get themeType => ThemeType.values[_sharedPreferences.getInt(_themeTypeKey) ?? 0];
|
||||||
set themeType(ThemeType value) => _sharedPreferences.setInt(_themeTypeKey, value.index);
|
set themeType(ThemeType value) => _sharedPreferences.setInt(_themeTypeKey, value.index);
|
||||||
|
|
||||||
|
Color get primaryColor => Color(_sharedPreferences.getInt(_primaryColorKey) ?? 0xff2196f3);
|
||||||
|
set primaryColor(Color value) => _sharedPreferences.setInt(_primaryColorKey, value.value);
|
||||||
|
|
||||||
bool get dynamicColor => _sharedPreferences.getBool(_dynamicColorKey) ?? false;
|
bool get dynamicColor => _sharedPreferences.getBool(_dynamicColorKey) ?? false;
|
||||||
set dynamicColor(bool value) => _sharedPreferences.setBool(_dynamicColorKey, value);
|
set dynamicColor(bool value) => _sharedPreferences.setBool(_dynamicColorKey, value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,10 +36,12 @@
|
||||||
"haptics": "Haptics",
|
"haptics": "Haptics",
|
||||||
"theme": "Theme",
|
"theme": "Theme",
|
||||||
"chooseTheme": "Choose theme",
|
"chooseTheme": "Choose theme",
|
||||||
"dynamicColor": "Dynamic color",
|
|
||||||
"themeLight": "Light",
|
"themeLight": "Light",
|
||||||
"themeDark": "Dark",
|
"themeDark": "Dark",
|
||||||
"themeSystemDefault": "System default",
|
"themeSystemDefault": "System default",
|
||||||
|
"dynamicColor": "Dynamic color",
|
||||||
|
"primaryColor": "Primary color",
|
||||||
|
"choosePrimaryColor": "Choose primary color",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
"sourceCode": "Source code",
|
"sourceCode": "Source code",
|
||||||
"reportIssue": "Report an issue",
|
"reportIssue": "Report an issue",
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:flutter/scheduler.dart';
|
||||||
import 'package:lightmeter/data/models/dynamic_colors_state.dart';
|
import 'package:lightmeter/data/models/dynamic_colors_state.dart';
|
||||||
import 'package:lightmeter/data/models/theme_type.dart';
|
import 'package:lightmeter/data/models/theme_type.dart';
|
||||||
import 'package:lightmeter/data/shared_prefs_service.dart';
|
import 'package:lightmeter/data/shared_prefs_service.dart';
|
||||||
|
import 'package:lightmeter/res/dimens.dart';
|
||||||
import 'package:material_color_utilities/material_color_utilities.dart';
|
import 'package:material_color_utilities/material_color_utilities.dart';
|
||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -20,6 +21,25 @@ class ThemeProvider extends StatefulWidget {
|
||||||
return context.findAncestorStateOfType<ThemeProviderState>()!;
|
return context.findAncestorStateOfType<ThemeProviderState>()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const primaryColorsList = [
|
||||||
|
Color(0xfff44336),
|
||||||
|
Color(0xffe91e63),
|
||||||
|
Color(0xff9c27b0),
|
||||||
|
Color(0xff673ab7),
|
||||||
|
Color(0xff3f51b5),
|
||||||
|
Color(0xff2196f3),
|
||||||
|
Color(0xff03a9f4),
|
||||||
|
Color(0xff00bcd4),
|
||||||
|
Color(0xff009688),
|
||||||
|
Color(0xff4caf50),
|
||||||
|
Color(0xff8bc34a),
|
||||||
|
Color(0xffcddc39),
|
||||||
|
Color(0xffffeb3b),
|
||||||
|
Color(0xffffc107),
|
||||||
|
Color(0xffff9800),
|
||||||
|
Color(0xffff5722),
|
||||||
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ThemeProvider> createState() => ThemeProviderState();
|
State<ThemeProvider> createState() => ThemeProviderState();
|
||||||
}
|
}
|
||||||
|
@ -29,7 +49,7 @@ class ThemeProviderState extends State<ThemeProvider> {
|
||||||
|
|
||||||
late final _themeTypeNotifier = ValueNotifier<ThemeType>(_prefs.themeType);
|
late final _themeTypeNotifier = ValueNotifier<ThemeType>(_prefs.themeType);
|
||||||
late final _dynamicColorNotifier = ValueNotifier<bool>(_prefs.dynamicColor);
|
late final _dynamicColorNotifier = ValueNotifier<bool>(_prefs.dynamicColor);
|
||||||
late final _primaryColorNotifier = ValueNotifier<Color>(const Color(0xFF2196f3));
|
late final _primaryColorNotifier = ValueNotifier<Color>(_prefs.primaryColor);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
@ -80,6 +100,11 @@ class ThemeProviderState extends State<ThemeProvider> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setPrimaryColor(Color color) {
|
||||||
|
_primaryColorNotifier.value = color;
|
||||||
|
_prefs.primaryColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
void enableDynamicColor(bool enable) {
|
void enableDynamicColor(bool enable) {
|
||||||
_dynamicColorNotifier.value = enable;
|
_dynamicColorNotifier.value = enable;
|
||||||
_prefs.dynamicColor = enable;
|
_prefs.dynamicColor = enable;
|
||||||
|
@ -148,9 +173,33 @@ class _ThemeDataProvider extends StatelessWidget {
|
||||||
return ThemeData(
|
return ThemeData(
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
brightness: scheme.brightness,
|
brightness: scheme.brightness,
|
||||||
|
primaryColor: primaryColor,
|
||||||
colorScheme: scheme,
|
colorScheme: scheme,
|
||||||
|
appBarTheme: AppBarTheme(
|
||||||
|
elevation: 4,
|
||||||
|
color: scheme.surface,
|
||||||
|
surfaceTintColor: scheme.surfaceTint,
|
||||||
|
),
|
||||||
|
cardTheme: CardTheme(
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
color: scheme.surface,
|
||||||
|
elevation: 4,
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
shadowColor: Colors.transparent,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(Dimens.borderRadiusL)),
|
||||||
|
surfaceTintColor: scheme.surfaceTint,
|
||||||
|
),
|
||||||
dialogBackgroundColor: scheme.surface,
|
dialogBackgroundColor: scheme.surface,
|
||||||
dialogTheme: DialogTheme(backgroundColor: scheme.surface),
|
dialogTheme: DialogTheme(
|
||||||
|
backgroundColor: scheme.surface,
|
||||||
|
surfaceTintColor: scheme.surfaceTint,
|
||||||
|
elevation: 6,
|
||||||
|
),
|
||||||
|
listTileTheme: ListTileThemeData(
|
||||||
|
style: ListTileStyle.list,
|
||||||
|
iconColor: scheme.onSurface,
|
||||||
|
textColor: scheme.onSurface,
|
||||||
|
),
|
||||||
scaffoldBackgroundColor: scheme.surface,
|
scaffoldBackgroundColor: scheme.surface,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
/// `valueM` represents the base value.
|
/// `valueM` represents the base value.
|
||||||
/// All other values differs by 8dp.
|
/// All other values differs by 8dp.
|
||||||
class Dimens {
|
class Dimens {
|
||||||
|
@ -13,6 +15,7 @@ class Dimens {
|
||||||
static const double grid56 = 56;
|
static const double grid56 = 56;
|
||||||
static const double grid168 = 168;
|
static const double grid168 = 168;
|
||||||
|
|
||||||
|
static const double paddingS = 8;
|
||||||
static const double paddingM = 16;
|
static const double paddingM = 16;
|
||||||
static const double paddingL = 24;
|
static const double paddingL = 24;
|
||||||
|
|
||||||
|
@ -31,4 +34,8 @@ class Dimens {
|
||||||
static const double cameraSliderTrackRadius = cameraSliderTrackHeight / 2;
|
static const double cameraSliderTrackRadius = cameraSliderTrackHeight / 2;
|
||||||
static const double cameraSliderHandleSize = 32;
|
static const double cameraSliderHandleSize = 32;
|
||||||
static const double cameraSliderHandleIconSize = cameraSliderHandleSize * 2 / 3;
|
static const double cameraSliderHandleIconSize = cameraSliderHandleSize * 2 / 3;
|
||||||
|
|
||||||
|
// Dialog
|
||||||
|
// Taken from `Dialog` documentation
|
||||||
|
static const EdgeInsets dialogMargin = EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lightmeter/res/dimens.dart';
|
import 'package:lightmeter/res/dimens.dart';
|
||||||
import 'package:lightmeter/screens/metering/components/bottom_controls/components/shared/widget_circle_filled.dart';
|
import 'package:lightmeter/screens/shared/filled_circle/widget_circle_filled.dart';
|
||||||
|
|
||||||
class MeteringMeasureButton extends StatefulWidget {
|
class MeteringMeasureButton extends StatefulWidget {
|
||||||
final double size;
|
final double size;
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:lightmeter/res/dimens.dart';
|
|
||||||
import 'package:lightmeter/screens/metering/components/bottom_controls/components/shared/widget_circle_filled.dart';
|
|
||||||
|
|
||||||
class MeteringSecondaryButton extends StatelessWidget {
|
|
||||||
final IconData icon;
|
|
||||||
final VoidCallback onPressed;
|
|
||||||
|
|
||||||
const MeteringSecondaryButton({
|
|
||||||
required this.icon,
|
|
||||||
required this.onPressed,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Center(
|
|
||||||
child: FilledCircle(
|
|
||||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
|
||||||
size: Dimens.grid48,
|
|
||||||
child: Center(
|
|
||||||
child: IconButton(
|
|
||||||
onPressed: onPressed,
|
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
|
||||||
icon: Icon(icon),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:lightmeter/res/dimens.dart';
|
||||||
|
|
||||||
|
import 'widget_bottom_controls.dart';
|
||||||
|
|
||||||
|
class MeteringBottomControlsProvider extends StatelessWidget {
|
||||||
|
final VoidCallback? onSwitchEvSourceType;
|
||||||
|
final VoidCallback onMeasure;
|
||||||
|
final VoidCallback onSettings;
|
||||||
|
|
||||||
|
const MeteringBottomControlsProvider({
|
||||||
|
required this.onSwitchEvSourceType,
|
||||||
|
required this.onMeasure,
|
||||||
|
required this.onSettings,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final scheme = Theme.of(context).colorScheme;
|
||||||
|
return IconButtonTheme(
|
||||||
|
data: IconButtonThemeData(
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: MaterialStatePropertyAll(scheme.surface),
|
||||||
|
elevation: const MaterialStatePropertyAll(4),
|
||||||
|
iconColor: MaterialStatePropertyAll(scheme.onSurface),
|
||||||
|
shadowColor: const MaterialStatePropertyAll(Colors.transparent),
|
||||||
|
surfaceTintColor: MaterialStatePropertyAll(scheme.surfaceTint),
|
||||||
|
fixedSize: const MaterialStatePropertyAll(Size(Dimens.grid48, Dimens.grid48)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: MeteringBottomControls(
|
||||||
|
onSwitchEvSourceType: onSwitchEvSourceType,
|
||||||
|
onMeasure: onMeasure,
|
||||||
|
onSettings: onSettings,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,6 @@ import 'package:lightmeter/res/dimens.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'components/measure_button/widget_button_measure.dart';
|
import 'components/measure_button/widget_button_measure.dart';
|
||||||
import 'components/secondary_button/widget_button_secondary.dart';
|
|
||||||
|
|
||||||
class MeteringBottomControls extends StatelessWidget {
|
class MeteringBottomControls extends StatelessWidget {
|
||||||
final VoidCallback? onSwitchEvSourceType;
|
final VoidCallback? onSwitchEvSourceType;
|
||||||
|
@ -36,11 +35,13 @@ class MeteringBottomControls extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
if (onSwitchEvSourceType != null)
|
if (onSwitchEvSourceType != null)
|
||||||
Expanded(
|
Expanded(
|
||||||
child: MeteringSecondaryButton(
|
child: Center(
|
||||||
onPressed: onSwitchEvSourceType!,
|
child: IconButton(
|
||||||
icon: context.watch<EvSourceType>() != EvSourceType.camera
|
onPressed: onSwitchEvSourceType,
|
||||||
|
icon: Icon(context.watch<EvSourceType>() != EvSourceType.camera
|
||||||
? Icons.camera_rear
|
? Icons.camera_rear
|
||||||
: Icons.wb_incandescent,
|
: Icons.wb_incandescent),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
|
@ -49,9 +50,11 @@ class MeteringBottomControls extends StatelessWidget {
|
||||||
onTap: onMeasure,
|
onTap: onMeasure,
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: MeteringSecondaryButton(
|
child: Center(
|
||||||
|
child: IconButton(
|
||||||
onPressed: onSettings,
|
onPressed: onSettings,
|
||||||
icon: Icons.settings,
|
icon: const Icon(Icons.settings),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -35,6 +35,8 @@ class AnimatedDialogState extends State<AnimatedDialog> with SingleTickerProvide
|
||||||
late final Animation<double> _borderRadiusAnimation;
|
late final Animation<double> _borderRadiusAnimation;
|
||||||
late final Animation<double> _closedOpacityAnimation;
|
late final Animation<double> _closedOpacityAnimation;
|
||||||
late final Animation<double> _openedOpacityAnimation;
|
late final Animation<double> _openedOpacityAnimation;
|
||||||
|
late final Animation<Color?> _foregroundColorAnimation;
|
||||||
|
late final Animation<double> _elevationAnimation;
|
||||||
|
|
||||||
bool _isDialogShown = false;
|
bool _isDialogShown = false;
|
||||||
|
|
||||||
|
@ -91,8 +93,10 @@ class AnimatedDialogState extends State<AnimatedDialog> with SingleTickerProvide
|
||||||
begin: _closedSize,
|
begin: _closedSize,
|
||||||
end: widget.openedSize ??
|
end: widget.openedSize ??
|
||||||
Size(
|
Size(
|
||||||
mediaQuery.size.width - mediaQuery.padding.horizontal - Dimens.paddingM * 2,
|
mediaQuery.size.width -
|
||||||
mediaQuery.size.height - mediaQuery.padding.vertical - Dimens.paddingM * 2,
|
mediaQuery.padding.horizontal -
|
||||||
|
Dimens.dialogMargin.horizontal,
|
||||||
|
mediaQuery.size.height - mediaQuery.padding.vertical - Dimens.dialogMargin.vertical,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
_sizeAnimation = _sizeTween.animate(_defaultCurvedAnimation);
|
_sizeAnimation = _sizeTween.animate(_defaultCurvedAnimation);
|
||||||
|
@ -112,6 +116,20 @@ class AnimatedDialogState extends State<AnimatedDialog> with SingleTickerProvide
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
_foregroundColorAnimation = ColorTween(
|
||||||
|
begin: Theme.of(context).colorScheme.primaryContainer,
|
||||||
|
end: Theme.of(context).colorScheme.surface,
|
||||||
|
).animate(_defaultCurvedAnimation);
|
||||||
|
|
||||||
|
_elevationAnimation = Tween<double>(
|
||||||
|
begin: 0,
|
||||||
|
end: Theme.of(context).dialogTheme.elevation!,
|
||||||
|
).animate(_defaultCurvedAnimation);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_animationController.dispose();
|
_animationController.dispose();
|
||||||
|
@ -125,14 +143,8 @@ class AnimatedDialogState extends State<AnimatedDialog> with SingleTickerProvide
|
||||||
onTap: _openDialog,
|
onTap: _openDialog,
|
||||||
child: Opacity(
|
child: Opacity(
|
||||||
opacity: _isDialogShown ? 0 : 1,
|
opacity: _isDialogShown ? 0 : 1,
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(Dimens.borderRadiusM),
|
|
||||||
child: ColoredBox(
|
|
||||||
color: Theme.of(context).colorScheme.primaryContainer,
|
|
||||||
child: widget.child ?? widget.closedChild,
|
child: widget.child ?? widget.closedChild,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,6 +163,8 @@ class AnimatedDialogState extends State<AnimatedDialog> with SingleTickerProvide
|
||||||
sizeAnimation: _sizeAnimation,
|
sizeAnimation: _sizeAnimation,
|
||||||
offsetAnimation: _offsetAnimation,
|
offsetAnimation: _offsetAnimation,
|
||||||
borderRadiusAnimation: _borderRadiusAnimation,
|
borderRadiusAnimation: _borderRadiusAnimation,
|
||||||
|
foregroundColorAnimation: _foregroundColorAnimation,
|
||||||
|
elevationAnimation: _elevationAnimation,
|
||||||
onDismiss: close,
|
onDismiss: close,
|
||||||
builder: widget.closedChild != null && widget.openedChild != null
|
builder: widget.closedChild != null && widget.openedChild != null
|
||||||
? (_) => _AnimatedSwitcher(
|
? (_) => _AnimatedSwitcher(
|
||||||
|
@ -195,6 +209,8 @@ class _AnimatedOverlay extends StatelessWidget {
|
||||||
final Animation<Size?> sizeAnimation;
|
final Animation<Size?> sizeAnimation;
|
||||||
final Animation<Size?> offsetAnimation;
|
final Animation<Size?> offsetAnimation;
|
||||||
final Animation<double> borderRadiusAnimation;
|
final Animation<double> borderRadiusAnimation;
|
||||||
|
final Animation<Color?> foregroundColorAnimation;
|
||||||
|
final Animation<double> elevationAnimation;
|
||||||
final VoidCallback onDismiss;
|
final VoidCallback onDismiss;
|
||||||
final Widget? child;
|
final Widget? child;
|
||||||
final Widget Function(BuildContext context)? builder;
|
final Widget Function(BuildContext context)? builder;
|
||||||
|
@ -205,6 +221,8 @@ class _AnimatedOverlay extends StatelessWidget {
|
||||||
required this.sizeAnimation,
|
required this.sizeAnimation,
|
||||||
required this.offsetAnimation,
|
required this.offsetAnimation,
|
||||||
required this.borderRadiusAnimation,
|
required this.borderRadiusAnimation,
|
||||||
|
required this.foregroundColorAnimation,
|
||||||
|
required this.elevationAnimation,
|
||||||
required this.onDismiss,
|
required this.onDismiss,
|
||||||
this.child,
|
this.child,
|
||||||
this.builder,
|
this.builder,
|
||||||
|
@ -236,7 +254,9 @@ class _AnimatedOverlay extends StatelessWidget {
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(borderRadiusAnimation.value),
|
borderRadius: BorderRadius.circular(borderRadiusAnimation.value),
|
||||||
child: Material(
|
child: Material(
|
||||||
color: Theme.of(context).colorScheme.primaryContainer,
|
elevation: elevationAnimation.value,
|
||||||
|
surfaceTintColor: Theme.of(context).colorScheme.surfaceTint,
|
||||||
|
color: foregroundColorAnimation.value,
|
||||||
child: builder?.call(context) ?? child,
|
child: builder?.call(context) ?? child,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -57,9 +57,7 @@ class _PhotographyValuePickerDialogState<T extends PhotographyValue>
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
ColoredBox(
|
Column(
|
||||||
color: Theme.of(context).colorScheme.primaryContainer,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(
|
padding: const EdgeInsets.fromLTRB(
|
||||||
|
@ -83,12 +81,11 @@ class _PhotographyValuePickerDialogState<T extends PhotographyValue>
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Divider(
|
Divider(
|
||||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
height: 0,
|
height: 0,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
|
@ -117,12 +114,10 @@ class _PhotographyValuePickerDialogState<T extends PhotographyValue>
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ColoredBox(
|
Column(
|
||||||
color: Theme.of(context).colorScheme.primaryContainer,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
children: [
|
||||||
Divider(
|
Divider(
|
||||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
height: 0,
|
height: 0,
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
|
@ -146,7 +141,6 @@ class _PhotographyValuePickerDialogState<T extends PhotographyValue>
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,7 +119,7 @@ class _NdValueTile extends StatelessWidget {
|
||||||
onChanged: onChanged,
|
onChanged: onChanged,
|
||||||
closedChild: ReadingValueContainer.singleValue(
|
closedChild: ReadingValueContainer.singleValue(
|
||||||
value: ReadingValue(
|
value: ReadingValue(
|
||||||
label: S.of(context).iso,
|
label: S.of(context).nd,
|
||||||
value: value.value.toString(),
|
value: value.value.toString(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -5,7 +5,7 @@ import 'package:lightmeter/data/models/photography_values/photography_value.dart
|
||||||
import 'package:lightmeter/environment.dart';
|
import 'package:lightmeter/environment.dart';
|
||||||
import 'package:lightmeter/providers/ev_source_type_provider.dart';
|
import 'package:lightmeter/providers/ev_source_type_provider.dart';
|
||||||
|
|
||||||
import 'components/bottom_controls/widget_bottom_controls.dart';
|
import 'components/bottom_controls/provider_bottom_controls.dart';
|
||||||
import 'components/camera_container/provider_container_camera.dart';
|
import 'components/camera_container/provider_container_camera.dart';
|
||||||
import 'components/light_sensor_container/provider_container_light_sensor.dart';
|
import 'components/light_sensor_container/provider_container_light_sensor.dart';
|
||||||
import 'bloc_metering.dart';
|
import 'bloc_metering.dart';
|
||||||
|
@ -57,7 +57,7 @@ class _MeteringScreenState extends State<MeteringScreen> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
MeteringBottomControls(
|
MeteringBottomControlsProvider(
|
||||||
onSwitchEvSourceType: context.read<Environment>().hasLightSensor
|
onSwitchEvSourceType: context.read<Environment>().hasLightSensor
|
||||||
? EvSourceTypeProvider.of(context).toggleType
|
? EvSourceTypeProvider.of(context).toggleType
|
||||||
: null,
|
: null,
|
||||||
|
|
|
@ -9,20 +9,6 @@ class DynamicColorListTile extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (context.read<DynamicColorState>() == DynamicColorState.unavailable) {
|
|
||||||
return Opacity(
|
|
||||||
opacity: 0.5,
|
|
||||||
child: IgnorePointer(
|
|
||||||
child: SwitchListTile(
|
|
||||||
secondary: const Icon(Icons.colorize),
|
|
||||||
title: Text(S.of(context).dynamicColor),
|
|
||||||
value: false,
|
|
||||||
enableFeedback: false,
|
|
||||||
onChanged: (value) {},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return SwitchListTile(
|
return SwitchListTile(
|
||||||
secondary: const Icon(Icons.colorize),
|
secondary: const Icon(Icons.colorize),
|
||||||
title: Text(S.of(context).dynamicColor),
|
title: Text(S.of(context).dynamicColor),
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
|
import 'package:lightmeter/providers/theme_provider.dart';
|
||||||
|
import 'package:lightmeter/res/dimens.dart';
|
||||||
|
import 'package:lightmeter/screens/shared/filled_circle/widget_circle_filled.dart';
|
||||||
|
|
||||||
|
class PrimaryColorDialogPicker extends StatefulWidget {
|
||||||
|
const PrimaryColorDialogPicker({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PrimaryColorDialogPicker> createState() => _PrimaryColorDialogPickerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PrimaryColorDialogPickerState extends State<PrimaryColorDialogPicker> {
|
||||||
|
late Color _selected = Theme.of(context).primaryColor;
|
||||||
|
late final ScrollController _scrollController = ScrollController(
|
||||||
|
initialScrollOffset:
|
||||||
|
ThemeProvider.primaryColorsList.indexOf(_selected) * (Dimens.grid48 + Dimens.grid8),
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
titlePadding: const EdgeInsets.fromLTRB(
|
||||||
|
Dimens.paddingL,
|
||||||
|
Dimens.paddingL,
|
||||||
|
Dimens.paddingL,
|
||||||
|
Dimens.paddingM,
|
||||||
|
),
|
||||||
|
title: Text(S.of(context).choosePrimaryColor),
|
||||||
|
content: SizedBox(
|
||||||
|
height: Dimens.grid48,
|
||||||
|
width: double.maxFinite,
|
||||||
|
child: ListView.separated(
|
||||||
|
controller: _scrollController,
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
separatorBuilder: (_, __) => const SizedBox(width: Dimens.grid8),
|
||||||
|
itemCount: ThemeProvider.primaryColorsList.length,
|
||||||
|
itemBuilder: (_, index) {
|
||||||
|
final color = ThemeProvider.primaryColorsList[index];
|
||||||
|
return _SelectableColorItem(
|
||||||
|
color: color,
|
||||||
|
selected: color.value == _selected.value,
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_selected = color;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actionsPadding: const EdgeInsets.fromLTRB(
|
||||||
|
Dimens.paddingL,
|
||||||
|
Dimens.paddingM,
|
||||||
|
Dimens.paddingL,
|
||||||
|
Dimens.paddingL,
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: Navigator.of(context).pop,
|
||||||
|
child: Text(S.of(context).cancel),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(_selected);
|
||||||
|
},
|
||||||
|
child: Text(S.of(context).save),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SelectableColorItem extends StatelessWidget {
|
||||||
|
final Color color;
|
||||||
|
final bool selected;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
_SelectableColorItem({
|
||||||
|
required this.color,
|
||||||
|
required this.selected,
|
||||||
|
required this.onTap,
|
||||||
|
}) : super(key: ValueKey(color));
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: FilledCircle(
|
||||||
|
size: Dimens.grid48,
|
||||||
|
color: color,
|
||||||
|
child: AnimatedSwitcher(
|
||||||
|
duration: Dimens.durationS,
|
||||||
|
child: selected
|
||||||
|
? Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: ThemeData.estimateBrightnessForColor(color) == Brightness.light
|
||||||
|
? Colors.black
|
||||||
|
: Colors.white,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:lightmeter/data/models/dynamic_colors_state.dart';
|
||||||
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
|
import 'package:lightmeter/providers/theme_provider.dart';
|
||||||
|
|
||||||
|
import 'components/primary_color_picker_dialog/widget_dialog_picker_primary_color.dart';
|
||||||
|
|
||||||
|
class PrimaryColorListTile extends StatelessWidget {
|
||||||
|
const PrimaryColorListTile({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (context.watch<DynamicColorState>() == DynamicColorState.enabled) {
|
||||||
|
return Opacity(
|
||||||
|
opacity: 0.5,
|
||||||
|
child: IgnorePointer(
|
||||||
|
child: ListTile(
|
||||||
|
leading: const Icon(Icons.palette),
|
||||||
|
title: Text(S.of(context).primaryColor),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ListTile(
|
||||||
|
leading: const Icon(Icons.palette),
|
||||||
|
title: Text(S.of(context).primaryColor),
|
||||||
|
onTap: () {
|
||||||
|
showDialog<Color>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => const PrimaryColorDialogPicker(),
|
||||||
|
).then((value) {
|
||||||
|
if (value != null) {
|
||||||
|
ThemeProvider.of(context).setPrimaryColor(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,10 +20,7 @@ class SettingsSection extends StatelessWidget {
|
||||||
Dimens.paddingM,
|
Dimens.paddingM,
|
||||||
Dimens.paddingM,
|
Dimens.paddingM,
|
||||||
),
|
),
|
||||||
child: Material(
|
child: Card(
|
||||||
clipBehavior: Clip.antiAlias,
|
|
||||||
borderRadius: BorderRadius.circular(Dimens.borderRadiusL),
|
|
||||||
color: Theme.of(context).colorScheme.primaryContainer,
|
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: Dimens.paddingM),
|
padding: const EdgeInsets.symmetric(vertical: Dimens.paddingM),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
@ -34,7 +31,10 @@ class SettingsSection extends StatelessWidget {
|
||||||
padding: const EdgeInsets.only(left: Dimens.paddingM),
|
padding: const EdgeInsets.only(left: Dimens.paddingM),
|
||||||
child: Text(
|
child: Text(
|
||||||
title,
|
title,
|
||||||
style: Theme.of(context).textTheme.labelLarge,
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.labelLarge
|
||||||
|
?.copyWith(color: Theme.of(context).colorScheme.onSurface),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
...children,
|
...children,
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:lightmeter/data/models/dynamic_colors_state.dart';
|
||||||
import 'package:lightmeter/generated/l10n.dart';
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
import 'package:lightmeter/res/dimens.dart';
|
import 'package:lightmeter/res/dimens.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'components/calibration/widget_list_tile_calibration.dart';
|
import 'components/calibration/widget_list_tile_calibration.dart';
|
||||||
import 'components/haptics/provider_list_tile_haptics.dart';
|
import 'components/haptics/provider_list_tile_haptics.dart';
|
||||||
|
import 'components/primary_color/widget_list_tile_primary_color.dart';
|
||||||
import 'components/report_issue/widget_list_tile_report_issue.dart';
|
import 'components/report_issue/widget_list_tile_report_issue.dart';
|
||||||
import 'components/shared/settings_section/widget_settings_section.dart';
|
import 'components/shared/settings_section/widget_settings_section.dart';
|
||||||
import 'components/source_code/widget_list_tile_source_code.dart';
|
import 'components/source_code/widget_list_tile_source_code.dart';
|
||||||
|
@ -63,9 +66,11 @@ class SettingsScreen extends StatelessWidget {
|
||||||
),
|
),
|
||||||
SettingsSection(
|
SettingsSection(
|
||||||
title: S.of(context).theme,
|
title: S.of(context).theme,
|
||||||
children: const [
|
children: [
|
||||||
ThemeTypeListTile(),
|
const ThemeTypeListTile(),
|
||||||
DynamicColorListTile(),
|
const PrimaryColorListTile(),
|
||||||
|
if (context.read<DynamicColorState>() != DynamicColorState.unavailable)
|
||||||
|
const DynamicColorListTile(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SettingsSection(
|
SettingsSection(
|
||||||
|
|
Loading…
Reference in a new issue