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/constants.dart'; import 'package:invoiceninja_flutter/data/models/account_model.dart'; import 'package:invoiceninja_flutter/data/models/models.dart'; import 'package:invoiceninja_flutter/redux/auth/auth_state.dart'; import 'package:invoiceninja_flutter/redux/client/client_selectors.dart'; import 'package:invoiceninja_flutter/redux/client/client_state.dart'; import 'package:invoiceninja_flutter/redux/company/company_state.dart'; import 'package:invoiceninja_flutter/redux/company_gateway/company_gateway_selectors.dart'; import 'package:invoiceninja_flutter/redux/credit/credit_selectors.dart'; import 'package:invoiceninja_flutter/redux/dashboard/dashboard_state.dart'; import 'package:invoiceninja_flutter/redux/design/design_selectors.dart'; import 'package:invoiceninja_flutter/redux/design/design_state.dart'; import 'package:invoiceninja_flutter/redux/document/document_state.dart'; import 'package:invoiceninja_flutter/redux/expense/expense_selectors.dart'; import 'package:invoiceninja_flutter/redux/expense/expense_state.dart'; import 'package:invoiceninja_flutter/redux/group/group_selectors.dart'; import 'package:invoiceninja_flutter/redux/invoice/invoice_selectors.dart'; import 'package:invoiceninja_flutter/redux/invoice/invoice_state.dart'; import 'package:invoiceninja_flutter/redux/payment/payment_selectors.dart'; import 'package:invoiceninja_flutter/redux/payment/payment_state.dart'; import 'package:invoiceninja_flutter/redux/product/product_selectors.dart'; import 'package:invoiceninja_flutter/redux/product/product_state.dart'; import 'package:invoiceninja_flutter/redux/project/project_selectors.dart'; import 'package:invoiceninja_flutter/redux/project/project_state.dart'; import 'package:invoiceninja_flutter/redux/quote/quote_selectors.dart'; import 'package:invoiceninja_flutter/redux/quote/quote_state.dart'; import 'package:invoiceninja_flutter/redux/static/static_state.dart'; import 'package:invoiceninja_flutter/redux/task/task_selectors.dart'; import 'package:invoiceninja_flutter/redux/task/task_state.dart'; import 'package:invoiceninja_flutter/redux/tax_rate/tax_rate_selectors.dart'; import 'package:invoiceninja_flutter/redux/ui/entity_ui_state.dart'; import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart'; import 'package:invoiceninja_flutter/redux/ui/pref_state.dart'; import 'package:invoiceninja_flutter/redux/ui/ui_state.dart'; import 'package:invoiceninja_flutter/redux/vendor/vendor_selectors.dart'; import 'package:invoiceninja_flutter/redux/vendor/vendor_state.dart'; import 'package:invoiceninja_flutter/ui/app/screen_imports.dart'; import 'package:invoiceninja_flutter/ui/client/edit/client_edit_vm.dart'; import 'package:invoiceninja_flutter/ui/company_gateway/edit/company_gateway_edit_vm.dart'; import 'package:invoiceninja_flutter/ui/credit/edit/credit_edit_vm.dart'; import 'package:invoiceninja_flutter/ui/design/edit/design_edit_vm.dart'; import 'package:invoiceninja_flutter/ui/group/edit/group_edit_vm.dart'; import 'package:invoiceninja_flutter/ui/product/edit/product_edit_vm.dart'; // STARTER: import - do not remove comment import 'package:invoiceninja_flutter/redux/webhook/webhook_state.dart'; import 'package:invoiceninja_flutter/ui/webhook/edit/webhook_edit_vm.dart'; import 'package:invoiceninja_flutter/redux/webhook/webhook_state.dart'; import 'package:invoiceninja_flutter/ui/webhook/edit/webhook_edit_vm.dart'; import 'package:invoiceninja_flutter/redux/webhook/webhook_selectors.dart'; import 'package:invoiceninja_flutter/redux/token/token_state.dart'; import 'package:invoiceninja_flutter/ui/token/edit/token_edit_vm.dart'; import 'package:invoiceninja_flutter/redux/token/token_selectors.dart'; import 'package:invoiceninja_flutter/redux/payment_term/payment_term_state.dart'; import 'package:invoiceninja_flutter/ui/payment_term/edit/payment_term_edit_vm.dart'; import 'package:invoiceninja_flutter/redux/payment_term/payment_term_selectors.dart'; import 'package:invoiceninja_flutter/redux/credit/credit_state.dart'; import 'package:invoiceninja_flutter/redux/user/user_state.dart'; import 'package:invoiceninja_flutter/redux/tax_rate/tax_rate_state.dart'; import 'package:invoiceninja_flutter/redux/company_gateway/company_gateway_state.dart'; import 'package:invoiceninja_flutter/redux/group/group_state.dart'; import 'package:invoiceninja_flutter/ui/tax_rate/edit/tax_rate_edit_vm.dart'; part 'app_state.g.dart'; abstract class AppState implements Built { factory AppState({ @required PrefState prefState, String currentRoute, String url, }) { return _$AppState._( isLoading: false, isSaving: false, isTesting: false, lastError: '', authState: AuthState(url: url), staticState: StaticState(), userCompanyStates: BuiltList( List.generate(kMaxNumberOfCompanies, (i) => i + 1) .map((index) => UserCompanyState()) .toList()), uiState: UIState(currentRoute: currentRoute), prefState: prefState ?? PrefState(), ); } AppState._(); @override @memoized int get hashCode; bool get isLoading; bool get isSaving; bool get isTesting; String get lastError; AuthState get authState; StaticState get staticState; PrefState get prefState; UIState get uiState; BuiltList get userCompanyStates; //factory AppState([void updates(AppStateBuilder b)]) = _$AppState; static Serializer get serializer => _$appStateSerializer; UserCompanyState get userCompanyState => userCompanyStates[uiState.selectedCompanyIndex]; bool get isLoaded { return productState.isLoaded && clientState.isLoaded; } AccountEntity get account => userCompany.account; CompanyEntity get company => userCompanyState.company; List get companies { final List list = []; for (var companyState in userCompanyStates) { if (companyState.company != null) { list.add(companyState.company); } } return list .where((CompanyEntity company) => (company.id ?? '').isNotEmpty) .toList(); } DashboardUIState get dashboardUIState => uiState.dashboardUIState; UserEntity get user => userCompanyState.user; UserCompanyEntity get userCompany => userCompanyState.userCompany; Credentials get credentials => Credentials(token: userCompanyState.token.token, url: authState.url); String get accentColor => user?.userCompany?.settings?.accentColor ?? kDefaultAccentColor; String get appVersion => '${account.currentVersion}-${kClientVersion.split('.').last}'; List get historyList => prefState.companyPrefs[uiState.selectedCompanyIndex].historyList .where((history) { final entityMap = getEntityMap(history.entityType); if (entityMap != null) { final entity = entityMap[history.id] as BaseEntity; if (entity?.isDeleted == true) { return false; } } return true; }).toList(); List get unfilteredHistoryList => prefState.companyPrefs[uiState.selectedCompanyIndex].historyList.toList(); bool shouldSelectEntity({EntityType entityType, List entityList}) { final entityUIState = getUIState(entityType); if (prefState.isMobile || uiState.isEditing || entityType.isSetting || entityList.isEmpty) { return false; } if ((entityUIState.selectedId ?? '').isEmpty) { return true; } else if (unfilteredHistoryList.isNotEmpty && unfilteredHistoryList.first.entityType != entityType) { // check if this needs to be added to the history return null; } return false; } // TODO add to starter.sh bool get isDataLoaded { if (clientState.lastUpdated == 0 || productState.lastUpdated == 0 || invoiceState.lastUpdated == 0 || paymentState.lastUpdated == 0 || quoteState.lastUpdated == 0 || creditState.lastUpdated == 0) { return false; } return true; } BuiltMap getEntityMap(EntityType type) { switch (type) { case EntityType.product: return productState.map; case EntityType.client: return clientState.map; case EntityType.invoice: return invoiceState.map; // STARTER: states switch map - do not remove comment case EntityType.webhook: return webhookState.map; case EntityType.token: return tokenState.map; case EntityType.paymentTerm: return paymentTermState.map; case EntityType.design: return designState.map; case EntityType.credit: return creditState.map; case EntityType.user: return userState.map; case EntityType.taxRate: return taxRateState.map; case EntityType.companyGateway: return companyGatewayState.map; case EntityType.group: return groupState.map; case EntityType.document: return documentState.map; case EntityType.expense: return expenseState.map; case EntityType.vendor: return vendorState.map; case EntityType.task: return taskState.map; case EntityType.project: return projectState.map; case EntityType.payment: return paymentState.map; case EntityType.quote: return quoteState.map; case EntityType.expenseCategory: // TODO move to expenseCategoryState.map return company.expenseCategoryMap; case EntityType.paymentType: return staticState.paymentTypeMap; case EntityType.currency: return staticState.currencyMap; case EntityType.country: return staticState.countryMap; case EntityType.language: return staticState.languageMap; case EntityType.industry: return staticState.industryMap; case EntityType.size: return staticState.sizeMap; case EntityType.gateway: return staticState.gatewayMap; case EntityType.dateFormat: return staticState.dateFormatMap; case EntityType.timezone: return staticState.timezoneMap; default: return null; } } BuiltList getEntityList(EntityType type) { switch (type) { case EntityType.product: return productState.list; case EntityType.client: return clientState.list; case EntityType.invoice: return invoiceState.list; // STARTER: states switch list - do not remove comment case EntityType.webhook: return webhookState.list; case EntityType.token: return tokenState.list; case EntityType.paymentTerm: return paymentTermState.list; case EntityType.design: return designState.list; case EntityType.credit: return creditState.list; case EntityType.user: return userState.list; case EntityType.taxRate: return taxRateState.list; case EntityType.companyGateway: return companyGatewayState.list; case EntityType.group: return groupState.list; case EntityType.document: return documentState.list; case EntityType.expense: return expenseState.list; case EntityType.vendor: return vendorState.list; case EntityType.task: return taskState.list; case EntityType.project: return projectState.list; case EntityType.payment: return paymentState.list; case EntityType.quote: return quoteState.list; default: return null; } } EntityUIState getUIState(EntityType type) { switch (type) { case EntityType.product: return productUIState; case EntityType.client: return clientUIState; case EntityType.invoice: return invoiceUIState; // STARTER: states switch - do not remove comment case EntityType.webhook: return webhookUIState; case EntityType.token: return tokenUIState; case EntityType.paymentTerm: return paymentTermUIState; case EntityType.design: return designUIState; case EntityType.credit: return creditUIState; case EntityType.user: return userUIState; case EntityType.taxRate: return taxRateUIState; case EntityType.companyGateway: return companyGatewayUIState; case EntityType.group: return groupUIState; case EntityType.document: return documentUIState; case EntityType.expense: return expenseUIState; case EntityType.vendor: return vendorUIState; case EntityType.task: return taskUIState; case EntityType.project: return projectUIState; case EntityType.payment: return paymentUIState; case EntityType.quote: return quoteUIState; default: return null; } } ListUIState getListState(EntityType type) { return getUIState(type).listUIState; } ProductState get productState => userCompanyState.productState; ProductUIState get productUIState => uiState.productUIState; ListUIState get productListState => uiState.productUIState.listUIState; ClientState get clientState => userCompanyState.clientState; ClientUIState get clientUIState => uiState.clientUIState; ListUIState get clientListState => uiState.clientUIState.listUIState; InvoiceState get invoiceState => userCompanyState.invoiceState; InvoiceUIState get invoiceUIState => uiState.invoiceUIState; ListUIState get invoiceListState => uiState.invoiceUIState.listUIState; // STARTER: state getters - do not remove comment WebhookState get webhookState => userCompanyState.webhookState; ListUIState get webhookListState => uiState.webhookUIState.listUIState; WebhookUIState get webhookUIState => uiState.webhookUIState; TokenState get tokenState => userCompanyState.tokenState; ListUIState get tokenListState => uiState.tokenUIState.listUIState; TokenUIState get tokenUIState => uiState.tokenUIState; PaymentTermState get paymentTermState => userCompanyState.paymentTermState; ListUIState get paymentTermListState => uiState.paymentTermUIState.listUIState; PaymentTermUIState get paymentTermUIState => uiState.paymentTermUIState; DesignState get designState => userCompanyState.designState; ListUIState get designListState => uiState.designUIState.listUIState; DesignUIState get designUIState => uiState.designUIState; CreditState get creditState => userCompanyState.creditState; ListUIState get creditListState => uiState.creditUIState.listUIState; CreditUIState get creditUIState => uiState.creditUIState; UserState get userState => userCompanyState.userState; ListUIState get userListState => uiState.userUIState.listUIState; UserUIState get userUIState => uiState.userUIState; TaxRateState get taxRateState => userCompanyState.taxRateState; ListUIState get taxRateListState => uiState.taxRateUIState.listUIState; TaxRateUIState get taxRateUIState => uiState.taxRateUIState; CompanyGatewayState get companyGatewayState => userCompanyState.companyGatewayState; ListUIState get companyGatewayListState => uiState.companyGatewayUIState.listUIState; CompanyGatewayUIState get companyGatewayUIState => uiState.companyGatewayUIState; GroupState get groupState => userCompanyState.groupState; ListUIState get groupListState => uiState.groupUIState.listUIState; GroupUIState get groupUIState => uiState.groupUIState; DocumentState get documentState => userCompanyState.documentState; ListUIState get documentListState => uiState.documentUIState.listUIState; DocumentUIState get documentUIState => uiState.documentUIState; ExpenseState get expenseState => userCompanyState.expenseState; ListUIState get expenseListState => uiState.expenseUIState.listUIState; ExpenseUIState get expenseUIState => uiState.expenseUIState; VendorState get vendorState => userCompanyState.vendorState; ListUIState get vendorListState => uiState.vendorUIState.listUIState; VendorUIState get vendorUIState => uiState.vendorUIState; TaskState get taskState => userCompanyState.taskState; ListUIState get taskListState => uiState.taskUIState.listUIState; TaskUIState get taskUIState => uiState.taskUIState; ProjectState get projectState => userCompanyState.projectState; ListUIState get projectListState => uiState.projectUIState.listUIState; ProjectUIState get projectUIState => uiState.projectUIState; PaymentState get paymentState => userCompanyState.paymentState; ListUIState get paymentListState => uiState.paymentUIState.listUIState; PaymentUIState get paymentUIState => uiState.paymentUIState; QuoteState get quoteState => userCompanyState.quoteState; ListUIState get quoteListState => uiState.quoteUIState.listUIState; QuoteUIState get quoteUIState => uiState.quoteUIState; SettingsUIState get settingsUIState => uiState.settingsUIState; bool hasChanges() { switch (uiState.currentRoute) { case ClientEditScreen.route: return hasClientChanges(clientUIState.editing, clientState.map); case ProductEditScreen.route: return hasProductChanges(productUIState.editing, productState.map); case InvoiceEditScreen.route: return hasInvoiceChanges(invoiceUIState.editing, invoiceState.map); case PaymentEditScreen.route: return hasPaymentChanges(paymentUIState.editing, paymentState.map); case QuoteEditScreen.route: return hasQuoteChanges(quoteUIState.editing, quoteState.map); case ProjectEditScreen.route: return hasProjectChanges(projectUIState.editing, projectState.map); case TaskEditScreen.route: return hasTaskChanges(taskUIState.editing, taskState.map); case VendorEditScreen.route: return hasVendorChanges(vendorUIState.editing, vendorState.map); case ExpenseEditScreen.route: return hasExpenseChanges(expenseUIState.editing, expenseState.map); case GroupEditScreen.route: return hasGroupChanges(groupUIState.editing, groupState.map); case TaxRateEditScreen.route: return hasTaxRateChanges(taxRateUIState.editing, taxRateState.map); case CompanyGatewayEditScreen.route: return hasCompanyGatewayChanges( companyGatewayUIState.editing, companyGatewayState.map); case CreditEditScreen.route: return hasCreditChanges(creditUIState.editing, creditState.map); // STARTER: has changes - do not remove comment case WebhookEditScreen.route: return hasWebhookChanges(webhookUIState.editing, webhookState.map); case TokenEditScreen.route: return hasTokenChanges(tokenUIState.editing, tokenState.map); case PaymentTermEditScreen.route: return hasPaymentTermChanges( paymentTermUIState.editing, paymentTermState.map); case DesignEditScreen.route: return hasDesignChanges(designUIState.editing, designState.map); } if (uiState.currentRoute.startsWith('/settings')) { return settingsUIState.isChanged; } if (uiState.currentRoute.endsWith('/edit') || uiState.currentRoute.endsWith('_edit')) { throw 'AppState.hasChanges is not defined for ${uiState.currentRoute}'; } return false; } bool supportsVersion(String version) { final parts = version.split('.'); final int major = int.parse(parts[0]); final int minor = int.parse(parts[1]); final int patch = int.parse(parts[2]); try { final serverParts = account.currentVersion.split('.'); final int serverMajor = int.parse(serverParts[0]); final int serverMinor = int.parse(serverParts[1]); final int serverPatch = int.parse(serverParts[2]); return serverMajor >= major && serverMinor >= minor && serverPatch >= patch; } catch (e) { return false; } } bool get reportErrors => account?.reportErrors ?? false; bool get isHosted => authState.isHosted ?? false; bool get isSelfHosted => authState.isSelfHost ?? false; bool get isWhiteLabeled => isSelfHosted || account.plan == kPlanWhiteLabel; /* TODO re-enable bool get isProPlan => isSelfHosted || account.plan == kPlanPro; bool get isEnterprisePlan => isSelfHosted || account.plan == kPlanEnterprise; */ bool get isProPlan => true; bool get isEnterprisePlan => true; bool get isMenuCollapsed => (prefState.isNotMobile && prefState.fullHeightFilter && prefState.showMenu && uiState.filterEntityType != null) || prefState.isMenuCollapsed; @override String toString() { //return 'Token: ${userCompanyStates.map((state) => state.token.token).where((name) => name.isNotEmpty).first}'; return 'URL: ${authState.url}, Route: ${uiState.currentRoute} Prev: ${uiState.previousRoute}'; } } class Credentials { Credentials({this.url, this.token}); String url; String token; }