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_localizations/flutter_localizations.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/screens/settings/settings_screen.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -52,27 +53,27 @@ class _ApplicationState extends State<Application> {
|
|||
child: Provider(
|
||||
create: (context) => PermissionsService(),
|
||||
child: StopTypeProvider(
|
||||
child: MaterialApp(
|
||||
theme: ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: lightColorScheme,
|
||||
child: ThemeProvider(
|
||||
initialPrimaryColor: const Color(0xFF2196f3),
|
||||
builder: (context, child) => MaterialApp(
|
||||
theme: context.watch<ThemeData>(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: S.delegate.supportedLocales,
|
||||
builder: (context, child) => MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
|
||||
child: child!,
|
||||
),
|
||||
home: const MeteringFlow(),
|
||||
routes: {
|
||||
"metering": (context) => const MeteringFlow(),
|
||||
"settings": (context) => const SettingsScreen(),
|
||||
},
|
||||
),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: S.delegate.supportedLocales,
|
||||
builder: (context, child) => MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
|
||||
child: child!,
|
||||
),
|
||||
home: const MeteringFlow(),
|
||||
routes: {
|
||||
"metering": (context) => const MeteringFlow(),
|
||||
"settings": (context) => const SettingsScreen(),
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
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/theme_type.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'models/iso_value.dart';
|
||||
|
@ -7,6 +8,8 @@ class UserPreferencesService {
|
|||
static const _isoKey = "ISO";
|
||||
static const _ndFilterKey = "ND";
|
||||
|
||||
static const _themeTypeKey = "ThemeType";
|
||||
|
||||
final SharedPreferences _sharedPreferences;
|
||||
|
||||
UserPreferencesService(this._sharedPreferences);
|
||||
|
@ -16,4 +19,7 @@ class UserPreferencesService {
|
|||
|
||||
NdValue get ndFilter => ndValues.firstWhere((v) => v.value == (_sharedPreferences.getInt(_ndFilterKey) ?? 0));
|
||||
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",
|
||||
"caffeine": "Caffeine",
|
||||
"keepsScreenOn": "Keeps screen on",
|
||||
"haptics": "Haptics"
|
||||
"haptics": "Haptics",
|
||||
"theme": "Theme",
|
||||
"chooseTheme": "Choose theme",
|
||||
"themeLight": "Light",
|
||||
"themeDark": "Dark",
|
||||
"themeSystemDefault": "System default"
|
||||
}
|
|
@ -1,22 +1,110 @@
|
|||
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';
|
||||
|
||||
ColorScheme get lightColorScheme {
|
||||
final scheme = Scheme.light(0xFF2196f3);
|
||||
return ColorScheme.light(
|
||||
primary: Color(scheme.primary),
|
||||
onPrimary: Color(scheme.onPrimary),
|
||||
primaryContainer: Color(scheme.primaryContainer),
|
||||
onPrimaryContainer: Color(scheme.onPrimaryContainer),
|
||||
secondary: Color(scheme.secondary),
|
||||
onSecondary: Color(scheme.onSecondary),
|
||||
error: Color(scheme.error),
|
||||
onError: Color(scheme.onError),
|
||||
background: Color(scheme.background),
|
||||
onBackground: Color(scheme.onBackground),
|
||||
surface: Color.alphaBlend(Color(scheme.primary).withOpacity(0.05), Color(scheme.background)),
|
||||
onSurface: Color(scheme.onSurface),
|
||||
surfaceVariant: Color.alphaBlend(Color(scheme.primary).withOpacity(0.5), Color(scheme.background)),
|
||||
onSurfaceVariant: Color(scheme.onSurfaceVariant),
|
||||
);
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
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),
|
||||
onPrimary: Color(scheme.onPrimary),
|
||||
primaryContainer: Color(scheme.primaryContainer),
|
||||
onPrimaryContainer: Color(scheme.onPrimaryContainer),
|
||||
secondary: Color(scheme.secondary),
|
||||
onSecondary: Color(scheme.onSecondary),
|
||||
error: Color(scheme.error),
|
||||
onError: Color(scheme.onError),
|
||||
background: Color(scheme.background),
|
||||
onBackground: Color(scheme.onBackground),
|
||||
surface: Color.alphaBlend(Color(scheme.primary).withOpacity(0.05), Color(scheme.background)),
|
||||
onSurface: Color(scheme.onSurface),
|
||||
surfaceVariant: Color.alphaBlend(Color(scheme.primary).withOpacity(0.5), Color(scheme.background)),
|
||||
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/haptics_tile.dart';
|
||||
import 'components/fractional_stops/list_tile_fractional_stops.dart';
|
||||
import 'components/theme_type_tile.dart';
|
||||
|
||||
class SettingsScreen extends StatelessWidget {
|
||||
const SettingsScreen({super.key});
|
||||
|
@ -40,6 +41,7 @@ class SettingsScreen extends StatelessWidget {
|
|||
const FractionalStopsListTile(),
|
||||
const CaffeineListTile(),
|
||||
const HapticsListTile(),
|
||||
const ThemeTypeListTile(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
Loading…
Reference in a new issue