Implemented theme brightness picker

implemented `ThemeProvider`

implemented theme switching

save selected theme type
This commit is contained in:
Vadim 2022-12-16 23:07:05 +03:00
parent b7908e4773
commit 9b0cba6ed7
8 changed files with 264 additions and 39 deletions

View file

@ -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,27 +53,27 @@ 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 [
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(),
},
), ),
), ),
), ),

View file

@ -0,0 +1 @@
enum ThemeType {light, dark, systemDefault}

View file

@ -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);
} }

View file

@ -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"
} }

View file

@ -1,22 +1,110 @@
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 {
primary: Color(scheme.primary), final Color initialPrimaryColor;
onPrimary: Color(scheme.onPrimary), final Widget? child;
primaryContainer: Color(scheme.primaryContainer), final TransitionBuilder? builder;
onPrimaryContainer: Color(scheme.onPrimaryContainer),
secondary: Color(scheme.secondary), const ThemeProvider({
onSecondary: Color(scheme.onSecondary), required this.initialPrimaryColor,
error: Color(scheme.error), this.child,
onError: Color(scheme.onError), this.builder,
background: Color(scheme.background), super.key,
onBackground: Color(scheme.onBackground), });
surface: Color.alphaBlend(Color(scheme.primary).withOpacity(0.05), Color(scheme.background)),
onSurface: Color(scheme.onSurface), static ThemeProviderState of(BuildContext context) {
surfaceVariant: Color.alphaBlend(Color(scheme.primary).withOpacity(0.5), Color(scheme.background)), return context.findAncestorStateOfType<ThemeProviderState>()!;
onSurfaceVariant: Color(scheme.onSurfaceVariant), }
);
@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,
);
}
} }

View 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),
),
],
);
}
}

View 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;
}
}
}

View file

@ -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(),
], ],
), ),
), ),