mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-24 16:30:40 +00:00
Implemented theme brightness picker
implemented `ThemeProvider` implemented theme switching save selected theme type
This commit is contained in:
parent
b7908e4773
commit
9b0cba6ed7
8 changed files with 264 additions and 39 deletions
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:lightmeter/data/ev_source/ev_source_type.dart';
|
import 'package:lightmeter/data/ev_source/ev_source_type.dart';
|
||||||
|
import 'package:lightmeter/data/models/theme_type.dart';
|
||||||
import 'package:lightmeter/data/permissions_service.dart';
|
import 'package:lightmeter/data/permissions_service.dart';
|
||||||
import 'package:lightmeter/screens/settings/settings_screen.dart';
|
import 'package:lightmeter/screens/settings/settings_screen.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -52,11 +53,10 @@ class _ApplicationState extends State<Application> {
|
||||||
child: Provider(
|
child: Provider(
|
||||||
create: (context) => PermissionsService(),
|
create: (context) => PermissionsService(),
|
||||||
child: StopTypeProvider(
|
child: StopTypeProvider(
|
||||||
child: MaterialApp(
|
child: ThemeProvider(
|
||||||
theme: ThemeData(
|
initialPrimaryColor: const Color(0xFF2196f3),
|
||||||
useMaterial3: true,
|
builder: (context, child) => MaterialApp(
|
||||||
colorScheme: lightColorScheme,
|
theme: context.watch<ThemeData>(),
|
||||||
),
|
|
||||||
localizationsDelegates: const [
|
localizationsDelegates: const [
|
||||||
S.delegate,
|
S.delegate,
|
||||||
GlobalMaterialLocalizations.delegate,
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
@ -76,6 +76,7 @@ class _ApplicationState extends State<Application> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
|
|
1
lib/data/models/theme_type.dart
Normal file
1
lib/data/models/theme_type.dart
Normal file
|
@ -0,0 +1 @@
|
||||||
|
enum ThemeType {light, dark, systemDefault}
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:lightmeter/data/models/nd_value.dart';
|
import 'package:lightmeter/data/models/nd_value.dart';
|
||||||
|
import 'package:lightmeter/data/models/theme_type.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
import 'models/iso_value.dart';
|
import 'models/iso_value.dart';
|
||||||
|
@ -7,6 +8,8 @@ class UserPreferencesService {
|
||||||
static const _isoKey = "ISO";
|
static const _isoKey = "ISO";
|
||||||
static const _ndFilterKey = "ND";
|
static const _ndFilterKey = "ND";
|
||||||
|
|
||||||
|
static const _themeTypeKey = "ThemeType";
|
||||||
|
|
||||||
final SharedPreferences _sharedPreferences;
|
final SharedPreferences _sharedPreferences;
|
||||||
|
|
||||||
UserPreferencesService(this._sharedPreferences);
|
UserPreferencesService(this._sharedPreferences);
|
||||||
|
@ -16,4 +19,7 @@ class UserPreferencesService {
|
||||||
|
|
||||||
NdValue get ndFilter => ndValues.firstWhere((v) => v.value == (_sharedPreferences.getInt(_ndFilterKey) ?? 0));
|
NdValue get ndFilter => ndValues.firstWhere((v) => v.value == (_sharedPreferences.getInt(_ndFilterKey) ?? 0));
|
||||||
set ndFilter(NdValue value) => _sharedPreferences.setInt(_ndFilterKey, value.value);
|
set ndFilter(NdValue value) => _sharedPreferences.setInt(_ndFilterKey, value.value);
|
||||||
|
|
||||||
|
ThemeType get themeType => ThemeType.values[_sharedPreferences.getInt(_themeTypeKey) ?? 0];
|
||||||
|
set themeType(ThemeType value) => _sharedPreferences.setInt(_themeTypeKey, value.index);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,5 +19,10 @@
|
||||||
"thirdStops": "1/3",
|
"thirdStops": "1/3",
|
||||||
"caffeine": "Caffeine",
|
"caffeine": "Caffeine",
|
||||||
"keepsScreenOn": "Keeps screen on",
|
"keepsScreenOn": "Keeps screen on",
|
||||||
"haptics": "Haptics"
|
"haptics": "Haptics",
|
||||||
|
"theme": "Theme",
|
||||||
|
"chooseTheme": "Choose theme",
|
||||||
|
"themeLight": "Light",
|
||||||
|
"themeDark": "Dark",
|
||||||
|
"themeSystemDefault": "System default"
|
||||||
}
|
}
|
|
@ -1,9 +1,92 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
import 'package:lightmeter/data/models/theme_type.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';
|
||||||
|
|
||||||
ColorScheme get lightColorScheme {
|
import 'package:provider/provider.dart';
|
||||||
final scheme = Scheme.light(0xFF2196f3);
|
|
||||||
return ColorScheme.light(
|
class ThemeProvider extends StatefulWidget {
|
||||||
|
final Color initialPrimaryColor;
|
||||||
|
final Widget? child;
|
||||||
|
final TransitionBuilder? builder;
|
||||||
|
|
||||||
|
const ThemeProvider({
|
||||||
|
required this.initialPrimaryColor,
|
||||||
|
this.child,
|
||||||
|
this.builder,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
static ThemeProviderState of(BuildContext context) {
|
||||||
|
return context.findAncestorStateOfType<ThemeProviderState>()!;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ThemeProvider> createState() => ThemeProviderState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ThemeProviderState extends State<ThemeProvider> {
|
||||||
|
late ThemeType _themeType;
|
||||||
|
late Color _primaryColor = widget.initialPrimaryColor;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_themeType = context.read<UserPreferencesService>().themeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Provider.value(
|
||||||
|
value: _themeType,
|
||||||
|
child: Provider.value(
|
||||||
|
value: _themeFromColor(
|
||||||
|
_primaryColor,
|
||||||
|
_mapThemeTypeToBrightness(_themeType),
|
||||||
|
),
|
||||||
|
builder: widget.builder,
|
||||||
|
child: widget.child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setThemeType(ThemeType themeType) {
|
||||||
|
if (themeType == _themeType) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_themeType = themeType;
|
||||||
|
});
|
||||||
|
context.read<UserPreferencesService>().themeType = themeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPrimaryColor(Color color) {
|
||||||
|
if (color == _primaryColor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_primaryColor = color;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Brightness _mapThemeTypeToBrightness(ThemeType themeType) {
|
||||||
|
switch (themeType) {
|
||||||
|
case ThemeType.light:
|
||||||
|
return Brightness.light;
|
||||||
|
case ThemeType.dark:
|
||||||
|
return Brightness.dark;
|
||||||
|
case ThemeType.systemDefault:
|
||||||
|
return SchedulerBinding.instance.platformDispatcher.platformBrightness;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ThemeData _themeFromColor(Color color, Brightness brightness) {
|
||||||
|
final scheme = brightness == Brightness.light ? Scheme.light(color.value) : Scheme.dark(color.value);
|
||||||
|
final colorScheme = ColorScheme(
|
||||||
|
brightness: brightness,
|
||||||
primary: Color(scheme.primary),
|
primary: Color(scheme.primary),
|
||||||
onPrimary: Color(scheme.onPrimary),
|
onPrimary: Color(scheme.onPrimary),
|
||||||
primaryContainer: Color(scheme.primaryContainer),
|
primaryContainer: Color(scheme.primaryContainer),
|
||||||
|
@ -19,4 +102,9 @@ ColorScheme get lightColorScheme {
|
||||||
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
74
lib/screens/settings/components/shared/dialog_picker.dart
Normal file
74
lib/screens/settings/components/shared/dialog_picker.dart
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
|
import 'package:lightmeter/res/dimens.dart';
|
||||||
|
|
||||||
|
class DialogPicker<T> extends StatefulWidget {
|
||||||
|
final String title;
|
||||||
|
final T selectedValue;
|
||||||
|
final List<T> values;
|
||||||
|
final String Function(BuildContext context, T value) titleAdapter;
|
||||||
|
|
||||||
|
const DialogPicker({
|
||||||
|
required this.title,
|
||||||
|
required this.selectedValue,
|
||||||
|
required this.values,
|
||||||
|
required this.titleAdapter,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DialogPicker<T>> createState() => _DialogPickerState<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DialogPickerState<T> extends State<DialogPicker<T>> {
|
||||||
|
late T _selected = widget.selectedValue;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
titlePadding: const EdgeInsets.fromLTRB(
|
||||||
|
Dimens.paddingL,
|
||||||
|
Dimens.paddingL,
|
||||||
|
Dimens.paddingL,
|
||||||
|
Dimens.paddingM,
|
||||||
|
),
|
||||||
|
title: Text(widget.title),
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: widget.values
|
||||||
|
.map(
|
||||||
|
(e) => RadioListTile(
|
||||||
|
value: e,
|
||||||
|
groupValue: _selected,
|
||||||
|
title: Text(widget.titleAdapter(context, e)),
|
||||||
|
onChanged: (T? value) {
|
||||||
|
if (value != null) {
|
||||||
|
setState(() {
|
||||||
|
_selected = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
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).select),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
48
lib/screens/settings/components/theme_type_tile.dart
Normal file
48
lib/screens/settings/components/theme_type_tile.dart
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:lightmeter/data/models/theme_type.dart';
|
||||||
|
import 'package:lightmeter/data/shared_prefs_service.dart';
|
||||||
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
|
import 'package:lightmeter/res/theme.dart';
|
||||||
|
import 'package:lightmeter/utils/stop_type_provider.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'shared/dialog_picker.dart';
|
||||||
|
|
||||||
|
class ThemeTypeListTile extends StatelessWidget {
|
||||||
|
const ThemeTypeListTile({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListTile(
|
||||||
|
leading: const Icon(Icons.brightness_6),
|
||||||
|
title: Text(S.of(context).theme),
|
||||||
|
trailing: Text(_typeToString(context, context.watch<ThemeType>())),
|
||||||
|
onTap: () {
|
||||||
|
showDialog<ThemeType>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => DialogPicker<ThemeType>(
|
||||||
|
title: S.of(context).chooseTheme,
|
||||||
|
selectedValue: context.read<ThemeType>(),
|
||||||
|
values: ThemeType.values,
|
||||||
|
titleAdapter: _typeToString,
|
||||||
|
),
|
||||||
|
).then((value) {
|
||||||
|
if (value != null) {
|
||||||
|
ThemeProvider.of(context).setThemeType(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _typeToString(BuildContext context, ThemeType themeType) {
|
||||||
|
switch (themeType) {
|
||||||
|
case ThemeType.light:
|
||||||
|
return S.of(context).themeLight;
|
||||||
|
case ThemeType.dark:
|
||||||
|
return S.of(context).themeDark;
|
||||||
|
case ThemeType.systemDefault:
|
||||||
|
return S.of(context).themeSystemDefault;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import 'package:lightmeter/res/dimens.dart';
|
||||||
import 'components/caffeine_tile.dart';
|
import 'components/caffeine_tile.dart';
|
||||||
import 'components/haptics_tile.dart';
|
import 'components/haptics_tile.dart';
|
||||||
import 'components/fractional_stops/list_tile_fractional_stops.dart';
|
import 'components/fractional_stops/list_tile_fractional_stops.dart';
|
||||||
|
import 'components/theme_type_tile.dart';
|
||||||
|
|
||||||
class SettingsScreen extends StatelessWidget {
|
class SettingsScreen extends StatelessWidget {
|
||||||
const SettingsScreen({super.key});
|
const SettingsScreen({super.key});
|
||||||
|
@ -40,6 +41,7 @@ class SettingsScreen extends StatelessWidget {
|
||||||
const FractionalStopsListTile(),
|
const FractionalStopsListTile(),
|
||||||
const CaffeineListTile(),
|
const CaffeineListTile(),
|
||||||
const HapticsListTile(),
|
const HapticsListTile(),
|
||||||
|
const ThemeTypeListTile(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
Loading…
Reference in a new issue