Enable dark mode

This commit is contained in:
unknown 2018-07-30 22:57:03 +03:00
parent a8fd12bc67
commit bdc8fe074b
12 changed files with 124 additions and 62 deletions

View File

@ -3,6 +3,12 @@ import 'package:flutter/material.dart';
// This version must be updated in tandem with the pubspec version.
const String kAppVersion = '0.1.2';
const String kSharedPrefEmail = 'email';
const String kSharedPrefPassword = 'password';
const String kSharedPrefUrl = 'url';
const String kSharedPrefSecret = 'secret';
const String kSharedPrefEnableDarkMode = 'enable_dark_mode';
const int kMinMajorAppVersion = 4;
const int kMinMinorAppVersion = 5;

View File

@ -1,12 +1,15 @@
import 'package:invoiceninja_flutter/ui/settings/settings_screen.dart';
import 'package:redux/redux.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux_logging/redux_logging.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/redux/app/app_middleware.dart';
import 'package:invoiceninja_flutter/redux/client/client_actions.dart';
import 'package:invoiceninja_flutter/redux/client/client_middleware.dart';
import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
import 'package:invoiceninja_flutter/ui/settings/settings_screen.dart';
import 'package:invoiceninja_flutter/ui/auth/init_screen.dart';
import 'package:invoiceninja_flutter/ui/client/client_screen.dart';
import 'package:invoiceninja_flutter/ui/client/edit/client_edit_vm.dart';
@ -14,7 +17,6 @@ import 'package:invoiceninja_flutter/ui/client/view/client_view_vm.dart';
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/invoice/view/invoice_view_vm.dart';
import 'package:invoiceninja_flutter/ui/product/edit/product_edit_vm.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/ui/auth/login_vm.dart';
import 'package:invoiceninja_flutter/ui/dashboard/dashboard_screen.dart';
import 'package:invoiceninja_flutter/ui/product/product_screen.dart';
@ -27,9 +29,9 @@ import 'package:invoiceninja_flutter/redux/product/product_actions.dart';
import 'package:invoiceninja_flutter/redux/product/product_middleware.dart';
import 'package:invoiceninja_flutter/redux/invoice/invoice_middleware.dart';
import 'package:invoiceninja_flutter/ui/invoice/invoice_screen.dart';
import 'package:redux_logging/redux_logging.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
void main() {
void main() async {
final store = Store<AppState>(appReducer,
initialState: AppState(),
middleware: []
@ -43,19 +45,23 @@ void main() {
LoggingMiddleware<dynamic>.printer(),
]));
runApp(InvoiceNinjaApp(store: store));
final prefs = await SharedPreferences.getInstance();
final enableDarkMode = prefs.getBool(kSharedPrefEnableDarkMode);
runApp(InvoiceNinjaApp(store: store, enableDarkMode: enableDarkMode));
}
class InvoiceNinjaApp extends StatefulWidget {
final Store<AppState> store;
const InvoiceNinjaApp({Key key, this.store}) : super(key: key);
final bool enableDarkMode;
const InvoiceNinjaApp({Key key, this.store, this.enableDarkMode})
: super(key: key);
@override
_InvoiceNinjaAppState createState() => _InvoiceNinjaAppState();
InvoiceNinjaAppState createState() => InvoiceNinjaAppState();
}
class _InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
class InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
@override
Widget build(BuildContext context) {
@ -69,24 +75,21 @@ class _InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
],
// light theme
theme: ThemeData().copyWith(
//accentColor: Colors.lightBlueAccent,
primaryColor: const Color(0xFF117cc1),
primaryColorLight: const Color(0xFF5dabf4),
primaryColorDark: const Color(0xFF0D5D91),
indicatorColor: Colors.white,
bottomAppBarColor: Colors.grey.shade300,
backgroundColor: Colors.grey.shade200,
buttonColor: const Color(0xFF0D5D91),
),
//dark theme
/*
theme: ThemeData(
brightness: Brightness.dark,
accentColor: Colors.lightBlueAccent,
),
*/
theme: widget.enableDarkMode
? ThemeData(
brightness: Brightness.dark,
accentColor: Colors.lightBlueAccent,
)
: ThemeData().copyWith(
//accentColor: Colors.lightBlueAccent,
primaryColor: const Color(0xFF117cc1),
primaryColorLight: const Color(0xFF5dabf4),
primaryColorDark: const Color(0xFF0D5D91),
indicatorColor: Colors.white,
bottomAppBarColor: Colors.grey.shade300,
backgroundColor: Colors.grey.shade200,
buttonColor: const Color(0xFF0D5D91),
),
title: 'Invoice Ninja',
routes: {

View File

@ -14,3 +14,9 @@ class LoadStaticSuccess {
LoadStaticSuccess(this.data);
}
class UserSettingsChanged implements PersistUI {
final bool enableDarkMode;
UserSettingsChanged({this.enableDarkMode});
}

View File

@ -105,6 +105,6 @@ abstract class AppState implements Built<AppState, AppStateBuilder> {
String toString() {
//return 'Is Loading: ${this.isLoading}, Invoice: ${this.invoiceUIState.selected}';
//return 'Date Formats: ${staticState.dateFormatMap}';
return 'Route: ${uiState.currentRoute}';
return 'Route: ${uiState.currentRoute}, Dark Mode: ${uiState.enableDarkMode}';
}
}

View File

@ -24,26 +24,29 @@ List<Middleware<AppState>> createStoreAuthMiddleware([
void _saveAuthLocal(dynamic action) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('email', action.email);
prefs.setString('url', action.url);
prefs.setString(kSharedPrefEmail, action.email);
prefs.setString(kSharedPrefUrl, action.url);
if (action.password == 'password') {
prefs.setString('password', action.password);
prefs.setString(kSharedPrefPassword, action.password);
}
if (action.secret == 'secret') {
prefs.setString('secret', action.secret);
prefs.setString(kSharedPrefSecret, action.secret);
}
}
void _loadAuthLocal(Store<AppState> store, dynamic action) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
final String email = prefs.getString('email') ?? Config.LOGIN_EMAIL;
final String password = prefs.getString('password') ?? Config.LOGIN_PASSWORD;
final String url = prefs.getString('url') ?? Config.LOGIN_URL;
final String secret = prefs.getString('secret') ?? Config.LOGIN_SECRET;
final String email = prefs.getString(kSharedPrefEmail) ?? Config.LOGIN_EMAIL;
final String password = prefs.getString(kSharedPrefPassword) ?? Config.LOGIN_PASSWORD;
final String url = prefs.getString(kSharedPrefUrl) ?? Config.LOGIN_URL;
final String secret = prefs.getString(kSharedPrefSecret) ?? Config.LOGIN_SECRET;
store.dispatch(UserLoginLoaded(email, password, url, secret));
final bool enableDarkMode = prefs.getBool(kSharedPrefEnableDarkMode) ?? false;
store.dispatch(UserSettingsChanged(enableDarkMode: enableDarkMode));
Navigator.of(action.context).pushReplacementNamed(LoginScreen.route);
}

View File

@ -1,3 +1,4 @@
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/client/client_reducer.dart';
import 'package:invoiceninja_flutter/redux/company/company_actions.dart';
import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
@ -11,12 +12,21 @@ UIState uiReducer(UIState state, dynamic action) {
return state.rebuild((b) => b
..selectedCompanyIndex = selectedCompanyIndexReducer(state.selectedCompanyIndex, action)
..currentRoute = currentRouteReducer(state.currentRoute, action)
..enableDarkMode = darkModeReducer(state.enableDarkMode, action)
..productUIState.replace(productUIReducer(state.productUIState, action))
..clientUIState.replace(clientUIReducer(state.clientUIState, action))
..invoiceUIState.replace(invoiceUIReducer(state.invoiceUIState, action))
);
}
Reducer<bool> darkModeReducer = combineReducers([
TypedReducer<bool, UserSettingsChanged>(updateDarkModeReducer),
]);
bool updateDarkModeReducer(bool enableDarkMode, UserSettingsChanged action) {
return action.enableDarkMode;
}
Reducer<String> currentRouteReducer = combineReducers([
TypedReducer<String, UpdateCurrentRoute>(updateCurrentRouteReducer),
]);

View File

@ -13,6 +13,7 @@ abstract class UIState implements Built<UIState, UIStateBuilder> {
return _$UIState._(
selectedCompanyIndex: 0,
currentRoute: LoginScreen.route,
enableDarkMode: false,
productUIState: ProductUIState(),
clientUIState: ClientUIState(),
invoiceUIState: InvoiceUIState(),
@ -22,6 +23,7 @@ abstract class UIState implements Built<UIState, UIStateBuilder> {
int get selectedCompanyIndex;
String get currentRoute;
bool get enableDarkMode;
ProductUIState get productUIState;
ClientUIState get clientUIState;
InvoiceUIState get invoiceUIState;

View File

@ -32,6 +32,9 @@ class _$UIStateSerializer implements StructuredSerializer<UIState> {
'currentRoute',
serializers.serialize(object.currentRoute,
specifiedType: const FullType(String)),
'enableDarkMode',
serializers.serialize(object.enableDarkMode,
specifiedType: const FullType(bool)),
'productUIState',
serializers.serialize(object.productUIState,
specifiedType: const FullType(ProductUIState)),
@ -65,6 +68,10 @@ class _$UIStateSerializer implements StructuredSerializer<UIState> {
result.currentRoute = serializers.deserialize(value,
specifiedType: const FullType(String)) as String;
break;
case 'enableDarkMode':
result.enableDarkMode = serializers.deserialize(value,
specifiedType: const FullType(bool)) as bool;
break;
case 'productUIState':
result.productUIState.replace(serializers.deserialize(value,
specifiedType: const FullType(ProductUIState)) as ProductUIState);
@ -90,6 +97,8 @@ class _$UIState extends UIState {
@override
final String currentRoute;
@override
final bool enableDarkMode;
@override
final ProductUIState productUIState;
@override
final ClientUIState clientUIState;
@ -102,6 +111,7 @@ class _$UIState extends UIState {
_$UIState._(
{this.selectedCompanyIndex,
this.currentRoute,
this.enableDarkMode,
this.productUIState,
this.clientUIState,
this.invoiceUIState})
@ -110,6 +120,8 @@ class _$UIState extends UIState {
throw new BuiltValueNullFieldError('UIState', 'selectedCompanyIndex');
if (currentRoute == null)
throw new BuiltValueNullFieldError('UIState', 'currentRoute');
if (enableDarkMode == null)
throw new BuiltValueNullFieldError('UIState', 'enableDarkMode');
if (productUIState == null)
throw new BuiltValueNullFieldError('UIState', 'productUIState');
if (clientUIState == null)
@ -131,6 +143,7 @@ class _$UIState extends UIState {
if (other is! UIState) return false;
return selectedCompanyIndex == other.selectedCompanyIndex &&
currentRoute == other.currentRoute &&
enableDarkMode == other.enableDarkMode &&
productUIState == other.productUIState &&
clientUIState == other.clientUIState &&
invoiceUIState == other.invoiceUIState;
@ -141,8 +154,10 @@ class _$UIState extends UIState {
return $jf($jc(
$jc(
$jc(
$jc($jc(0, selectedCompanyIndex.hashCode),
currentRoute.hashCode),
$jc(
$jc($jc(0, selectedCompanyIndex.hashCode),
currentRoute.hashCode),
enableDarkMode.hashCode),
productUIState.hashCode),
clientUIState.hashCode),
invoiceUIState.hashCode));
@ -153,6 +168,7 @@ class _$UIState extends UIState {
return (newBuiltValueToStringHelper('UIState')
..add('selectedCompanyIndex', selectedCompanyIndex)
..add('currentRoute', currentRoute)
..add('enableDarkMode', enableDarkMode)
..add('productUIState', productUIState)
..add('clientUIState', clientUIState)
..add('invoiceUIState', invoiceUIState))
@ -172,6 +188,11 @@ class UIStateBuilder implements Builder<UIState, UIStateBuilder> {
String get currentRoute => _$this._currentRoute;
set currentRoute(String currentRoute) => _$this._currentRoute = currentRoute;
bool _enableDarkMode;
bool get enableDarkMode => _$this._enableDarkMode;
set enableDarkMode(bool enableDarkMode) =>
_$this._enableDarkMode = enableDarkMode;
ProductUIStateBuilder _productUIState;
ProductUIStateBuilder get productUIState =>
_$this._productUIState ??= new ProductUIStateBuilder();
@ -196,6 +217,7 @@ class UIStateBuilder implements Builder<UIState, UIStateBuilder> {
if (_$v != null) {
_selectedCompanyIndex = _$v.selectedCompanyIndex;
_currentRoute = _$v.currentRoute;
_enableDarkMode = _$v.enableDarkMode;
_productUIState = _$v.productUIState?.toBuilder();
_clientUIState = _$v.clientUIState?.toBuilder();
_invoiceUIState = _$v.invoiceUIState?.toBuilder();
@ -223,6 +245,7 @@ class UIStateBuilder implements Builder<UIState, UIStateBuilder> {
new _$UIState._(
selectedCompanyIndex: selectedCompanyIndex,
currentRoute: currentRoute,
enableDarkMode: enableDarkMode,
productUIState: productUIState.build(),
clientUIState: clientUIState.build(),
invoiceUIState: invoiceUIState.build());

View File

@ -1,11 +1,9 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/ui/auth/login_vm.dart';
import 'package:redux/redux.dart';
import 'package:invoiceninja_flutter/ui/app/app_drawer.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/auth/auth_actions.dart';
import 'package:invoiceninja_flutter/redux/company/company_selectors.dart';
import 'package:invoiceninja_flutter/redux/company/company_actions.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';

View File

@ -6,13 +6,9 @@ import 'package:invoiceninja_flutter/utils/localization.dart';
class SettingsList extends StatelessWidget {
final SettingsListVM viewModel;
final Function onThemeChange;
final bool isDark;
const SettingsList({
Key key,
@required this.isDark,
@required this.onThemeChange,
@required this.viewModel,
}) : super(key: key);
@ -22,8 +18,8 @@ class SettingsList extends StatelessWidget {
children: <Widget>[
SwitchListTile(
title: Text(AppLocalization.of(context).darkMode),
value: isDark ?? false,
onChanged: (value) => viewModel.onDarkModeChanged(value),
value: viewModel.enableDarkMode,
onChanged: (value) => viewModel.onDarkModeChanged(context, value),
secondary: Icon(Icons.color_lens),
),
ListTile(

View File

@ -1,5 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:redux/redux.dart';
import 'package:invoiceninja_flutter/ui/auth/login_vm.dart';
import 'package:invoiceninja_flutter/ui/settings/settings_list.dart';
@ -8,10 +12,6 @@ import 'package:invoiceninja_flutter/redux/auth/auth_actions.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SettingsListBuilder extends StatelessWidget {
//final Function onThemeChange;
//final bool isDark;
//const SettingsListBuilder(this.onThemeChange, this.isDark, {Key key})
// : super(key: key);
const SettingsListBuilder({Key key}) : super(key: key);
@ -28,27 +28,40 @@ class SettingsListBuilder extends StatelessWidget {
class SettingsListVM {
final Function(BuildContext context) onLogoutTapped;
final Function(bool value) onDarkModeChanged;
final Function(BuildContext context, bool value) onDarkModeChanged;
final bool enableDarkMode;
SettingsListVM({
@required this.onLogoutTapped,
@required this.onDarkModeChanged,
@required this.enableDarkMode,
});
static SettingsListVM fromStore(Store<AppState> store) {
void _warnRestart(BuildContext context) {
final localization = AppLocalization.of(context);
showDialog<AlertDialog>(
context: context,
builder: (BuildContext context) => AlertDialog(
semanticLabel: localization.restartAppToApplyChange,
title: Text(localization.restartAppToApplyChange),
actions: <Widget>[
new FlatButton(
child: Text(localization.ok.toUpperCase()),
onPressed: () => Navigator.pop(context))
],
),
);
}
return SettingsListVM(onLogoutTapped: (BuildContext context) {
Navigator.popUntil(context, ModalRoute.withName(LoginScreen.route));
/*
while (Navigator.of(context).canPop()) {
Navigator.of(context).pop();
}
Navigator.of(context).pushReplacementNamed(LoginScreen.route);
*/
store.dispatch(UserLogout());
}, onDarkModeChanged: (bool value) async {
print('value: $value');
}, onDarkModeChanged: (BuildContext context, bool value) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool('darkMode', value);
});
prefs.setBool(kSharedPrefEnableDarkMode, value);
store.dispatch(UserSettingsChanged(enableDarkMode: value));
_warnRestart(context);
}, enableDarkMode: store.state.uiState.enableDarkMode);
}
}

View File

@ -172,6 +172,7 @@ class AppLocalization {
'done': 'Done',
'please_enter_a_client_or_contact_name': 'Please enter a client or contact name',
'dark_mode': 'Dark Mode',
'restart_app_to_apply_change': 'Restart the app to apply the change',
'payment': 'Payment',
'payments': 'Payments',
@ -354,6 +355,7 @@ class AppLocalization {
String get done => _localizedValues[locale.languageCode]['done'];
String get pleaseEnterAClientOrContactName => _localizedValues[locale.languageCode]['please_enter_a_client_or_contact_name'];
String get darkMode => _localizedValues[locale.languageCode]['dark_mode'];
String get restartAppToApplyChange => _localizedValues[locale.languageCode]['restart_app_to_apply_change'];
String get payment => _localizedValues[locale.languageCode]['payment'];
String get payments => _localizedValues[locale.languageCode]['payments'];