From dab542e652c4c10b49eddbe43e256d7d422686e1 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 31 Oct 2019 12:54:29 +0200 Subject: [PATCH] Settings --- lib/redux/payment/payment_selectors.dart | 43 ++++++++++++ lib/redux/quote/quote_selectors.dart | 38 +++++++++++ lib/ui/settings/settings_list.dart | 4 +- lib/ui/settings/templates_and_reminders.dart | 68 +++++++++++++++++-- .../settings/templates_and_reminders_vm.dart | 41 +++++++++-- lib/ui/user/view/user_view.dart | 5 +- lib/utils/i18n.dart | 2 + 7 files changed, 189 insertions(+), 12 deletions(-) diff --git a/lib/redux/payment/payment_selectors.dart b/lib/redux/payment/payment_selectors.dart index 9c0d6c01b..c0bea3c6e 100644 --- a/lib/redux/payment/payment_selectors.dart +++ b/lib/redux/payment/payment_selectors.dart @@ -141,6 +141,49 @@ String paymentStatsForClient( return str; } +var memoizedPaymentStatsForUser = memo5((String userId, + BuiltMap paymentMap, + BuiltMap invoiceMap, + String activeLabel, + String archivedLabel) => + paymentStatsForClient( + userId, paymentMap, invoiceMap, activeLabel, archivedLabel)); + +String paymentStatsForUser( + String userId, + BuiltMap paymentMap, + BuiltMap invoiceMap, + String activeLabel, + String archivedLabel) { + int countActive = 0; + int countArchived = 0; + paymentMap.forEach((paymentId, payment) { + if (invoiceMap.containsKey(payment.invoiceId)) + // TODO enable this code + //&& invoiceMap[payment.invoiceId].userId == userId) + { + if (payment.isActive) { + countActive++; + } else if (payment.isArchived) { + countArchived++; + } + } + }); + + String str = ''; + if (countActive > 0) { + str = '$countActive $activeLabel'; + if (countArchived > 0) { + str += ' • '; + } + } + if (countArchived > 0) { + str += '$countArchived $archivedLabel'; + } + + return str; +} + bool hasPaymentChanges( PaymentEntity payment, BuiltMap paymentMap) => payment.isNew ? payment.isChanged : payment != paymentMap[payment.id]; diff --git a/lib/redux/quote/quote_selectors.dart b/lib/redux/quote/quote_selectors.dart index 2c058051b..e0c54291f 100644 --- a/lib/redux/quote/quote_selectors.dart +++ b/lib/redux/quote/quote_selectors.dart @@ -98,6 +98,44 @@ String quoteStatsForClient( return str; } +var memoizedQuoteStatsForUser = memo4((String userId, + BuiltMap quoteMap, + String activeLabel, + String archivedLabel) => + quoteStatsForUser(userId, quoteMap, activeLabel, archivedLabel)); + +String quoteStatsForUser( + String userId, + BuiltMap quoteMap, + String activeLabel, + String archivedLabel) { + int countActive = 0; + int countArchived = 0; + quoteMap.forEach((quoteId, quote) { + // TODO change to user id match + if (quote.clientId == userId) { + if (quote.isActive) { + countActive++; + } else if (quote.isArchived) { + countArchived++; + } + } + }); + + String str = ''; + if (countActive > 0) { + str = '$countActive $activeLabel'; + if (countArchived > 0) { + str += ' • '; + } + } + if (countArchived > 0) { + str += '$countArchived $archivedLabel'; + } + + return str; +} + bool hasQuoteChanges( InvoiceEntity quote, BuiltMap quoteMap) => quote.isNew ? quote.isChanged : quote != quoteMap[quote.id]; diff --git a/lib/ui/settings/settings_list.dart b/lib/ui/settings/settings_list.dart index 73b663d70..23d58c37e 100644 --- a/lib/ui/settings/settings_list.dart +++ b/lib/ui/settings/settings_list.dart @@ -158,12 +158,12 @@ class SettingsList extends StatelessWidget { viewModel: viewModel, icon: FontAwesomeIcons.solidEnvelope, ), - /* SettingsListTile( section: kSettingsTemplatesAndReminders, viewModel: viewModel, - icon: FontAwesomeIcons.solidClock, + icon: FontAwesomeIcons.file, ), + /* SettingsListTile( section: kSettingsCreditCardsAndBanks, viewModel: viewModel, diff --git a/lib/ui/settings/templates_and_reminders.dart b/lib/ui/settings/templates_and_reminders.dart index 1c222a8cf..ade2f24b5 100644 --- a/lib/ui/settings/templates_and_reminders.dart +++ b/lib/ui/settings/templates_and_reminders.dart @@ -1,8 +1,10 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:invoiceninja_flutter/ui/app/forms/app_form.dart'; import 'package:invoiceninja_flutter/ui/settings/settings_scaffold.dart'; import 'package:invoiceninja_flutter/ui/settings/templates_and_reminders_vm.dart'; import 'package:invoiceninja_flutter/utils/localization.dart'; +import 'package:invoiceninja_flutter/utils/platforms.dart'; class TemplatesAndReminders extends StatefulWidget { const TemplatesAndReminders({ @@ -18,8 +20,9 @@ class TemplatesAndReminders extends StatefulWidget { class _TemplatesAndRemindersState extends State with SingleTickerProviderStateMixin { - //static final GlobalKey _formKey = GlobalKey(); + static final GlobalKey _formKey = GlobalKey(); + FocusScopeNode _focusNode; TabController _controller; bool autoValidate = false; @@ -31,11 +34,13 @@ class _TemplatesAndRemindersState extends State @override void initState() { super.initState(); - _controller = TabController(vsync: this, length: 3); + _focusNode = FocusScopeNode(); + _controller = TabController(vsync: this, length: 7); } @override void dispose() { + _focusNode.dispose(); _controller.dispose(); _controllers.forEach((dynamic controller) { controller.removeListener(_onChanged); @@ -75,12 +80,65 @@ class _TemplatesAndRemindersState extends State @override Widget build(BuildContext context) { final localization = AppLocalization.of(context); - //final viewModel = widget.viewModel; + final viewModel = widget.viewModel; + final state = viewModel.state; return SettingsScaffold( title: localization.templatesAndReminders, - onSavePressed: null, - body: SizedBox(), + onSavePressed: viewModel.onSavePressed, + appBarBottom: TabBar( + key: ValueKey(state.settingsUIState.updatedAt), + controller: _controller, + isScrollable: true, + tabs: [ + Tab( + text: localization.invoices, + ), + Tab( + text: localization.quotes, + ), + Tab( + text: localization.payments, + ), + Tab( + text: localization.firstReminder, + ), + Tab( + text: localization.secondReminder, + ), + Tab( + text: localization.thirdReminder, + ), + Tab( + text: localization.endlessReminder, + ), + ], + ), + body: AppTabForm( + tabController: _controller, + formKey: _formKey, + focusNode: _focusNode, + children: [ + TemplateEditor(), + TemplateEditor(), + TemplateEditor(), + TemplateEditor(), + TemplateEditor(), + TemplateEditor(), + TemplateEditor(), + ], + ), + ); + } +} + +class TemplateEditor extends StatelessWidget { + @override + Widget build(BuildContext context) { + return ListView( + children: [ + Text('test') + ], ); } } diff --git a/lib/ui/settings/templates_and_reminders_vm.dart b/lib/ui/settings/templates_and_reminders_vm.dart index 44605a810..526632d06 100644 --- a/lib/ui/settings/templates_and_reminders_vm.dart +++ b/lib/ui/settings/templates_and_reminders_vm.dart @@ -2,7 +2,14 @@ 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/data/models/company_model.dart'; +import 'package:invoiceninja_flutter/data/models/entities.dart'; +import 'package:invoiceninja_flutter/redux/client/client_actions.dart'; +import 'package:invoiceninja_flutter/redux/group/group_actions.dart'; +import 'package:invoiceninja_flutter/redux/settings/settings_actions.dart'; import 'package:invoiceninja_flutter/ui/settings/templates_and_reminders.dart'; +import 'package:invoiceninja_flutter/utils/completers.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:redux/redux.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart'; @@ -27,6 +34,8 @@ class TemplatesAndRemindersScreen extends StatelessWidget { class TemplatesAndRemindersVM { TemplatesAndRemindersVM({ @required this.state, + @required this.settings, + @required this.onSettingsChanged, @required this.onSavePressed, @required this.onCancelPressed, }); @@ -35,13 +44,37 @@ class TemplatesAndRemindersVM { final state = store.state; return TemplatesAndRemindersVM( - state: state, - onSavePressed: null, - onCancelPressed: null, - ); + state: state, + settings: state.uiState.settingsUIState.settings, + onSettingsChanged: (settings) { + store.dispatch(UpdateSettings(settings: settings)); + }, + onCancelPressed: (context) => store.dispatch(ResetSettings()), + onSavePressed: (context) { + final settingsUIState = state.uiState.settingsUIState; + final completer = snackBarCompleter( + context, AppLocalization.of(context).savedSettings); + switch (settingsUIState.entityType) { + case EntityType.company: + store.dispatch(SaveCompanyRequest( + completer: completer, + company: settingsUIState.userCompany.company)); + break; + case EntityType.group: + store.dispatch(SaveGroupRequest( + completer: completer, group: settingsUIState.group)); + break; + case EntityType.client: + store.dispatch(SaveClientRequest( + completer: completer, client: settingsUIState.client)); + break; + } + }); } final AppState state; + final SettingsEntity settings; + final Function(SettingsEntity) onSettingsChanged; final Function(BuildContext) onSavePressed; final Function(BuildContext) onCancelPressed; } diff --git a/lib/ui/user/view/user_view.dart b/lib/ui/user/view/user_view.dart index 1e98d12a6..9c21831a3 100644 --- a/lib/ui/user/view/user_view.dart +++ b/lib/ui/user/view/user_view.dart @@ -2,6 +2,8 @@ import 'package:flutter_redux/flutter_redux.dart'; import 'package:invoiceninja_flutter/data/models/entities.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/invoice/invoice_selectors.dart'; +import 'package:invoiceninja_flutter/redux/payment/payment_selectors.dart'; +import 'package:invoiceninja_flutter/redux/quote/quote_selectors.dart'; import 'package:invoiceninja_flutter/ui/app/actions_menu_button.dart'; import 'package:invoiceninja_flutter/ui/app/buttons/edit_icon_button.dart'; import 'package:invoiceninja_flutter/ui/app/entities/entity_state_title.dart'; @@ -29,6 +31,7 @@ class UserView extends StatelessWidget { final localization = AppLocalization.of(context); final user = viewModel.user; final state = StoreProvider.of(context).state; + final company = state.selectedCompany; final userCompany = state.userCompany; return Scaffold( @@ -100,7 +103,6 @@ class UserView extends StatelessWidget { localization.active, localization.archived), ), - /* EntityListTile( bottomPadding: 1, icon: getEntityIcon(EntityType.payment), @@ -131,6 +133,7 @@ class UserView extends StatelessWidget { localization.archived), ) : Container(), + /* company.isModuleEnabled(EntityType.project) ? EntityListTile( bottomPadding: 1, diff --git a/lib/utils/i18n.dart b/lib/utils/i18n.dart index ed6a73ea1..5469431c2 100644 --- a/lib/utils/i18n.dart +++ b/lib/utils/i18n.dart @@ -14,6 +14,7 @@ abstract class LocaleCodeAware { mixin LocalizationsProvider on LocaleCodeAware { static final Map> _localizedValues = { 'en': { + 'endless_reminder': 'Endless Reminder', 'filtered_by_user': 'Filtered by User', 'administrator': 'Administrator', 'administrator_help': 'Allow user to manage users, change settings and modify all records', @@ -15404,6 +15405,7 @@ mixin LocalizationsProvider on LocaleCodeAware { String get filteredByUser => _localizedValues[localeCode]['filtered_by_user']; + String get endlessReminder => _localizedValues[localeCode]['endless_reminder']; String lookup(String key) { final lookupKey = toSnakeCase(key);