mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-22 07:20:39 +00:00
ML-6 Add support for dynamic colors on A12+ (#8)
* added dynamic colors * made dynamic colors enabled/disabled * fixed tests
This commit is contained in:
parent
9be413752a
commit
9477f80ada
11 changed files with 176 additions and 62 deletions
|
@ -30,20 +30,21 @@ class Application extends StatelessWidget {
|
||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
Provider(create: (_) => UserPreferencesService(snapshot.data!)),
|
Provider(create: (_) => UserPreferencesService(snapshot.data!)),
|
||||||
Provider(create: (_) => HapticsService()),
|
Provider(create: (_) => const HapticsService()),
|
||||||
|
Provider(create: (_) => PermissionsService()),
|
||||||
Provider.value(value: evSource),
|
Provider.value(value: evSource),
|
||||||
],
|
],
|
||||||
child: Provider(
|
|
||||||
create: (_) => PermissionsService(),
|
|
||||||
child: StopTypeProvider(
|
child: StopTypeProvider(
|
||||||
child: ThemeProvider(
|
child: ThemeProvider(
|
||||||
initialPrimaryColor: const Color(0xFF2196f3),
|
builder: (context, _) {
|
||||||
builder: (context, child) => AnnotatedRegion(
|
final systemIconsBrightness =
|
||||||
|
ThemeData.estimateBrightnessForColor(context.watch<ThemeData>().colorScheme.onSurface);
|
||||||
|
return AnnotatedRegion(
|
||||||
value: SystemUiOverlayStyle(
|
value: SystemUiOverlayStyle(
|
||||||
statusBarColor: Colors.transparent,
|
statusBarColor: Colors.transparent,
|
||||||
statusBarIconBrightness:
|
statusBarIconBrightness: systemIconsBrightness,
|
||||||
ThemeData.estimateBrightnessForColor(context.watch<ThemeData>().colorScheme.onSurface),
|
|
||||||
systemNavigationBarColor: context.watch<ThemeData>().colorScheme.surface,
|
systemNavigationBarColor: context.watch<ThemeData>().colorScheme.surface,
|
||||||
|
systemNavigationBarIconBrightness: systemIconsBrightness,
|
||||||
),
|
),
|
||||||
child: MaterialApp(
|
child: MaterialApp(
|
||||||
theme: context.watch<ThemeData>(),
|
theme: context.watch<ThemeData>(),
|
||||||
|
@ -64,8 +65,8 @@ class Application extends StatelessWidget {
|
||||||
"settings": (context) => const SettingsScreen(),
|
"settings": (context) => const SettingsScreen(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import 'package:vibration/vibration.dart';
|
import 'package:vibration/vibration.dart';
|
||||||
|
|
||||||
class HapticsService {
|
class HapticsService {
|
||||||
|
const HapticsService();
|
||||||
|
|
||||||
Future<void> quickVibration() async => _tryVibrate(duration: 25, amplitude: 96);
|
Future<void> quickVibration() async => _tryVibrate(duration: 25, amplitude: 96);
|
||||||
|
|
||||||
Future<void> responseVibration() async => _tryVibrate(duration: 50, amplitude: 128);
|
Future<void> responseVibration() async => _tryVibrate(duration: 50, amplitude: 128);
|
||||||
|
|
1
lib/data/models/dynamic_colors_state.dart
Normal file
1
lib/data/models/dynamic_colors_state.dart
Normal file
|
@ -0,0 +1 @@
|
||||||
|
enum DynamicColorsState { unavailable, enabled, disabled }
|
|
@ -10,6 +10,7 @@ class UserPreferencesService {
|
||||||
|
|
||||||
static const _hapticsKey = "haptics";
|
static const _hapticsKey = "haptics";
|
||||||
static const _themeTypeKey = "themeType";
|
static const _themeTypeKey = "themeType";
|
||||||
|
static const _dynamicColorsKey = "dynamicColors";
|
||||||
|
|
||||||
final SharedPreferences _sharedPreferences;
|
final SharedPreferences _sharedPreferences;
|
||||||
|
|
||||||
|
@ -26,4 +27,7 @@ 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);
|
||||||
|
|
||||||
|
bool get dynamicColors => _sharedPreferences.getBool(_dynamicColorsKey) ?? false;
|
||||||
|
set dynamicColors(bool value) => _sharedPreferences.setBool(_dynamicColorsKey, value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
"haptics": "Haptics",
|
"haptics": "Haptics",
|
||||||
"theme": "Theme",
|
"theme": "Theme",
|
||||||
"chooseTheme": "Choose theme",
|
"chooseTheme": "Choose theme",
|
||||||
|
"dynamicColors": "Dynamic colors",
|
||||||
"themeLight": "Light",
|
"themeLight": "Light",
|
||||||
"themeDark": "Dark",
|
"themeDark": "Dark",
|
||||||
"themeSystemDefault": "System default",
|
"themeSystemDefault": "System default",
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.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:material_color_utilities/material_color_utilities.dart';
|
import 'package:material_color_utilities/material_color_utilities.dart';
|
||||||
|
@ -7,13 +9,9 @@ import 'package:material_color_utilities/material_color_utilities.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class ThemeProvider extends StatefulWidget {
|
class ThemeProvider extends StatefulWidget {
|
||||||
final Color initialPrimaryColor;
|
|
||||||
final Widget? child;
|
|
||||||
final TransitionBuilder? builder;
|
final TransitionBuilder? builder;
|
||||||
|
|
||||||
const ThemeProvider({
|
const ThemeProvider({
|
||||||
required this.initialPrimaryColor,
|
|
||||||
this.child,
|
|
||||||
this.builder,
|
this.builder,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
@ -27,53 +25,50 @@ class ThemeProvider extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ThemeProviderState extends State<ThemeProvider> {
|
class ThemeProviderState extends State<ThemeProvider> {
|
||||||
late ThemeType _themeType;
|
late final _themeTypeNotifier = ValueNotifier<ThemeType>(context.read<UserPreferencesService>().themeType);
|
||||||
late Color _primaryColor = widget.initialPrimaryColor;
|
late final _dynamicColorsNotifier = ValueNotifier<bool>(context.read<UserPreferencesService>().dynamicColors);
|
||||||
|
late final _primaryColorNotifier = ValueNotifier<Color>(const Color(0xFF2196f3));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void dispose() {
|
||||||
super.initState();
|
_themeTypeNotifier.dispose();
|
||||||
_themeType = context.read<UserPreferencesService>().themeType;
|
_dynamicColorsNotifier.dispose();
|
||||||
|
_primaryColorNotifier.dispose();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Provider.value(
|
return ValueListenableBuilder(
|
||||||
value: _themeType,
|
valueListenable: _themeTypeNotifier,
|
||||||
child: Provider.value(
|
builder: (_, themeType, __) => Provider.value(
|
||||||
value: _themeFromColor(
|
value: themeType,
|
||||||
_primaryColor,
|
child: ValueListenableBuilder(
|
||||||
_mapThemeTypeToBrightness(_themeType),
|
valueListenable: _dynamicColorsNotifier,
|
||||||
),
|
builder: (_, useDynamicColors, __) => _DynamicColorsProvider(
|
||||||
|
useDynamicColors: useDynamicColors,
|
||||||
|
themeBrightness: _themeBrightness,
|
||||||
|
builder: (_, dynamicPrimaryColor) => ValueListenableBuilder(
|
||||||
|
valueListenable: _primaryColorNotifier,
|
||||||
|
builder: (_, primaryColor, __) => _ThemeDataProvider(
|
||||||
|
primaryColor: dynamicPrimaryColor ?? primaryColor,
|
||||||
|
brightness: _themeBrightness,
|
||||||
builder: widget.builder,
|
builder: widget.builder,
|
||||||
child: widget.child,
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setThemeType(ThemeType themeType) {
|
void setThemeType(ThemeType themeType) {
|
||||||
if (themeType == _themeType) {
|
_themeTypeNotifier.value = themeType;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_themeType = themeType;
|
|
||||||
});
|
|
||||||
context.read<UserPreferencesService>().themeType = themeType;
|
context.read<UserPreferencesService>().themeType = themeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setPrimaryColor(Color color) {
|
Brightness get _themeBrightness {
|
||||||
if (color == _primaryColor) {
|
switch (_themeTypeNotifier.value) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_primaryColor = color;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Brightness _mapThemeTypeToBrightness(ThemeType themeType) {
|
|
||||||
switch (themeType) {
|
|
||||||
case ThemeType.light:
|
case ThemeType.light:
|
||||||
return Brightness.light;
|
return Brightness.light;
|
||||||
case ThemeType.dark:
|
case ThemeType.dark:
|
||||||
|
@ -83,9 +78,85 @@ class ThemeProviderState extends State<ThemeProvider> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ThemeData _themeFromColor(Color color, Brightness brightness) {
|
void enableDynamicColors(bool enable) {
|
||||||
final scheme = brightness == Brightness.light ? Scheme.light(color.value) : Scheme.dark(color.value);
|
_dynamicColorsNotifier.value = enable;
|
||||||
final colorScheme = ColorScheme(
|
context.read<UserPreferencesService>().dynamicColors = enable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DynamicColorsProvider extends StatelessWidget {
|
||||||
|
final bool useDynamicColors;
|
||||||
|
final Brightness themeBrightness;
|
||||||
|
final Widget Function(BuildContext context, Color? primaryColor) builder;
|
||||||
|
|
||||||
|
const _DynamicColorsProvider({
|
||||||
|
required this.useDynamicColors,
|
||||||
|
required this.themeBrightness,
|
||||||
|
required this.builder,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DynamicColorBuilder(
|
||||||
|
builder: (lightDynamic, darkDynamic) {
|
||||||
|
late final DynamicColorsState state;
|
||||||
|
late final Color? dynamicPrimaryColor;
|
||||||
|
if (lightDynamic != null && darkDynamic != null) {
|
||||||
|
if (useDynamicColors) {
|
||||||
|
dynamicPrimaryColor = (themeBrightness == Brightness.light ? lightDynamic : darkDynamic).primary;
|
||||||
|
state = DynamicColorsState.enabled;
|
||||||
|
} else {
|
||||||
|
dynamicPrimaryColor = null;
|
||||||
|
state = DynamicColorsState.disabled;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dynamicPrimaryColor = null;
|
||||||
|
state = DynamicColorsState.unavailable;
|
||||||
|
}
|
||||||
|
return Provider.value(
|
||||||
|
value: state,
|
||||||
|
child: builder(context, dynamicPrimaryColor),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ThemeDataProvider extends StatelessWidget {
|
||||||
|
final Color primaryColor;
|
||||||
|
final Brightness brightness;
|
||||||
|
final TransitionBuilder? builder;
|
||||||
|
|
||||||
|
const _ThemeDataProvider({
|
||||||
|
required this.primaryColor,
|
||||||
|
required this.brightness,
|
||||||
|
required this.builder,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Provider.value(
|
||||||
|
value: _themeFromColorScheme(_colorSchemeFromColor()),
|
||||||
|
builder: builder,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ThemeData _themeFromColorScheme(ColorScheme scheme) {
|
||||||
|
return ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
|
bottomAppBarColor: scheme.surface,
|
||||||
|
brightness: scheme.brightness,
|
||||||
|
colorScheme: scheme,
|
||||||
|
dialogBackgroundColor: scheme.surface,
|
||||||
|
dialogTheme: DialogTheme(backgroundColor: scheme.surface),
|
||||||
|
scaffoldBackgroundColor: scheme.surface,
|
||||||
|
toggleableActiveColor: scheme.primary,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorScheme _colorSchemeFromColor() {
|
||||||
|
final scheme = brightness == Brightness.light ? Scheme.light(primaryColor.value) : Scheme.dark(primaryColor.value);
|
||||||
|
return ColorScheme(
|
||||||
brightness: brightness,
|
brightness: brightness,
|
||||||
primary: Color(scheme.primary),
|
primary: Color(scheme.primary),
|
||||||
onPrimary: Color(scheme.onPrimary),
|
onPrimary: Color(scheme.onPrimary),
|
||||||
|
@ -102,9 +173,5 @@ class ThemeProviderState extends State<ThemeProvider> {
|
||||||
surfaceVariant: Color.alphaBlend(Color(scheme.primary).withOpacity(0.5), Color(scheme.background)),
|
surfaceVariant: Color.alphaBlend(Color(scheme.primary).withOpacity(0.5), Color(scheme.background)),
|
||||||
onSurfaceVariant: Color(scheme.onSurfaceVariant),
|
onSurfaceVariant: Color(scheme.onSurfaceVariant),
|
||||||
);
|
);
|
||||||
return ThemeData(
|
|
||||||
useMaterial3: true,
|
|
||||||
colorScheme: colorScheme,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
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/res/theme.dart';
|
||||||
|
|
||||||
|
class DynamicColorsListTile extends StatelessWidget {
|
||||||
|
const DynamicColorsListTile({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SwitchListTile(
|
||||||
|
secondary: const Icon(Icons.colorize),
|
||||||
|
title: Text(S.of(context).dynamicColors),
|
||||||
|
value: context.watch<DynamicColorsState>() == DynamicColorsState.enabled,
|
||||||
|
onChanged: ThemeProvider.of(context).enableDynamicColors,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,10 +2,9 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:lightmeter/data/models/theme_type.dart';
|
import 'package:lightmeter/data/models/theme_type.dart';
|
||||||
import 'package:lightmeter/generated/l10n.dart';
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
import 'package:lightmeter/res/theme.dart';
|
import 'package:lightmeter/res/theme.dart';
|
||||||
|
import 'package:lightmeter/screens/settings/components/shared/widget_dialog_picker.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'shared/widget_dialog_picker.dart';
|
|
||||||
|
|
||||||
class ThemeTypeListTile extends StatelessWidget {
|
class ThemeTypeListTile extends StatelessWidget {
|
||||||
const ThemeTypeListTile({super.key});
|
const ThemeTypeListTile({super.key});
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:lightmeter/data/models/dynamic_colors_state.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'components/widget_list_tile_dynamic_colors.dart';
|
||||||
|
import 'components/widget_list_tile_theme_type.dart';
|
||||||
|
|
||||||
|
class ThemeSettings extends StatelessWidget {
|
||||||
|
const ThemeSettings({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
const ThemeTypeListTile(),
|
||||||
|
if (context.read<DynamicColorsState>() != DynamicColorsState.unavailable) const DynamicColorsListTile(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ import 'package:lightmeter/res/dimens.dart';
|
||||||
|
|
||||||
import 'components/haptics/provider_list_tile_haptics.dart';
|
import 'components/haptics/provider_list_tile_haptics.dart';
|
||||||
import 'components/widget_list_tile_fractional_stops.dart';
|
import 'components/widget_list_tile_fractional_stops.dart';
|
||||||
import 'components/widget_list_tile_theme_type.dart';
|
import 'components/theme/widget_settings_theme.dart';
|
||||||
import 'components/widget_label_version.dart';
|
import 'components/widget_label_version.dart';
|
||||||
|
|
||||||
class SettingsScreen extends StatelessWidget {
|
class SettingsScreen extends StatelessWidget {
|
||||||
|
@ -13,7 +13,6 @@ class SettingsScreen extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
top: false,
|
top: false,
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
|
@ -42,7 +41,7 @@ class SettingsScreen extends StatelessWidget {
|
||||||
[
|
[
|
||||||
const StopTypeListTile(),
|
const StopTypeListTile(),
|
||||||
const HapticsListTileProvider(),
|
const HapticsListTileProvider(),
|
||||||
const ThemeTypeListTile(),
|
const ThemeSettings(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -9,6 +9,7 @@ environment:
|
||||||
dependencies:
|
dependencies:
|
||||||
camera: 0.10.0+4
|
camera: 0.10.0+4
|
||||||
exif: 3.1.2
|
exif: 3.1.2
|
||||||
|
dynamic_color: 1.5.4
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_bloc: 8.1.1
|
flutter_bloc: 8.1.1
|
||||||
|
@ -16,7 +17,7 @@ dependencies:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
intl: 0.17.0
|
intl: 0.17.0
|
||||||
intl_utils: 2.8.1
|
intl_utils: 2.8.1
|
||||||
material_color_utilities: 0.2.0
|
material_color_utilities: 0.1.5
|
||||||
package_info_plus: 3.0.2
|
package_info_plus: 3.0.2
|
||||||
permission_handler: 10.2.0
|
permission_handler: 10.2.0
|
||||||
provider: 6.0.4
|
provider: 6.0.4
|
||||||
|
@ -28,10 +29,10 @@ dev_dependencies:
|
||||||
flutter_launcher_icons: 0.11.0
|
flutter_launcher_icons: 0.11.0
|
||||||
flutter_lints: 2.0.0
|
flutter_lints: 2.0.0
|
||||||
flutter_native_splash: 2.2.16
|
flutter_native_splash: 2.2.16
|
||||||
test: 1.21.6
|
test: 1.22.2
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
material_color_utilities: 0.2.0
|
test_api: 0.4.12
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
Loading…
Reference in a new issue