This commit is contained in:
Hillel Coren 2020-03-03 17:10:12 +02:00
parent 0729a5a785
commit ebef3441aa
52 changed files with 3577 additions and 140 deletions

View File

@ -0,0 +1,117 @@
import 'package:built_value/built_value.dart';
import 'package:built_collection/built_collection.dart';
import 'package:built_value/serializer.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/utils/formatting.dart';
part 'design_model.g.dart';
abstract class DesignListResponse
implements Built<DesignListResponse, DesignListResponseBuilder> {
factory DesignListResponse([void updates(DesignListResponseBuilder b)]) =
_$DesignListResponse;
DesignListResponse._();
BuiltList<DesignEntity> get data;
static Serializer<DesignListResponse> get serializer =>
_$designListResponseSerializer;
}
abstract class DesignItemResponse
implements Built<DesignItemResponse, DesignItemResponseBuilder> {
factory DesignItemResponse([void updates(DesignItemResponseBuilder b)]) =
_$DesignItemResponse;
DesignItemResponse._();
DesignEntity get data;
static Serializer<DesignItemResponse> get serializer =>
_$designItemResponseSerializer;
}
class DesignFields {
static const String name = 'name';
}
abstract class DesignEntity extends Object
with BaseEntity
implements Built<DesignEntity, DesignEntityBuilder> {
factory DesignEntity() {
return _$DesignEntity._(
id: BaseEntity.nextId,
isChanged: false,
// STARTER: constructor - do not remove comment
);
}
DesignEntity._();
String get displayName {
// STARTER: display name - do not remove comment
}
int compareTo(DesignEntity design, String sortField, bool sortAscending) {
int response = 0;
DesignEntity designA = sortAscending ? this : design;
DesignEntity designB = sortAscending ? design : this;
switch (sortField) {
// STARTER: sort switch - do not remove comment
}
if (response == 0) {
// STARTER: sort default - do not remove comment
} else {
return response;
}
}
bool matchesSearch(String filter) {
if (filter == null || filter.isEmpty) {
return true;
}
filter = filter.toLowerCase();
// STARTER: filter - do not remove comment
return false;
}
@override
bool matchesFilter(String filter) {
if (filter == null || filter.isEmpty) {
return true;
}
filter = filter.toLowerCase();
return false;
}
@override
String matchesFilterValue(String filter) {
if (filter == null || filter.isEmpty) {
return null;
}
filter = filter.toLowerCase();
return null;
}
@override
String get listDisplayName => null;
@override
double get listDisplayAmount => null;
@override
FormatNumberType get listDisplayAmountType => null;
static Serializer<DesignEntity> get serializer => _$designEntitySerializer;
}

View File

@ -0,0 +1,598 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'design_model.dart';
// **************************************************************************
// BuiltValueGenerator
// **************************************************************************
Serializer<DesignListResponse> _$designListResponseSerializer =
new _$DesignListResponseSerializer();
Serializer<DesignItemResponse> _$designItemResponseSerializer =
new _$DesignItemResponseSerializer();
Serializer<DesignEntity> _$designEntitySerializer =
new _$DesignEntitySerializer();
class _$DesignListResponseSerializer
implements StructuredSerializer<DesignListResponse> {
@override
final Iterable<Type> types = const [DesignListResponse, _$DesignListResponse];
@override
final String wireName = 'DesignListResponse';
@override
Iterable<Object> serialize(Serializers serializers, DesignListResponse object,
{FullType specifiedType = FullType.unspecified}) {
final result = <Object>[
'data',
serializers.serialize(object.data,
specifiedType:
const FullType(BuiltList, const [const FullType(DesignEntity)])),
];
return result;
}
@override
DesignListResponse deserialize(
Serializers serializers, Iterable<Object> serialized,
{FullType specifiedType = FullType.unspecified}) {
final result = new DesignListResponseBuilder();
final iterator = serialized.iterator;
while (iterator.moveNext()) {
final key = iterator.current as String;
iterator.moveNext();
final dynamic value = iterator.current;
switch (key) {
case 'data':
result.data.replace(serializers.deserialize(value,
specifiedType: const FullType(
BuiltList, const [const FullType(DesignEntity)]))
as BuiltList<Object>);
break;
}
}
return result.build();
}
}
class _$DesignItemResponseSerializer
implements StructuredSerializer<DesignItemResponse> {
@override
final Iterable<Type> types = const [DesignItemResponse, _$DesignItemResponse];
@override
final String wireName = 'DesignItemResponse';
@override
Iterable<Object> serialize(Serializers serializers, DesignItemResponse object,
{FullType specifiedType = FullType.unspecified}) {
final result = <Object>[
'data',
serializers.serialize(object.data,
specifiedType: const FullType(DesignEntity)),
];
return result;
}
@override
DesignItemResponse deserialize(
Serializers serializers, Iterable<Object> serialized,
{FullType specifiedType = FullType.unspecified}) {
final result = new DesignItemResponseBuilder();
final iterator = serialized.iterator;
while (iterator.moveNext()) {
final key = iterator.current as String;
iterator.moveNext();
final dynamic value = iterator.current;
switch (key) {
case 'data':
result.data.replace(serializers.deserialize(value,
specifiedType: const FullType(DesignEntity)) as DesignEntity);
break;
}
}
return result.build();
}
}
class _$DesignEntitySerializer implements StructuredSerializer<DesignEntity> {
@override
final Iterable<Type> types = const [DesignEntity, _$DesignEntity];
@override
final String wireName = 'DesignEntity';
@override
Iterable<Object> serialize(Serializers serializers, DesignEntity object,
{FullType specifiedType = FullType.unspecified}) {
final result = <Object>[];
if (object.isChanged != null) {
result
..add('isChanged')
..add(serializers.serialize(object.isChanged,
specifiedType: const FullType(bool)));
}
if (object.createdAt != null) {
result
..add('created_at')
..add(serializers.serialize(object.createdAt,
specifiedType: const FullType(int)));
}
if (object.updatedAt != null) {
result
..add('updated_at')
..add(serializers.serialize(object.updatedAt,
specifiedType: const FullType(int)));
}
if (object.archivedAt != null) {
result
..add('archived_at')
..add(serializers.serialize(object.archivedAt,
specifiedType: const FullType(int)));
}
if (object.isDeleted != null) {
result
..add('is_deleted')
..add(serializers.serialize(object.isDeleted,
specifiedType: const FullType(bool)));
}
if (object.createdUserId != null) {
result
..add('user_id')
..add(serializers.serialize(object.createdUserId,
specifiedType: const FullType(String)));
}
if (object.assignedUserId != null) {
result
..add('assigned_user_id')
..add(serializers.serialize(object.assignedUserId,
specifiedType: const FullType(String)));
}
if (object.subEntityType != null) {
result
..add('entity_type')
..add(serializers.serialize(object.subEntityType,
specifiedType: const FullType(EntityType)));
}
if (object.id != null) {
result
..add('id')
..add(serializers.serialize(object.id,
specifiedType: const FullType(String)));
}
return result;
}
@override
DesignEntity deserialize(Serializers serializers, Iterable<Object> serialized,
{FullType specifiedType = FullType.unspecified}) {
final result = new DesignEntityBuilder();
final iterator = serialized.iterator;
while (iterator.moveNext()) {
final key = iterator.current as String;
iterator.moveNext();
final dynamic value = iterator.current;
switch (key) {
case 'isChanged':
result.isChanged = serializers.deserialize(value,
specifiedType: const FullType(bool)) as bool;
break;
case 'created_at':
result.createdAt = serializers.deserialize(value,
specifiedType: const FullType(int)) as int;
break;
case 'updated_at':
result.updatedAt = serializers.deserialize(value,
specifiedType: const FullType(int)) as int;
break;
case 'archived_at':
result.archivedAt = serializers.deserialize(value,
specifiedType: const FullType(int)) as int;
break;
case 'is_deleted':
result.isDeleted = serializers.deserialize(value,
specifiedType: const FullType(bool)) as bool;
break;
case 'user_id':
result.createdUserId = serializers.deserialize(value,
specifiedType: const FullType(String)) as String;
break;
case 'assigned_user_id':
result.assignedUserId = serializers.deserialize(value,
specifiedType: const FullType(String)) as String;
break;
case 'entity_type':
result.subEntityType = serializers.deserialize(value,
specifiedType: const FullType(EntityType)) as EntityType;
break;
case 'id':
result.id = serializers.deserialize(value,
specifiedType: const FullType(String)) as String;
break;
}
}
return result.build();
}
}
class _$DesignListResponse extends DesignListResponse {
@override
final BuiltList<DesignEntity> data;
factory _$DesignListResponse(
[void Function(DesignListResponseBuilder) updates]) =>
(new DesignListResponseBuilder()..update(updates)).build();
_$DesignListResponse._({this.data}) : super._() {
if (data == null) {
throw new BuiltValueNullFieldError('DesignListResponse', 'data');
}
}
@override
DesignListResponse rebuild(
void Function(DesignListResponseBuilder) updates) =>
(toBuilder()..update(updates)).build();
@override
DesignListResponseBuilder toBuilder() =>
new DesignListResponseBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is DesignListResponse && data == other.data;
}
@override
int get hashCode {
return $jf($jc(0, data.hashCode));
}
@override
String toString() {
return (newBuiltValueToStringHelper('DesignListResponse')
..add('data', data))
.toString();
}
}
class DesignListResponseBuilder
implements Builder<DesignListResponse, DesignListResponseBuilder> {
_$DesignListResponse _$v;
ListBuilder<DesignEntity> _data;
ListBuilder<DesignEntity> get data =>
_$this._data ??= new ListBuilder<DesignEntity>();
set data(ListBuilder<DesignEntity> data) => _$this._data = data;
DesignListResponseBuilder();
DesignListResponseBuilder get _$this {
if (_$v != null) {
_data = _$v.data?.toBuilder();
_$v = null;
}
return this;
}
@override
void replace(DesignListResponse other) {
if (other == null) {
throw new ArgumentError.notNull('other');
}
_$v = other as _$DesignListResponse;
}
@override
void update(void Function(DesignListResponseBuilder) updates) {
if (updates != null) updates(this);
}
@override
_$DesignListResponse build() {
_$DesignListResponse _$result;
try {
_$result = _$v ?? new _$DesignListResponse._(data: data.build());
} catch (_) {
String _$failedField;
try {
_$failedField = 'data';
data.build();
} catch (e) {
throw new BuiltValueNestedFieldError(
'DesignListResponse', _$failedField, e.toString());
}
rethrow;
}
replace(_$result);
return _$result;
}
}
class _$DesignItemResponse extends DesignItemResponse {
@override
final DesignEntity data;
factory _$DesignItemResponse(
[void Function(DesignItemResponseBuilder) updates]) =>
(new DesignItemResponseBuilder()..update(updates)).build();
_$DesignItemResponse._({this.data}) : super._() {
if (data == null) {
throw new BuiltValueNullFieldError('DesignItemResponse', 'data');
}
}
@override
DesignItemResponse rebuild(
void Function(DesignItemResponseBuilder) updates) =>
(toBuilder()..update(updates)).build();
@override
DesignItemResponseBuilder toBuilder() =>
new DesignItemResponseBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is DesignItemResponse && data == other.data;
}
@override
int get hashCode {
return $jf($jc(0, data.hashCode));
}
@override
String toString() {
return (newBuiltValueToStringHelper('DesignItemResponse')
..add('data', data))
.toString();
}
}
class DesignItemResponseBuilder
implements Builder<DesignItemResponse, DesignItemResponseBuilder> {
_$DesignItemResponse _$v;
DesignEntityBuilder _data;
DesignEntityBuilder get data => _$this._data ??= new DesignEntityBuilder();
set data(DesignEntityBuilder data) => _$this._data = data;
DesignItemResponseBuilder();
DesignItemResponseBuilder get _$this {
if (_$v != null) {
_data = _$v.data?.toBuilder();
_$v = null;
}
return this;
}
@override
void replace(DesignItemResponse other) {
if (other == null) {
throw new ArgumentError.notNull('other');
}
_$v = other as _$DesignItemResponse;
}
@override
void update(void Function(DesignItemResponseBuilder) updates) {
if (updates != null) updates(this);
}
@override
_$DesignItemResponse build() {
_$DesignItemResponse _$result;
try {
_$result = _$v ?? new _$DesignItemResponse._(data: data.build());
} catch (_) {
String _$failedField;
try {
_$failedField = 'data';
data.build();
} catch (e) {
throw new BuiltValueNestedFieldError(
'DesignItemResponse', _$failedField, e.toString());
}
rethrow;
}
replace(_$result);
return _$result;
}
}
class _$DesignEntity extends DesignEntity {
@override
final bool isChanged;
@override
final int createdAt;
@override
final int updatedAt;
@override
final int archivedAt;
@override
final bool isDeleted;
@override
final String createdUserId;
@override
final String assignedUserId;
@override
final EntityType subEntityType;
@override
final String id;
factory _$DesignEntity([void Function(DesignEntityBuilder) updates]) =>
(new DesignEntityBuilder()..update(updates)).build();
_$DesignEntity._(
{this.isChanged,
this.createdAt,
this.updatedAt,
this.archivedAt,
this.isDeleted,
this.createdUserId,
this.assignedUserId,
this.subEntityType,
this.id})
: super._();
@override
DesignEntity rebuild(void Function(DesignEntityBuilder) updates) =>
(toBuilder()..update(updates)).build();
@override
DesignEntityBuilder toBuilder() => new DesignEntityBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is DesignEntity &&
isChanged == other.isChanged &&
createdAt == other.createdAt &&
updatedAt == other.updatedAt &&
archivedAt == other.archivedAt &&
isDeleted == other.isDeleted &&
createdUserId == other.createdUserId &&
assignedUserId == other.assignedUserId &&
subEntityType == other.subEntityType &&
id == other.id;
}
@override
int get hashCode {
return $jf($jc(
$jc(
$jc(
$jc(
$jc(
$jc(
$jc(
$jc($jc(0, isChanged.hashCode),
createdAt.hashCode),
updatedAt.hashCode),
archivedAt.hashCode),
isDeleted.hashCode),
createdUserId.hashCode),
assignedUserId.hashCode),
subEntityType.hashCode),
id.hashCode));
}
@override
String toString() {
return (newBuiltValueToStringHelper('DesignEntity')
..add('isChanged', isChanged)
..add('createdAt', createdAt)
..add('updatedAt', updatedAt)
..add('archivedAt', archivedAt)
..add('isDeleted', isDeleted)
..add('createdUserId', createdUserId)
..add('assignedUserId', assignedUserId)
..add('subEntityType', subEntityType)
..add('id', id))
.toString();
}
}
class DesignEntityBuilder
implements Builder<DesignEntity, DesignEntityBuilder> {
_$DesignEntity _$v;
bool _isChanged;
bool get isChanged => _$this._isChanged;
set isChanged(bool isChanged) => _$this._isChanged = isChanged;
int _createdAt;
int get createdAt => _$this._createdAt;
set createdAt(int createdAt) => _$this._createdAt = createdAt;
int _updatedAt;
int get updatedAt => _$this._updatedAt;
set updatedAt(int updatedAt) => _$this._updatedAt = updatedAt;
int _archivedAt;
int get archivedAt => _$this._archivedAt;
set archivedAt(int archivedAt) => _$this._archivedAt = archivedAt;
bool _isDeleted;
bool get isDeleted => _$this._isDeleted;
set isDeleted(bool isDeleted) => _$this._isDeleted = isDeleted;
String _createdUserId;
String get createdUserId => _$this._createdUserId;
set createdUserId(String createdUserId) =>
_$this._createdUserId = createdUserId;
String _assignedUserId;
String get assignedUserId => _$this._assignedUserId;
set assignedUserId(String assignedUserId) =>
_$this._assignedUserId = assignedUserId;
EntityType _subEntityType;
EntityType get subEntityType => _$this._subEntityType;
set subEntityType(EntityType subEntityType) =>
_$this._subEntityType = subEntityType;
String _id;
String get id => _$this._id;
set id(String id) => _$this._id = id;
DesignEntityBuilder();
DesignEntityBuilder get _$this {
if (_$v != null) {
_isChanged = _$v.isChanged;
_createdAt = _$v.createdAt;
_updatedAt = _$v.updatedAt;
_archivedAt = _$v.archivedAt;
_isDeleted = _$v.isDeleted;
_createdUserId = _$v.createdUserId;
_assignedUserId = _$v.assignedUserId;
_subEntityType = _$v.subEntityType;
_id = _$v.id;
_$v = null;
}
return this;
}
@override
void replace(DesignEntity other) {
if (other == null) {
throw new ArgumentError.notNull('other');
}
_$v = other as _$DesignEntity;
}
@override
void update(void Function(DesignEntityBuilder) updates) {
if (updates != null) updates(this);
}
@override
_$DesignEntity build() {
final _$result = _$v ??
new _$DesignEntity._(
isChanged: isChanged,
createdAt: createdAt,
updatedAt: updatedAt,
archivedAt: archivedAt,
isDeleted: isDeleted,
createdUserId: createdUserId,
assignedUserId: assignedUserId,
subEntityType: subEntityType,
id: id);
replace(_$result);
return _$result;
}
}
// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new

View File

@ -33,11 +33,12 @@ class EntityType extends EnumClass {
static const EntityType payment = _$payment;
static const EntityType group = _$group;
static const EntityType user = _$user;
static const EntityType company = _$company;
static const EntityType gateway = _$gateway;
static const EntityType gatewayToken = _$gatewayToken;
static const EntityType invoiceItem = _$invoiceItem;
static const EntityType design = _$design;
// STARTER: entity type - do not remove comment
static const EntityType quoteItem = _$quoteItem;
static const EntityType contact = _$contact;
static const EntityType vendorContact = _$vendorContact;

View File

@ -29,6 +29,7 @@ const EntityType _$company = const EntityType._('company');
const EntityType _$gateway = const EntityType._('gateway');
const EntityType _$gatewayToken = const EntityType._('gatewayToken');
const EntityType _$invoiceItem = const EntityType._('invoiceItem');
const EntityType _$design = const EntityType._('design');
const EntityType _$quoteItem = const EntityType._('quoteItem');
const EntityType _$contact = const EntityType._('contact');
const EntityType _$vendorContact = const EntityType._('vendorContact');
@ -92,6 +93,8 @@ EntityType _$typeValueOf(String name) {
return _$gatewayToken;
case 'invoiceItem':
return _$invoiceItem;
case 'design':
return _$design;
case 'quoteItem':
return _$quoteItem;
case 'contact':
@ -150,6 +153,7 @@ final BuiltSet<EntityType> _$typeValues =
_$gateway,
_$gatewayToken,
_$invoiceItem,
_$design,
_$quoteItem,
_$contact,
_$vendorContact,

View File

@ -2,7 +2,6 @@ import 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import 'package:invoiceninja_flutter/utils/strings.dart';
export 'package:invoiceninja_flutter/data/models/client_model.dart';
export 'package:invoiceninja_flutter/data/models/company_model.dart';
export 'package:invoiceninja_flutter/data/models/credit_model.dart';
@ -13,7 +12,9 @@ export 'package:invoiceninja_flutter/data/models/expense_model.dart';
export 'package:invoiceninja_flutter/data/models/invoice_model.dart';
export 'package:invoiceninja_flutter/data/models/payment_model.dart';
export 'package:invoiceninja_flutter/data/models/product_model.dart';
export 'package:invoiceninja_flutter/data/models/design_model.dart';
export 'package:invoiceninja_flutter/data/models/project_model.dart';
//
export 'package:invoiceninja_flutter/data/models/static/country_model.dart';
export 'package:invoiceninja_flutter/data/models/static/currency_model.dart';
export 'package:invoiceninja_flutter/data/models/static/date_format_model.dart';

View File

@ -63,4 +63,3 @@ class QuoteFields {
static const String archivedAt = 'archivedAt';
static const String isDeleted = 'isDeleted';
}

View File

@ -36,6 +36,9 @@ import 'package:invoiceninja_flutter/redux/payment/payment_state.dart';
import 'package:invoiceninja_flutter/redux/quote/quote_state.dart';
// STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/data/models/design_model.dart';
import 'package:invoiceninja_flutter/redux/design/design_state.dart';
import 'package:invoiceninja_flutter/data/models/credit_model.dart';
import 'package:invoiceninja_flutter/redux/credit/credit_state.dart';
@ -112,6 +115,8 @@ part 'serializers.g.dart';
TaxRateItemResponse,
TaxRateListResponse,
// STARTER: serializers - do not remove comment
DesignEntity,
InvoiceEntity,
PaymentableEntity,

View File

@ -45,6 +45,9 @@ Serializers _$serializers = (new Serializers().toBuilder()
..add(DatetimeFormatEntity.serializer)
..add(DatetimeFormatItemResponse.serializer)
..add(DatetimeFormatListResponse.serializer)
..add(DesignEntity.serializer)
..add(DesignState.serializer)
..add(DesignUIState.serializer)
..add(DocumentEntity.serializer)
..add(DocumentItemResponse.serializer)
..add(DocumentListResponse.serializer)
@ -383,6 +386,8 @@ Serializers _$serializers = (new Serializers().toBuilder()
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(String), const FullType(PaymentTypeEntity)]), () => new MapBuilder<String, PaymentTypeEntity>())
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(String), const FullType(CountryEntity)]), () => new MapBuilder<String, CountryEntity>())
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(String), const FullType(InvoiceStatusEntity)]), () => new MapBuilder<String, InvoiceStatusEntity>())
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(String), const FullType(DesignEntity)]), () => new MapBuilder<String, DesignEntity>())
..addBuilderFactory(const FullType(BuiltList, const [const FullType(String)]), () => new ListBuilder<String>())
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(String), const FullType(DocumentEntity)]), () => new MapBuilder<String, DocumentEntity>())
..addBuilderFactory(const FullType(BuiltList, const [const FullType(String)]), () => new ListBuilder<String>())
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(String), const FullType(ExpenseEntity)]), () => new MapBuilder<String, ExpenseEntity>())

View File

@ -21,7 +21,7 @@ class CreditRepository {
'${credentials.url}/credits/$entityId?', credentials.token);
final InvoiceItemResponse creditResponse =
serializers.deserializeWith(InvoiceItemResponse.serializer, response);
serializers.deserializeWith(InvoiceItemResponse.serializer, response);
return creditResponse.data;
}
@ -37,7 +37,7 @@ class CreditRepository {
final dynamic response = await webClient.get(url, credentials.token);
final InvoiceListResponse creditResponse =
serializers.deserializeWith(InvoiceListResponse.serializer, response);
serializers.deserializeWith(InvoiceListResponse.serializer, response);
return creditResponse.data;
}
@ -52,7 +52,7 @@ class CreditRepository {
data: json.encode({'ids': ids}));
final InvoiceListResponse invoiceResponse =
serializers.deserializeWith(InvoiceListResponse.serializer, response);
serializers.deserializeWith(InvoiceListResponse.serializer, response);
return invoiceResponse.data.toList();
}
@ -72,11 +72,11 @@ class CreditRepository {
url += '?action=' + action.toString();
}
response =
await webClient.put(url, credentials.token, data: json.encode(data));
await webClient.put(url, credentials.token, data: json.encode(data));
}
final InvoiceItemResponse creditResponse =
serializers.deserializeWith(InvoiceItemResponse.serializer, response);
serializers.deserializeWith(InvoiceItemResponse.serializer, response);
return creditResponse.data;
}

View File

@ -0,0 +1,84 @@
import 'dart:async';
import 'dart:convert';
import 'dart:core';
import 'package:invoiceninja_flutter/.env.dart';
import 'package:built_collection/built_collection.dart';
import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/data/models/serializers.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/data/web_client.dart';
class DesignRepository {
const DesignRepository({
this.webClient = const WebClient(),
});
final WebClient webClient;
Future<DesignEntity> loadItem(
Credentials credentials, String entityId) async {
final dynamic response = await webClient.get(
'${credentials.url}/designs/$entityId', credentials.token);
final DesignItemResponse designResponse =
serializers.deserializeWith(DesignItemResponse.serializer, response);
return designResponse.data;
}
Future<BuiltList<DesignEntity>> loadList(
Credentials credentials, int updatedAt) async {
String url = credentials.url + '/designs?';
if (updatedAt > 0) {
url += '&updated_at=${updatedAt - kUpdatedAtBufferSeconds}';
}
final dynamic response = await webClient.get(url, credentials.token);
final DesignListResponse designResponse =
serializers.deserializeWith(DesignListResponse.serializer, response);
return designResponse.data;
}
Future<List<DesignEntity>> bulkAction(
Credentials credentials, List<String> ids, EntityAction action) async {
var url = credentials.url + '/designs/bulk?';
if (action != null) {
url += '&action=' + action.toString();
}
final dynamic response = await webClient.post(url, credentials.token,
data: json.encode({'ids': ids}));
final DesignListResponse designResponse =
serializers.deserializeWith(DesignListResponse.serializer, response);
return designResponse.data.toList();
}
Future<DesignEntity> saveData(Credentials credentials, DesignEntity design,
[EntityAction action]) async {
final data = serializers.serializeWith(DesignEntity.serializer, design);
dynamic response;
if (design.isNew) {
response = await webClient.post(
credentials.url + '/designs', credentials.token,
data: json.encode(data));
} else {
var url = credentials.url + '/designs/' + design.id.toString();
if (action != null) {
url += '?action=' + action.toString();
}
response =
await webClient.put(url, credentials.token, data: json.encode(data));
}
final DesignItemResponse designResponse =
serializers.deserializeWith(DesignItemResponse.serializer, response);
return designResponse.data;
}
}

View File

@ -51,6 +51,12 @@ import 'package:sentry/sentry.dart';
import 'package:shared_preferences/shared_preferences.dart';
// STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/ui/design/design_screen.dart';
import 'package:invoiceninja_flutter/ui/design/edit/design_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/design/view/design_view_vm.dart';
import 'package:invoiceninja_flutter/redux/design/design_actions.dart';
import 'package:invoiceninja_flutter/redux/design/design_middleware.dart';
import 'package:invoiceninja_flutter/ui/credit/credit_screen.dart';
import 'package:invoiceninja_flutter/ui/credit/edit/credit_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/credit/view/credit_view_vm.dart';
@ -100,6 +106,7 @@ void main({bool isTesting = false}) async {
..addAll(createStoreSettingsMiddleware())
..addAll(createStoreReportsMiddleware())
// STARTER: middleware - do not remove comment
..addAll(createStoreDesignsMiddleware())
..addAll(createStoreCreditsMiddleware())
..addAll(createStoreUsersMiddleware())
..addAll(createStoreTaxRatesMiddleware())
@ -351,6 +358,9 @@ class InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
QuoteEditScreen.route: (context) => QuoteEditScreen(),
QuoteEmailScreen.route: (context) => QuoteEmailScreen(),
// STARTER: routes - do not remove comment
DesignScreen.route: (context) => DesignScreenBuilder(),
DesignViewScreen.route: (context) => DesignViewScreen(),
DesignEditScreen.route: (context) => DesignEditScreen(),
CreditScreen.route: (context) => CreditScreenBuilder(),
CreditViewScreen.route: (context) => CreditViewScreen(),
CreditEditScreen.route: (context) => CreditEditScreen(),

View File

@ -27,6 +27,8 @@ import 'package:invoiceninja_flutter/utils/dialogs.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
// STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/design/design_actions.dart';
import 'package:invoiceninja_flutter/redux/credit/credit_actions.dart';
class PersistUI {}
@ -232,6 +234,13 @@ void filterEntitiesByType({
));
break;
// STARTER: filter - do not remove comment
case EntityType.design:
store.dispatch(FilterDesignsByEntity(
entityId: filterEntity.id,
entityType: filterEntity.entityType,
));
break;
case EntityType.credit:
store.dispatch(FilterCreditsByEntity(
entityId: filterEntity.id,
@ -307,6 +316,10 @@ void viewEntitiesByType({
store.dispatch(ViewGroupList(navigator: navigator));
break;
// STARTER: view list - do not remove comment
case EntityType.design:
store.dispatch(ViewDesignList(navigator: navigator));
break;
case EntityType.credit:
store.dispatch(ViewCreditList(navigator: navigator));
break;
@ -434,6 +447,14 @@ void viewEntityById({
));
break;
// STARTER: view - do not remove comment
case EntityType.design:
store.dispatch(ViewDesign(
designId: entityId,
navigator: navigator,
force: force,
));
break;
case EntityType.credit:
store.dispatch(ViewCredit(
creditId: entityId,
@ -555,6 +576,14 @@ void createEntityByType(
));
break;
// STARTER: create type - do not remove comment
case EntityType.design:
store.dispatch(EditDesign(
navigator: navigator,
force: force,
design: DesignEntity(state: state),
));
break;
case EntityType.credit:
store.dispatch(EditCredit(
navigator: navigator,
@ -696,6 +725,15 @@ void createEntity({
));
break;
// STARTER: create - do not remove comment
case EntityType.design:
store.dispatch(EditDesign(
navigator: navigator,
design: entity,
force: force,
completer: completer,
));
break;
case EntityType.credit:
store.dispatch(EditCredit(
navigator: navigator,
@ -886,6 +924,19 @@ void editEntityById(
));
break;
// STARTER: edit - do not remove comment
case EntityType.design:
store.dispatch(EditDesign(
design: map[entityId],
navigator: navigator,
completer: completer ??
snackBarCompleter<DesignEntity>(
context,
entity.isNew
? localization.createdDesign
: localization.updatedDesign),
));
break;
case EntityType.credit:
store.dispatch(EditCredit(
credit: map[entityId],
@ -968,6 +1019,10 @@ void handleEntitiesActions(
handleDocumentAction(context, entities, action);
break;
// STARTER: actions - do not remove comment
case EntityType.design:
handleDesignAction(context, entities, action);
break;
case EntityType.credit:
handleCreditAction(context, entities, action);
break;

View File

@ -18,6 +18,8 @@ import 'package:invoiceninja_flutter/redux/auth/auth_reducer.dart';
import 'package:invoiceninja_flutter/redux/company/company_reducer.dart';
import 'package:invoiceninja_flutter/redux/static/static_reducer.dart';
// STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/design/design_actions.dart';
import 'package:invoiceninja_flutter/redux/credit/credit_actions.dart';
// We create the State reducer by combining many smaller reducers into one!
@ -85,6 +87,10 @@ final lastErrorReducer = combineReducers<String>([
return '${action.error}';
}),
// STARTER: errors - do not remove comment
TypedReducer<String, LoadDesignsFailure>((state, action) {
return '${action.error}';
}),
TypedReducer<String, LoadCreditsFailure>((state, action) {
return '${action.error}';
}),

View File

@ -164,6 +164,9 @@ abstract class AppState implements Built<AppState, AppStateBuilder> {
case EntityType.invoice:
return invoiceState.map;
// STARTER: states switch map - do not remove comment
case EntityType.design:
return designState.map;
case EntityType.credit:
return creditState.map;
@ -224,6 +227,9 @@ abstract class AppState implements Built<AppState, AppStateBuilder> {
case EntityType.invoice:
return invoiceState.list;
// STARTER: states switch list - do not remove comment
case EntityType.design:
return designState.list;
case EntityType.credit:
return creditState.list;
@ -263,6 +269,9 @@ abstract class AppState implements Built<AppState, AppStateBuilder> {
case EntityType.invoice:
return invoiceUIState;
// STARTER: states switch - do not remove comment
case EntityType.design:
return designUIState;
case EntityType.credit:
return creditUIState;
@ -316,6 +325,10 @@ abstract class AppState implements Built<AppState, AppStateBuilder> {
ListUIState get invoiceListState => uiState.invoiceUIState.listUIState;
// STARTER: state getters - do not remove comment
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;
@ -423,6 +436,8 @@ abstract class AppState implements Built<AppState, AppStateBuilder> {
case CreditEditScreen.route:
return hasCreditChanges(creditUIState.editing, creditState.map);
// STARTER: has changes - do not remove comment
case DesignEditScreen.route:
return hasDesignChanges(designUIState.editing, designState.map);
}
if (uiState.currentRoute.startsWith('/settings')) {

View File

@ -18,6 +18,8 @@ import 'package:invoiceninja_flutter/redux/payment/payment_reducer.dart';
import 'package:invoiceninja_flutter/redux/quote/quote_reducer.dart';
// STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/design/design_reducer.dart';
import 'package:invoiceninja_flutter/redux/credit/credit_reducer.dart';
import 'package:invoiceninja_flutter/redux/user/user_reducer.dart';
@ -40,6 +42,7 @@ UserCompanyState companyReducer(UserCompanyState state, dynamic action) {
..vendorState.replace(vendorsReducer(state.vendorState, action))
..taskState.replace(tasksReducer(state.taskState, action))
// STARTER: reducer - do not remove comment
..designState.replace(designsReducer(state.designState, action))
..creditState.replace(creditsReducer(state.creditState, action))
..userState.replace(usersReducer(state.userState, action))
..taxRateState.replace(taxRatesReducer(state.taxRateState, action))

View File

@ -7,6 +7,8 @@ import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
// STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/design/design_state.dart';
import 'package:invoiceninja_flutter/redux/credit/credit_state.dart';
import 'package:invoiceninja_flutter/redux/user/user_state.dart';
@ -39,6 +41,8 @@ abstract class UserCompanyState
paymentState: PaymentState(),
quoteState: QuoteState(),
// STARTER: constructor - do not remove comment
designState: DesignState(),
creditState: CreditState(),
userState: UserState(),
@ -74,6 +78,8 @@ abstract class UserCompanyState
QuoteState get quoteState;
// STARTER: fields - do not remove comment
DesignState get designState;
CreditState get creditState;
UserState get userState;

View File

@ -52,6 +52,9 @@ class _$UserCompanyStateSerializer
'quoteState',
serializers.serialize(object.quoteState,
specifiedType: const FullType(QuoteState)),
'designState',
serializers.serialize(object.designState,
specifiedType: const FullType(DesignState)),
'creditState',
serializers.serialize(object.creditState,
specifiedType: const FullType(CreditState)),
@ -134,6 +137,10 @@ class _$UserCompanyStateSerializer
result.quoteState.replace(serializers.deserialize(value,
specifiedType: const FullType(QuoteState)) as QuoteState);
break;
case 'designState':
result.designState.replace(serializers.deserialize(value,
specifiedType: const FullType(DesignState)) as DesignState);
break;
case 'creditState':
result.creditState.replace(serializers.deserialize(value,
specifiedType: const FullType(CreditState)) as CreditState);
@ -321,6 +328,8 @@ class _$UserCompanyState extends UserCompanyState {
@override
final QuoteState quoteState;
@override
final DesignState designState;
@override
final CreditState creditState;
@override
final UserState userState;
@ -347,6 +356,7 @@ class _$UserCompanyState extends UserCompanyState {
this.projectState,
this.paymentState,
this.quoteState,
this.designState,
this.creditState,
this.userState,
this.taxRateState,
@ -383,6 +393,9 @@ class _$UserCompanyState extends UserCompanyState {
if (quoteState == null) {
throw new BuiltValueNullFieldError('UserCompanyState', 'quoteState');
}
if (designState == null) {
throw new BuiltValueNullFieldError('UserCompanyState', 'designState');
}
if (creditState == null) {
throw new BuiltValueNullFieldError('UserCompanyState', 'creditState');
}
@ -424,6 +437,7 @@ class _$UserCompanyState extends UserCompanyState {
projectState == other.projectState &&
paymentState == other.paymentState &&
quoteState == other.quoteState &&
designState == other.designState &&
creditState == other.creditState &&
userState == other.userState &&
taxRateState == other.taxRateState &&
@ -449,21 +463,24 @@ class _$UserCompanyState extends UserCompanyState {
$jc(
$jc(
$jc(
0,
userCompany
$jc(
0,
userCompany
.hashCode),
documentState
.hashCode),
documentState
productState
.hashCode),
productState
clientState
.hashCode),
clientState.hashCode),
invoiceState.hashCode),
expenseState.hashCode),
vendorState.hashCode),
taskState.hashCode),
projectState.hashCode),
paymentState.hashCode),
quoteState.hashCode),
invoiceState.hashCode),
expenseState.hashCode),
vendorState.hashCode),
taskState.hashCode),
projectState.hashCode),
paymentState.hashCode),
quoteState.hashCode),
designState.hashCode),
creditState.hashCode),
userState.hashCode),
taxRateState.hashCode),
@ -485,6 +502,7 @@ class _$UserCompanyState extends UserCompanyState {
..add('projectState', projectState)
..add('paymentState', paymentState)
..add('quoteState', quoteState)
..add('designState', designState)
..add('creditState', creditState)
..add('userState', userState)
..add('taxRateState', taxRateState)
@ -563,6 +581,12 @@ class UserCompanyStateBuilder
set quoteState(QuoteStateBuilder quoteState) =>
_$this._quoteState = quoteState;
DesignStateBuilder _designState;
DesignStateBuilder get designState =>
_$this._designState ??= new DesignStateBuilder();
set designState(DesignStateBuilder designState) =>
_$this._designState = designState;
CreditStateBuilder _creditState;
CreditStateBuilder get creditState =>
_$this._creditState ??= new CreditStateBuilder();
@ -607,6 +631,7 @@ class UserCompanyStateBuilder
_projectState = _$v.projectState?.toBuilder();
_paymentState = _$v.paymentState?.toBuilder();
_quoteState = _$v.quoteState?.toBuilder();
_designState = _$v.designState?.toBuilder();
_creditState = _$v.creditState?.toBuilder();
_userState = _$v.userState?.toBuilder();
_taxRateState = _$v.taxRateState?.toBuilder();
@ -647,6 +672,7 @@ class UserCompanyStateBuilder
projectState: projectState.build(),
paymentState: paymentState.build(),
quoteState: quoteState.build(),
designState: designState.build(),
creditState: creditState.build(),
userState: userState.build(),
taxRateState: taxRateState.build(),
@ -677,6 +703,8 @@ class UserCompanyStateBuilder
paymentState.build();
_$failedField = 'quoteState';
quoteState.build();
_$failedField = 'designState';
designState.build();
_$failedField = 'creditState';
creditState.build();
_$failedField = 'userState';

View File

@ -35,10 +35,10 @@ class EditCredit extends AbstractNavigatorAction
implements PersistUI, PersistPrefs {
EditCredit(
{this.credit,
@required NavigatorState navigator,
this.creditItemIndex,
this.completer,
this.force = false})
@required NavigatorState navigator,
this.creditItemIndex,
this.completer,
this.force = false})
: super(navigator: navigator);
final InvoiceEntity credit;
@ -360,14 +360,14 @@ class FilterCreditsByCustom4 implements PersistUI {
Future handleCreditAction(
BuildContext context, List<BaseEntity> credits, EntityAction action) async {
assert(
[
EntityAction.restore,
EntityAction.archive,
EntityAction.delete,
EntityAction.toggleMultiselect
].contains(action) ||
credits.length == 1,
'Cannot perform this action on more than one credit');
[
EntityAction.restore,
EntityAction.archive,
EntityAction.delete,
EntityAction.toggleMultiselect
].contains(action) ||
credits.length == 1,
'Cannot perform this action on more than one credit');
final store = StoreProvider.of<AppState>(context);
final localization = AppLocalization.of(context);
@ -402,7 +402,7 @@ Future handleCreditAction(
case EntityAction.sendEmail:
store.dispatch(ShowEmailCredit(
completer:
snackBarCompleter<Null>(context, localization.emailedCredit),
snackBarCompleter<Null>(context, localization.emailedCredit),
credit: credit,
context: context));
break;

View File

@ -120,7 +120,7 @@ Middleware<AppState> _showEmailCredit() {
if (isMobile(action.context)) {
final emailWasSent =
await Navigator.of(action.context).pushNamed(CreditEmailScreen.route);
await Navigator.of(action.context).pushNamed(CreditEmailScreen.route);
if (action.completer != null && emailWasSent != null && emailWasSent) {
action.completer.complete(null);
@ -133,10 +133,10 @@ Middleware<AppState> _archiveCredit(CreditRepository repository) {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as ArchiveCreditsRequest;
final prevCredits =
action.creditIds.map((id) => store.state.creditState.map[id]).toList();
action.creditIds.map((id) => store.state.creditState.map[id]).toList();
repository
.bulkAction(
store.state.credentials, action.creditIds, EntityAction.archive)
store.state.credentials, action.creditIds, EntityAction.archive)
.then((List<InvoiceEntity> credits) {
store.dispatch(ArchiveCreditsSuccess(credits));
if (action.completer != null) {
@ -158,11 +158,11 @@ Middleware<AppState> _deleteCredit(CreditRepository repository) {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as DeleteCreditsRequest;
final prevCredits =
action.creditIds.map((id) => store.state.creditState.map[id]).toList();
action.creditIds.map((id) => store.state.creditState.map[id]).toList();
repository
.bulkAction(
store.state.credentials, action.creditIds, EntityAction.delete)
store.state.credentials, action.creditIds, EntityAction.delete)
.then((List<InvoiceEntity> credits) {
store.dispatch(DeleteCreditsSuccess(credits));
if (action.completer != null) {
@ -184,11 +184,11 @@ Middleware<AppState> _restoreCredit(CreditRepository repository) {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as RestoreCreditsRequest;
final prevCredits =
action.creditIds.map((id) => store.state.creditState.map[id]).toList();
action.creditIds.map((id) => store.state.creditState.map[id]).toList();
repository
.bulkAction(
store.state.credentials, action.creditIds, EntityAction.restore)
store.state.credentials, action.creditIds, EntityAction.restore)
.then((List<InvoiceEntity> credits) {
store.dispatch(RestoreCreditsSuccess(credits));
if (action.completer != null) {
@ -235,7 +235,7 @@ Middleware<AppState> _emailCredit(CreditRepository repository) {
final origCredit = store.state.creditState.map[action.creditId];
repository
.emailCredit(store.state.credentials, origCredit, action.template,
action.subject, action.body)
action.subject, action.body)
.then((void _) {
store.dispatch(EmailCreditSuccess());
if (action.completer != null) {
@ -292,7 +292,9 @@ Middleware<AppState> _loadCredit(CreditRepository repository) {
}
store.dispatch(LoadCreditRequest());
repository.loadItem(store.state.credentials, action.creditId).then((credit) {
repository
.loadItem(store.state.credentials, action.creditId)
.then((credit) {
store.dispatch(LoadCreditSuccess(credit));
if (action.completer != null) {

View File

@ -33,8 +33,9 @@ String filtercreditDropdownReducer(
Reducer<String> selectedIdReducer = combineReducers([
TypedReducer<String, ViewCredit>((selectedId, action) => action.creditId),
TypedReducer<String, AddCreditSuccess>(
(selectedId, action) => action.credit.id),
TypedReducer<String, ShowEmailCredit>((selectedId, action) => action.credit.id),
(selectedId, action) => action.credit.id),
TypedReducer<String, ShowEmailCredit>(
(selectedId, action) => action.credit.id),
TypedReducer<String, SelectCompany>((selectedId, action) => ''),
]);
@ -97,7 +98,7 @@ InvoiceEntity _updateEditing(InvoiceEntity credit, dynamic action) {
InvoiceEntity _addCreditItem(InvoiceEntity credit, AddCreditItem action) {
return credit.rebuild(
(b) => b..lineItems.add(action.creditItem ?? InvoiceItemEntity()));
(b) => b..lineItems.add(action.creditItem ?? InvoiceItemEntity()));
}
InvoiceEntity _addCreditItems(InvoiceEntity credit, AddCreditItems action) {
@ -227,7 +228,8 @@ ListUIState _addToListMultiselect(
ListUIState _removeFromListMultiselect(
ListUIState creditListState, RemoveFromCreditMultiselect action) {
return creditListState.rebuild((b) => b..selectedIds.remove(action.entity.id));
return creditListState
.rebuild((b) => b..selectedIds.remove(action.entity.id));
}
ListUIState _clearListMultiselect(
@ -368,5 +370,6 @@ CreditState _updateCredit(CreditState creditState, dynamic action) {
return creditState.rebuild((b) => b..map[action.credit.id] = action.credit);
}
CreditState _setLoadedCredits(CreditState creditState, LoadCreditsSuccess action) =>
CreditState _setLoadedCredits(
CreditState creditState, LoadCreditsSuccess action) =>
creditState.loadCredits(action.credits);

View File

@ -8,10 +8,11 @@ ClientEntity creditClientSelector(
return clientMap[credit.clientId];
}
var memoizedFilteredCreditList = memo4((BuiltMap<String, InvoiceEntity> creditMap,
BuiltList<String> creditList,
BuiltMap<String, ClientEntity> clientMap,
ListUIState creditListState) =>
var memoizedFilteredCreditList = memo4((BuiltMap<String, InvoiceEntity>
creditMap,
BuiltList<String> creditList,
BuiltMap<String, ClientEntity> clientMap,
ListUIState creditListState) =>
filteredCreditsSelector(creditMap, creditList, clientMap, creditListState));
List<String> filteredCreditsSelector(
@ -64,7 +65,7 @@ List<String> filteredCreditsSelector(
}
var memoizedCreditStatsForClient = memo2(
(String clientId, BuiltMap<String, InvoiceEntity> creditMap) =>
(String clientId, BuiltMap<String, InvoiceEntity> creditMap) =>
creditStatsForClient(clientId, creditMap));
EntityStats creditStatsForClient(
@ -85,13 +86,13 @@ EntityStats creditStatsForClient(
}
var memoizedCreditStatsForUser = memo2(
(String userId, BuiltMap<String, InvoiceEntity> creditMap) =>
(String userId, BuiltMap<String, InvoiceEntity> creditMap) =>
creditStatsForUser(userId, creditMap));
EntityStats creditStatsForUser(
String userId,
BuiltMap<String, InvoiceEntity> creditMap,
) {
String userId,
BuiltMap<String, InvoiceEntity> creditMap,
) {
int countActive = 0;
int countArchived = 0;
creditMap.forEach((creditId, credit) {
@ -108,5 +109,5 @@ EntityStats creditStatsForUser(
}
bool hasCreditChanges(
InvoiceEntity credit, BuiltMap<String, InvoiceEntity> creditMap) =>
InvoiceEntity credit, BuiltMap<String, InvoiceEntity> creditMap) =>
credit.isNew ? credit.isChanged : credit != creditMap[credit.id];

View File

@ -0,0 +1,335 @@
import 'dart:async';
import 'package:built_collection/built_collection.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/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/settings/settings_actions.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/utils/platforms.dart';
class ViewDesignList extends AbstractNavigatorAction implements PersistUI {
ViewDesignList({
@required NavigatorState navigator,
this.force = false,
}) : super(navigator: navigator);
final bool force;
}
class ViewDesign extends AbstractNavigatorAction
implements PersistUI, PersistPrefs {
ViewDesign({
@required NavigatorState navigator,
@required this.designId,
this.force = false,
}) : super(navigator: navigator);
final String designId;
final bool force;
}
class EditDesign extends AbstractNavigatorAction
implements PersistUI, PersistPrefs {
EditDesign(
{@required this.design,
@required NavigatorState navigator,
this.completer,
this.cancelCompleter,
this.force = false})
: super(navigator: navigator);
final DesignEntity design;
final Completer completer;
final Completer cancelCompleter;
final bool force;
}
class UpdateDesign implements PersistUI {
UpdateDesign(this.design);
final DesignEntity design;
}
class LoadDesign {
LoadDesign({this.completer, this.designId});
final Completer completer;
final String designId;
}
class LoadDesignActivity {
LoadDesignActivity({this.completer, this.designId});
final Completer completer;
final String designId;
}
class LoadDesigns {
LoadDesigns({this.completer, this.force = false});
final Completer completer;
final bool force;
}
class LoadDesignRequest implements StartLoading {}
class LoadDesignFailure implements StopLoading {
LoadDesignFailure(this.error);
final dynamic error;
@override
String toString() {
return 'LoadDesignFailure{error: $error}';
}
}
class LoadDesignSuccess implements StopLoading, PersistData {
LoadDesignSuccess(this.design);
final DesignEntity design;
@override
String toString() {
return 'LoadDesignSuccess{design: $design}';
}
}
class LoadDesignsRequest implements StartLoading {}
class LoadDesignsFailure implements StopLoading {
LoadDesignsFailure(this.error);
final dynamic error;
@override
String toString() {
return 'LoadDesignsFailure{error: $error}';
}
}
class LoadDesignsSuccess implements StopLoading, PersistData {
LoadDesignsSuccess(this.designs);
final BuiltList<DesignEntity> designs;
@override
String toString() {
return 'LoadDesignsSuccess{designs: $designs}';
}
}
class SaveDesignRequest implements StartSaving {
SaveDesignRequest({this.completer, this.design});
final Completer completer;
final DesignEntity design;
}
class SaveDesignSuccess implements StopSaving, PersistData, PersistUI {
SaveDesignSuccess(this.design);
final DesignEntity design;
}
class AddDesignSuccess implements StopSaving, PersistData, PersistUI {
AddDesignSuccess(this.design);
final DesignEntity design;
}
class SaveDesignFailure implements StopSaving {
SaveDesignFailure(this.error);
final Object error;
}
class ArchiveDesignsRequest implements StartSaving {
ArchiveDesignsRequest(this.completer, this.designIds);
final Completer completer;
final List<String> designIds;
}
class ArchiveDesignsSuccess implements StopSaving, PersistData {
ArchiveDesignsSuccess(this.designs);
final List<DesignEntity> designs;
}
class ArchiveDesignsFailure implements StopSaving {
ArchiveDesignsFailure(this.designs);
final List<DesignEntity> designs;
}
class DeleteDesignsRequest implements StartSaving {
DeleteDesignsRequest(this.completer, this.designIds);
final Completer completer;
final List<String> designIds;
}
class DeleteDesignsSuccess implements StopSaving, PersistData {
DeleteDesignsSuccess(this.designs);
final List<DesignEntity> designs;
}
class DeleteDesignsFailure implements StopSaving {
DeleteDesignsFailure(this.designs);
final List<DesignEntity> designs;
}
class RestoreDesignsRequest implements StartSaving {
RestoreDesignsRequest(this.completer, this.designIds);
final Completer completer;
final List<String> designIds;
}
class RestoreDesignsSuccess implements StopSaving, PersistData {
RestoreDesignsSuccess(this.design);
final List<DesignEntity> designs;
}
class RestoreDesignsFailure implements StopSaving {
RestoreDesignsFailure(this.design);
final List<DesignEntity> designs;
}
class FilterDesigns implements PersistUI {
FilterDesigns(this.filter);
final String filter;
}
class SortDesigns implements PersistUI {
SortDesigns(this.field);
final String field;
}
class FilterDesignsByState implements PersistUI {
FilterDesignsByState(this.state);
final EntityState state;
}
class FilterDesignsByCustom1 implements PersistUI {
FilterDesignsByCustom1(this.value);
final String value;
}
class FilterDesignsByCustom2 implements PersistUI {
FilterDesignsByCustom2(this.value);
final String value;
}
class FilterDesignsByCustom3 implements PersistUI {
FilterDesignsByCustom3(this.value);
final String value;
}
class FilterDesignsByCustom4 implements PersistUI {
FilterDesignsByCustom4(this.value);
final String value;
}
class FilterDesignsByEntity implements PersistUI {
FilterDesignsByEntity({this.entityId, this.entityType});
final String entityId;
final EntityType entityType;
}
void handleDesignAction(
BuildContext context, List<BaseEntity> designs, EntityAction action) {
if (designs.isEmpty) {
return;
}
final store = StoreProvider.of<AppState>(context);
final state = store.state;
final CompanyEntity company = state.company;
final localization = AppLocalization.of(context);
final design = designs.first as DesignEntity;
final designIds = designs.map((design) => design.id).toList();
switch (action) {
case EntityAction.edit:
editEntity(context: context, entity: design);
break;
case EntityAction.restore:
store.dispatch(RestoreDesignsRequest(
snackBarCompleter<Null>(context, localization.restoredDesign),
designIds));
break;
case EntityAction.archive:
store.dispatch(ArchiveDesignsRequest(
snackBarCompleter<Null>(context, localization.archivedDesign),
designIds));
break;
case EntityAction.delete:
store.dispatch(DeleteDesignsRequest(
snackBarCompleter<Null>(context, localization.deletedDesign),
designIds));
break;
case EntityAction.toggleMultiselect:
if (!store.state.designListState.isInMultiselect()) {
store.dispatch(StartDesignMultiselect(context: context));
}
if (designs.isEmpty) {
break;
}
for (final design in designs) {
if (!store.state.designListState.isSelected(design.id)) {
store.dispatch(
AddToDesignMultiselect(context: context, entity: design));
} else {
store.dispatch(
RemoveFromDesignMultiselect(context: context, entity: design));
}
}
break;
}
}
class StartDesignMultiselect {
StartDesignMultiselect({@required this.context});
final BuildContext context;
}
class AddToDesignMultiselect {
AddToDesignMultiselect({@required this.context, @required this.entity});
final BuildContext context;
final BaseEntity entity;
}
class RemoveFromDesignMultiselect {
RemoveFromDesignMultiselect({@required this.context, @required this.entity});
final BuildContext context;
final BaseEntity entity;
}
class ClearDesignMultiselect {
ClearDesignMultiselect({@required this.context});
final BuildContext context;
}

View File

@ -0,0 +1,275 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:redux/redux.dart';
import 'package:invoiceninja_flutter/utils/platforms.dart';
import 'package:invoiceninja_flutter/redux/app/app_middleware.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
import 'package:invoiceninja_flutter/ui/design/design_screen.dart';
import 'package:invoiceninja_flutter/ui/design/edit/design_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/design/view/design_view_vm.dart';
import 'package:invoiceninja_flutter/redux/design/design_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/data/repositories/design_repository.dart';
List<Middleware<AppState>> createStoreDesignsMiddleware([
DesignRepository repository = const DesignRepository(),
]) {
final viewDesignList = _viewDesignList();
final viewDesign = _viewDesign();
final editDesign = _editDesign();
final loadDesigns = _loadDesigns(repository);
final loadDesign = _loadDesign(repository);
final saveDesign = _saveDesign(repository);
final archiveDesign = _archiveDesign(repository);
final deleteDesign = _deleteDesign(repository);
final restoreDesign = _restoreDesign(repository);
return [
TypedMiddleware<AppState, ViewDesignList>(viewDesignList),
TypedMiddleware<AppState, ViewDesign>(viewDesign),
TypedMiddleware<AppState, EditDesign>(editDesign),
TypedMiddleware<AppState, LoadDesigns>(loadDesigns),
TypedMiddleware<AppState, LoadDesign>(loadDesign),
TypedMiddleware<AppState, SaveDesignRequest>(saveDesign),
TypedMiddleware<AppState, ArchiveDesignRequest>(archiveDesign),
TypedMiddleware<AppState, DeleteDesignRequest>(deleteDesign),
TypedMiddleware<AppState, RestoreDesignRequest>(restoreDesign),
];
}
Middleware<AppState> _editDesign() {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as EditDesign;
if (!action.force &&
hasChanges(store: store, context: action.context, action: action)) {
return;
}
next(action);
store.dispatch(UpdateCurrentRoute(DesignEditScreen.route));
if (isMobile(action.context)) {
action.navigator.pushNamed(DesignEditScreen.route);
}
};
}
Middleware<AppState> _viewDesign() {
return (Store<AppState> store, dynamic dynamicAction,
NextDispatcher next) async {
final action = dynamicAction as ViewDesign;
if (!action.force &&
hasChanges(store: store, context: action.context, action: action)) {
return;
}
next(action);
store.dispatch(UpdateCurrentRoute(DesignViewScreen.route));
if (isMobile(action.context)) {
Navigator.of(action.context).pushNamed(DesignViewScreen.route);
}
};
}
Middleware<AppState> _viewDesignList() {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as ViewDesignList;
if (!action.force &&
hasChanges(store: store, context: action.context, action: action)) {
return;
}
next(action);
if (store.state.designState.isStale) {
store.dispatch(LoadDesigns());
}
store.dispatch(UpdateCurrentRoute(DesignScreen.route));
if (isMobile(action.context)) {
Navigator.of(action.context).pushNamedAndRemoveUntil(
DesignScreen.route, (Route<dynamic> route) => false);
}
};
}
Middleware<AppState> _archiveDesign(DesignRepository repository) {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as ArchiveDesignsRequest;
final prevDesigns =
action.designIds.map((id) => store.state.designState.map[id]).toList();
repository
.bulkAction(
store.state.credentials, action.designIds, EntityAction.archive)
.then((List<DesignEntity> designs) {
store.dispatch(ArchiveDesignsSuccess(designs));
if (action.completer != null) {
action.completer.complete(null);
}
}).catchError((Object error) {
print(error);
store.dispatch(ArchiveDesignsFailure(prevDesigns));
if (action.completer != null) {
action.completer.completeError(error);
}
});
next(action);
};
}
Middleware<AppState> _deleteDesign(DesignRepository repository) {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as DeleteDesignsRequest;
final prevDesigns =
action.designIds.map((id) => store.state.designState.map[id]).toList();
repository
.bulkAction(
store.state.credentials, action.designIds, EntityAction.delete)
.then((List<DesignEntity> designs) {
store.dispatch(DeleteDesignsSuccess(designs));
if (action.completer != null) {
action.completer.complete(null);
}
}).catchError((Object error) {
print(error);
store.dispatch(DeleteDesignsFailure(prevDesigns));
if (action.completer != null) {
action.completer.completeError(error);
}
});
next(action);
};
}
Middleware<AppState> _restoreDesign(DesignRepository repository) {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as RestoreDesignsRequest;
final prevDesigns =
action.designIds.map((id) => store.state.designState.map[id]).toList();
repository
.bulkAction(
store.state.credentials, action.designIds, EntityAction.restore)
.then((List<DesignEntity> designs) {
store.dispatch(RestoreDesignSuccess(designs));
if (action.completer != null) {
action.completer.complete(null);
}
}).catchError((Object error) {
print(error);
store.dispatch(RestoreDesignFailure(prevDesigns));
if (action.completer != null) {
action.completer.completeError(error);
}
});
next(action);
};
}
Middleware<AppState> _saveDesign(DesignRepository repository) {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as SaveDesignRequest;
repository
.saveData(store.state.credentials, action.design)
.then((DesignEntity design) {
if (action.design.isNew) {
store.dispatch(AddDesignSuccess(design));
} else {
store.dispatch(SaveDesignSuccess(design));
}
action.completer.complete(design);
final designUIState = store.state.designUIState;
if (designUIState.saveCompleter != null) {
designUIState.saveCompleter.complete(design);
}
}).catchError((Object error) {
print(error);
store.dispatch(SaveDesignFailure(error));
action.completer.completeError(error);
});
next(action);
};
}
Middleware<AppState> _loadDesign(DesignRepository repository) {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as LoadDesign;
final AppState state = store.state;
if (state.isLoading) {
next(action);
return;
}
store.dispatch(LoadDesignRequest());
repository.loadItem(state.credentials, action.designId).then((design) {
store.dispatch(LoadDesignSuccess(design));
if (action.completer != null) {
action.completer.complete(null);
}
}).catchError((Object error) {
print(error);
store.dispatch(LoadDesignFailure(error));
if (action.completer != null) {
action.completer.completeError(error);
}
});
next(action);
};
}
Middleware<AppState> _loadDesigns(DesignRepository repository) {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as LoadDesigns;
final AppState state = store.state;
if (!state.designState.isStale && !action.force) {
next(action);
return;
}
if (state.isLoading) {
next(action);
return;
}
final int updatedAt = (state.designState.lastUpdated / 1000).round();
store.dispatch(LoadDesignsRequest());
repository.loadList(state.credentials, updatedAt).then((data) {
store.dispatch(LoadDesignsSuccess(data));
if (action.completer != null) {
action.completer.complete(null);
}
/*
if (state.productState.isStale) {
store.dispatch(LoadProducts());
}
*/
}).catchError((Object error) {
print(error);
store.dispatch(LoadDesignsFailure(error));
if (action.completer != null) {
action.completer.completeError(error);
}
});
next(action);
};
}

View File

@ -0,0 +1,275 @@
import 'package:redux/redux.dart';
import 'package:built_collection/built_collection.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/company/company_actions.dart';
import 'package:invoiceninja_flutter/redux/ui/entity_ui_state.dart';
import 'package:invoiceninja_flutter/redux/design/design_actions.dart';
import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart';
import 'package:invoiceninja_flutter/redux/design/design_actions.dart';
import 'package:invoiceninja_flutter/redux/design/design_state.dart';
import 'package:invoiceninja_flutter/data/models/entities.dart';
EntityUIState designUIReducer(DesignUIState state, dynamic action) {
return state.rebuild((b) => b
..listUIState.replace(designListReducer(state.listUIState, action))
..editing.replace(editingReducer(state.editing, action))
..selectedId = selectedIdReducer(state.selectedId, action));
}
Reducer<String> selectedIdReducer = combineReducers([
TypedReducer<String, ViewDesign>(
(String selectedId, dynamic action) => action.designId),
TypedReducer<String, AddDesignSuccess>(
(String selectedId, dynamic action) => action.design.id),
TypedReducer<String, SelectCompany>((selectedId, action) => ''),
]);
final editingReducer = combineReducers<DesignEntity>([
TypedReducer<DesignEntity, SaveDesignSuccess>(_updateEditing),
TypedReducer<DesignEntity, AddDesignSuccess>(_updateEditing),
TypedReducer<DesignEntity, RestoreDesignSuccess>((designs, action) {
return action.designs[0];
}),
TypedReducer<DesignEntity, ArchiveDesignSuccess>((designs, action) {
return action.designs[0];
}),
TypedReducer<DesignEntity, DeleteDesignSuccess>((designs, action) {
return action.designs[0];
}),
TypedReducer<DesignEntity, EditDesign>(_updateEditing),
TypedReducer<DesignEntity, UpdateDesign>((design, action) {
return action.design.rebuild((b) => b..isChanged = true);
}),
TypedReducer<DesignEntity, SelectCompany>(_clearEditing),
TypedReducer<DesignEntity, DiscardChanges>(_clearEditing),
]);
DesignEntity _clearEditing(DesignEntity design, dynamic action) {
return DesignEntity();
}
DesignEntity _updateEditing(DesignEntity design, dynamic action) {
return action.design;
}
final designListReducer = combineReducers<ListUIState>([
TypedReducer<ListUIState, SortDesigns>(_sortDesigns),
TypedReducer<ListUIState, FilterDesignsByState>(_filterDesignsByState),
TypedReducer<ListUIState, FilterDesigns>(_filterDesigns),
TypedReducer<ListUIState, FilterDesignsByCustom1>(_filterDesignsByCustom1),
TypedReducer<ListUIState, FilterDesignsByCustom2>(_filterDesignsByCustom2),
TypedReducer<ListUIState, FilterDesignsByEntity>(_filterDesignsByClient),
TypedReducer<ListUIState, StartDesignMultiselect>(_startListMultiselect),
TypedReducer<ListUIState, AddToDesignMultiselect>(_addToListMultiselect),
TypedReducer<ListUIState, RemoveFromDesignMultiselect>(
_removeFromListMultiselect),
TypedReducer<ListUIState, ClearDesignMultiselect>(_clearListMultiselect),
]);
ListUIState _filterDesignsByClient(
ListUIState designListState, FilterDesignsByEntity action) {
return designListState.rebuild((b) => b
..filterEntityId = action.entityId
..filterEntityType = action.entityType);
}
ListUIState _filterDesignsByCustom1(
ListUIState designListState, FilterDesignsByCustom1 action) {
if (designListState.custom1Filters.contains(action.value)) {
return designListState
.rebuild((b) => b..custom1Filters.remove(action.value));
} else {
return designListState.rebuild((b) => b..custom1Filters.add(action.value));
}
}
ListUIState _filterDesignsByCustom2(
ListUIState designListState, FilterDesignsByCustom2 action) {
if (designListState.custom2Filters.contains(action.value)) {
return designListState
.rebuild((b) => b..custom2Filters.remove(action.value));
} else {
return designListState.rebuild((b) => b..custom2Filters.add(action.value));
}
}
ListUIState _filterDesignsByState(
ListUIState designListState, FilterDesignsByState action) {
if (designListState.stateFilters.contains(action.state)) {
return designListState.rebuild((b) => b..stateFilters.remove(action.state));
} else {
return designListState.rebuild((b) => b..stateFilters.add(action.state));
}
}
ListUIState _filterDesigns(ListUIState designListState, FilterDesigns action) {
return designListState.rebuild((b) => b
..filter = action.filter
..filterClearedAt = action.filter == null
? DateTime.now().millisecondsSinceEpoch
: designListState.filterClearedAt);
}
ListUIState _sortDesigns(ListUIState designListState, SortDesigns action) {
return designListState.rebuild((b) => b
..sortAscending = b.sortField != action.field || !b.sortAscending
..sortField = action.field);
}
ListUIState _startListMultiselect(
ListUIState productListState, StartDesignMultiselect action) {
return productListState.rebuild((b) => b..selectedIds = ListBuilder());
}
ListUIState _addToListMultiselect(
ListUIState productListState, AddToDesignMultiselect action) {
return productListState.rebuild((b) => b..selectedIds.add(action.entity.id));
}
ListUIState _removeFromListMultiselect(
ListUIState productListState, RemoveFromDesignMultiselect action) {
return productListState
.rebuild((b) => b..selectedIds.remove(action.entity.id));
}
ListUIState _clearListMultiselect(
ListUIState productListState, ClearDesignMultiselect action) {
return productListState.rebuild((b) => b..selectedIds = null);
}
final designsReducer = combineReducers<DesignState>([
TypedReducer<DesignState, SaveDesignSuccess>(_updateDesign),
TypedReducer<DesignState, AddDesignSuccess>(_addDesign),
TypedReducer<DesignState, LoadDesignsSuccess>(_setLoadedDesigns),
TypedReducer<DesignState, LoadDesignSuccess>(_setLoadedDesign),
TypedReducer<DesignState, ArchiveDesignRequest>(_archiveDesignRequest),
TypedReducer<DesignState, ArchiveDesignSuccess>(_archiveDesignSuccess),
TypedReducer<DesignState, ArchiveDesignFailure>(_archiveDesignFailure),
TypedReducer<DesignState, DeleteDesignRequest>(_deleteDesignRequest),
TypedReducer<DesignState, DeleteDesignSuccess>(_deleteDesignSuccess),
TypedReducer<DesignState, DeleteDesignFailure>(_deleteDesignFailure),
TypedReducer<DesignState, RestoreDesignRequest>(_restoreDesignRequest),
TypedReducer<DesignState, RestoreDesignSuccess>(_restoreDesignSuccess),
TypedReducer<DesignState, RestoreDesignFailure>(_restoreDesignFailure),
]);
DesignState _archiveDesignRequest(
DesignState designState, ArchiveDesignsRequest action) {
final designs = action.designIds.map((id) => designState.map[id]).toList();
for (int i = 0; i < designs.length; i++) {
designs[i] = designs[i]
.rebuild((b) => b..archivedAt = DateTime.now().millisecondsSinceEpoch);
}
return designState.rebuild((b) {
for (final design in designs) {
b.map[design.id] = design;
}
});
}
DesignState _archiveDesignSuccess(
DesignState designState, ArchiveDesignsSuccess action) {
return designState.rebuild((b) {
for (final design in action.designs) {
b.map[design.id] = design;
}
});
}
DesignState _archiveDesignFailure(
DesignState designState, ArchiveDesignsFailure action) {
return designState.rebuild((b) {
for (final design in action.designs) {
b.map[design.id] = design;
}
});
}
DesignState _deleteDesignRequest(
DesignState designState, DeleteDesignsRequest action) {
final designs = action.designIds.map((id) => designState.map[id]).toList();
for (int i = 0; i < designs.length; i++) {
designs[i] = designs[i].rebuild((b) => b
..archivedAt = DateTime.now().millisecondsSinceEpoch
..isDeleted = true);
}
return designState.rebuild((b) {
for (final design in designs) {
b.map[design.id] = design;
}
});
}
DesignState _deleteDesignSuccess(
DesignState designState, DeleteDesignsSuccess action) {
return designState.rebuild((b) {
for (final design in action.designs) {
b.map[design.id] = design;
}
});
}
DesignState _deleteDesignFailure(
DesignState designState, DeleteDesignsFailure action) {
return designState.rebuild((b) {
for (final design in action.designs) {
b.map[design.id] = design;
}
});
}
DesignState _restoreDesignRequest(
DesignState designState, RestoreDesignsRequest action) {
final designs = action.designIds.map((id) => designState.map[id]).toList();
for (int i = 0; i < designs.length; i++) {
designs[i] = designs[i].rebuild((b) => b
..archivedAt = null
..isDeleted = false);
}
return designState.rebuild((b) {
for (final design in designs) {
b.map[design.id] = design;
}
});
}
DesignState _restoreDesignSuccess(
DesignState designState, RestoreDesignsSuccess action) {
return designState.rebuild((b) {
for (final design in action.designs) {
b.map[design.id] = design;
}
});
}
DesignState _restoreDesignFailure(
DesignState designState, RestoreDesignsFailure action) {
return designState.rebuild((b) {
for (final design in action.designs) {
b.map[design.id] = design;
}
});
}
DesignState _addDesign(DesignState designState, AddDesignSuccess action) {
return designState.rebuild((b) => b
..map[action.design.id] = action.design
..list.add(action.design.id));
}
DesignState _updateDesign(DesignState designState, SaveDesignSuccess action) {
return designState.rebuild((b) => b..map[action.design.id] = action.design);
}
DesignState _setLoadedDesign(
DesignState designState, LoadDesignSuccess action) {
return designState.rebuild((b) => b..map[action.design.id] = action.design);
}
DesignState _setLoadedDesigns(
DesignState designState, LoadDesignsSuccess action) =>
designState.loadDesigns(action.designs);

View File

@ -0,0 +1,65 @@
import 'package:invoiceninja_flutter/data/models/design_model.dart';
import 'package:memoize/memoize.dart';
import 'package:built_collection/built_collection.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart';
var memoizedDropdownDesignList = memo3(
(BuiltMap<String, DesignEntity> designMap, BuiltList<String> designList,
String clientId) =>
dropdownDesignsSelector(designMap, designList, clientId));
List<String> dropdownDesignsSelector(BuiltMap<String, DesignEntity> designMap,
BuiltList<String> designList, String clientId) {
final list = designList.where((designId) {
final design = designMap[designId];
/*
if (clientId != null && clientId > 0 && design.clientId != clientId) {
return false;
}
*/
return design.isActive;
}).toList();
list.sort((designAId, designBId) {
final designA = designMap[designAId];
final designB = designMap[designBId];
return designA.compareTo(designB, DesignFields.name, true);
});
return list;
}
var memoizedFilteredDesignList = memo3(
(BuiltMap<String, DesignEntity> designMap, BuiltList<String> designList,
ListUIState designListState) =>
filteredDesignsSelector(designMap, designList, designListState));
List<String> filteredDesignsSelector(BuiltMap<String, DesignEntity> designMap,
BuiltList<String> designList, ListUIState designListState) {
final list = designList.where((designId) {
final design = designMap[designId];
if (designListState.filterEntityId != null &&
design.entityId != designListState.filterEntityId) {
return false;
} else {}
if (!design.matchesStates(designListState.stateFilters)) {
return false;
}
return design.matchesFilter(designListState.filter);
}).toList();
list.sort((designAId, designBId) {
final designA = designMap[designAId];
final designB = designMap[designBId];
return designA.compareTo(
designB, designListState.sortField, designListState.sortAscending);
});
return list;
}
bool hasDesignChanges(
DesignEntity design, BuiltMap<String, DesignEntity> designMap) =>
design.isNew ? design.isChanged : design != designMap[design.id];

View File

@ -0,0 +1,75 @@
import 'dart:async';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import 'package:built_collection/built_collection.dart';
import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/data/models/design_model.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/data/models/models.dart';
part 'design_state.g.dart';
abstract class DesignState implements Built<DesignState, DesignStateBuilder> {
factory DesignState() {
return _$DesignState._(
lastUpdated: 0,
map: BuiltMap<String, DesignEntity>(),
list: BuiltList<String>(),
);
}
DesignState._();
@nullable
int get lastUpdated;
BuiltMap<String, DesignEntity> get map;
BuiltList<String> get list;
bool get isStale {
if (!isLoaded) {
return true;
}
return DateTime.now().millisecondsSinceEpoch - lastUpdated >
kMillisecondsToRefreshData;
}
bool get isLoaded => lastUpdated != null && lastUpdated > 0;
DesignState loadDesigns(BuiltList<DesignEntity> clients) {
final map = Map<String, DesignEntity>.fromIterable(
clients,
key: (dynamic item) => item.id,
value: (dynamic item) => item,
);
return rebuild((b) => b
..lastUpdated = DateTime.now().millisecondsSinceEpoch
..map.addAll(map)
..list.replace(map.keys));
}
static Serializer<DesignState> get serializer => _$designStateSerializer;
}
abstract class DesignUIState extends Object
with EntityUIState
implements Built<DesignUIState, DesignUIStateBuilder> {
factory DesignUIState() {
return _$DesignUIState._(
listUIState: ListUIState(DesignFields.name),
editing: DesignEntity(),
selectedId: '',
);
}
DesignUIState._();
@nullable
DesignEntity get editing;
@override
bool get isCreatingNew => editing.isNew;
static Serializer<DesignUIState> get serializer => _$designUIStateSerializer;
}

View File

@ -0,0 +1,406 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'design_state.dart';
// **************************************************************************
// BuiltValueGenerator
// **************************************************************************
Serializer<DesignState> _$designStateSerializer = new _$DesignStateSerializer();
Serializer<DesignUIState> _$designUIStateSerializer =
new _$DesignUIStateSerializer();
class _$DesignStateSerializer implements StructuredSerializer<DesignState> {
@override
final Iterable<Type> types = const [DesignState, _$DesignState];
@override
final String wireName = 'DesignState';
@override
Iterable<Object> serialize(Serializers serializers, DesignState object,
{FullType specifiedType = FullType.unspecified}) {
final result = <Object>[
'map',
serializers.serialize(object.map,
specifiedType: const FullType(BuiltMap,
const [const FullType(String), const FullType(DesignEntity)])),
'list',
serializers.serialize(object.list,
specifiedType:
const FullType(BuiltList, const [const FullType(String)])),
];
if (object.lastUpdated != null) {
result
..add('lastUpdated')
..add(serializers.serialize(object.lastUpdated,
specifiedType: const FullType(int)));
}
return result;
}
@override
DesignState deserialize(Serializers serializers, Iterable<Object> serialized,
{FullType specifiedType = FullType.unspecified}) {
final result = new DesignStateBuilder();
final iterator = serialized.iterator;
while (iterator.moveNext()) {
final key = iterator.current as String;
iterator.moveNext();
final dynamic value = iterator.current;
switch (key) {
case 'lastUpdated':
result.lastUpdated = serializers.deserialize(value,
specifiedType: const FullType(int)) as int;
break;
case 'map':
result.map.replace(serializers.deserialize(value,
specifiedType: const FullType(BuiltMap, const [
const FullType(String),
const FullType(DesignEntity)
])));
break;
case 'list':
result.list.replace(serializers.deserialize(value,
specifiedType:
const FullType(BuiltList, const [const FullType(String)]))
as BuiltList<Object>);
break;
}
}
return result.build();
}
}
class _$DesignUIStateSerializer implements StructuredSerializer<DesignUIState> {
@override
final Iterable<Type> types = const [DesignUIState, _$DesignUIState];
@override
final String wireName = 'DesignUIState';
@override
Iterable<Object> serialize(Serializers serializers, DesignUIState object,
{FullType specifiedType = FullType.unspecified}) {
final result = <Object>[
'listUIState',
serializers.serialize(object.listUIState,
specifiedType: const FullType(ListUIState)),
];
if (object.editing != null) {
result
..add('editing')
..add(serializers.serialize(object.editing,
specifiedType: const FullType(DesignEntity)));
}
if (object.selectedId != null) {
result
..add('selectedId')
..add(serializers.serialize(object.selectedId,
specifiedType: const FullType(String)));
}
return result;
}
@override
DesignUIState deserialize(
Serializers serializers, Iterable<Object> serialized,
{FullType specifiedType = FullType.unspecified}) {
final result = new DesignUIStateBuilder();
final iterator = serialized.iterator;
while (iterator.moveNext()) {
final key = iterator.current as String;
iterator.moveNext();
final dynamic value = iterator.current;
switch (key) {
case 'editing':
result.editing.replace(serializers.deserialize(value,
specifiedType: const FullType(DesignEntity)) as DesignEntity);
break;
case 'listUIState':
result.listUIState.replace(serializers.deserialize(value,
specifiedType: const FullType(ListUIState)) as ListUIState);
break;
case 'selectedId':
result.selectedId = serializers.deserialize(value,
specifiedType: const FullType(String)) as String;
break;
}
}
return result.build();
}
}
class _$DesignState extends DesignState {
@override
final int lastUpdated;
@override
final BuiltMap<String, DesignEntity> map;
@override
final BuiltList<String> list;
factory _$DesignState([void Function(DesignStateBuilder) updates]) =>
(new DesignStateBuilder()..update(updates)).build();
_$DesignState._({this.lastUpdated, this.map, this.list}) : super._() {
if (map == null) {
throw new BuiltValueNullFieldError('DesignState', 'map');
}
if (list == null) {
throw new BuiltValueNullFieldError('DesignState', 'list');
}
}
@override
DesignState rebuild(void Function(DesignStateBuilder) updates) =>
(toBuilder()..update(updates)).build();
@override
DesignStateBuilder toBuilder() => new DesignStateBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is DesignState &&
lastUpdated == other.lastUpdated &&
map == other.map &&
list == other.list;
}
@override
int get hashCode {
return $jf(
$jc($jc($jc(0, lastUpdated.hashCode), map.hashCode), list.hashCode));
}
@override
String toString() {
return (newBuiltValueToStringHelper('DesignState')
..add('lastUpdated', lastUpdated)
..add('map', map)
..add('list', list))
.toString();
}
}
class DesignStateBuilder implements Builder<DesignState, DesignStateBuilder> {
_$DesignState _$v;
int _lastUpdated;
int get lastUpdated => _$this._lastUpdated;
set lastUpdated(int lastUpdated) => _$this._lastUpdated = lastUpdated;
MapBuilder<String, DesignEntity> _map;
MapBuilder<String, DesignEntity> get map =>
_$this._map ??= new MapBuilder<String, DesignEntity>();
set map(MapBuilder<String, DesignEntity> map) => _$this._map = map;
ListBuilder<String> _list;
ListBuilder<String> get list => _$this._list ??= new ListBuilder<String>();
set list(ListBuilder<String> list) => _$this._list = list;
DesignStateBuilder();
DesignStateBuilder get _$this {
if (_$v != null) {
_lastUpdated = _$v.lastUpdated;
_map = _$v.map?.toBuilder();
_list = _$v.list?.toBuilder();
_$v = null;
}
return this;
}
@override
void replace(DesignState other) {
if (other == null) {
throw new ArgumentError.notNull('other');
}
_$v = other as _$DesignState;
}
@override
void update(void Function(DesignStateBuilder) updates) {
if (updates != null) updates(this);
}
@override
_$DesignState build() {
_$DesignState _$result;
try {
_$result = _$v ??
new _$DesignState._(
lastUpdated: lastUpdated, map: map.build(), list: list.build());
} catch (_) {
String _$failedField;
try {
_$failedField = 'map';
map.build();
_$failedField = 'list';
list.build();
} catch (e) {
throw new BuiltValueNestedFieldError(
'DesignState', _$failedField, e.toString());
}
rethrow;
}
replace(_$result);
return _$result;
}
}
class _$DesignUIState extends DesignUIState {
@override
final DesignEntity editing;
@override
final ListUIState listUIState;
@override
final String selectedId;
@override
final Completer<SelectableEntity> saveCompleter;
@override
final Completer<Null> cancelCompleter;
factory _$DesignUIState([void Function(DesignUIStateBuilder) updates]) =>
(new DesignUIStateBuilder()..update(updates)).build();
_$DesignUIState._(
{this.editing,
this.listUIState,
this.selectedId,
this.saveCompleter,
this.cancelCompleter})
: super._() {
if (listUIState == null) {
throw new BuiltValueNullFieldError('DesignUIState', 'listUIState');
}
}
@override
DesignUIState rebuild(void Function(DesignUIStateBuilder) updates) =>
(toBuilder()..update(updates)).build();
@override
DesignUIStateBuilder toBuilder() => new DesignUIStateBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is DesignUIState &&
editing == other.editing &&
listUIState == other.listUIState &&
selectedId == other.selectedId &&
saveCompleter == other.saveCompleter &&
cancelCompleter == other.cancelCompleter;
}
@override
int get hashCode {
return $jf($jc(
$jc(
$jc($jc($jc(0, editing.hashCode), listUIState.hashCode),
selectedId.hashCode),
saveCompleter.hashCode),
cancelCompleter.hashCode));
}
@override
String toString() {
return (newBuiltValueToStringHelper('DesignUIState')
..add('editing', editing)
..add('listUIState', listUIState)
..add('selectedId', selectedId)
..add('saveCompleter', saveCompleter)
..add('cancelCompleter', cancelCompleter))
.toString();
}
}
class DesignUIStateBuilder
implements Builder<DesignUIState, DesignUIStateBuilder> {
_$DesignUIState _$v;
DesignEntityBuilder _editing;
DesignEntityBuilder get editing =>
_$this._editing ??= new DesignEntityBuilder();
set editing(DesignEntityBuilder editing) => _$this._editing = editing;
ListUIStateBuilder _listUIState;
ListUIStateBuilder get listUIState =>
_$this._listUIState ??= new ListUIStateBuilder();
set listUIState(ListUIStateBuilder listUIState) =>
_$this._listUIState = listUIState;
String _selectedId;
String get selectedId => _$this._selectedId;
set selectedId(String selectedId) => _$this._selectedId = selectedId;
Completer<SelectableEntity> _saveCompleter;
Completer<SelectableEntity> get saveCompleter => _$this._saveCompleter;
set saveCompleter(Completer<SelectableEntity> saveCompleter) =>
_$this._saveCompleter = saveCompleter;
Completer<Null> _cancelCompleter;
Completer<Null> get cancelCompleter => _$this._cancelCompleter;
set cancelCompleter(Completer<Null> cancelCompleter) =>
_$this._cancelCompleter = cancelCompleter;
DesignUIStateBuilder();
DesignUIStateBuilder get _$this {
if (_$v != null) {
_editing = _$v.editing?.toBuilder();
_listUIState = _$v.listUIState?.toBuilder();
_selectedId = _$v.selectedId;
_saveCompleter = _$v.saveCompleter;
_cancelCompleter = _$v.cancelCompleter;
_$v = null;
}
return this;
}
@override
void replace(DesignUIState other) {
if (other == null) {
throw new ArgumentError.notNull('other');
}
_$v = other as _$DesignUIState;
}
@override
void update(void Function(DesignUIStateBuilder) updates) {
if (updates != null) updates(this);
}
@override
_$DesignUIState build() {
_$DesignUIState _$result;
try {
_$result = _$v ??
new _$DesignUIState._(
editing: _editing?.build(),
listUIState: listUIState.build(),
selectedId: selectedId,
saveCompleter: saveCompleter,
cancelCompleter: cancelCompleter);
} catch (_) {
String _$failedField;
try {
_$failedField = 'editing';
_editing?.build();
_$failedField = 'listUIState';
listUIState.build();
} catch (e) {
throw new BuiltValueNestedFieldError(
'DesignUIState', _$failedField, e.toString());
}
rethrow;
}
replace(_$result);
return _$result;
}
}
// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new

View File

@ -23,6 +23,8 @@ import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
import 'package:invoiceninja_flutter/redux/user/user_actions.dart';
import 'package:invoiceninja_flutter/redux/vendor/vendor_actions.dart';
// STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/design/design_actions.dart';
import 'package:invoiceninja_flutter/redux/credit/credit_actions.dart';
import 'package:redux/redux.dart';
@ -397,6 +399,13 @@ Reducer<BuiltList<HistoryRecord>> historyReducer = combineReducers([
_addToHistory(historyList,
HistoryRecord(id: action.group.id, entityType: EntityType.group))),
// STARTER: history - do not remove comment
TypedReducer<BuiltList<HistoryRecord>, ViewDesign>((historyList, action) =>
_addToHistory(historyList,
HistoryRecord(id: action.designId, entityType: EntityType.design))),
TypedReducer<BuiltList<HistoryRecord>, EditDesign>((historyList, action) =>
_addToHistory(historyList,
HistoryRecord(id: action.design.id, entityType: EntityType.design))),
TypedReducer<BuiltList<HistoryRecord>, ViewCredit>((historyList, action) =>
_addToHistory(historyList,
HistoryRecord(id: action.creditId, entityType: EntityType.credit))),

View File

@ -22,6 +22,8 @@ import 'package:invoiceninja_flutter/redux/quote/quote_reducer.dart';
import 'package:invoiceninja_flutter/redux/task/task_reducer.dart';
import 'package:invoiceninja_flutter/redux/vendor/vendor_reducer.dart';
// STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/design/design_reducer.dart';
import 'package:invoiceninja_flutter/redux/credit/credit_reducer.dart';
import 'package:invoiceninja_flutter/redux/user/user_reducer.dart';
@ -49,6 +51,7 @@ UIState uiReducer(UIState state, dynamic action) {
.replace(dashboardUIReducer(state.dashboardUIState, action))
..reportsUIState.replace(reportsUIReducer(state.reportsUIState, action))
// STARTER: reducer - do not remove comment
..designUIState.replace(designUIReducer(state.designUIState, action))
..creditUIState.replace(creditUIReducer(state.creditUIState, action))
..userUIState.replace(userUIReducer(state.userUIState, action))
..taxRateUIState.replace(taxRateUIReducer(state.taxRateUIState, action))

View File

@ -16,6 +16,8 @@ import 'package:invoiceninja_flutter/redux/task/task_state.dart';
import 'package:invoiceninja_flutter/redux/vendor/vendor_state.dart';
// STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/design/design_state.dart';
import 'package:invoiceninja_flutter/redux/credit/credit_state.dart';
import 'package:invoiceninja_flutter/redux/user/user_state.dart';
@ -37,6 +39,8 @@ abstract class UIState implements Built<UIState, UIStateBuilder> {
clientUIState: ClientUIState(),
invoiceUIState: InvoiceUIState(),
// STARTER: constructor - do not remove comment
designUIState: DesignUIState(),
creditUIState: CreditUIState(),
userUIState: UserUIState(),
@ -77,6 +81,8 @@ abstract class UIState implements Built<UIState, UIStateBuilder> {
InvoiceUIState get invoiceUIState;
// STARTER: properties - do not remove comment
DesignUIState get designUIState;
CreditUIState get creditUIState;
UserUIState get userUIState;

View File

@ -42,6 +42,9 @@ class _$UIStateSerializer implements StructuredSerializer<UIState> {
'invoiceUIState',
serializers.serialize(object.invoiceUIState,
specifiedType: const FullType(InvoiceUIState)),
'designUIState',
serializers.serialize(object.designUIState,
specifiedType: const FullType(DesignUIState)),
'creditUIState',
serializers.serialize(object.creditUIState,
specifiedType: const FullType(CreditUIState)),
@ -142,6 +145,10 @@ class _$UIStateSerializer implements StructuredSerializer<UIState> {
result.invoiceUIState.replace(serializers.deserialize(value,
specifiedType: const FullType(InvoiceUIState)) as InvoiceUIState);
break;
case 'designUIState':
result.designUIState.replace(serializers.deserialize(value,
specifiedType: const FullType(DesignUIState)) as DesignUIState);
break;
case 'creditUIState':
result.creditUIState.replace(serializers.deserialize(value,
specifiedType: const FullType(CreditUIState)) as CreditUIState);
@ -228,6 +235,8 @@ class _$UIState extends UIState {
@override
final InvoiceUIState invoiceUIState;
@override
final DesignUIState designUIState;
@override
final CreditUIState creditUIState;
@override
final UserUIState userUIState;
@ -269,6 +278,7 @@ class _$UIState extends UIState {
this.productUIState,
this.clientUIState,
this.invoiceUIState,
this.designUIState,
this.creditUIState,
this.userUIState,
this.taxRateUIState,
@ -308,6 +318,9 @@ class _$UIState extends UIState {
if (invoiceUIState == null) {
throw new BuiltValueNullFieldError('UIState', 'invoiceUIState');
}
if (designUIState == null) {
throw new BuiltValueNullFieldError('UIState', 'designUIState');
}
if (creditUIState == null) {
throw new BuiltValueNullFieldError('UIState', 'creditUIState');
}
@ -372,6 +385,7 @@ class _$UIState extends UIState {
productUIState == other.productUIState &&
clientUIState == other.clientUIState &&
invoiceUIState == other.invoiceUIState &&
designUIState == other.designUIState &&
creditUIState == other.creditUIState &&
userUIState == other.userUIState &&
taxRateUIState == other.taxRateUIState &&
@ -408,12 +422,12 @@ class _$UIState extends UIState {
$jc(
$jc(
$jc(
$jc($jc($jc($jc($jc(0, selectedCompanyIndex.hashCode), currentRoute.hashCode), previousRoute.hashCode), filter.hashCode),
filterClearedAt.hashCode),
dashboardUIState.hashCode),
productUIState.hashCode),
clientUIState.hashCode),
invoiceUIState.hashCode),
$jc($jc($jc($jc($jc($jc(0, selectedCompanyIndex.hashCode), currentRoute.hashCode), previousRoute.hashCode), filter.hashCode), filterClearedAt.hashCode),
dashboardUIState.hashCode),
productUIState.hashCode),
clientUIState.hashCode),
invoiceUIState.hashCode),
designUIState.hashCode),
creditUIState.hashCode),
userUIState.hashCode),
taxRateUIState.hashCode),
@ -442,6 +456,7 @@ class _$UIState extends UIState {
..add('productUIState', productUIState)
..add('clientUIState', clientUIState)
..add('invoiceUIState', invoiceUIState)
..add('designUIState', designUIState)
..add('creditUIState', creditUIState)
..add('userUIState', userUIState)
..add('taxRateUIState', taxRateUIState)
@ -510,6 +525,12 @@ class UIStateBuilder implements Builder<UIState, UIStateBuilder> {
set invoiceUIState(InvoiceUIStateBuilder invoiceUIState) =>
_$this._invoiceUIState = invoiceUIState;
DesignUIStateBuilder _designUIState;
DesignUIStateBuilder get designUIState =>
_$this._designUIState ??= new DesignUIStateBuilder();
set designUIState(DesignUIStateBuilder designUIState) =>
_$this._designUIState = designUIState;
CreditUIStateBuilder _creditUIState;
CreditUIStateBuilder get creditUIState =>
_$this._creditUIState ??= new CreditUIStateBuilder();
@ -608,6 +629,7 @@ class UIStateBuilder implements Builder<UIState, UIStateBuilder> {
_productUIState = _$v.productUIState?.toBuilder();
_clientUIState = _$v.clientUIState?.toBuilder();
_invoiceUIState = _$v.invoiceUIState?.toBuilder();
_designUIState = _$v.designUIState?.toBuilder();
_creditUIState = _$v.creditUIState?.toBuilder();
_userUIState = _$v.userUIState?.toBuilder();
_taxRateUIState = _$v.taxRateUIState?.toBuilder();
@ -655,6 +677,7 @@ class UIStateBuilder implements Builder<UIState, UIStateBuilder> {
productUIState: productUIState.build(),
clientUIState: clientUIState.build(),
invoiceUIState: invoiceUIState.build(),
designUIState: designUIState.build(),
creditUIState: creditUIState.build(),
userUIState: userUIState.build(),
taxRateUIState: taxRateUIState.build(),
@ -680,6 +703,8 @@ class UIStateBuilder implements Builder<UIState, UIStateBuilder> {
clientUIState.build();
_$failedField = 'invoiceUIState';
invoiceUIState.build();
_$failedField = 'designUIState';
designUIState.build();
_$failedField = 'creditUIState';
creditUIState.build();
_$failedField = 'userUIState';

View File

@ -28,6 +28,8 @@ import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/utils/platforms.dart';
import 'package:url_launcher/url_launcher.dart';
// STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/design/design_actions.dart';
class MenuDrawer extends StatelessWidget {
const MenuDrawer({
@ -256,6 +258,14 @@ class MenuDrawer extends StatelessWidget {
title: localization.expenses,
),
// STARTER: menu - do not remove comment
DrawerTile(
company: company,
entityType: EntityType.design,
icon: getEntityIcon(EntityType.design),
title: localization.designs,
},
),
DrawerTile(
company: company,
icon: getEntityIcon(EntityType.reports),

View File

@ -54,22 +54,22 @@ class CreditListVM extends EntityListVM {
List<String> tableColumns,
EntityType entityType,
}) : super(
state: state,
user: user,
listState: listState,
invoiceList: invoiceList,
invoiceMap: invoiceMap,
clientMap: clientMap,
filter: filter,
isLoading: isLoading,
isLoaded: isLoaded,
onInvoiceTap: onInvoiceTap,
onRefreshed: onRefreshed,
onClearEntityFilterPressed: onClearEntityFilterPressed,
onViewEntityFilterPressed: onViewEntityFilterPressed,
tableColumns: tableColumns,
entityType: entityType,
);
state: state,
user: user,
listState: listState,
invoiceList: invoiceList,
invoiceMap: invoiceMap,
clientMap: clientMap,
filter: filter,
isLoading: isLoading,
isLoaded: isLoaded,
onInvoiceTap: onInvoiceTap,
onRefreshed: onRefreshed,
onClearEntityFilterPressed: onClearEntityFilterPressed,
onViewEntityFilterPressed: onViewEntityFilterPressed,
tableColumns: tableColumns,
entityType: entityType,
);
static CreditListVM fromStore(Store<AppState> store) {
Future<Null> _handleRefresh(BuildContext context) {
@ -105,7 +105,7 @@ class CreditListVM extends EntityListVM {
entityId: state.creditListState.filterEntityId,
entityType: state.creditListState.filterEntityType),
onEntityAction: (BuildContext context, List<BaseEntity> credits,
EntityAction action) =>
EntityAction action) =>
handleCreditAction(context, credits, action),
tableColumns: CreditPresenter.getTableFields(state.userCompany),
entityType: EntityType.credit,

View File

@ -29,7 +29,7 @@ class CreditPresenter extends EntityPresenter {
return Text(credit.number);
case CreditFields.client:
return Text((state.clientState.map[credit.clientId] ??
ClientEntity(id: credit.clientId))
ClientEntity(id: credit.clientId))
.listDisplayName);
case CreditFields.date:
return Text(formatDate(credit.date, context));

View File

@ -26,7 +26,7 @@ class _CreditEditState extends State<CreditEdit>
TabController _controller;
static final GlobalKey<FormState> _formKey =
GlobalKey<FormState>(debugLabel: '_creditEdit');
GlobalKey<FormState>(debugLabel: '_creditEdit');
static const kDetailsScreen = 0;
static const kItemScreen = 1;
@ -39,7 +39,7 @@ class _CreditEditState extends State<CreditEdit>
final viewModel = widget.viewModel;
final index =
viewModel.invoiceItemIndex != null ? kItemScreen : kDetailsScreen;
viewModel.invoiceItemIndex != null ? kItemScreen : kDetailsScreen;
_controller = TabController(vsync: this, length: 3, initialIndex: index);
}
@ -79,33 +79,33 @@ class _CreditEditState extends State<CreditEdit>
appBarBottom: state.prefState.isDesktop
? null
: TabBar(
controller: _controller,
//isScrollable: true,
tabs: [
Tab(
text: localization.details,
),
Tab(
text: localization.items,
),
Tab(
text: localization.notes,
),
],
),
controller: _controller,
//isScrollable: true,
tabs: [
Tab(
text: localization.details,
),
Tab(
text: localization.items,
),
Tab(
text: localization.notes,
),
],
),
body: Form(
key: _formKey,
child: state.prefState.isDesktop
? CreditEditDetailsScreen()
: TabBarView(
key: ValueKey('__credit_${viewModel.invoice.id}__'),
controller: _controller,
children: <Widget>[
CreditEditDetailsScreen(),
CreditEditItemsScreen(),
CreditEditNotesScreen(),
],
),
key: ValueKey('__credit_${viewModel.invoice.id}__'),
controller: _controller,
children: <Widget>[
CreditEditDetailsScreen(),
CreditEditItemsScreen(),
CreditEditNotesScreen(),
],
),
),
bottomNavigationBar: BottomAppBar(
color: Theme.of(context).primaryColor,
@ -133,8 +133,8 @@ class _CreditEditState extends State<CreditEdit>
excluded: invoice.lineItems
.where((item) => item.isTask || item.isExpense)
.map((item) => item.isTask
? viewModel.state.taskState.map[item.taskId]
: viewModel.state.expenseState.map[item.expenseId])
? viewModel.state.taskState.map[item.taskId]
: viewModel.state.expenseState.map[item.expenseId])
.toList(),
clientId: invoice.clientId,
onItemsSelected: (items, [clientId]) {

View File

@ -74,7 +74,8 @@ class CreditEditItemsVM extends EntityEditItemsVM {
if (index == credit.lineItems.length) {
store.dispatch(AddCreditItem(creditItem: creditItem));
} else {
store.dispatch(UpdateCreditItem(creditItem: creditItem, index: index));
store.dispatch(
UpdateCreditItem(creditItem: creditItem, index: index));
}
});
}

View File

@ -46,16 +46,16 @@ class CreditEditVM extends EntityEditVM {
bool isSaving,
Function(BuildContext) onCancelPressed,
}) : super(
state: state,
company: company,
invoice: invoice,
invoiceItemIndex: invoiceItemIndex,
origInvoice: origInvoice,
onSavePressed: onSavePressed,
onItemsAdded: onItemsAdded,
isSaving: isSaving,
onCancelPressed: onCancelPressed,
);
state: state,
company: company,
invoice: invoice,
invoiceItemIndex: invoiceItemIndex,
origInvoice: origInvoice,
onSavePressed: onSavePressed,
onItemsAdded: onItemsAdded,
isSaving: isSaving,
onCancelPressed: onCancelPressed,
);
factory CreditEditVM.fromStore(Store<AppState> store) {
final AppState state = store.state;
@ -75,7 +75,8 @@ class CreditEditVM extends EntityEditVM {
if (isMobile(context)) {
store.dispatch(UpdateCurrentRoute(CreditViewScreen.route));
if (credit.isNew) {
Navigator.of(context).pushReplacementNamed(CreditViewScreen.route);
Navigator.of(context)
.pushReplacementNamed(CreditViewScreen.route);
} else {
Navigator.of(context).pop(savedCredit);
}

View File

@ -56,22 +56,22 @@ class CreditViewVM extends EntityViewVM {
Function(BuildContext, DocumentEntity) onDeleteDocument,
Function(BuildContext, DocumentEntity) onViewExpense,
}) : super(
state: state,
company: company,
invoice: invoice,
client: client,
isSaving: isSaving,
isDirty: isDirty,
onActionSelected: onEntityAction,
onEditPressed: onEditPressed,
onClientPressed: onClientPressed,
onPaymentsPressed: onPaymentsPressed,
onPaymentPressed: onPaymentPressed,
onRefreshed: onRefreshed,
onUploadDocument: onUploadDocument,
onDeleteDocument: onDeleteDocument,
onViewExpense: onViewExpense,
);
state: state,
company: company,
invoice: invoice,
client: client,
isSaving: isSaving,
isDirty: isDirty,
onActionSelected: onEntityAction,
onEditPressed: onEditPressed,
onClientPressed: onClientPressed,
onPaymentsPressed: onPaymentsPressed,
onPaymentPressed: onPaymentPressed,
onRefreshed: onRefreshed,
onUploadDocument: onUploadDocument,
onDeleteDocument: onDeleteDocument,
onViewExpense: onViewExpense,
);
factory CreditViewVM.fromStore(Store<AppState> store) {
final state = store.state;
@ -125,8 +125,8 @@ class CreditViewVM extends EntityViewVM {
completer.future.then((client) {
Scaffold.of(context).showSnackBar(SnackBar(
content: SnackBarRow(
message: AppLocalization.of(context).uploadedDocument,
)));
message: AppLocalization.of(context).uploadedDocument,
)));
}).catchError((Object error) {
showDialog<ErrorDialog>(
context: context,

View File

@ -0,0 +1,167 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/design/design_actions.dart';
import 'package:invoiceninja_flutter/redux/ui/pref_state.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.dart';
import 'package:invoiceninja_flutter/ui/app/help_text.dart';
import 'package:invoiceninja_flutter/ui/app/lists/list_divider.dart';
import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
import 'package:invoiceninja_flutter/ui/app/presenters/entity_presenter.dart';
import 'package:invoiceninja_flutter/ui/design/design_presenter.dart';
import 'package:invoiceninja_flutter/ui/app/tables/entity_datatable.dart';
import 'package:invoiceninja_flutter/ui/design/design_list_item.dart';
import 'package:invoiceninja_flutter/ui/design/design_list_vm.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
class DesignList extends StatefulWidget {
const DesignList({
Key key,
@required this.viewModel,
}) : super(key: key);
final DesignListVM viewModel;
@override
_DesignListState createState() => _DesignListState();
}
class _DesignListState extends State<DesignList> {
EntityDataTableSource dataTableSource;
@override
void initState() {
super.initState();
final viewModel = widget.viewModel;
dataTableSource = EntityDataTableSource(
context: context,
entityType: EntityType.design,
editingId: viewModel.state.designUIState.editing.id,
tableColumns: viewModel.tableColumns,
entityList: viewModel.designList,
entityMap: viewModel.designMap,
entityPresenter: DesignPresenter(),
onTap: (BaseEntity design) => viewModel.onDesignTap(context, design));
}
@override
void didUpdateWidget(DesignList oldWidget) {
super.didUpdateWidget(oldWidget);
final viewModel = widget.viewModel;
dataTableSource.editingId = viewModel.state.designUIState.editing.id;
dataTableSource.entityList = viewModel.designList;
dataTableSource.entityMap = viewModel.designMap;
// ignore: invalid_use_of_visible_for_testing_member, invalid_use_of_protected_member
dataTableSource.notifyListeners();
}
@override
Widget build(BuildContext context) {
final store = StoreProvider.of<AppState>(context);
final viewModel = widget.viewModel;
final state = viewModel.state;
final listUIState = state.uiState.designUIState.listUIState;
final isInMultiselect = listUIState.isInMultiselect();
final isList = state.prefState.moduleLayout == ModuleLayout.list;
final designList = viewModel.designList;
if (!viewModel.isLoaded) {
return viewModel.isLoading ? LoadingIndicator() : SizedBox();
} else if (viewModel.designMap.isEmpty) {
return HelpText(AppLocalization.of(context).noRecordsFound);
}
if (state.shouldSelectEntity(
entityType: EntityType.design, hasRecords: designList.isNotEmpty)) {
viewEntityById(
context: context,
entityType: EntityType.design,
entityId: designList.isEmpty ? null : designList.first,
);
}
final listOrTable = () {
if (isList) {
return ListView.separated(
separatorBuilder: (context, index) => ListDivider(),
itemCount: viewModel.designList.length,
itemBuilder: (BuildContext context, index) {
final designId = viewModel.designList[index];
final design = viewModel.designMap[designId];
return DesignListItem(
user: viewModel.state.user,
filter: viewModel.filter,
design: design,
onEntityAction: (EntityAction action) {
if (action == EntityAction.more) {
showEntityActionsDialog(
entities: [design],
context: context,
);
} else {
handleDesignAction(context, [design], action);
}
},
onTap: () => viewModel.onDesignTap(context, design),
onLongPress: () async {
final longPressIsSelection =
state.prefState.longPressSelectionIsDefault ?? true;
if (longPressIsSelection && !isInMultiselect) {
handleDesignAction(
context, [design], EntityAction.toggleMultiselect);
} else {
showEntityActionsDialog(
entities: [design],
context: context,
);
}
},
isChecked: isInMultiselect && listUIState.isSelected(design.id),
);
});
} else {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(12),
child: PaginatedDataTable(
onSelectAll: (value) {
final designs = viewModel.designList
.map<DesignEntity>(
(designId) => viewModel.designMap[designId])
.where((design) => value != listUIState.isSelected(design.id))
.toList();
handleDesignAction(
context, designs, EntityAction.toggleMultiselect);
},
columns: [
if (!listUIState.isInMultiselect()) DataColumn(label: SizedBox()),
...viewModel.tableColumns.map((field) => DataColumn(
label: Text(AppLocalization.of(context).lookup(field)),
numeric: EntityPresenter.isFieldNumeric(field),
onSort: (int columnIndex, bool ascending) =>
store.dispatch(SortDesigns(field)))),
],
source: dataTableSource,
header: DatatableHeader(
entityType: EntityType.design,
onClearPressed: viewModel.onClearEntityFilterPressed,
),
),
));
}
};
return RefreshIndicator(
onRefresh: () => viewModel.onRefreshed(context),
child: listOrTable(),
);
}
}

View File

@ -0,0 +1,104 @@
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/data/models/design_model.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/ui/app/entity_state_label.dart';
import 'package:invoiceninja_flutter/utils/formatting.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/app/dismissible_entity.dart';
class DesignListItem extends StatelessWidget {
const DesignListItem({
@required this.user,
@required this.onEntityAction,
@required this.onTap,
@required this.onLongPress,
@required this.design,
@required this.filter,
this.onCheckboxChanged,
this.isChecked = false,
});
final UserEntity user;
final Function(EntityAction) onEntityAction;
final GestureTapCallback onTap;
final GestureTapCallback onLongPress;
final DesignEntity design;
final String filter;
final Function(bool) onCheckboxChanged;
final bool isChecked;
static final designItemKey = (int id) => Key('__design_item_${id}__');
@override
Widget build(BuildContext context) {
final store = StoreProvider.of<AppState>(context);
final state = store.state;
final uiState = state.uiState;
final designUIState = uiState.designUIState;
final listUIState = designUIState.listUIState;
final isInMultiselect = listUIState.isInMultiselect();
final showCheckbox = onCheckboxChanged != null || isInMultiselect;
final filterMatch = filter != null && filter.isNotEmpty
? design.matchesFilterValue(filter)
: null;
final subtitle = filterMatch;
return DismissibleEntity(
userCompany: state.userCompany,
entity: design,
isSelected: design.id ==
(uiState.isEditing
? designUIState.editing.id
: designUIState.selectedId),
onEntityAction: onEntityAction,
child: ListTile(
onTap: isInMultiselect
? () => onEntityAction(EntityAction.toggleMultiselect)
: onTap,
onLongPress: onLongPress,
leading: showCheckbox
? IgnorePointer(
ignoring: listUIState.isInMultiselect(),
child: Checkbox(
value: isChecked,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
onChanged: (value) => onCheckboxChanged(value),
activeColor: Theme.of(context).accentColor,
),
)
: null,
title: Container(
width: MediaQuery.of(context).size.width,
child: Row(
children: <Widget>[
Expanded(
child: Text(
design.name,
style: Theme.of(context).textTheme.headline6,
),
),
Text(formatNumber(design.listDisplayAmount, context),
style: Theme.of(context).textTheme.headline6),
],
),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
subtitle != null && subtitle.isNotEmpty
? Text(
subtitle,
maxLines: 3,
overflow: TextOverflow.ellipsis,
)
: Container(),
EntityStateLabel(design),
],
),
),
);
}
}

View File

@ -0,0 +1,110 @@
import 'dart:async';
import 'package:invoiceninja_flutter/data/models/design_model.dart';
import 'package:redux/redux.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:built_collection/built_collection.dart';
import 'package:invoiceninja_flutter/redux/client/client_actions.dart';
import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/redux/design/design_selectors.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/design/design_list.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/design/design_actions.dart';
class DesignListBuilder extends StatelessWidget {
const DesignListBuilder({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, DesignListVM>(
converter: DesignListVM.fromStore,
builder: (context, viewModel) {
return DesignList(
viewModel: viewModel,
);
},
);
}
}
class DesignListVM {
DesignListVM({
@required this.state,
@required this.userCompany,
@required this.designList,
@required this.designMap,
@required this.filter,
@required this.isLoading,
@required this.isLoaded,
@required this.onDesignTap,
@required this.listState,
@required this.onRefreshed,
@required this.onEntityAction,
@required this.tableColumns,
@required this.onClearEntityFilterPressed,
@required this.onViewEntityFilterPressed,
});
static DesignListVM fromStore(Store<AppState> store) {
Future<Null> _handleRefresh(BuildContext context) {
if (store.state.isLoading) {
return Future<Null>(null);
}
final completer = snackBarCompleter<Null>(
context, AppLocalization.of(context).refreshComplete);
store.dispatch(LoadDesigns(completer: completer, force: true));
return completer.future;
}
final state = store.state;
return DesignListVM(
state: state,
userCompany: state.userCompany,
listState: state.designListState,
designList: memoizedFilteredDesignList(
state.designState.map, state.designState.list, state.designListState),
designMap: state.designState.map,
isLoading: state.isLoading,
isLoaded: state.designState.isLoaded,
filter: state.designUIState.listUIState.filter,
onClearEntityFilterPressed: () => store.dispatch(FilterDesignsByEntity()),
onViewEntityFilterPressed: (BuildContext context) => viewEntityById(
context: context,
entityId: state.designListState.filterEntityId,
entityType: state.designListState.filterEntityType),
onDesignTap: (context, design) {
if (store.state.designListState.isInMultiselect()) {
handleDesignAction(context, [design], EntityAction.toggleMultiselect);
} else {
viewEntity(context: context, entity: design);
}
},
onEntityAction: (BuildContext context, List<BaseEntity> designs,
EntityAction action) =>
handleDesignAction(context, designs, action),
onRefreshed: (context) => _handleRefresh(context),
);
}
final AppState state;
final UserCompanyEntity userCompany;
final List<String> designList;
final BuiltMap<String, DesignEntity> designMap;
final ListUIState listState;
final String filter;
final bool isLoading;
final bool isLoaded;
final Function(BuildContext, DesignEntity) onDesignTap;
final Function(BuildContext) onRefreshed;
final Function(BuildContext, List<BaseEntity>, EntityAction) onEntityAction;
final Function onClearEntityFilterPressed;
final Function(BuildContext) onViewEntityFilterPressed;
final List<String> tableColumns;
}

View File

@ -0,0 +1,24 @@
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/data/models/design_model.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/ui/app/presenters/entity_presenter.dart';
import 'package:invoiceninja_flutter/utils/formatting.dart';
class DesignPresenter extends EntityPresenter {
static List<String> getTableFields(UserCompanyEntity userCompany) {
return [];
}
@override
Widget getField({String field, BuildContext context}) {
final state = StoreProvider.of<AppState>(context).state;
final design = entity as InvoiceEntity;
switch (field) {
}
return super.getField(field: field, context: context);
}
}

View File

@ -0,0 +1,148 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/design/design_actions.dart';
import 'package:invoiceninja_flutter/ui/app/app_bottom_bar.dart';
import 'package:invoiceninja_flutter/ui/app/forms/save_cancel_buttons.dart';
import 'package:invoiceninja_flutter/ui/app/list_scaffold.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.dart';
import 'package:invoiceninja_flutter/ui/app/list_filter.dart';
import 'package:invoiceninja_flutter/ui/app/list_filter_button.dart';
import 'package:invoiceninja_flutter/ui/design/design_list_vm.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'design_screen_vm.dart';
class DesignScreen extends StatelessWidget {
const DesignScreen({
Key key,
@required this.viewModel,
}) : super(key: key);
static const String route = '/design';
final DesignScreenVM viewModel;
@override
Widget build(BuildContext context) {
final store = StoreProvider.of<AppState>(context);
final state = store.state;
final company = state.company;
final userCompany = state.userCompany;
final localization = AppLocalization.of(context);
final listUIState = state.uiState.designUIState.listUIState;
final isInMultiselect = listUIState.isInMultiselect();
return ListScaffold(
isChecked: isInMultiselect &&
listUIState.selectedIds.length == viewModel.designList.length,
showCheckbox: isInMultiselect,
onHamburgerLongPress: () => store.dispatch(StartDesignMultiselect()),
onCheckboxChanged: (value) {
final designs = viewModel.designList
.map<DesignEntity>((designId) => viewModel.designMap[designId])
.where((design) => value != listUIState.isSelected(design.id))
.toList();
handleDesignAction(context, designs, EntityAction.toggleMultiselect);
},
appBarTitle: ListFilter(
title: localization.designs,
key: ValueKey(state.designListState.filterClearedAt),
filter: state.designListState.filter,
onFilterChanged: (value) {
store.dispatch(FilterDesigns(value));
},
),
appBarActions: [
if (!viewModel.isInMultiselect)
ListFilterButton(
filter: state.designListState.filter,
onFilterPressed: (String value) {
store.dispatch(FilterDesigns(value));
},
),
if (viewModel.isInMultiselect)
SaveCancelButtons(
saveLabel: localization.done,
onSavePressed: listUIState.selectedIds.isEmpty
? null
: (context) async {
final designs = listUIState.selectedIds
.map<DesignEntity>(
(designId) => viewModel.designMap[designId])
.toList();
await showEntityActionsDialog(
entities: designs,
context: context,
multiselect: true,
completer: Completer<Null>()
..future.then<dynamic>(
(_) => store.dispatch(ClearDesignMultiselect())),
);
},
onCancelPressed: (context) =>
store.dispatch(ClearDesignMultiselect()),
),
],
body: DesignListBuilder(),
bottomNavigationBar: AppBottomBar(
entityType: EntityType.design,
onSelectedSortField: (value) {
store.dispatch(SortDesigns(value));
},
sortFields: [
DesignFields.name,
DesignFields.balance,
DesignFields.updatedAt,
],
onSelectedState: (EntityState state, value) {
store.dispatch(FilterDesignsByState(state));
},
onCheckboxPressed: () {
if (store.state.designListState.isInMultiselect()) {
store.dispatch(ClearDesignMultiselect());
} else {
store.dispatch(StartDesignMultiselect());
}
},
customValues1: company.getCustomFieldValues(CustomFieldType.design1,
excludeBlank: true),
customValues2: company.getCustomFieldValues(CustomFieldType.design2,
excludeBlank: true),
customValues3: company.getCustomFieldValues(CustomFieldType.design3,
excludeBlank: true),
customValues4: company.getCustomFieldValues(CustomFieldType.design4,
excludeBlank: true),
onSelectedCustom1: (value) =>
store.dispatch(FilterDesignsByCustom1(value)),
onSelectedCustom2: (value) =>
store.dispatch(FilterDesignsByCustom2(value)),
onSelectedCustom3: (value) =>
store.dispatch(FilterDesignsByCustom3(value)),
onSelectedCustom4: (value) =>
store.dispatch(FilterDesignsByCustom4(value)),
),
floatingActionButton: userCompany.canCreate(EntityType.design)
? FloatingActionButton(
heroTag: 'design_fab',
backgroundColor: Theme.of(context).primaryColorDark,
onPressed: () {
createEntityByType(
context: context, entityType: EntityType.design);
},
child: Icon(
Icons.add,
color: Colors.white,
),
tooltip: localization.newDesign,
)
: null,
);
}
}

View File

@ -0,0 +1,59 @@
import 'package:built_collection/built_collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/design/design_actions.dart';
import 'package:invoiceninja_flutter/redux/design/design_selectors.dart';
import 'package:redux/redux.dart';
import 'design_screen.dart';
class DesignScreenBuilder extends StatelessWidget {
const DesignScreenBuilder({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, DesignScreenVM>(
converter: DesignScreenVM.fromStore,
builder: (context, vm) {
return DesignScreen(
viewModel: vm,
);
},
);
}
}
class DesignScreenVM {
DesignScreenVM({
@required this.isInMultiselect,
@required this.designList,
@required this.userCompany,
@required this.onEntityAction,
@required this.designMap,
});
final bool isInMultiselect;
final UserCompanyEntity userCompany;
final List<String> designList;
final Function(BuildContext, List<BaseEntity>, EntityAction) onEntityAction;
final BuiltMap<String, DesignEntity> designMap;
static DesignScreenVM fromStore(Store<AppState> store) {
final state = store.state;
return DesignScreenVM(
designMap: state.designState.map,
designList: memoizedFilteredDesignList(
state.designState.map, state.designState.list, state.designListState),
userCompany: state.userCompany,
isInMultiselect: state.designListState.isInMultiselect(),
onEntityAction: (BuildContext context, List<BaseEntity> designs,
EntityAction action) =>
handleDesignAction(context, designs, action),
);
}
}

View File

@ -0,0 +1,102 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/ui/app/form_card.dart';
import 'package:invoiceninja_flutter/ui/design/edit/design_edit_vm.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
class DesignEdit extends StatefulWidget {
const DesignEdit({
Key key,
@required this.viewModel,
}) : super(key: key);
final DesignEditVM viewModel;
@override
_DesignEditState createState() => _DesignEditState();
}
class _DesignEditState extends State<DesignEdit> {
static final GlobalKey<FormState> _formKey =
GlobalKey<FormState>(debugLabel: '_designEdit');
final _debouncer = Debouncer();
// STARTER: controllers - do not remove comment
List<TextEditingController> _controllers = [];
@override
void didChangeDependencies() {
_controllers = [
// STARTER: array - do not remove comment
];
_controllers.forEach((controller) => controller.removeListener(_onChanged));
final design = widget.viewModel.design;
// STARTER: read value - do not remove comment
_controllers.forEach((controller) => controller.addListener(_onChanged));
super.didChangeDependencies();
}
@override
void dispose() {
_controllers.forEach((controller) {
controller.removeListener(_onChanged);
controller.dispose();
});
super.dispose();
}
void _onChanged() {
_debouncer.run(() {
final design = widget.viewModel.design.rebuild((b) => b
// STARTER: set value - do not remove comment
);
if (design != widget.viewModel.design) {
widget.viewModel.onChanged(design);
}
});
}
@override
Widget build(BuildContext context) {
final viewModel = widget.viewModel;
final localization = AppLocalization.of(context);
final design = viewModel.design;
return EditScaffold(
onCancelPressed: (context) => viewModel.onCancelPressed(context),
onSavePressed: (context) {
final bool isValid = _formKey.currentState.validate();
setState(() {
_autoValidate = !isValid;
});
if (!isValid) {
return;
}
viewModel.onSavePressed(context);
},
body: Form(
key: _formKey,
child: Builder(builder: (BuildContext context) {
return ListView(
children: <Widget>[
FormCard(
children: <Widget>[
// STARTER: widgets - do not remove comment
],
),
],
);
})),
);
}
}

View File

@ -0,0 +1,105 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
import 'package:invoiceninja_flutter/ui/design/design_screen.dart';
import 'package:invoiceninja_flutter/utils/platforms.dart';
import 'package:redux/redux.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/app/dialogs/error_dialog.dart';
import 'package:invoiceninja_flutter/ui/design/view/design_view_vm.dart';
import 'package:invoiceninja_flutter/redux/design/design_actions.dart';
import 'package:invoiceninja_flutter/data/models/design_model.dart';
import 'package:invoiceninja_flutter/ui/design/edit/design_edit.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
class DesignEditScreen extends StatelessWidget {
const DesignEditScreen({Key key}) : super(key: key);
static const String route = '/design/edit';
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, DesignEditVM>(
converter: (Store<AppState> store) {
return DesignEditVM.fromStore(store);
},
builder: (context, viewModel) {
return DesignEdit(
viewModel: viewModel,
key: ValueKey(viewModel.design.id),
);
},
);
}
}
class DesignEditVM {
DesignEditVM({
@required this.state,
@required this.design,
@required this.company,
@required this.onChanged,
@required this.isSaving,
@required this.origDesign,
@required this.onSavePressed,
@required this.onCancelPressed,
@required this.isLoading,
});
factory DesignEditVM.fromStore(Store<AppState> store) {
final state = store.state;
final design = state.designUIState.editing;
return DesignEditVM(
state: state,
isLoading: state.isLoading,
isSaving: state.isSaving,
origDesign: state.designState.map[design.id],
design: design,
company: state.selectedCompany,
onChanged: (DesignEntity design) {
store.dispatch(UpdateDesign(design));
},
onCancelPressed: (BuildContext context) {
store.dispatch(
EditDesign(design: DesignEntity(), context: context, force: true));
store.dispatch(UpdateCurrentRoute(state.uiState.previousRoute));
},
onSavePressed: (BuildContext context) {
final Completer<DesignEntity> completer = new Completer<DesignEntity>();
store.dispatch(SaveDesignRequest(completer: completer, design: design));
return completer.future.then((savedDesign) {
if (isMobile(context)) {
store.dispatch(UpdateCurrentRoute(DesignViewScreen.route));
if (design.isNew) {
Navigator.of(context)
.pushReplacementNamed(DesignViewScreen.route);
} else {
Navigator.of(context).pop(savedDesign);
}
} else {
store.dispatch(ViewDesign(
context: context, designId: savedDesign.id, force: true));
}
}).catchError((Object error) {
showDialog<ErrorDialog>(
context: context,
builder: (BuildContext context) {
return ErrorDialog(error);
});
});
},
);
}
final DesignEntity design;
final CompanyEntity company;
final Function(DesignEntity) onChanged;
final Function(BuildContext) onSavePressed;
final Function(BuildContext) onCancelPressed;
final bool isLoading;
final bool isSaving;
final DesignEntity origDesign;
final AppState state;
}

View File

@ -0,0 +1,51 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/ui/app/buttons/edit_icon_button.dart';
import 'package:invoiceninja_flutter/ui/app/actions_menu_button.dart';
import 'package:invoiceninja_flutter/ui/design/view/design_view_vm.dart';
import 'package:invoiceninja_flutter/ui/app/form_card.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_state_title.dart';
class DesignView extends StatefulWidget {
const DesignView({
Key key,
@required this.viewModel,
}) : super(key: key);
final DesignViewVM viewModel;
@override
_DesignViewState createState() => new _DesignViewState();
}
class _DesignViewState extends State<DesignView> {
@override
Widget build(BuildContext context) {
final viewModel = widget.viewModel;
final userCompany = viewModel.state.userCompany;
final design = viewModel.design;
return Scaffold(
appBar: AppBar(
title: EntityStateTitle(entity: design),
actions: [
userCompany.canEditEntity(design)
? EditIconButton(
isVisible: !design.isDeleted,
onPressed: () => viewModel.onEditPressed(context),
)
: Container(),
ActionMenuButton(
entityActions: design.getActions(userCompany: userCompany),
isSaving: viewModel.isSaving,
entity: design,
onSelected: viewModel.onEntityAction,
)
],
),
body: FormCard(children: [
// STARTER: widgets - do not remove comment
]),
);
}
}

View File

@ -0,0 +1,81 @@
import 'dart:async';
import 'package:invoiceninja_flutter/ui/app/snackbar_row.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/ui/design/design_screen.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:redux/redux.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/redux/design/design_actions.dart';
import 'package:invoiceninja_flutter/data/models/design_model.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/design/view/design_view.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
class DesignViewScreen extends StatelessWidget {
const DesignViewScreen({Key key}) : super(key: key);
static const String route = '/design/view';
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, DesignViewVM>(
converter: (Store<AppState> store) {
return DesignViewVM.fromStore(store);
},
builder: (context, vm) {
return DesignView(
viewModel: vm,
);
},
);
}
}
class DesignViewVM {
DesignViewVM({
@required this.state,
@required this.design,
@required this.company,
@required this.onEntityAction,
@required this.onRefreshed,
@required this.isSaving,
@required this.isLoading,
@required this.isDirty,
});
factory DesignViewVM.fromStore(Store<AppState> store) {
final state = store.state;
final design = state.designState.map[state.designUIState.selectedId] ??
DesignEntity(id: state.designUIState.selectedId);
Future<Null> _handleRefresh(BuildContext context) {
final completer = snackBarCompleter<Null>(
context, AppLocalization.of(context).refreshComplete);
store.dispatch(LoadDesign(completer: completer, designId: design.id));
return completer.future;
}
return DesignViewVM(
state: state,
company: state.selectedCompany,
isSaving: state.isSaving,
isLoading: state.isLoading,
isDirty: design.isNew,
design: design,
onRefreshed: (context) => _handleRefresh(context),
onEntityAction: (BuildContext context, EntityAction action) =>
handleDesignAction(context, design, action),
);
}
final AppState state;
final DesignEntity design;
final CompanyEntity company;
final Function(BuildContext, EntityAction) onEntityAction;
final Function(BuildContext) onRefreshed;
final bool isSaving;
final bool isLoading;
final bool isDirty;
}

View File

@ -28,7 +28,7 @@ import 'package:memoize/memoize.dart';
import 'package:path_provider/path_provider.dart';
import 'package:redux/redux.dart';
import 'package:invoiceninja_flutter/utils/web_stub.dart'
if (dart.library.html) 'package:invoiceninja_flutter/utils/web.dart';
if (dart.library.html) 'package:invoiceninja_flutter/utils/web.dart';
class ReportsScreenBuilder extends StatelessWidget {
const ReportsScreenBuilder({Key key}) : super(key: key);

View File

@ -15,6 +15,17 @@ mixin LocalizationsProvider on LocaleCodeAware {
static final Map<String, Map<String, String>> _localizedValues = {
'en': {
// STARTER: lang key - do not remove comment
'design': 'Design',
'designs': 'Designs',
'new_design': 'New Design',
'edit_design': 'Edit Design',
'created_design': 'Successfully created design',
'updated_design': 'Successfully updated design',
'archived_design': 'Successfully archived design',
'deleted_design': 'Successfully deleted design',
'removed_design': 'Successfully removed design',
'restored_design': 'Successfully restored design',
'proposals': 'Proposals',
'tickets': 'Tickets',
'recurring_invoices': 'Recurring Invoices',
@ -32780,6 +32791,16 @@ mixin LocalizationsProvider on LocaleCodeAware {
String get appUpdated => _localizedValues[localeCode]['app_updated'];
// STARTER: lang field - do not remove comment
String get design => _localizedValues[localeCode][' design'];
String get designs => _localizedValues[localeCode]['designs'];
String get newDesign => _localizedValues[localeCode]['new_design'];
String get createdDesign => _localizedValues[localeCode]['created_design'];
String get updatedDesign => _localizedValues[localeCode]['updated_design'];
String get archivedDesign => _localizedValues[localeCode]['archived_design'];
String get deletedDesign => _localizedValues[localeCode]['deleted_design'];
String get restoredDesign => _localizedValues[localeCode]['restored_design'];
String get editDesign => _localizedValues[localeCode]['edit_design'];
String get newCredit => _localizedValues[localeCode]['new_credit'];
String get createdCredit => _localizedValues[localeCode]['created_credit'];

View File

@ -438,6 +438,10 @@ else
code="String get ${module_camel} => _localizedValues[localeCode][' ${module_snake}']; String get ${module_camel}s => _localizedValues[localeCode]['${module_snake}s']; String get new${Module} => _localizedValues[localeCode]['new_${module_snake}']; String get created${Module} => _localizedValues[localeCode]['created_${module_snake}']; String get updated${Module} => _localizedValues[localeCode]['updated_${module_snake}']; String get archived${Module} => _localizedValues[localeCode]['archived_${module_snake}']; String get deleted${Module} => _localizedValues[localeCode]['deleted_${module_snake}']; String get restored${Module} => _localizedValues[localeCode]['restored_${module_snake}']; String get edit${Module} => _localizedValues[localeCode]['edit_${module_snake}'];${lineBreak}"
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/utils/i18n.dart
comment="STARTER: entity type - do not remove comment"
code="static const EntityType ${module_camel} = _\$${module_camel}${lineBreak}"
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/data/models/entities.dart
echo "Generating built files.."
flutter packages pub run build_runner clean
flutter packages pub run build_runner build --delete-conflicting-outputs

View File

@ -2,10 +2,10 @@ import 'package:built_value/built_value.dart';
import 'package:built_collection/built_collection.dart';
import 'package:built_value/serializer.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/utils/formatting.dart';
part 'stub_model.g.dart';
abstract class StubListResponse
implements Built<StubListResponse, StubListResponseBuilder> {
factory StubListResponse([void updates(StubListResponseBuilder b)]) =
@ -48,6 +48,8 @@ abstract class StubEntity extends Object with BaseEntity implements Built<StubEn
);
}
StubEntity._();
String get displayName {
// STARTER: display name - do not remove comment
}
@ -80,6 +82,36 @@ abstract class StubEntity extends Object with BaseEntity implements Built<StubEn
return false;
}
StubEntity._();
@override
bool matchesFilter(String filter) {
if (filter == null || filter.isEmpty) {
return true;
}
filter = filter.toLowerCase();
return false;
}
@override
String matchesFilterValue(String filter) {
if (filter == null || filter.isEmpty) {
return null;
}
filter = filter.toLowerCase();
return null;
}
@override
String get listDisplayName => null;
@override
double get listDisplayAmount => null;
@override
FormatNumberType get listDisplayAmountType => null;
static Serializer<StubEntity> get serializer => _$stubEntitySerializer;
}