Automatic light vs dark theme #424
This commit is contained in:
parent
e7562ec21b
commit
c4ce409d5c
|
|
@ -163,6 +163,10 @@ const String kPlanPro = 'pro';
|
|||
const String kPlanEnterprise = 'enterprise';
|
||||
const String kPlanWhiteLabel = 'white_label';
|
||||
|
||||
const String kBrightnessLight = 'light';
|
||||
const String kBrightnessDark = 'dark';
|
||||
const String kBrightnessSytem = 'system';
|
||||
|
||||
const String kColorThemeLight = 'light';
|
||||
const String kColorThemeDark = 'dark';
|
||||
|
||||
|
|
|
|||
|
|
@ -236,6 +236,9 @@ Future<AppState> _initialState(bool isTesting) async {
|
|||
print('## Error: Failed to load prefs: $e');
|
||||
}
|
||||
}
|
||||
prefState = prefState.rebuild((b) => b
|
||||
..enableDarkModeSystem =
|
||||
WidgetsBinding.instance.window.platformBrightness == Brightness.dark);
|
||||
|
||||
String browserRoute;
|
||||
if (kIsWeb && prefState.isDesktop) {
|
||||
|
|
|
|||
|
|
@ -172,6 +172,14 @@ class InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
|
|||
void initState() {
|
||||
super.initState();
|
||||
|
||||
final window = WidgetsBinding.instance.window;
|
||||
window.onPlatformBrightnessChanged = () {
|
||||
WidgetsBinding.instance.handlePlatformBrightnessChanged();
|
||||
widget.store.dispatch(UpdateUserPreferences(
|
||||
enableDarkModeSystem: window.platformBrightness == Brightness.dark));
|
||||
setState(() {});
|
||||
};
|
||||
|
||||
if (kIsWeb) {
|
||||
WebUtils.warnChanges(widget.store);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -156,7 +156,8 @@ class UpdateUserPreferences implements PersistPrefs {
|
|||
this.appLayout,
|
||||
this.moduleLayout,
|
||||
this.sidebar,
|
||||
this.enableDarkMode,
|
||||
this.darkModeType,
|
||||
this.enableDarkModeSystem,
|
||||
this.requireAuthentication,
|
||||
this.longPressSelectionIsDefault,
|
||||
this.textScaleFactor,
|
||||
|
|
@ -187,7 +188,8 @@ class UpdateUserPreferences implements PersistPrefs {
|
|||
final AppSidebar sidebar;
|
||||
final AppSidebarMode menuMode;
|
||||
final AppSidebarMode historyMode;
|
||||
final bool enableDarkMode;
|
||||
final String darkModeType;
|
||||
final bool enableDarkModeSystem;
|
||||
final bool longPressSelectionIsDefault;
|
||||
final bool requireAuthentication;
|
||||
final bool isPreviewVisible;
|
||||
|
|
|
|||
|
|
@ -76,7 +76,9 @@ PrefState prefReducer(
|
|||
..textScaleFactor = textScaleFactorReducer(state.textScaleFactor, action)
|
||||
..isMenuVisible = menuVisibleReducer(state.isMenuVisible, action)
|
||||
..isHistoryVisible = historyVisibleReducer(state.isHistoryVisible, action)
|
||||
..enableDarkMode = darkModeReducer(state.enableDarkMode, action)
|
||||
..darkModeType = darkModeTypeReducer(state.darkModeType, action)
|
||||
..enableDarkModeSystem =
|
||||
darkModeSystemReducer(state.enableDarkModeSystem, action)
|
||||
..enableTooltips = enableTooltipsReducer(state.enableTooltips, action)
|
||||
..enableFlexibleSearch =
|
||||
enableFlexibleSearchReducer(state.enableFlexibleSearch, action)
|
||||
|
|
@ -337,9 +339,15 @@ Reducer<AppSidebarMode> historySidebarReducer = combineReducers([
|
|||
}),
|
||||
]);
|
||||
|
||||
Reducer<bool> darkModeReducer = combineReducers([
|
||||
Reducer<String> darkModeTypeReducer = combineReducers([
|
||||
TypedReducer<String, UpdateUserPreferences>((enableDarkMode, action) {
|
||||
return action.darkModeType ?? enableDarkMode;
|
||||
}),
|
||||
]);
|
||||
|
||||
Reducer<bool> darkModeSystemReducer = combineReducers([
|
||||
TypedReducer<bool, UpdateUserPreferences>((enableDarkMode, action) {
|
||||
return action.enableDarkMode ?? enableDarkMode;
|
||||
return action.enableDarkModeSystem ?? enableDarkMode;
|
||||
}),
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import 'package:invoiceninja_flutter/data/models/static/color_theme_model.dart';
|
|||
part 'pref_state.g.dart';
|
||||
|
||||
abstract class PrefState implements Built<PrefState, PrefStateBuilder> {
|
||||
factory PrefState({ModuleLayout moduleLayout}) {
|
||||
factory PrefState() {
|
||||
return _$PrefState._(
|
||||
appLayout: AppLayout.desktop,
|
||||
moduleLayout: ModuleLayout.table,
|
||||
|
|
@ -26,7 +26,8 @@ abstract class PrefState implements Built<PrefState, PrefStateBuilder> {
|
|||
rowsPerPage: 10,
|
||||
isMenuVisible: true,
|
||||
isHistoryVisible: false,
|
||||
enableDarkMode: false,
|
||||
darkModeType: kBrightnessSytem,
|
||||
enableDarkModeSystem: false,
|
||||
enableFlexibleSearch: false,
|
||||
editAfterSaving: true,
|
||||
requireAuthentication: false,
|
||||
|
|
@ -131,7 +132,9 @@ abstract class PrefState implements Built<PrefState, PrefStateBuilder> {
|
|||
|
||||
bool get isHistoryVisible;
|
||||
|
||||
bool get enableDarkMode;
|
||||
String get darkModeType;
|
||||
|
||||
bool get enableDarkModeSystem;
|
||||
|
||||
bool get isFilterVisible;
|
||||
|
||||
|
|
@ -169,6 +172,10 @@ abstract class PrefState implements Built<PrefState, PrefStateBuilder> {
|
|||
|
||||
BuiltMap<EntityType, PrefStateSortField> get sortFields;
|
||||
|
||||
bool get enableDarkMode => darkModeType == kBrightnessSytem
|
||||
? enableDarkModeSystem
|
||||
: darkModeType == kBrightnessDark;
|
||||
|
||||
ColorTheme get colorThemeModel => colorThemesMap.containsKey(colorTheme)
|
||||
? colorThemesMap[colorTheme]
|
||||
: colorThemesMap[kColorThemeLight];
|
||||
|
|
@ -238,7 +245,7 @@ abstract class PrefState implements Built<PrefState, PrefStateBuilder> {
|
|||
..useSidebarEditor.replace(BuiltMap<EntityType, bool>())
|
||||
..useSidebarViewer.replace(BuiltMap<EntityType, bool>())
|
||||
..sortFields.replace(BuiltMap<EntityType, PrefStateSortField>())
|
||||
..customColors.replace(builder.enableDarkMode == true
|
||||
..customColors.replace(builder.darkModeType == kBrightnessDark
|
||||
? BuiltMap<String, String>()
|
||||
: BuiltMap<String, String>(PrefState.CONTRAST_COLORS))
|
||||
..showKanban = false
|
||||
|
|
@ -260,8 +267,9 @@ abstract class PrefState implements Built<PrefState, PrefStateBuilder> {
|
|||
..enableTooltips = true
|
||||
..enableNativeBrowser = false
|
||||
..textScaleFactor = 1
|
||||
..colorTheme =
|
||||
builder.enableDarkMode == true ? kColorThemeLight : kColorThemeLight;
|
||||
..colorTheme = builder.darkModeType == kBrightnessDark
|
||||
? kColorThemeDark
|
||||
: kColorThemeLight;
|
||||
|
||||
static Serializer<PrefState> get serializer => _$prefStateSerializer;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -165,8 +165,11 @@ class _$PrefStateSerializer implements StructuredSerializer<PrefState> {
|
|||
'isHistoryVisible',
|
||||
serializers.serialize(object.isHistoryVisible,
|
||||
specifiedType: const FullType(bool)),
|
||||
'enableDarkMode',
|
||||
serializers.serialize(object.enableDarkMode,
|
||||
'darkModeType',
|
||||
serializers.serialize(object.darkModeType,
|
||||
specifiedType: const FullType(String)),
|
||||
'enableDarkModeSystem',
|
||||
serializers.serialize(object.enableDarkModeSystem,
|
||||
specifiedType: const FullType(bool)),
|
||||
'isFilterVisible',
|
||||
serializers.serialize(object.isFilterVisible,
|
||||
|
|
@ -316,8 +319,12 @@ class _$PrefStateSerializer implements StructuredSerializer<PrefState> {
|
|||
result.isHistoryVisible = serializers.deserialize(value,
|
||||
specifiedType: const FullType(bool)) as bool;
|
||||
break;
|
||||
case 'enableDarkMode':
|
||||
result.enableDarkMode = serializers.deserialize(value,
|
||||
case 'darkModeType':
|
||||
result.darkModeType = serializers.deserialize(value,
|
||||
specifiedType: const FullType(String)) as String;
|
||||
break;
|
||||
case 'enableDarkModeSystem':
|
||||
result.enableDarkModeSystem = serializers.deserialize(value,
|
||||
specifiedType: const FullType(bool)) as bool;
|
||||
break;
|
||||
case 'isFilterVisible':
|
||||
|
|
@ -665,7 +672,9 @@ class _$PrefState extends PrefState {
|
|||
@override
|
||||
final bool isHistoryVisible;
|
||||
@override
|
||||
final bool enableDarkMode;
|
||||
final String darkModeType;
|
||||
@override
|
||||
final bool enableDarkModeSystem;
|
||||
@override
|
||||
final bool isFilterVisible;
|
||||
@override
|
||||
|
|
@ -725,7 +734,8 @@ class _$PrefState extends PrefState {
|
|||
this.enableTouchEvents,
|
||||
this.enableFlexibleSearch,
|
||||
this.isHistoryVisible,
|
||||
this.enableDarkMode,
|
||||
this.darkModeType,
|
||||
this.enableDarkModeSystem,
|
||||
this.isFilterVisible,
|
||||
this.persistData,
|
||||
this.persistUI,
|
||||
|
|
@ -778,7 +788,9 @@ class _$PrefState extends PrefState {
|
|||
BuiltValueNullFieldError.checkNotNull(
|
||||
isHistoryVisible, r'PrefState', 'isHistoryVisible');
|
||||
BuiltValueNullFieldError.checkNotNull(
|
||||
enableDarkMode, r'PrefState', 'enableDarkMode');
|
||||
darkModeType, r'PrefState', 'darkModeType');
|
||||
BuiltValueNullFieldError.checkNotNull(
|
||||
enableDarkModeSystem, r'PrefState', 'enableDarkModeSystem');
|
||||
BuiltValueNullFieldError.checkNotNull(
|
||||
isFilterVisible, r'PrefState', 'isFilterVisible');
|
||||
BuiltValueNullFieldError.checkNotNull(
|
||||
|
|
@ -845,7 +857,8 @@ class _$PrefState extends PrefState {
|
|||
enableTouchEvents == other.enableTouchEvents &&
|
||||
enableFlexibleSearch == other.enableFlexibleSearch &&
|
||||
isHistoryVisible == other.isHistoryVisible &&
|
||||
enableDarkMode == other.enableDarkMode &&
|
||||
darkModeType == other.darkModeType &&
|
||||
enableDarkModeSystem == other.enableDarkModeSystem &&
|
||||
isFilterVisible == other.isFilterVisible &&
|
||||
persistData == other.persistData &&
|
||||
persistUI == other.persistUI &&
|
||||
|
|
@ -888,7 +901,8 @@ class _$PrefState extends PrefState {
|
|||
_$hash = $jc(_$hash, enableTouchEvents.hashCode);
|
||||
_$hash = $jc(_$hash, enableFlexibleSearch.hashCode);
|
||||
_$hash = $jc(_$hash, isHistoryVisible.hashCode);
|
||||
_$hash = $jc(_$hash, enableDarkMode.hashCode);
|
||||
_$hash = $jc(_$hash, darkModeType.hashCode);
|
||||
_$hash = $jc(_$hash, enableDarkModeSystem.hashCode);
|
||||
_$hash = $jc(_$hash, isFilterVisible.hashCode);
|
||||
_$hash = $jc(_$hash, persistData.hashCode);
|
||||
_$hash = $jc(_$hash, persistUI.hashCode);
|
||||
|
|
@ -931,7 +945,8 @@ class _$PrefState extends PrefState {
|
|||
..add('enableTouchEvents', enableTouchEvents)
|
||||
..add('enableFlexibleSearch', enableFlexibleSearch)
|
||||
..add('isHistoryVisible', isHistoryVisible)
|
||||
..add('enableDarkMode', enableDarkMode)
|
||||
..add('darkModeType', darkModeType)
|
||||
..add('enableDarkModeSystem', enableDarkModeSystem)
|
||||
..add('isFilterVisible', isFilterVisible)
|
||||
..add('persistData', persistData)
|
||||
..add('persistUI', persistUI)
|
||||
|
|
@ -1040,10 +1055,14 @@ class PrefStateBuilder implements Builder<PrefState, PrefStateBuilder> {
|
|||
set isHistoryVisible(bool isHistoryVisible) =>
|
||||
_$this._isHistoryVisible = isHistoryVisible;
|
||||
|
||||
bool _enableDarkMode;
|
||||
bool get enableDarkMode => _$this._enableDarkMode;
|
||||
set enableDarkMode(bool enableDarkMode) =>
|
||||
_$this._enableDarkMode = enableDarkMode;
|
||||
String _darkModeType;
|
||||
String get darkModeType => _$this._darkModeType;
|
||||
set darkModeType(String darkModeType) => _$this._darkModeType = darkModeType;
|
||||
|
||||
bool _enableDarkModeSystem;
|
||||
bool get enableDarkModeSystem => _$this._enableDarkModeSystem;
|
||||
set enableDarkModeSystem(bool enableDarkModeSystem) =>
|
||||
_$this._enableDarkModeSystem = enableDarkModeSystem;
|
||||
|
||||
bool _isFilterVisible;
|
||||
bool get isFilterVisible => _$this._isFilterVisible;
|
||||
|
|
@ -1161,7 +1180,8 @@ class PrefStateBuilder implements Builder<PrefState, PrefStateBuilder> {
|
|||
_enableTouchEvents = $v.enableTouchEvents;
|
||||
_enableFlexibleSearch = $v.enableFlexibleSearch;
|
||||
_isHistoryVisible = $v.isHistoryVisible;
|
||||
_enableDarkMode = $v.enableDarkMode;
|
||||
_darkModeType = $v.darkModeType;
|
||||
_enableDarkModeSystem = $v.enableDarkModeSystem;
|
||||
_isFilterVisible = $v.isFilterVisible;
|
||||
_persistData = $v.persistData;
|
||||
_persistUI = $v.persistUI;
|
||||
|
|
@ -1229,7 +1249,8 @@ class PrefStateBuilder implements Builder<PrefState, PrefStateBuilder> {
|
|||
enableTouchEvents: BuiltValueNullFieldError.checkNotNull(enableTouchEvents, r'PrefState', 'enableTouchEvents'),
|
||||
enableFlexibleSearch: BuiltValueNullFieldError.checkNotNull(enableFlexibleSearch, r'PrefState', 'enableFlexibleSearch'),
|
||||
isHistoryVisible: BuiltValueNullFieldError.checkNotNull(isHistoryVisible, r'PrefState', 'isHistoryVisible'),
|
||||
enableDarkMode: BuiltValueNullFieldError.checkNotNull(enableDarkMode, r'PrefState', 'enableDarkMode'),
|
||||
darkModeType: BuiltValueNullFieldError.checkNotNull(darkModeType, r'PrefState', 'darkModeType'),
|
||||
enableDarkModeSystem: BuiltValueNullFieldError.checkNotNull(enableDarkModeSystem, r'PrefState', 'enableDarkModeSystem'),
|
||||
isFilterVisible: BuiltValueNullFieldError.checkNotNull(isFilterVisible, r'PrefState', 'isFilterVisible'),
|
||||
persistData: BuiltValueNullFieldError.checkNotNull(persistData, r'PrefState', 'persistData'),
|
||||
persistUI: BuiltValueNullFieldError.checkNotNull(persistUI, r'PrefState', 'persistUI'),
|
||||
|
|
|
|||
|
|
@ -368,6 +368,7 @@ class _DeviceSettingsState extends State<DeviceSettings>
|
|||
primary: true,
|
||||
children: [
|
||||
FormCard(children: [
|
||||
/*
|
||||
SwitchListTile(
|
||||
title: Text(localization.darkMode),
|
||||
value: prefState.enableDarkMode,
|
||||
|
|
@ -379,6 +380,29 @@ class _DeviceSettingsState extends State<DeviceSettings>
|
|||
activeColor: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
*/
|
||||
AppDropdownButton<String>(
|
||||
labelText: localization.lightDarkMode,
|
||||
value: prefState.darkModeType,
|
||||
onChanged: (dynamic brightness) {
|
||||
viewModel.onDarkModeChanged(context, brightness);
|
||||
},
|
||||
items: [
|
||||
DropdownMenuItem(
|
||||
child: Text(
|
||||
'${localization.system} (${prefState.enableDarkModeSystem ? localization.dark : localization.light})',
|
||||
),
|
||||
value: kBrightnessSytem,
|
||||
),
|
||||
DropdownMenuItem(
|
||||
child: Text(localization.light),
|
||||
value: kBrightnessLight,
|
||||
),
|
||||
DropdownMenuItem(
|
||||
child: Text(localization.dark),
|
||||
value: kBrightnessDark,
|
||||
),
|
||||
]),
|
||||
AppDropdownButton<String>(
|
||||
labelText: localization.statusColorTheme,
|
||||
value: state.prefState.colorTheme,
|
||||
|
|
|
|||
|
|
@ -74,11 +74,12 @@ class DeviceSettingsVM {
|
|||
context, AppLocalization.of(context).endedAllSessions);
|
||||
store.dispatch(UserLogoutAll(completer: completer));
|
||||
},
|
||||
onDarkModeChanged: (BuildContext context, bool value) async {
|
||||
onDarkModeChanged: (BuildContext context, String value) async {
|
||||
store.dispatch(UpdateUserPreferences(
|
||||
enableDarkMode: value,
|
||||
colorTheme: value ? kColorThemeDark : kColorThemeLight,
|
||||
customColors: value
|
||||
darkModeType: value,
|
||||
colorTheme:
|
||||
value == kBrightnessDark ? kColorThemeDark : kColorThemeLight,
|
||||
customColors: value == kBrightnessDark
|
||||
? BuiltMap<String, String>()
|
||||
: BuiltMap<String, String>(PrefState.CONTRAST_COLORS)));
|
||||
store.dispatch(UpdatedSetting());
|
||||
|
|
@ -205,7 +206,7 @@ class DeviceSettingsVM {
|
|||
final AppState state;
|
||||
final Function(BuildContext) onRefreshTap;
|
||||
final Function(BuildContext) onLogoutTap;
|
||||
final Function(BuildContext, bool) onDarkModeChanged;
|
||||
final Function(BuildContext, String) onDarkModeChanged;
|
||||
final Function(BuildContext, BuiltMap<String, String>) onCustomColorsChanged;
|
||||
final Function(BuildContext, AppLayout) onLayoutChanged;
|
||||
final Function(BuildContext, AppSidebarMode) onMenuModeChanged;
|
||||
|
|
|
|||
|
|
@ -257,20 +257,26 @@ class _SettingsWizardState extends State<SettingsWizard> {
|
|||
final darkMode = LayoutBuilder(builder: (context, constraints) {
|
||||
return ToggleButtons(
|
||||
children: [
|
||||
Text(localization.system),
|
||||
Text(localization.light),
|
||||
Text(localization.dark),
|
||||
],
|
||||
constraints: BoxConstraints.expand(
|
||||
width: (constraints.maxWidth / 2) - 2, height: 40),
|
||||
width: (constraints.maxWidth / 3) - 2, height: 40),
|
||||
isSelected: [
|
||||
!state.prefState.enableDarkMode,
|
||||
state.prefState.enableDarkMode,
|
||||
state.prefState.darkModeType == kBrightnessSytem,
|
||||
state.prefState.darkModeType == kBrightnessLight,
|
||||
state.prefState.darkModeType == kBrightnessDark,
|
||||
],
|
||||
onPressed: (index) {
|
||||
final isDark = index == 1;
|
||||
final isDark = index == 2;
|
||||
store.dispatch(
|
||||
UpdateUserPreferences(
|
||||
enableDarkMode: isDark,
|
||||
darkModeType: index == 0
|
||||
? kBrightnessSytem
|
||||
: index == 1
|
||||
? kBrightnessLight
|
||||
: kBrightnessDark,
|
||||
colorTheme: isDark ? kColorThemeDark : kColorThemeLight,
|
||||
customColors: isDark
|
||||
? BuiltMap<String, String>()
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ mixin LocalizationsProvider on LocaleCodeAware {
|
|||
static final Map<String, Map<String, String>> _localizedValues = {
|
||||
'en': {
|
||||
// STARTER: lang key - do not remove comment
|
||||
'light_dark_mode': 'Light/Dark Mode',
|
||||
'activities': 'Activities',
|
||||
'routing_id': 'Routing ID',
|
||||
'enable_e_invoice': 'Enable E-Invoice',
|
||||
|
|
@ -102330,6 +102331,10 @@ mixin LocalizationsProvider on LocaleCodeAware {
|
|||
_localizedValues[localeCode]['routing_id'] ??
|
||||
_localizedValues['en']['routing_id'];
|
||||
|
||||
String get lightDarkMode =>
|
||||
_localizedValues[localeCode]['light_dark_mode'] ??
|
||||
_localizedValues['en']['light_dark_mode'];
|
||||
|
||||
// STARTER: lang field - do not remove comment
|
||||
|
||||
String lookup(String key) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue