From 5363cb33acfb67c1842e98572e362abf7eb95062 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 14 Nov 2019 15:21:00 +0200 Subject: [PATCH] Sidebar --- lib/constants.dart | 11 ++- lib/data/models/serializers.g.dart | 1 + lib/main.dart | 2 +- lib/redux/app/app_actions.dart | 4 + lib/redux/app/app_state.dart | 4 +- lib/redux/ui/ui_reducer.dart | 14 +++ lib/redux/ui/ui_state.dart | 36 ++++++-- lib/redux/ui/ui_state.g.dart | 90 +++++++++++++++++++- lib/ui/app/history_drawer.dart | 13 ++- lib/ui/settings/device_settings_list.dart | 41 ++++++++- lib/ui/settings/device_settings_list_vm.dart | 27 +++++- lib/utils/i18n.dart | 15 ++++ 12 files changed, 234 insertions(+), 24 deletions(-) diff --git a/lib/constants.dart b/lib/constants.dart index d6e320ee9..9dab051f5 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -23,12 +23,17 @@ const String kSharedPrefSecret = 'secret'; const String kSharedPrefToken = 'api_token'; const String kSharedPrefEnableDarkMode = 'enable_dark_mode'; const String kSharedPrefAccentColor = 'accent_color'; -const String kSharedPrefLongPressSelectionIsDefault = 'long_press_multiselect'; -const String kSharedPrefEmailPayment = 'email_payment'; -const String kSharedPrefAutoStartTasks = 'auto_start_tasks'; +const String kSharedPrefLongPressSelection = 'long_press_multiselect'; const String kSharedPrefAppVersion = 'app_version'; const String kSharedPrefRequireAuthentication = 'require_authentication'; +const String kSharedPrefLayout = 'layout'; +const String kSharedPrefMenuMode = 'menu_mode'; +const String kSharedPrefHistoryMode = 'history_mode'; + +// TODO remove these const String kSharedPrefAddDocumentsToInvoice = 'add_documents_to_invoice'; +const String kSharedPrefEmailPayment = 'email_payment'; +const String kSharedPrefAutoStartTasks = 'auto_start_tasks'; const String kProductPlanPro = 'v1_pro_yearly'; const String kProductPlanEnterprise2 = 'v1_enterprise_2_yearly'; diff --git a/lib/data/models/serializers.g.dart b/lib/data/models/serializers.g.dart index cf3cd28fc..8f18717db 100644 --- a/lib/data/models/serializers.g.dart +++ b/lib/data/models/serializers.g.dart @@ -9,6 +9,7 @@ part of 'serializers.dart'; Serializers _$serializers = (new Serializers().toBuilder() ..add(ActivityEntity.serializer) ..add(AppLayout.serializer) + ..add(AppSidebarMode.serializer) ..add(AppState.serializer) ..add(AuthState.serializer) ..add(ClientEntity.serializer) diff --git a/lib/main.dart b/lib/main.dart index 1d89f91eb..56117216e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -79,7 +79,7 @@ void main({bool isTesting = false}) async { accentColor = prefs.getString(kSharedPrefAccentColor) ?? kDefaultAccentColor; longPressSelectionIsDefault = - prefs.getBool(kSharedPrefLongPressSelectionIsDefault) ?? false; + prefs.getBool(kSharedPrefLongPressSelection) ?? false; requireAuthentication = prefs.getBool(kSharedPrefRequireAuthentication) ?? false; } diff --git a/lib/redux/app/app_actions.dart b/lib/redux/app/app_actions.dart index a85150469..ac5606a42 100644 --- a/lib/redux/app/app_actions.dart +++ b/lib/redux/app/app_actions.dart @@ -72,6 +72,8 @@ class UserSettingsChanged implements PersistUI { this.longPressSelectionIsDefault, this.addDocumentsToInvoice, this.accentColor, + this.menuMode, + this.historyMode, }); final bool enableDarkMode; @@ -81,6 +83,8 @@ class UserSettingsChanged implements PersistUI { final bool autoStartTasks; final bool addDocumentsToInvoice; final String accentColor; + final AppSidebarMode menuMode; + final AppSidebarMode historyMode; } class LoadAccountSuccess { diff --git a/lib/redux/app/app_state.dart b/lib/redux/app/app_state.dart index 9d346fc20..2f664883b 100644 --- a/lib/redux/app/app_state.dart +++ b/lib/redux/app/app_state.dart @@ -431,13 +431,13 @@ abstract class AppState implements Built { String toString() { //return 'Custom fields [UI]: ${uiState.settingsUIState.userCompany.company.customFields}, [DB] ${selectedCompany.customFields}'; //return 'Permissions: ${uiState.userUIState.editing.id}'; - //return 'Layout: ${uiState.layout}'; + return 'Layout: ${uiState.layout}, menu: ${uiState.menuSidebarMode}, history: ${uiState.historySidebarMode}'; //return 'Sidebars - isMenuVisible: ${uiState.isMenuVisible}, isHistoryVisible: ${uiState.isHistoryVisible}'; //return 'Gateway: ${uiState.companyGatewayUIState.editing.feesAndLimitsMap}'; //return 'Routes: Current: ${uiState.currentRoute} Prev: ${uiState.previousRoute}'; //return 'Route: ${uiState.currentRoute}, Setting Type: ${uiState.settingsUIState.entityType}, Name: ${uiState.settingsUIState.settings.name}, Updated: ${uiState.settingsUIState.updatedAt}'; //return 'Route: ${uiState.currentRoute}, Previous: ${uiState.previousRoute}, Layout: ${uiState.layout}, Menu: ${uiState.isMenuVisible}, History: ${uiState.isHistoryVisible}'; - return 'Route: ${uiState.currentRoute} Prev: ${uiState.previousRoute}'; + //return 'Route: ${uiState.currentRoute} Prev: ${uiState.previousRoute}'; } } diff --git a/lib/redux/ui/ui_reducer.dart b/lib/redux/ui/ui_reducer.dart index d08faa776..fb8514121 100644 --- a/lib/redux/ui/ui_reducer.dart +++ b/lib/redux/ui/ui_reducer.dart @@ -36,6 +36,8 @@ UIState uiReducer(UIState state, dynamic action) { ..selectedCompanyIndex = selectedCompanyIndexReducer(state.selectedCompanyIndex, action) ..layout = layoutReducer(state.layout, action) + ..menuSidebarMode = manuSidebarReducer(state.menuSidebarMode, action) + ..historySidebarMode = historySidebarReducer(state.menuSidebarMode, action) ..isMenuVisible = menuVisibleReducer(state.isMenuVisible, action) ..isHistoryVisible = historyVisibleReducer(state.isHistoryVisible, action) ..previousRoute = state.currentRoute == currentRoute @@ -100,6 +102,18 @@ Reducer layoutReducer = combineReducers([ }), ]); +Reducer manuSidebarReducer = combineReducers([ + TypedReducer((mode, action) { + return action.menuMode ?? mode; + }), +]); + +Reducer historySidebarReducer = combineReducers([ + TypedReducer((mode, action) { + return action.historyMode ?? mode; + }), +]); + Reducer emailPaymentReducer = combineReducers([ TypedReducer((emailPayment, action) { return action.emailPayment ?? emailPayment; diff --git a/lib/redux/ui/ui_state.dart b/lib/redux/ui/ui_state.dart index 102e70dc3..f2cfcf210 100644 --- a/lib/redux/ui/ui_state.dart +++ b/lib/redux/ui/ui_state.dart @@ -1,6 +1,7 @@ import 'package:built_collection/built_collection.dart'; import 'package:built_value/built_value.dart'; import 'package:built_value/serializer.dart'; +import 'package:flutter/foundation.dart'; import 'package:invoiceninja_flutter/data/models/company_model.dart'; import 'package:invoiceninja_flutter/redux/client/client_state.dart'; import 'package:invoiceninja_flutter/redux/company/company_state.dart'; @@ -30,17 +31,21 @@ part 'ui_state.g.dart'; abstract class UIState implements Built { factory UIState( CompanyEntity company, { - bool enableDarkMode, - String accentColor, - bool requireAuthentication, - bool longPressSelectionIsDefault, - AppLayout layout, - bool isTesting, + @required bool enableDarkMode, + @required String accentColor, + @required bool requireAuthentication, + @required bool longPressSelectionIsDefault, + @required AppLayout layout, + @required bool isTesting, }) { return _$UIState._( selectedCompanyIndex: 0, //layout: layout ?? AppLayout.mobile, layout: layout ?? AppLayout.tablet, + historySidebarMode: (layout ?? AppLayout.tablet) == AppLayout.tablet + ? AppSidebarMode.hide + : AppSidebarMode.float, + menuSidebarMode: AppSidebarMode.float, isTesting: isTesting ?? false, isMenuVisible: true, isHistoryVisible: false, @@ -76,6 +81,10 @@ abstract class UIState implements Built { AppLayout get layout; + AppSidebarMode get menuSidebarMode; + + AppSidebarMode get historySidebarMode; + bool get isTesting; bool get isMenuVisible; @@ -194,3 +203,18 @@ class AppSidebar extends EnumClass { static AppSidebar valueOf(String name) => _$valueOfSidebar(name); } + +class AppSidebarMode extends EnumClass { + const AppSidebarMode._(String name) : super(name); + + static Serializer get serializer => + _$appSidebarModeSerializer; + + static const AppSidebarMode float = _$float; + static const AppSidebarMode hide = _$hide; + static const AppSidebarMode collapse = _$collapse; + + static BuiltSet get values => _$valuesSidebarMode; + + static AppSidebarMode valueOf(String name) => _$valueOfSidebarMode(name); +} diff --git a/lib/redux/ui/ui_state.g.dart b/lib/redux/ui/ui_state.g.dart index 47c0bcb9b..a26588812 100644 --- a/lib/redux/ui/ui_state.g.dart +++ b/lib/redux/ui/ui_state.g.dart @@ -49,9 +49,35 @@ final BuiltSet _$valuesSidebar = _$history, ]); +const AppSidebarMode _$float = const AppSidebarMode._('float'); +const AppSidebarMode _$hide = const AppSidebarMode._('hide'); +const AppSidebarMode _$collapse = const AppSidebarMode._('collapse'); + +AppSidebarMode _$valueOfSidebarMode(String name) { + switch (name) { + case 'float': + return _$float; + case 'hide': + return _$hide; + case 'collapse': + return _$collapse; + default: + throw new ArgumentError(name); + } +} + +final BuiltSet _$valuesSidebarMode = + new BuiltSet(const [ + _$float, + _$hide, + _$collapse, +]); + Serializer _$uIStateSerializer = new _$UIStateSerializer(); Serializer _$appLayoutSerializer = new _$AppLayoutSerializer(); Serializer _$appSidebarSerializer = new _$AppSidebarSerializer(); +Serializer _$appSidebarModeSerializer = + new _$AppSidebarModeSerializer(); class _$UIStateSerializer implements StructuredSerializer { @override @@ -66,6 +92,12 @@ class _$UIStateSerializer implements StructuredSerializer { 'layout', serializers.serialize(object.layout, specifiedType: const FullType(AppLayout)), + 'menuSidebarMode', + serializers.serialize(object.menuSidebarMode, + specifiedType: const FullType(AppSidebarMode)), + 'historySidebarMode', + serializers.serialize(object.historySidebarMode, + specifiedType: const FullType(AppSidebarMode)), 'isTesting', serializers.serialize(object.isTesting, specifiedType: const FullType(bool)), @@ -181,6 +213,14 @@ class _$UIStateSerializer implements StructuredSerializer { result.layout = serializers.deserialize(value, specifiedType: const FullType(AppLayout)) as AppLayout; break; + case 'menuSidebarMode': + result.menuSidebarMode = serializers.deserialize(value, + specifiedType: const FullType(AppSidebarMode)) as AppSidebarMode; + break; + case 'historySidebarMode': + result.historySidebarMode = serializers.deserialize(value, + specifiedType: const FullType(AppSidebarMode)) as AppSidebarMode; + break; case 'isTesting': result.isTesting = serializers.deserialize(value, specifiedType: const FullType(bool)) as bool; @@ -346,10 +386,32 @@ class _$AppSidebarSerializer implements PrimitiveSerializer { AppSidebar.valueOf(serialized as String); } +class _$AppSidebarModeSerializer + implements PrimitiveSerializer { + @override + final Iterable types = const [AppSidebarMode]; + @override + final String wireName = 'AppSidebarMode'; + + @override + Object serialize(Serializers serializers, AppSidebarMode object, + {FullType specifiedType = FullType.unspecified}) => + object.name; + + @override + AppSidebarMode deserialize(Serializers serializers, Object serialized, + {FullType specifiedType = FullType.unspecified}) => + AppSidebarMode.valueOf(serialized as String); +} + class _$UIState extends UIState { @override final AppLayout layout; @override + final AppSidebarMode menuSidebarMode; + @override + final AppSidebarMode historySidebarMode; + @override final bool isTesting; @override final bool isMenuVisible; @@ -415,6 +477,8 @@ class _$UIState extends UIState { _$UIState._( {this.layout, + this.menuSidebarMode, + this.historySidebarMode, this.isTesting, this.isMenuVisible, this.isHistoryVisible, @@ -449,6 +513,12 @@ class _$UIState extends UIState { if (layout == null) { throw new BuiltValueNullFieldError('UIState', 'layout'); } + if (menuSidebarMode == null) { + throw new BuiltValueNullFieldError('UIState', 'menuSidebarMode'); + } + if (historySidebarMode == null) { + throw new BuiltValueNullFieldError('UIState', 'historySidebarMode'); + } if (isTesting == null) { throw new BuiltValueNullFieldError('UIState', 'isTesting'); } @@ -548,6 +618,8 @@ class _$UIState extends UIState { if (identical(other, this)) return true; return other is UIState && layout == other.layout && + menuSidebarMode == other.menuSidebarMode && + historySidebarMode == other.historySidebarMode && isTesting == other.isTesting && isMenuVisible == other.isMenuVisible && isHistoryVisible == other.isHistoryVisible && @@ -600,7 +672,7 @@ class _$UIState extends UIState { $jc( $jc( $jc( - $jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc(0, layout.hashCode), isTesting.hashCode), isMenuVisible.hashCode), isHistoryVisible.hashCode), selectedCompanyIndex.hashCode), currentRoute.hashCode), previousRoute.hashCode), enableDarkMode.hashCode), accentColor.hashCode), longPressSelectionIsDefault.hashCode), requireAuthentication.hashCode), emailPayment.hashCode), + $jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc(0, layout.hashCode), menuSidebarMode.hashCode), historySidebarMode.hashCode), isTesting.hashCode), isMenuVisible.hashCode), isHistoryVisible.hashCode), selectedCompanyIndex.hashCode), currentRoute.hashCode), previousRoute.hashCode), enableDarkMode.hashCode), accentColor.hashCode), longPressSelectionIsDefault.hashCode), requireAuthentication.hashCode), emailPayment.hashCode), autoStartTasks.hashCode), addDocumentsToInvoice.hashCode), filter.hashCode), @@ -626,6 +698,8 @@ class _$UIState extends UIState { String toString() { return (newBuiltValueToStringHelper('UIState') ..add('layout', layout) + ..add('menuSidebarMode', menuSidebarMode) + ..add('historySidebarMode', historySidebarMode) ..add('isTesting', isTesting) ..add('isMenuVisible', isMenuVisible) ..add('isHistoryVisible', isHistoryVisible) @@ -667,6 +741,16 @@ class UIStateBuilder implements Builder { AppLayout get layout => _$this._layout; set layout(AppLayout layout) => _$this._layout = layout; + AppSidebarMode _menuSidebarMode; + AppSidebarMode get menuSidebarMode => _$this._menuSidebarMode; + set menuSidebarMode(AppSidebarMode menuSidebarMode) => + _$this._menuSidebarMode = menuSidebarMode; + + AppSidebarMode _historySidebarMode; + AppSidebarMode get historySidebarMode => _$this._historySidebarMode; + set historySidebarMode(AppSidebarMode historySidebarMode) => + _$this._historySidebarMode = historySidebarMode; + bool _isTesting; bool get isTesting => _$this._isTesting; set isTesting(bool isTesting) => _$this._isTesting = isTesting; @@ -834,6 +918,8 @@ class UIStateBuilder implements Builder { UIStateBuilder get _$this { if (_$v != null) { _layout = _$v.layout; + _menuSidebarMode = _$v.menuSidebarMode; + _historySidebarMode = _$v.historySidebarMode; _isTesting = _$v.isTesting; _isMenuVisible = _$v.isMenuVisible; _isHistoryVisible = _$v.isHistoryVisible; @@ -889,6 +975,8 @@ class UIStateBuilder implements Builder { _$result = _$v ?? new _$UIState._( layout: layout, + menuSidebarMode: menuSidebarMode, + historySidebarMode: historySidebarMode, isTesting: isTesting, isMenuVisible: isMenuVisible, isHistoryVisible: isHistoryVisible, diff --git a/lib/ui/app/history_drawer.dart b/lib/ui/app/history_drawer.dart index 34efe4964..607232b39 100644 --- a/lib/ui/app/history_drawer.dart +++ b/lib/ui/app/history_drawer.dart @@ -1,6 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:invoiceninja_flutter/ui/app/history_drawer_vm.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; class HistoryDrawer extends StatelessWidget { const HistoryDrawer({ @@ -12,14 +13,12 @@ class HistoryDrawer extends StatelessWidget { @override Widget build(BuildContext context) { + final localization = AppLocalization.of(context); + return Drawer( - child: SafeArea( - child: Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('History') - ], + child: Scaffold( + appBar: AppBar( + title: Text(localization.history), ), ), ); diff --git a/lib/ui/settings/device_settings_list.dart b/lib/ui/settings/device_settings_list.dart index 8c3862994..04bbff1fd 100644 --- a/lib/ui/settings/device_settings_list.dart +++ b/lib/ui/settings/device_settings_list.dart @@ -30,7 +30,8 @@ class _DeviceSettingsState extends State { Widget build(BuildContext context) { final localization = AppLocalization.of(context); final viewModel = widget.viewModel; - final uiState = viewModel.state.uiState; + final state = viewModel.state; + final uiState = state.uiState; return WillPopScope( onWillPop: () async { @@ -65,6 +66,44 @@ class _DeviceSettingsState extends State { ), ], ), + AppDropdownButton( + labelText: localization.menuSidebar, + value: state.uiState.menuSidebarMode, + items: [ + DropdownMenuItem( + child: Text(localization.showOrHide), + value: AppSidebarMode.hide, + ), + DropdownMenuItem( + child: Text(localization.float), + value: AppSidebarMode.float, + ), + /* TODO implement + DropdownMenuItem( + child: Text(localization.collapse), + value: AppSidebarMode.collapse, + ), + */ + ], + onChanged: (dynamic value) => + viewModel.onMenuModeChanged(context, value), + ), + AppDropdownButton( + labelText: localization.historySidebar, + value: state.uiState.historySidebarMode, + items: [ + DropdownMenuItem( + child: Text(localization.showOrHide), + value: AppSidebarMode.hide, + ), + DropdownMenuItem( + child: Text(localization.float), + value: AppSidebarMode.float, + ), + ], + onChanged: (dynamic value) => + viewModel.onHistoryModeChanged(context, value), + ), FormColorPicker( labelText: localization.accentColor, initialValue: uiState.accentColor, diff --git a/lib/ui/settings/device_settings_list_vm.dart b/lib/ui/settings/device_settings_list_vm.dart index 497e7f9f8..f8cd82abb 100644 --- a/lib/ui/settings/device_settings_list_vm.dart +++ b/lib/ui/settings/device_settings_list_vm.dart @@ -45,6 +45,8 @@ class DeviceSettingsVM { @required this.onAccentColorChanged, @required this.onLongPressSelectionIsDefault, @required this.authenticationSupported, + @required this.onMenuModeChanged, + @required this.onHistoryModeChanged, }); static DeviceSettingsVM fromStore(Store store) { @@ -121,12 +123,30 @@ class DeviceSettingsVM { onLongPressSelectionIsDefault: (BuildContext context, bool value) async { if (!kIsWeb) { final SharedPreferences prefs = await SharedPreferences.getInstance(); - prefs.setBool(kSharedPrefLongPressSelectionIsDefault, value); + prefs.setBool(kSharedPrefLongPressSelection, value); } store.dispatch(UserSettingsChanged(longPressSelectionIsDefault: value)); AppBuilder.of(context).rebuild(); }, - onLayoutChanged: (BuildContext context, AppLayout value) { + onMenuModeChanged: (context, value) async { + if (!kIsWeb) { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + prefs.setString(kSharedPrefMenuMode, '$value'); + } + store.dispatch(UserSettingsChanged(menuMode: value)); + }, + onHistoryModeChanged: (context, value) async { + if (!kIsWeb) { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + prefs.setString(kSharedPrefHistoryMode, '$value'); + } + store.dispatch(UserSettingsChanged(historyMode: value)); + }, + onLayoutChanged: (BuildContext context, AppLayout value) async { + if (!kIsWeb) { + final SharedPreferences prefs = await SharedPreferences.getInstance(); + prefs.setString(kSharedPrefLayout, '$value'); + } store.dispatch(UpdateLayout(value)); AppBuilder.of(context).rebuild(); if (value == AppLayout.mobile) { @@ -134,7 +154,6 @@ class DeviceSettingsVM { } else { store.dispatch(ViewMainScreen(context)); } - }, onRequireAuthenticationChanged: (BuildContext context, bool value) async { bool authenticated = false; @@ -174,6 +193,8 @@ class DeviceSettingsVM { final Function(BuildContext) onRefreshTap; final Function(BuildContext, bool) onDarkModeChanged; final Function(BuildContext, AppLayout) onLayoutChanged; + final Function(BuildContext, AppSidebarMode) onMenuModeChanged; + final Function(BuildContext, AppSidebarMode) onHistoryModeChanged; final Function(BuildContext, String) onAccentColorChanged; final Function(BuildContext, bool) onAutoStartTasksChanged; final Function(BuildContext, bool) onLongPressSelectionIsDefault; diff --git a/lib/utils/i18n.dart b/lib/utils/i18n.dart index e1083cd79..23897372e 100644 --- a/lib/utils/i18n.dart +++ b/lib/utils/i18n.dart @@ -14,6 +14,11 @@ abstract class LocaleCodeAware { mixin LocalizationsProvider on LocaleCodeAware { static final Map> _localizedValues = { 'en': { + 'float': 'Float', + 'collapse': 'Collapse', + 'show_or_hide': 'Show/hide', + 'menu_sidebar': 'Menu Sidebar', + 'history_sidebar': 'History Sidebar', 'tablet': 'Tablet', 'mobile': 'Mobile', 'desktop': 'Desktop', @@ -15742,6 +15747,16 @@ mixin LocalizationsProvider on LocaleCodeAware { String get tablet => _localizedValues[localeCode]['tablet']; + String get float => _localizedValues[localeCode]['float']; + + String get collapse => _localizedValues[localeCode]['collapse']; + + String get showOrHide => _localizedValues[localeCode]['show_or_hide']; + + String get menuSidebar => _localizedValues[localeCode]['menu_sidebar']; + + String get historySidebar => _localizedValues[localeCode]['history_sidebar']; + String lookup(String key) { final lookupKey = toSnakeCase(key); return _localizedValues[localeCode][lookupKey] ??