Add sidebar/fullwidth editor toggle

This commit is contained in:
Hillel Coren 2020-12-07 10:30:20 +02:00
parent ed434db90e
commit fb36a7ef33
18 changed files with 128 additions and 53 deletions

View File

@ -460,6 +460,8 @@ Serializers _$serializers = (new Serializers().toBuilder()
const FullType(BuiltList, const [const FullType(String)])
]),
() => new MapBuilder<EntityType, BuiltList<String>>())
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(EntityType), const FullType(bool)]), () => new MapBuilder<EntityType, bool>())
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(String), const FullType(CompanyPrefState)]), () => new MapBuilder<String, CompanyPrefState>())
..addBuilderFactory(
const FullType(BuiltMap, const [
const FullType(String),
@ -477,7 +479,6 @@ Serializers _$serializers = (new Serializers().toBuilder()
..addBuilderFactory(const FullType(BuiltList, const [const FullType(String)]), () => new ListBuilder<String>())
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(String), const FullType(CompanyGatewayEntity)]), () => new MapBuilder<String, CompanyGatewayEntity>())
..addBuilderFactory(const FullType(BuiltList, const [const FullType(String)]), () => new ListBuilder<String>())
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(String), const FullType(CompanyPrefState)]), () => new MapBuilder<String, CompanyPrefState>())
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(String), const FullType(CurrencyEntity)]), () => new MapBuilder<String, CurrencyEntity>())
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(String), const FullType(SizeEntity)]), () => new MapBuilder<String, SizeEntity>())
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(String), const FullType(GatewayEntity)]), () => new MapBuilder<String, GatewayEntity>())

View File

@ -105,6 +105,12 @@ class LoadStaticSuccess implements PersistStatic {
final StaticDataEntity data;
}
class ToggleEditorLayout implements PersistPrefs {
ToggleEditorLayout(this.entityType);
final EntityType entityType;
}
class UpdateUserPreferences implements PersistPrefs {
UpdateUserPreferences({
this.appLayout,

View File

@ -283,7 +283,6 @@ Middleware<AppState> _createLoadState(
completer.future.then((_) {
final layout = calculateLayout(action.context);
if (store.state.prefState.isNotMobile && layout == AppLayout.mobile) {
print('## View dashboard');
store.dispatch(UpdateUserPreferences(appLayout: layout));
AppBuilder.of(action.context).rebuild();
WidgetsBinding.instance.addPostFrameCallback((duration) {
@ -320,7 +319,7 @@ List<String> _getRoutes(AppState state) {
.forEach((part) {
if (part == 'edit') {
// Only restore new unsaved entities to prevent conflicts
final bool isNew = state.getUIState(entityType).isCreatingNew;
final bool isNew = state.getUIState(entityType)?.isCreatingNew ?? false;
if (isNew) {
route += '/edit';
} else if (entityType != EntityType.product) {

View File

@ -196,6 +196,7 @@ Middleware<AppState> _createRefreshRequest(AuthRepository repository) {
if (state.isSaving ||
state.isLoading ||
(state.company.isLarge && !state.isLoaded)) {
print('Skipping refresh request');
next(action);
return;
}

View File

@ -24,21 +24,13 @@ import 'package:invoiceninja_flutter/redux/vendor/vendor_actions.dart';
// STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/task_status/task_status_actions.dart';
import 'package:invoiceninja_flutter/redux/expense_category/expense_category_actions.dart';
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_actions.dart';
import 'package:invoiceninja_flutter/redux/webhook/webhook_actions.dart';
import 'package:invoiceninja_flutter/redux/token/token_actions.dart';
import 'package:invoiceninja_flutter/redux/payment_term/payment_term_actions.dart';
import 'package:invoiceninja_flutter/redux/design/design_actions.dart';
import 'package:invoiceninja_flutter/redux/credit/credit_actions.dart';
import 'package:redux/redux.dart';
PrefState prefReducer(
@ -63,10 +55,23 @@ PrefState prefReducer(
..longPressSelectionIsDefault =
longPressReducer(state.longPressSelectionIsDefault, action)
..requireAuthentication =
requireAuthenticationReducer(state.requireAuthentication, action),
requireAuthenticationReducer(state.requireAuthentication, action)
..useSidebarEditor
.replace(sidebarEditorReducer(state.useSidebarEditor, action)),
);
}
Reducer<BuiltMap<EntityType, bool>> sidebarEditorReducer = combineReducers([
TypedReducer<BuiltMap<EntityType, bool>, ToggleEditorLayout>((value, action) {
final entityType = action.entityType;
if (value.containsKey(entityType)) {
return value.rebuild((b) => b..[entityType] = !value[entityType]);
} else {
return value.rebuild((b) => b..[entityType] = true);
}
}),
]);
Reducer<bool> menuVisibleReducer = combineReducers([
TypedReducer<bool, UpdateUserPreferences>((value, action) {
return action.sidebar == AppSidebar.menu ? !value : value;

View File

@ -12,6 +12,7 @@ abstract class PrefState implements Built<PrefState, PrefStateBuilder> {
appLayout: AppLayout.desktop,
moduleLayout: ModuleLayout.table,
isPreviewVisible: true,
useSidebarEditor: BuiltMap<EntityType, bool>(),
menuSidebarMode: AppSidebarMode.collapse,
historySidebarMode: AppSidebarMode.float,
rowsPerPage: 10,
@ -39,6 +40,8 @@ abstract class PrefState implements Built<PrefState, PrefStateBuilder> {
AppSidebarMode get historySidebarMode;
BuiltMap<EntityType, bool> get useSidebarEditor;
bool get isPreviewVisible;
bool get isMenuVisible;
@ -59,6 +62,9 @@ abstract class PrefState implements Built<PrefState, PrefStateBuilder> {
bool get isDesktop => appLayout == AppLayout.desktop;
bool isEditorFullScreen(EntityType entityType) =>
isDesktop && (useSidebarEditor[entityType] ?? true);
bool get isNotDesktop => !isDesktop;
bool get isMobile => appLayout == AppLayout.mobile;
@ -88,6 +94,10 @@ abstract class PrefState implements Built<PrefState, PrefStateBuilder> {
menuSidebarMode == AppSidebarMode.collapse &&
!isMenuVisible;
// ignore: unused_element
static void _initializeBuilder(PrefStateBuilder builder) =>
builder..useSidebarEditor.replace(BuiltMap<EntityType, bool>());
static Serializer<PrefState> get serializer => _$prefStateSerializer;
}

View File

@ -123,6 +123,10 @@ class _$PrefStateSerializer implements StructuredSerializer<PrefState> {
'historySidebarMode',
serializers.serialize(object.historySidebarMode,
specifiedType: const FullType(AppSidebarMode)),
'useSidebarEditor',
serializers.serialize(object.useSidebarEditor,
specifiedType: const FullType(BuiltMap,
const [const FullType(EntityType), const FullType(bool)])),
'isPreviewVisible',
serializers.serialize(object.isPreviewVisible,
specifiedType: const FullType(bool)),
@ -185,6 +189,11 @@ class _$PrefStateSerializer implements StructuredSerializer<PrefState> {
result.historySidebarMode = serializers.deserialize(value,
specifiedType: const FullType(AppSidebarMode)) as AppSidebarMode;
break;
case 'useSidebarEditor':
result.useSidebarEditor.replace(serializers.deserialize(value,
specifiedType: const FullType(BuiltMap,
const [const FullType(EntityType), const FullType(bool)])));
break;
case 'isPreviewVisible':
result.isPreviewVisible = serializers.deserialize(value,
specifiedType: const FullType(bool)) as bool;
@ -412,6 +421,8 @@ class _$PrefState extends PrefState {
@override
final AppSidebarMode historySidebarMode;
@override
final BuiltMap<EntityType, bool> useSidebarEditor;
@override
final bool isPreviewVisible;
@override
final bool isMenuVisible;
@ -438,6 +449,7 @@ class _$PrefState extends PrefState {
this.moduleLayout,
this.menuSidebarMode,
this.historySidebarMode,
this.useSidebarEditor,
this.isPreviewVisible,
this.isMenuVisible,
this.isHistoryVisible,
@ -460,6 +472,9 @@ class _$PrefState extends PrefState {
if (historySidebarMode == null) {
throw new BuiltValueNullFieldError('PrefState', 'historySidebarMode');
}
if (useSidebarEditor == null) {
throw new BuiltValueNullFieldError('PrefState', 'useSidebarEditor');
}
if (isPreviewVisible == null) {
throw new BuiltValueNullFieldError('PrefState', 'isPreviewVisible');
}
@ -505,6 +520,7 @@ class _$PrefState extends PrefState {
moduleLayout == other.moduleLayout &&
menuSidebarMode == other.menuSidebarMode &&
historySidebarMode == other.historySidebarMode &&
useSidebarEditor == other.useSidebarEditor &&
isPreviewVisible == other.isPreviewVisible &&
isMenuVisible == other.isMenuVisible &&
isHistoryVisible == other.isHistoryVisible &&
@ -530,10 +546,14 @@ class _$PrefState extends PrefState {
$jc(
$jc(
$jc(
$jc($jc(0, appLayout.hashCode),
moduleLayout.hashCode),
menuSidebarMode.hashCode),
historySidebarMode.hashCode),
$jc(
$jc(
$jc(0,
appLayout.hashCode),
moduleLayout.hashCode),
menuSidebarMode.hashCode),
historySidebarMode.hashCode),
useSidebarEditor.hashCode),
isPreviewVisible.hashCode),
isMenuVisible.hashCode),
isHistoryVisible.hashCode),
@ -552,6 +572,7 @@ class _$PrefState extends PrefState {
..add('moduleLayout', moduleLayout)
..add('menuSidebarMode', menuSidebarMode)
..add('historySidebarMode', historySidebarMode)
..add('useSidebarEditor', useSidebarEditor)
..add('isPreviewVisible', isPreviewVisible)
..add('isMenuVisible', isMenuVisible)
..add('isHistoryVisible', isHistoryVisible)
@ -587,6 +608,12 @@ class PrefStateBuilder implements Builder<PrefState, PrefStateBuilder> {
set historySidebarMode(AppSidebarMode historySidebarMode) =>
_$this._historySidebarMode = historySidebarMode;
MapBuilder<EntityType, bool> _useSidebarEditor;
MapBuilder<EntityType, bool> get useSidebarEditor =>
_$this._useSidebarEditor ??= new MapBuilder<EntityType, bool>();
set useSidebarEditor(MapBuilder<EntityType, bool> useSidebarEditor) =>
_$this._useSidebarEditor = useSidebarEditor;
bool _isPreviewVisible;
bool get isPreviewVisible => _$this._isPreviewVisible;
set isPreviewVisible(bool isPreviewVisible) =>
@ -632,7 +659,9 @@ class PrefStateBuilder implements Builder<PrefState, PrefStateBuilder> {
set companyPrefs(MapBuilder<String, CompanyPrefState> companyPrefs) =>
_$this._companyPrefs = companyPrefs;
PrefStateBuilder();
PrefStateBuilder() {
PrefState._initializeBuilder(this);
}
PrefStateBuilder get _$this {
if (_$v != null) {
@ -640,6 +669,7 @@ class PrefStateBuilder implements Builder<PrefState, PrefStateBuilder> {
_moduleLayout = _$v.moduleLayout;
_menuSidebarMode = _$v.menuSidebarMode;
_historySidebarMode = _$v.historySidebarMode;
_useSidebarEditor = _$v.useSidebarEditor?.toBuilder();
_isPreviewVisible = _$v.isPreviewVisible;
_isMenuVisible = _$v.isMenuVisible;
_isHistoryVisible = _$v.isHistoryVisible;
@ -677,6 +707,7 @@ class PrefStateBuilder implements Builder<PrefState, PrefStateBuilder> {
moduleLayout: moduleLayout,
menuSidebarMode: menuSidebarMode,
historySidebarMode: historySidebarMode,
useSidebarEditor: useSidebarEditor.build(),
isPreviewVisible: isPreviewVisible,
isMenuVisible: isMenuVisible,
isHistoryVisible: isHistoryVisible,
@ -689,6 +720,9 @@ class PrefStateBuilder implements Builder<PrefState, PrefStateBuilder> {
} catch (_) {
String _$failedField;
try {
_$failedField = 'useSidebarEditor';
useSidebarEditor.build();
_$failedField = 'companyPrefs';
companyPrefs.build();
} catch (e) {

View File

@ -75,13 +75,16 @@ class MainScreen extends StatelessWidget {
final isEmail = subRoute == '/email';
if ([
InvoiceScreen.route,
QuoteScreen.route,
CreditScreen.route,
RecurringInvoiceScreen.route,
].contains(mainRoute) &&
(isEdit || isEmail)) {
isFullScreen = true;
InvoiceScreen.route,
QuoteScreen.route,
CreditScreen.route,
RecurringInvoiceScreen.route,
].contains(mainRoute)) {
if (isEmail) {
isFullScreen = true;
} else if (isEdit) {
isFullScreen = prefState.isEditorFullScreen(EntityType.invoice);
}
}
if (DesignEditScreen.route == uiState.currentRoute) {
isFullScreen = true;

View File

@ -30,7 +30,7 @@ class CreditEditDetailsScreen extends StatelessWidget {
return CreditEditDetailsVM.fromStore(store);
},
builder: (context, viewModel) {
if (viewModel.state.prefState.isDesktop) {
if (viewModel.state.prefState.isEditorFullScreen(EntityType.invoice)) {
return InvoiceEditDesktop(
viewModel: viewModel,
entityViewModel: this.viewModel,

View File

@ -27,7 +27,7 @@ class CreditEditItemsScreen extends StatelessWidget {
return CreditEditItemsVM.fromStore(store, isTasks);
},
builder: (context, viewModel) {
if (viewModel.state.prefState.isDesktop) {
if (viewModel.state.prefState.isEditorFullScreen(EntityType.invoice)) {
return InvoiceEditItemsDesktop(
viewModel: viewModel,
entityViewModel: this.viewModel,

View File

@ -1,5 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/data/models/entities.dart';
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_contacts_vm.dart';
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_details_vm.dart';
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_footer.dart';
@ -9,7 +10,6 @@ import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_item_selector.dart';
import 'package:invoiceninja_flutter/ui/app/edit_scaffold.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/utils/platforms.dart';
class InvoiceEdit extends StatefulWidget {
const InvoiceEdit({
@ -69,9 +69,11 @@ class _InvoiceEditState extends State<InvoiceEdit>
final viewModel = widget.viewModel;
final invoice = viewModel.invoice;
final state = viewModel.state;
final prefState = state.prefState;
final isFullscreen = prefState.isEditorFullScreen(EntityType.invoice);
return EditScaffold(
isFullscreen: state.prefState.isDesktop,
isFullscreen: isFullscreen,
entity: invoice,
title: invoice.isNew ? localization.newInvoice : localization.editInvoice,
onCancelPressed: (context) => viewModel.onCancelPressed(context),
@ -90,7 +92,7 @@ class _InvoiceEditState extends State<InvoiceEdit>
viewModel.onSavePressed(context);
},
appBarBottom: state.prefState.isDesktop
appBarBottom: isFullscreen
? null
: TabBar(
controller: _controller,
@ -112,7 +114,7 @@ class _InvoiceEditState extends State<InvoiceEdit>
),
body: Form(
key: _formKey,
child: state.prefState.isDesktop
child: isFullscreen
? InvoiceEditDetailsScreen(
viewModel: widget.viewModel,
)
@ -152,7 +154,7 @@ class _InvoiceEditState extends State<InvoiceEdit>
clientId: invoice.clientId,
onItemsSelected: (items, [clientId]) {
viewModel.onItemsAdded(items, clientId);
if (isNotDesktop(context)) {
if (!isFullscreen) {
_controller.animateTo(kItemScreen);
}
},

View File

@ -28,7 +28,7 @@ class InvoiceEditDetailsScreen extends StatelessWidget {
return InvoiceEditDetailsVM.fromStore(store);
},
builder: (context, viewModel) {
if (viewModel.state.prefState.isDesktop) {
if (viewModel.state.prefState.isEditorFullScreen(EntityType.invoice)) {
return InvoiceEditDesktop(
viewModel: viewModel,
entityViewModel: this.viewModel,

View File

@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/data/models/entities.dart';
import 'package:invoiceninja_flutter/data/models/invoice_model.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/invoice/invoice_selectors.dart';
import 'package:invoiceninja_flutter/ui/app/app_border.dart';
@ -20,31 +22,43 @@ class InvoiceEditFooter extends StatelessWidget {
final state = store.state;
final total = formatNumber(
invoice.calculateTotal(precision: precisionForInvoice(state, invoice)),
context, clientId: invoice.clientId);
context,
clientId: invoice.clientId);
return BottomAppBar(
color: Theme
.of(context)
.cardColor,
color: Theme.of(context).cardColor,
shape: CircularNotchedRectangle(),
child: SizedBox(
height: kTopBottomBarHeight,
child: AppBorder(
isTop: true,
child: Padding(
padding: const EdgeInsets.only(left: 16),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
'${localization.lookup('${invoice.entityType}_total')}: $total',
style: TextStyle(
color: state.prefState.enableDarkMode
? Colors.white
: Colors.black,
fontSize: 20.0,
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
InkWell(
onTap: () =>
store.dispatch(ToggleEditorLayout(EntityType.invoice)),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Icon(Icons.view_sidebar),
),
),
),
AppBorder(
isLeft: true,
child: Padding(
padding: const EdgeInsets.only(left: 16, top: 8),
child: Text(
'${localization.lookup('${invoice.entityType}_total')}: $total',
style: TextStyle(
color: state.prefState.enableDarkMode
? Colors.white
: Colors.black,
fontSize: 20.0,
),
),
),
),
],
),
),
),

View File

@ -26,7 +26,7 @@ class InvoiceEditItemsScreen extends StatelessWidget {
return InvoiceEditItemsVM.fromStore(store, isTasks);
},
builder: (context, viewModel) {
if (viewModel.state.prefState.isDesktop) {
if (viewModel.state.prefState.isEditorFullScreen(EntityType.invoice)) {
return InvoiceEditItemsDesktop(
viewModel: viewModel,
entityViewModel: this.viewModel,

View File

@ -29,7 +29,7 @@ class QuoteEditDetailsScreen extends StatelessWidget {
return QuoteEditDetailsVM.fromStore(store);
},
builder: (context, viewModel) {
if (viewModel.state.prefState.isDesktop) {
if (viewModel.state.prefState.isEditorFullScreen(EntityType.invoice)) {
return InvoiceEditDesktop(
viewModel: viewModel,
entityViewModel: this.viewModel,

View File

@ -25,7 +25,7 @@ class QuoteEditItemsScreen extends StatelessWidget {
return QuoteEditItemsVM.fromStore(store);
},
builder: (context, viewModel) {
if (viewModel.state.prefState.isDesktop) {
if (viewModel.state.prefState.isEditorFullScreen(EntityType.invoice)) {
return InvoiceEditItemsDesktop(
viewModel: viewModel,
entityViewModel: this.viewModel,

View File

@ -30,7 +30,7 @@ class RecurringInvoiceEditDetailsScreen extends StatelessWidget {
return RecurringInvoiceEditDetailsVM.fromStore(store);
},
builder: (context, viewModel) {
if (viewModel.state.prefState.isDesktop) {
if (viewModel.state.prefState.isEditorFullScreen(EntityType.invoice)) {
return InvoiceEditDesktop(
viewModel: viewModel,
entityViewModel: this.viewModel,

View File

@ -27,7 +27,7 @@ class RecurringInvoiceEditItemsScreen extends StatelessWidget {
return RecurringInvoiceEditItemsVM.fromStore(store, isTasks);
},
builder: (context, viewModel) {
if (viewModel.state.prefState.isDesktop) {
if (viewModel.state.prefState.isEditorFullScreen(EntityType.invoice)) {
return InvoiceEditItemsDesktop(
viewModel: viewModel,
entityViewModel: this.viewModel,