Initial commit

This commit is contained in:
2025-05-04 02:54:07 +02:00
commit 5cc0bba09a
69 changed files with 8690 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
import 'package:isar/isar.dart';
part 'account.g.dart';
@collection
class Account {
Id id = Isar.autoIncrement;
String? name;
}

View File

@@ -0,0 +1,469 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'account.dart';
// **************************************************************************
// IsarCollectionGenerator
// **************************************************************************
// coverage:ignore-file
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
extension GetAccountCollection on Isar {
IsarCollection<Account> get accounts => this.collection();
}
const AccountSchema = CollectionSchema(
name: r'Account',
id: -6646797162501847804,
properties: {
r'name': PropertySchema(id: 0, name: r'name', type: IsarType.string),
},
estimateSize: _accountEstimateSize,
serialize: _accountSerialize,
deserialize: _accountDeserialize,
deserializeProp: _accountDeserializeProp,
idName: r'id',
indexes: {},
links: {},
embeddedSchemas: {},
getId: _accountGetId,
getLinks: _accountGetLinks,
attach: _accountAttach,
version: '3.1.0+1',
);
int _accountEstimateSize(
Account object,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
var bytesCount = offsets.last;
{
final value = object.name;
if (value != null) {
bytesCount += 3 + value.length * 3;
}
}
return bytesCount;
}
void _accountSerialize(
Account object,
IsarWriter writer,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
writer.writeString(offsets[0], object.name);
}
Account _accountDeserialize(
Id id,
IsarReader reader,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
final object = Account();
object.id = id;
object.name = reader.readStringOrNull(offsets[0]);
return object;
}
P _accountDeserializeProp<P>(
IsarReader reader,
int propertyId,
int offset,
Map<Type, List<int>> allOffsets,
) {
switch (propertyId) {
case 0:
return (reader.readStringOrNull(offset)) as P;
default:
throw IsarError('Unknown property with id $propertyId');
}
}
Id _accountGetId(Account object) {
return object.id;
}
List<IsarLinkBase<dynamic>> _accountGetLinks(Account object) {
return [];
}
void _accountAttach(IsarCollection<dynamic> col, Id id, Account object) {
object.id = id;
}
extension AccountQueryWhereSort on QueryBuilder<Account, Account, QWhere> {
QueryBuilder<Account, Account, QAfterWhere> anyId() {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(const IdWhereClause.any());
});
}
}
extension AccountQueryWhere on QueryBuilder<Account, Account, QWhereClause> {
QueryBuilder<Account, Account, QAfterWhereClause> idEqualTo(Id id) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IdWhereClause.between(lower: id, upper: id));
});
}
QueryBuilder<Account, Account, QAfterWhereClause> idNotEqualTo(Id id) {
return QueryBuilder.apply(this, (query) {
if (query.whereSort == Sort.asc) {
return query
.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: false),
)
.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: false),
);
} else {
return query
.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: false),
)
.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: false),
);
}
});
}
QueryBuilder<Account, Account, QAfterWhereClause> idGreaterThan(
Id id, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: include),
);
});
}
QueryBuilder<Account, Account, QAfterWhereClause> idLessThan(
Id id, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: include),
);
});
}
QueryBuilder<Account, Account, QAfterWhereClause> idBetween(
Id lowerId,
Id upperId, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.between(
lower: lowerId,
includeLower: includeLower,
upper: upperId,
includeUpper: includeUpper,
),
);
});
}
}
extension AccountQueryFilter
on QueryBuilder<Account, Account, QFilterCondition> {
QueryBuilder<Account, Account, QAfterFilterCondition> idEqualTo(Id value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.equalTo(property: r'id', value: value),
);
});
}
QueryBuilder<Account, Account, QAfterFilterCondition> idGreaterThan(
Id value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.greaterThan(
include: include,
property: r'id',
value: value,
),
);
});
}
QueryBuilder<Account, Account, QAfterFilterCondition> idLessThan(
Id value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.lessThan(
include: include,
property: r'id',
value: value,
),
);
});
}
QueryBuilder<Account, Account, QAfterFilterCondition> idBetween(
Id lower,
Id upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.between(
property: r'id',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
),
);
});
}
QueryBuilder<Account, Account, QAfterFilterCondition> nameIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
const FilterCondition.isNull(property: r'name'),
);
});
}
QueryBuilder<Account, Account, QAfterFilterCondition> nameIsNotNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
const FilterCondition.isNotNull(property: r'name'),
);
});
}
QueryBuilder<Account, Account, QAfterFilterCondition> nameEqualTo(
String? value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.equalTo(
property: r'name',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Account, Account, QAfterFilterCondition> nameGreaterThan(
String? value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.greaterThan(
include: include,
property: r'name',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Account, Account, QAfterFilterCondition> nameLessThan(
String? value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.lessThan(
include: include,
property: r'name',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Account, Account, QAfterFilterCondition> nameBetween(
String? lower,
String? upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.between(
property: r'name',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Account, Account, QAfterFilterCondition> nameStartsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.startsWith(
property: r'name',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Account, Account, QAfterFilterCondition> nameEndsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.endsWith(
property: r'name',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Account, Account, QAfterFilterCondition> nameContains(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.contains(
property: r'name',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Account, Account, QAfterFilterCondition> nameMatches(
String pattern, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.matches(
property: r'name',
wildcard: pattern,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Account, Account, QAfterFilterCondition> nameIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.equalTo(property: r'name', value: ''),
);
});
}
QueryBuilder<Account, Account, QAfterFilterCondition> nameIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.greaterThan(property: r'name', value: ''),
);
});
}
}
extension AccountQueryObject
on QueryBuilder<Account, Account, QFilterCondition> {}
extension AccountQueryLinks
on QueryBuilder<Account, Account, QFilterCondition> {}
extension AccountQuerySortBy on QueryBuilder<Account, Account, QSortBy> {
QueryBuilder<Account, Account, QAfterSortBy> sortByName() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'name', Sort.asc);
});
}
QueryBuilder<Account, Account, QAfterSortBy> sortByNameDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'name', Sort.desc);
});
}
}
extension AccountQuerySortThenBy
on QueryBuilder<Account, Account, QSortThenBy> {
QueryBuilder<Account, Account, QAfterSortBy> thenById() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.asc);
});
}
QueryBuilder<Account, Account, QAfterSortBy> thenByIdDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.desc);
});
}
QueryBuilder<Account, Account, QAfterSortBy> thenByName() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'name', Sort.asc);
});
}
QueryBuilder<Account, Account, QAfterSortBy> thenByNameDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'name', Sort.desc);
});
}
}
extension AccountQueryWhereDistinct
on QueryBuilder<Account, Account, QDistinct> {
QueryBuilder<Account, Account, QDistinct> distinctByName({
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'name', caseSensitive: caseSensitive);
});
}
}
extension AccountQueryProperty
on QueryBuilder<Account, Account, QQueryProperty> {
QueryBuilder<Account, int, QQueryOperations> idProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'id');
});
}
QueryBuilder<Account, String?, QQueryOperations> nameProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'name');
});
}
}

View File

@@ -0,0 +1,20 @@
import 'package:isar/isar.dart';
import 'package:okane/database/collections/account.dart';
part 'beneficiary.g.dart';
enum BeneficiaryType { account, other }
@collection
class Beneficiary {
Id id = Isar.autoIncrement;
late String name;
@Enumerated(EnumType.ordinal)
late BeneficiaryType type;
final account = IsarLink<Account>();
String? imagePath;
}

View File

@@ -0,0 +1,810 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'beneficiary.dart';
// **************************************************************************
// IsarCollectionGenerator
// **************************************************************************
// coverage:ignore-file
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
extension GetBeneficiaryCollection on Isar {
IsarCollection<Beneficiary> get beneficiarys => this.collection();
}
const BeneficiarySchema = CollectionSchema(
name: r'Beneficiary',
id: -7106369371336791482,
properties: {
r'imagePath': PropertySchema(
id: 0,
name: r'imagePath',
type: IsarType.string,
),
r'name': PropertySchema(id: 1, name: r'name', type: IsarType.string),
r'type': PropertySchema(
id: 2,
name: r'type',
type: IsarType.byte,
enumMap: _BeneficiarytypeEnumValueMap,
),
},
estimateSize: _beneficiaryEstimateSize,
serialize: _beneficiarySerialize,
deserialize: _beneficiaryDeserialize,
deserializeProp: _beneficiaryDeserializeProp,
idName: r'id',
indexes: {},
links: {
r'account': LinkSchema(
id: -725531860126526319,
name: r'account',
target: r'Account',
single: true,
),
},
embeddedSchemas: {},
getId: _beneficiaryGetId,
getLinks: _beneficiaryGetLinks,
attach: _beneficiaryAttach,
version: '3.1.0+1',
);
int _beneficiaryEstimateSize(
Beneficiary object,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
var bytesCount = offsets.last;
{
final value = object.imagePath;
if (value != null) {
bytesCount += 3 + value.length * 3;
}
}
bytesCount += 3 + object.name.length * 3;
return bytesCount;
}
void _beneficiarySerialize(
Beneficiary object,
IsarWriter writer,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
writer.writeString(offsets[0], object.imagePath);
writer.writeString(offsets[1], object.name);
writer.writeByte(offsets[2], object.type.index);
}
Beneficiary _beneficiaryDeserialize(
Id id,
IsarReader reader,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
final object = Beneficiary();
object.id = id;
object.imagePath = reader.readStringOrNull(offsets[0]);
object.name = reader.readString(offsets[1]);
object.type =
_BeneficiarytypeValueEnumMap[reader.readByteOrNull(offsets[2])] ??
BeneficiaryType.account;
return object;
}
P _beneficiaryDeserializeProp<P>(
IsarReader reader,
int propertyId,
int offset,
Map<Type, List<int>> allOffsets,
) {
switch (propertyId) {
case 0:
return (reader.readStringOrNull(offset)) as P;
case 1:
return (reader.readString(offset)) as P;
case 2:
return (_BeneficiarytypeValueEnumMap[reader.readByteOrNull(offset)] ??
BeneficiaryType.account)
as P;
default:
throw IsarError('Unknown property with id $propertyId');
}
}
const _BeneficiarytypeEnumValueMap = {'account': 0, 'other': 1};
const _BeneficiarytypeValueEnumMap = {
0: BeneficiaryType.account,
1: BeneficiaryType.other,
};
Id _beneficiaryGetId(Beneficiary object) {
return object.id;
}
List<IsarLinkBase<dynamic>> _beneficiaryGetLinks(Beneficiary object) {
return [object.account];
}
void _beneficiaryAttach(
IsarCollection<dynamic> col,
Id id,
Beneficiary object,
) {
object.id = id;
object.account.attach(col, col.isar.collection<Account>(), r'account', id);
}
extension BeneficiaryQueryWhereSort
on QueryBuilder<Beneficiary, Beneficiary, QWhere> {
QueryBuilder<Beneficiary, Beneficiary, QAfterWhere> anyId() {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(const IdWhereClause.any());
});
}
}
extension BeneficiaryQueryWhere
on QueryBuilder<Beneficiary, Beneficiary, QWhereClause> {
QueryBuilder<Beneficiary, Beneficiary, QAfterWhereClause> idEqualTo(Id id) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IdWhereClause.between(lower: id, upper: id));
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterWhereClause> idNotEqualTo(
Id id,
) {
return QueryBuilder.apply(this, (query) {
if (query.whereSort == Sort.asc) {
return query
.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: false),
)
.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: false),
);
} else {
return query
.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: false),
)
.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: false),
);
}
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterWhereClause> idGreaterThan(
Id id, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: include),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterWhereClause> idLessThan(
Id id, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: include),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterWhereClause> idBetween(
Id lowerId,
Id upperId, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.between(
lower: lowerId,
includeLower: includeLower,
upper: upperId,
includeUpper: includeUpper,
),
);
});
}
}
extension BeneficiaryQueryFilter
on QueryBuilder<Beneficiary, Beneficiary, QFilterCondition> {
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition> idEqualTo(
Id value,
) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.equalTo(property: r'id', value: value),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition> idGreaterThan(
Id value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.greaterThan(
include: include,
property: r'id',
value: value,
),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition> idLessThan(
Id value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.lessThan(
include: include,
property: r'id',
value: value,
),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition> idBetween(
Id lower,
Id upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.between(
property: r'id',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition>
imagePathIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
const FilterCondition.isNull(property: r'imagePath'),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition>
imagePathIsNotNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
const FilterCondition.isNotNull(property: r'imagePath'),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition>
imagePathEqualTo(String? value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.equalTo(
property: r'imagePath',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition>
imagePathGreaterThan(
String? value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.greaterThan(
include: include,
property: r'imagePath',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition>
imagePathLessThan(
String? value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.lessThan(
include: include,
property: r'imagePath',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition>
imagePathBetween(
String? lower,
String? upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.between(
property: r'imagePath',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition>
imagePathStartsWith(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.startsWith(
property: r'imagePath',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition>
imagePathEndsWith(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.endsWith(
property: r'imagePath',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition>
imagePathContains(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.contains(
property: r'imagePath',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition>
imagePathMatches(String pattern, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.matches(
property: r'imagePath',
wildcard: pattern,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition>
imagePathIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.equalTo(property: r'imagePath', value: ''),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition>
imagePathIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.greaterThan(property: r'imagePath', value: ''),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition> nameEqualTo(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.equalTo(
property: r'name',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition> nameGreaterThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.greaterThan(
include: include,
property: r'name',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition> nameLessThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.lessThan(
include: include,
property: r'name',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition> nameBetween(
String lower,
String upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.between(
property: r'name',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition> nameStartsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.startsWith(
property: r'name',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition> nameEndsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.endsWith(
property: r'name',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition> nameContains(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.contains(
property: r'name',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition> nameMatches(
String pattern, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.matches(
property: r'name',
wildcard: pattern,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition> nameIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.equalTo(property: r'name', value: ''),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition>
nameIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.greaterThan(property: r'name', value: ''),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition> typeEqualTo(
BeneficiaryType value,
) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.equalTo(property: r'type', value: value),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition> typeGreaterThan(
BeneficiaryType value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.greaterThan(
include: include,
property: r'type',
value: value,
),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition> typeLessThan(
BeneficiaryType value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.lessThan(
include: include,
property: r'type',
value: value,
),
);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition> typeBetween(
BeneficiaryType lower,
BeneficiaryType upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.between(
property: r'type',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
),
);
});
}
}
extension BeneficiaryQueryObject
on QueryBuilder<Beneficiary, Beneficiary, QFilterCondition> {}
extension BeneficiaryQueryLinks
on QueryBuilder<Beneficiary, Beneficiary, QFilterCondition> {
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition> account(
FilterQuery<Account> q,
) {
return QueryBuilder.apply(this, (query) {
return query.link(q, r'account');
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterFilterCondition>
accountIsNull() {
return QueryBuilder.apply(this, (query) {
return query.linkLength(r'account', 0, true, 0, true);
});
}
}
extension BeneficiaryQuerySortBy
on QueryBuilder<Beneficiary, Beneficiary, QSortBy> {
QueryBuilder<Beneficiary, Beneficiary, QAfterSortBy> sortByImagePath() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'imagePath', Sort.asc);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterSortBy> sortByImagePathDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'imagePath', Sort.desc);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterSortBy> sortByName() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'name', Sort.asc);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterSortBy> sortByNameDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'name', Sort.desc);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterSortBy> sortByType() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'type', Sort.asc);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterSortBy> sortByTypeDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'type', Sort.desc);
});
}
}
extension BeneficiaryQuerySortThenBy
on QueryBuilder<Beneficiary, Beneficiary, QSortThenBy> {
QueryBuilder<Beneficiary, Beneficiary, QAfterSortBy> thenById() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.asc);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterSortBy> thenByIdDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.desc);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterSortBy> thenByImagePath() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'imagePath', Sort.asc);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterSortBy> thenByImagePathDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'imagePath', Sort.desc);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterSortBy> thenByName() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'name', Sort.asc);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterSortBy> thenByNameDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'name', Sort.desc);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterSortBy> thenByType() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'type', Sort.asc);
});
}
QueryBuilder<Beneficiary, Beneficiary, QAfterSortBy> thenByTypeDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'type', Sort.desc);
});
}
}
extension BeneficiaryQueryWhereDistinct
on QueryBuilder<Beneficiary, Beneficiary, QDistinct> {
QueryBuilder<Beneficiary, Beneficiary, QDistinct> distinctByImagePath({
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'imagePath', caseSensitive: caseSensitive);
});
}
QueryBuilder<Beneficiary, Beneficiary, QDistinct> distinctByName({
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'name', caseSensitive: caseSensitive);
});
}
QueryBuilder<Beneficiary, Beneficiary, QDistinct> distinctByType() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'type');
});
}
}
extension BeneficiaryQueryProperty
on QueryBuilder<Beneficiary, Beneficiary, QQueryProperty> {
QueryBuilder<Beneficiary, int, QQueryOperations> idProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'id');
});
}
QueryBuilder<Beneficiary, String?, QQueryOperations> imagePathProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'imagePath');
});
}
QueryBuilder<Beneficiary, String, QQueryOperations> nameProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'name');
});
}
QueryBuilder<Beneficiary, BeneficiaryType, QQueryOperations> typeProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'type');
});
}
}

View File

@@ -0,0 +1,10 @@
import 'package:isar/isar.dart';
part 'expense_category.g.dart';
@collection
class ExpenseCategory {
Id id = Isar.autoIncrement;
late String name;
}

View File

@@ -0,0 +1,451 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'expense_category.dart';
// **************************************************************************
// IsarCollectionGenerator
// **************************************************************************
// coverage:ignore-file
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
extension GetExpenseCategoryCollection on Isar {
IsarCollection<ExpenseCategory> get expenseCategorys => this.collection();
}
const ExpenseCategorySchema = CollectionSchema(
name: r'ExpenseCategory',
id: -6352499903118634,
properties: {
r'name': PropertySchema(id: 0, name: r'name', type: IsarType.string),
},
estimateSize: _expenseCategoryEstimateSize,
serialize: _expenseCategorySerialize,
deserialize: _expenseCategoryDeserialize,
deserializeProp: _expenseCategoryDeserializeProp,
idName: r'id',
indexes: {},
links: {},
embeddedSchemas: {},
getId: _expenseCategoryGetId,
getLinks: _expenseCategoryGetLinks,
attach: _expenseCategoryAttach,
version: '3.1.0+1',
);
int _expenseCategoryEstimateSize(
ExpenseCategory object,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
var bytesCount = offsets.last;
bytesCount += 3 + object.name.length * 3;
return bytesCount;
}
void _expenseCategorySerialize(
ExpenseCategory object,
IsarWriter writer,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
writer.writeString(offsets[0], object.name);
}
ExpenseCategory _expenseCategoryDeserialize(
Id id,
IsarReader reader,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
final object = ExpenseCategory();
object.id = id;
object.name = reader.readString(offsets[0]);
return object;
}
P _expenseCategoryDeserializeProp<P>(
IsarReader reader,
int propertyId,
int offset,
Map<Type, List<int>> allOffsets,
) {
switch (propertyId) {
case 0:
return (reader.readString(offset)) as P;
default:
throw IsarError('Unknown property with id $propertyId');
}
}
Id _expenseCategoryGetId(ExpenseCategory object) {
return object.id;
}
List<IsarLinkBase<dynamic>> _expenseCategoryGetLinks(ExpenseCategory object) {
return [];
}
void _expenseCategoryAttach(
IsarCollection<dynamic> col,
Id id,
ExpenseCategory object,
) {
object.id = id;
}
extension ExpenseCategoryQueryWhereSort
on QueryBuilder<ExpenseCategory, ExpenseCategory, QWhere> {
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterWhere> anyId() {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(const IdWhereClause.any());
});
}
}
extension ExpenseCategoryQueryWhere
on QueryBuilder<ExpenseCategory, ExpenseCategory, QWhereClause> {
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterWhereClause> idEqualTo(
Id id,
) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IdWhereClause.between(lower: id, upper: id));
});
}
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterWhereClause>
idNotEqualTo(Id id) {
return QueryBuilder.apply(this, (query) {
if (query.whereSort == Sort.asc) {
return query
.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: false),
)
.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: false),
);
} else {
return query
.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: false),
)
.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: false),
);
}
});
}
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterWhereClause>
idGreaterThan(Id id, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: include),
);
});
}
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterWhereClause> idLessThan(
Id id, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: include),
);
});
}
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterWhereClause> idBetween(
Id lowerId,
Id upperId, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.between(
lower: lowerId,
includeLower: includeLower,
upper: upperId,
includeUpper: includeUpper,
),
);
});
}
}
extension ExpenseCategoryQueryFilter
on QueryBuilder<ExpenseCategory, ExpenseCategory, QFilterCondition> {
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterFilterCondition>
idEqualTo(Id value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.equalTo(property: r'id', value: value),
);
});
}
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterFilterCondition>
idGreaterThan(Id value, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.greaterThan(
include: include,
property: r'id',
value: value,
),
);
});
}
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterFilterCondition>
idLessThan(Id value, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.lessThan(
include: include,
property: r'id',
value: value,
),
);
});
}
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterFilterCondition>
idBetween(
Id lower,
Id upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.between(
property: r'id',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
),
);
});
}
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterFilterCondition>
nameEqualTo(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.equalTo(
property: r'name',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterFilterCondition>
nameGreaterThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.greaterThan(
include: include,
property: r'name',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterFilterCondition>
nameLessThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.lessThan(
include: include,
property: r'name',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterFilterCondition>
nameBetween(
String lower,
String upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.between(
property: r'name',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterFilterCondition>
nameStartsWith(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.startsWith(
property: r'name',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterFilterCondition>
nameEndsWith(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.endsWith(
property: r'name',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterFilterCondition>
nameContains(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.contains(
property: r'name',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterFilterCondition>
nameMatches(String pattern, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.matches(
property: r'name',
wildcard: pattern,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterFilterCondition>
nameIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.equalTo(property: r'name', value: ''),
);
});
}
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterFilterCondition>
nameIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.greaterThan(property: r'name', value: ''),
);
});
}
}
extension ExpenseCategoryQueryObject
on QueryBuilder<ExpenseCategory, ExpenseCategory, QFilterCondition> {}
extension ExpenseCategoryQueryLinks
on QueryBuilder<ExpenseCategory, ExpenseCategory, QFilterCondition> {}
extension ExpenseCategoryQuerySortBy
on QueryBuilder<ExpenseCategory, ExpenseCategory, QSortBy> {
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterSortBy> sortByName() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'name', Sort.asc);
});
}
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterSortBy>
sortByNameDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'name', Sort.desc);
});
}
}
extension ExpenseCategoryQuerySortThenBy
on QueryBuilder<ExpenseCategory, ExpenseCategory, QSortThenBy> {
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterSortBy> thenById() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.asc);
});
}
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterSortBy> thenByIdDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.desc);
});
}
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterSortBy> thenByName() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'name', Sort.asc);
});
}
QueryBuilder<ExpenseCategory, ExpenseCategory, QAfterSortBy>
thenByNameDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'name', Sort.desc);
});
}
}
extension ExpenseCategoryQueryWhereDistinct
on QueryBuilder<ExpenseCategory, ExpenseCategory, QDistinct> {
QueryBuilder<ExpenseCategory, ExpenseCategory, QDistinct> distinctByName({
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'name', caseSensitive: caseSensitive);
});
}
}
extension ExpenseCategoryQueryProperty
on QueryBuilder<ExpenseCategory, ExpenseCategory, QQueryProperty> {
QueryBuilder<ExpenseCategory, int, QQueryOperations> idProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'id');
});
}
QueryBuilder<ExpenseCategory, String, QQueryOperations> nameProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'name');
});
}
}

View File

@@ -0,0 +1,18 @@
import 'package:isar/isar.dart';
import 'package:okane/database/collections/account.dart';
import 'package:okane/database/collections/template.dart';
part 'recurrent.g.dart';
@collection
class RecurringTransaction {
Id id = Isar.autoIncrement;
late int days;
DateTime? lastExecution;
final template = IsarLink<TransactionTemplate>();
final account = IsarLink<Account>();
}

View File

@@ -0,0 +1,633 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'recurrent.dart';
// **************************************************************************
// IsarCollectionGenerator
// **************************************************************************
// coverage:ignore-file
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
extension GetRecurringTransactionCollection on Isar {
IsarCollection<RecurringTransaction> get recurringTransactions =>
this.collection();
}
const RecurringTransactionSchema = CollectionSchema(
name: r'RecurringTransaction',
id: 969840479390105118,
properties: {
r'days': PropertySchema(id: 0, name: r'days', type: IsarType.long),
r'lastExecution': PropertySchema(
id: 1,
name: r'lastExecution',
type: IsarType.dateTime,
),
},
estimateSize: _recurringTransactionEstimateSize,
serialize: _recurringTransactionSerialize,
deserialize: _recurringTransactionDeserialize,
deserializeProp: _recurringTransactionDeserializeProp,
idName: r'id',
indexes: {},
links: {
r'template': LinkSchema(
id: -8891369755965227865,
name: r'template',
target: r'TransactionTemplate',
single: true,
),
r'account': LinkSchema(
id: -6028551496614242115,
name: r'account',
target: r'Account',
single: true,
),
},
embeddedSchemas: {},
getId: _recurringTransactionGetId,
getLinks: _recurringTransactionGetLinks,
attach: _recurringTransactionAttach,
version: '3.1.0+1',
);
int _recurringTransactionEstimateSize(
RecurringTransaction object,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
var bytesCount = offsets.last;
return bytesCount;
}
void _recurringTransactionSerialize(
RecurringTransaction object,
IsarWriter writer,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
writer.writeLong(offsets[0], object.days);
writer.writeDateTime(offsets[1], object.lastExecution);
}
RecurringTransaction _recurringTransactionDeserialize(
Id id,
IsarReader reader,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
final object = RecurringTransaction();
object.days = reader.readLong(offsets[0]);
object.id = id;
object.lastExecution = reader.readDateTimeOrNull(offsets[1]);
return object;
}
P _recurringTransactionDeserializeProp<P>(
IsarReader reader,
int propertyId,
int offset,
Map<Type, List<int>> allOffsets,
) {
switch (propertyId) {
case 0:
return (reader.readLong(offset)) as P;
case 1:
return (reader.readDateTimeOrNull(offset)) as P;
default:
throw IsarError('Unknown property with id $propertyId');
}
}
Id _recurringTransactionGetId(RecurringTransaction object) {
return object.id;
}
List<IsarLinkBase<dynamic>> _recurringTransactionGetLinks(
RecurringTransaction object,
) {
return [object.template, object.account];
}
void _recurringTransactionAttach(
IsarCollection<dynamic> col,
Id id,
RecurringTransaction object,
) {
object.id = id;
object.template.attach(
col,
col.isar.collection<TransactionTemplate>(),
r'template',
id,
);
object.account.attach(col, col.isar.collection<Account>(), r'account', id);
}
extension RecurringTransactionQueryWhereSort
on QueryBuilder<RecurringTransaction, RecurringTransaction, QWhere> {
QueryBuilder<RecurringTransaction, RecurringTransaction, QAfterWhere>
anyId() {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(const IdWhereClause.any());
});
}
}
extension RecurringTransactionQueryWhere
on QueryBuilder<RecurringTransaction, RecurringTransaction, QWhereClause> {
QueryBuilder<RecurringTransaction, RecurringTransaction, QAfterWhereClause>
idEqualTo(Id id) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IdWhereClause.between(lower: id, upper: id));
});
}
QueryBuilder<RecurringTransaction, RecurringTransaction, QAfterWhereClause>
idNotEqualTo(Id id) {
return QueryBuilder.apply(this, (query) {
if (query.whereSort == Sort.asc) {
return query
.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: false),
)
.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: false),
);
} else {
return query
.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: false),
)
.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: false),
);
}
});
}
QueryBuilder<RecurringTransaction, RecurringTransaction, QAfterWhereClause>
idGreaterThan(Id id, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: include),
);
});
}
QueryBuilder<RecurringTransaction, RecurringTransaction, QAfterWhereClause>
idLessThan(Id id, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: include),
);
});
}
QueryBuilder<RecurringTransaction, RecurringTransaction, QAfterWhereClause>
idBetween(
Id lowerId,
Id upperId, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.between(
lower: lowerId,
includeLower: includeLower,
upper: upperId,
includeUpper: includeUpper,
),
);
});
}
}
extension RecurringTransactionQueryFilter
on
QueryBuilder<
RecurringTransaction,
RecurringTransaction,
QFilterCondition
> {
QueryBuilder<
RecurringTransaction,
RecurringTransaction,
QAfterFilterCondition
>
daysEqualTo(int value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.equalTo(property: r'days', value: value),
);
});
}
QueryBuilder<
RecurringTransaction,
RecurringTransaction,
QAfterFilterCondition
>
daysGreaterThan(int value, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.greaterThan(
include: include,
property: r'days',
value: value,
),
);
});
}
QueryBuilder<
RecurringTransaction,
RecurringTransaction,
QAfterFilterCondition
>
daysLessThan(int value, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.lessThan(
include: include,
property: r'days',
value: value,
),
);
});
}
QueryBuilder<
RecurringTransaction,
RecurringTransaction,
QAfterFilterCondition
>
daysBetween(
int lower,
int upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.between(
property: r'days',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
),
);
});
}
QueryBuilder<
RecurringTransaction,
RecurringTransaction,
QAfterFilterCondition
>
idEqualTo(Id value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.equalTo(property: r'id', value: value),
);
});
}
QueryBuilder<
RecurringTransaction,
RecurringTransaction,
QAfterFilterCondition
>
idGreaterThan(Id value, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.greaterThan(
include: include,
property: r'id',
value: value,
),
);
});
}
QueryBuilder<
RecurringTransaction,
RecurringTransaction,
QAfterFilterCondition
>
idLessThan(Id value, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.lessThan(
include: include,
property: r'id',
value: value,
),
);
});
}
QueryBuilder<
RecurringTransaction,
RecurringTransaction,
QAfterFilterCondition
>
idBetween(
Id lower,
Id upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.between(
property: r'id',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
),
);
});
}
QueryBuilder<
RecurringTransaction,
RecurringTransaction,
QAfterFilterCondition
>
lastExecutionIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
const FilterCondition.isNull(property: r'lastExecution'),
);
});
}
QueryBuilder<
RecurringTransaction,
RecurringTransaction,
QAfterFilterCondition
>
lastExecutionIsNotNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
const FilterCondition.isNotNull(property: r'lastExecution'),
);
});
}
QueryBuilder<
RecurringTransaction,
RecurringTransaction,
QAfterFilterCondition
>
lastExecutionEqualTo(DateTime? value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.equalTo(property: r'lastExecution', value: value),
);
});
}
QueryBuilder<
RecurringTransaction,
RecurringTransaction,
QAfterFilterCondition
>
lastExecutionGreaterThan(DateTime? value, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.greaterThan(
include: include,
property: r'lastExecution',
value: value,
),
);
});
}
QueryBuilder<
RecurringTransaction,
RecurringTransaction,
QAfterFilterCondition
>
lastExecutionLessThan(DateTime? value, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.lessThan(
include: include,
property: r'lastExecution',
value: value,
),
);
});
}
QueryBuilder<
RecurringTransaction,
RecurringTransaction,
QAfterFilterCondition
>
lastExecutionBetween(
DateTime? lower,
DateTime? upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.between(
property: r'lastExecution',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
),
);
});
}
}
extension RecurringTransactionQueryObject
on
QueryBuilder<
RecurringTransaction,
RecurringTransaction,
QFilterCondition
> {}
extension RecurringTransactionQueryLinks
on
QueryBuilder<
RecurringTransaction,
RecurringTransaction,
QFilterCondition
> {
QueryBuilder<
RecurringTransaction,
RecurringTransaction,
QAfterFilterCondition
>
template(FilterQuery<TransactionTemplate> q) {
return QueryBuilder.apply(this, (query) {
return query.link(q, r'template');
});
}
QueryBuilder<
RecurringTransaction,
RecurringTransaction,
QAfterFilterCondition
>
templateIsNull() {
return QueryBuilder.apply(this, (query) {
return query.linkLength(r'template', 0, true, 0, true);
});
}
QueryBuilder<
RecurringTransaction,
RecurringTransaction,
QAfterFilterCondition
>
account(FilterQuery<Account> q) {
return QueryBuilder.apply(this, (query) {
return query.link(q, r'account');
});
}
QueryBuilder<
RecurringTransaction,
RecurringTransaction,
QAfterFilterCondition
>
accountIsNull() {
return QueryBuilder.apply(this, (query) {
return query.linkLength(r'account', 0, true, 0, true);
});
}
}
extension RecurringTransactionQuerySortBy
on QueryBuilder<RecurringTransaction, RecurringTransaction, QSortBy> {
QueryBuilder<RecurringTransaction, RecurringTransaction, QAfterSortBy>
sortByDays() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'days', Sort.asc);
});
}
QueryBuilder<RecurringTransaction, RecurringTransaction, QAfterSortBy>
sortByDaysDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'days', Sort.desc);
});
}
QueryBuilder<RecurringTransaction, RecurringTransaction, QAfterSortBy>
sortByLastExecution() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'lastExecution', Sort.asc);
});
}
QueryBuilder<RecurringTransaction, RecurringTransaction, QAfterSortBy>
sortByLastExecutionDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'lastExecution', Sort.desc);
});
}
}
extension RecurringTransactionQuerySortThenBy
on QueryBuilder<RecurringTransaction, RecurringTransaction, QSortThenBy> {
QueryBuilder<RecurringTransaction, RecurringTransaction, QAfterSortBy>
thenByDays() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'days', Sort.asc);
});
}
QueryBuilder<RecurringTransaction, RecurringTransaction, QAfterSortBy>
thenByDaysDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'days', Sort.desc);
});
}
QueryBuilder<RecurringTransaction, RecurringTransaction, QAfterSortBy>
thenById() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.asc);
});
}
QueryBuilder<RecurringTransaction, RecurringTransaction, QAfterSortBy>
thenByIdDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.desc);
});
}
QueryBuilder<RecurringTransaction, RecurringTransaction, QAfterSortBy>
thenByLastExecution() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'lastExecution', Sort.asc);
});
}
QueryBuilder<RecurringTransaction, RecurringTransaction, QAfterSortBy>
thenByLastExecutionDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'lastExecution', Sort.desc);
});
}
}
extension RecurringTransactionQueryWhereDistinct
on QueryBuilder<RecurringTransaction, RecurringTransaction, QDistinct> {
QueryBuilder<RecurringTransaction, RecurringTransaction, QDistinct>
distinctByDays() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'days');
});
}
QueryBuilder<RecurringTransaction, RecurringTransaction, QDistinct>
distinctByLastExecution() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'lastExecution');
});
}
}
extension RecurringTransactionQueryProperty
on
QueryBuilder<
RecurringTransaction,
RecurringTransaction,
QQueryProperty
> {
QueryBuilder<RecurringTransaction, int, QQueryOperations> idProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'id');
});
}
QueryBuilder<RecurringTransaction, int, QQueryOperations> daysProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'days');
});
}
QueryBuilder<RecurringTransaction, DateTime?, QQueryOperations>
lastExecutionProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'lastExecution');
});
}
}

View File

@@ -0,0 +1,23 @@
import 'package:isar/isar.dart';
import 'package:okane/database/collections/account.dart';
import 'package:okane/database/collections/beneficiary.dart';
import 'package:okane/database/collections/expense_category.dart';
part 'template.g.dart';
@collection
class TransactionTemplate {
Id id = Isar.autoIncrement;
late String name;
late double amount;
late bool recurring;
final expenseCategory = IsarLink<ExpenseCategory>();
final beneficiary = IsarLink<Beneficiary>();
final account = IsarLink<Account>();
}

View File

@@ -0,0 +1,721 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'template.dart';
// **************************************************************************
// IsarCollectionGenerator
// **************************************************************************
// coverage:ignore-file
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
extension GetTransactionTemplateCollection on Isar {
IsarCollection<TransactionTemplate> get transactionTemplates =>
this.collection();
}
const TransactionTemplateSchema = CollectionSchema(
name: r'TransactionTemplate',
id: -2324989530163310644,
properties: {
r'amount': PropertySchema(id: 0, name: r'amount', type: IsarType.double),
r'name': PropertySchema(id: 1, name: r'name', type: IsarType.string),
r'recurring': PropertySchema(
id: 2,
name: r'recurring',
type: IsarType.bool,
),
},
estimateSize: _transactionTemplateEstimateSize,
serialize: _transactionTemplateSerialize,
deserialize: _transactionTemplateDeserialize,
deserializeProp: _transactionTemplateDeserializeProp,
idName: r'id',
indexes: {},
links: {
r'expenseCategory': LinkSchema(
id: 3013186211408715712,
name: r'expenseCategory',
target: r'ExpenseCategory',
single: true,
),
r'beneficiary': LinkSchema(
id: -7565656011019083791,
name: r'beneficiary',
target: r'Beneficiary',
single: true,
),
r'account': LinkSchema(
id: 2465433941426054606,
name: r'account',
target: r'Account',
single: true,
),
},
embeddedSchemas: {},
getId: _transactionTemplateGetId,
getLinks: _transactionTemplateGetLinks,
attach: _transactionTemplateAttach,
version: '3.1.0+1',
);
int _transactionTemplateEstimateSize(
TransactionTemplate object,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
var bytesCount = offsets.last;
bytesCount += 3 + object.name.length * 3;
return bytesCount;
}
void _transactionTemplateSerialize(
TransactionTemplate object,
IsarWriter writer,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
writer.writeDouble(offsets[0], object.amount);
writer.writeString(offsets[1], object.name);
writer.writeBool(offsets[2], object.recurring);
}
TransactionTemplate _transactionTemplateDeserialize(
Id id,
IsarReader reader,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
final object = TransactionTemplate();
object.amount = reader.readDouble(offsets[0]);
object.id = id;
object.name = reader.readString(offsets[1]);
object.recurring = reader.readBool(offsets[2]);
return object;
}
P _transactionTemplateDeserializeProp<P>(
IsarReader reader,
int propertyId,
int offset,
Map<Type, List<int>> allOffsets,
) {
switch (propertyId) {
case 0:
return (reader.readDouble(offset)) as P;
case 1:
return (reader.readString(offset)) as P;
case 2:
return (reader.readBool(offset)) as P;
default:
throw IsarError('Unknown property with id $propertyId');
}
}
Id _transactionTemplateGetId(TransactionTemplate object) {
return object.id;
}
List<IsarLinkBase<dynamic>> _transactionTemplateGetLinks(
TransactionTemplate object,
) {
return [object.expenseCategory, object.beneficiary, object.account];
}
void _transactionTemplateAttach(
IsarCollection<dynamic> col,
Id id,
TransactionTemplate object,
) {
object.id = id;
object.expenseCategory.attach(
col,
col.isar.collection<ExpenseCategory>(),
r'expenseCategory',
id,
);
object.beneficiary.attach(
col,
col.isar.collection<Beneficiary>(),
r'beneficiary',
id,
);
object.account.attach(col, col.isar.collection<Account>(), r'account', id);
}
extension TransactionTemplateQueryWhereSort
on QueryBuilder<TransactionTemplate, TransactionTemplate, QWhere> {
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterWhere> anyId() {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(const IdWhereClause.any());
});
}
}
extension TransactionTemplateQueryWhere
on QueryBuilder<TransactionTemplate, TransactionTemplate, QWhereClause> {
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterWhereClause>
idEqualTo(Id id) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IdWhereClause.between(lower: id, upper: id));
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterWhereClause>
idNotEqualTo(Id id) {
return QueryBuilder.apply(this, (query) {
if (query.whereSort == Sort.asc) {
return query
.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: false),
)
.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: false),
);
} else {
return query
.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: false),
)
.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: false),
);
}
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterWhereClause>
idGreaterThan(Id id, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: include),
);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterWhereClause>
idLessThan(Id id, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: include),
);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterWhereClause>
idBetween(
Id lowerId,
Id upperId, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.between(
lower: lowerId,
includeLower: includeLower,
upper: upperId,
includeUpper: includeUpper,
),
);
});
}
}
extension TransactionTemplateQueryFilter
on
QueryBuilder<
TransactionTemplate,
TransactionTemplate,
QFilterCondition
> {
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterFilterCondition>
amountEqualTo(double value, {double epsilon = Query.epsilon}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.equalTo(
property: r'amount',
value: value,
epsilon: epsilon,
),
);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterFilterCondition>
amountGreaterThan(
double value, {
bool include = false,
double epsilon = Query.epsilon,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.greaterThan(
include: include,
property: r'amount',
value: value,
epsilon: epsilon,
),
);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterFilterCondition>
amountLessThan(
double value, {
bool include = false,
double epsilon = Query.epsilon,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.lessThan(
include: include,
property: r'amount',
value: value,
epsilon: epsilon,
),
);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterFilterCondition>
amountBetween(
double lower,
double upper, {
bool includeLower = true,
bool includeUpper = true,
double epsilon = Query.epsilon,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.between(
property: r'amount',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
epsilon: epsilon,
),
);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterFilterCondition>
idEqualTo(Id value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.equalTo(property: r'id', value: value),
);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterFilterCondition>
idGreaterThan(Id value, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.greaterThan(
include: include,
property: r'id',
value: value,
),
);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterFilterCondition>
idLessThan(Id value, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.lessThan(
include: include,
property: r'id',
value: value,
),
);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterFilterCondition>
idBetween(
Id lower,
Id upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.between(
property: r'id',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
),
);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterFilterCondition>
nameEqualTo(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.equalTo(
property: r'name',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterFilterCondition>
nameGreaterThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.greaterThan(
include: include,
property: r'name',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterFilterCondition>
nameLessThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.lessThan(
include: include,
property: r'name',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterFilterCondition>
nameBetween(
String lower,
String upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.between(
property: r'name',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterFilterCondition>
nameStartsWith(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.startsWith(
property: r'name',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterFilterCondition>
nameEndsWith(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.endsWith(
property: r'name',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterFilterCondition>
nameContains(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.contains(
property: r'name',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterFilterCondition>
nameMatches(String pattern, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.matches(
property: r'name',
wildcard: pattern,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterFilterCondition>
nameIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.equalTo(property: r'name', value: ''),
);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterFilterCondition>
nameIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.greaterThan(property: r'name', value: ''),
);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterFilterCondition>
recurringEqualTo(bool value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.equalTo(property: r'recurring', value: value),
);
});
}
}
extension TransactionTemplateQueryObject
on
QueryBuilder<
TransactionTemplate,
TransactionTemplate,
QFilterCondition
> {}
extension TransactionTemplateQueryLinks
on
QueryBuilder<
TransactionTemplate,
TransactionTemplate,
QFilterCondition
> {
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterFilterCondition>
expenseCategory(FilterQuery<ExpenseCategory> q) {
return QueryBuilder.apply(this, (query) {
return query.link(q, r'expenseCategory');
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterFilterCondition>
expenseCategoryIsNull() {
return QueryBuilder.apply(this, (query) {
return query.linkLength(r'expenseCategory', 0, true, 0, true);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterFilterCondition>
beneficiary(FilterQuery<Beneficiary> q) {
return QueryBuilder.apply(this, (query) {
return query.link(q, r'beneficiary');
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterFilterCondition>
beneficiaryIsNull() {
return QueryBuilder.apply(this, (query) {
return query.linkLength(r'beneficiary', 0, true, 0, true);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterFilterCondition>
account(FilterQuery<Account> q) {
return QueryBuilder.apply(this, (query) {
return query.link(q, r'account');
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterFilterCondition>
accountIsNull() {
return QueryBuilder.apply(this, (query) {
return query.linkLength(r'account', 0, true, 0, true);
});
}
}
extension TransactionTemplateQuerySortBy
on QueryBuilder<TransactionTemplate, TransactionTemplate, QSortBy> {
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterSortBy>
sortByAmount() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'amount', Sort.asc);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterSortBy>
sortByAmountDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'amount', Sort.desc);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterSortBy>
sortByName() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'name', Sort.asc);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterSortBy>
sortByNameDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'name', Sort.desc);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterSortBy>
sortByRecurring() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'recurring', Sort.asc);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterSortBy>
sortByRecurringDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'recurring', Sort.desc);
});
}
}
extension TransactionTemplateQuerySortThenBy
on QueryBuilder<TransactionTemplate, TransactionTemplate, QSortThenBy> {
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterSortBy>
thenByAmount() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'amount', Sort.asc);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterSortBy>
thenByAmountDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'amount', Sort.desc);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterSortBy>
thenById() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.asc);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterSortBy>
thenByIdDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.desc);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterSortBy>
thenByName() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'name', Sort.asc);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterSortBy>
thenByNameDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'name', Sort.desc);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterSortBy>
thenByRecurring() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'recurring', Sort.asc);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QAfterSortBy>
thenByRecurringDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'recurring', Sort.desc);
});
}
}
extension TransactionTemplateQueryWhereDistinct
on QueryBuilder<TransactionTemplate, TransactionTemplate, QDistinct> {
QueryBuilder<TransactionTemplate, TransactionTemplate, QDistinct>
distinctByAmount() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'amount');
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QDistinct>
distinctByName({bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'name', caseSensitive: caseSensitive);
});
}
QueryBuilder<TransactionTemplate, TransactionTemplate, QDistinct>
distinctByRecurring() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'recurring');
});
}
}
extension TransactionTemplateQueryProperty
on QueryBuilder<TransactionTemplate, TransactionTemplate, QQueryProperty> {
QueryBuilder<TransactionTemplate, int, QQueryOperations> idProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'id');
});
}
QueryBuilder<TransactionTemplate, double, QQueryOperations> amountProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'amount');
});
}
QueryBuilder<TransactionTemplate, String, QQueryOperations> nameProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'name');
});
}
QueryBuilder<TransactionTemplate, bool, QQueryOperations>
recurringProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'recurring');
});
}
}

View File

@@ -0,0 +1,23 @@
import 'package:isar/isar.dart';
import 'package:okane/database/collections/account.dart';
import 'package:okane/database/collections/beneficiary.dart';
import 'package:okane/database/collections/expense_category.dart';
part 'transaction.g.dart';
@collection
class Transaction {
Id id = Isar.autoIncrement;
late double amount;
late List<String> tags;
late DateTime date;
final expenseCategory = IsarLink<ExpenseCategory>();
final account = IsarLink<Account>();
final beneficiary = IsarLink<Beneficiary>();
}

View File

@@ -0,0 +1,775 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'transaction.dart';
// **************************************************************************
// IsarCollectionGenerator
// **************************************************************************
// coverage:ignore-file
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
extension GetTransactionCollection on Isar {
IsarCollection<Transaction> get transactions => this.collection();
}
const TransactionSchema = CollectionSchema(
name: r'Transaction',
id: 5320225499417954855,
properties: {
r'amount': PropertySchema(id: 0, name: r'amount', type: IsarType.double),
r'date': PropertySchema(id: 1, name: r'date', type: IsarType.dateTime),
r'tags': PropertySchema(id: 2, name: r'tags', type: IsarType.stringList),
},
estimateSize: _transactionEstimateSize,
serialize: _transactionSerialize,
deserialize: _transactionDeserialize,
deserializeProp: _transactionDeserializeProp,
idName: r'id',
indexes: {},
links: {
r'expenseCategory': LinkSchema(
id: 490804775908778298,
name: r'expenseCategory',
target: r'ExpenseCategory',
single: true,
),
r'account': LinkSchema(
id: -8467990729867616553,
name: r'account',
target: r'Account',
single: true,
),
r'beneficiary': LinkSchema(
id: -1184196133247909686,
name: r'beneficiary',
target: r'Beneficiary',
single: true,
),
},
embeddedSchemas: {},
getId: _transactionGetId,
getLinks: _transactionGetLinks,
attach: _transactionAttach,
version: '3.1.0+1',
);
int _transactionEstimateSize(
Transaction object,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
var bytesCount = offsets.last;
bytesCount += 3 + object.tags.length * 3;
{
for (var i = 0; i < object.tags.length; i++) {
final value = object.tags[i];
bytesCount += value.length * 3;
}
}
return bytesCount;
}
void _transactionSerialize(
Transaction object,
IsarWriter writer,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
writer.writeDouble(offsets[0], object.amount);
writer.writeDateTime(offsets[1], object.date);
writer.writeStringList(offsets[2], object.tags);
}
Transaction _transactionDeserialize(
Id id,
IsarReader reader,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
final object = Transaction();
object.amount = reader.readDouble(offsets[0]);
object.date = reader.readDateTime(offsets[1]);
object.id = id;
object.tags = reader.readStringList(offsets[2]) ?? [];
return object;
}
P _transactionDeserializeProp<P>(
IsarReader reader,
int propertyId,
int offset,
Map<Type, List<int>> allOffsets,
) {
switch (propertyId) {
case 0:
return (reader.readDouble(offset)) as P;
case 1:
return (reader.readDateTime(offset)) as P;
case 2:
return (reader.readStringList(offset) ?? []) as P;
default:
throw IsarError('Unknown property with id $propertyId');
}
}
Id _transactionGetId(Transaction object) {
return object.id;
}
List<IsarLinkBase<dynamic>> _transactionGetLinks(Transaction object) {
return [object.expenseCategory, object.account, object.beneficiary];
}
void _transactionAttach(
IsarCollection<dynamic> col,
Id id,
Transaction object,
) {
object.id = id;
object.expenseCategory.attach(
col,
col.isar.collection<ExpenseCategory>(),
r'expenseCategory',
id,
);
object.account.attach(col, col.isar.collection<Account>(), r'account', id);
object.beneficiary.attach(
col,
col.isar.collection<Beneficiary>(),
r'beneficiary',
id,
);
}
extension TransactionQueryWhereSort
on QueryBuilder<Transaction, Transaction, QWhere> {
QueryBuilder<Transaction, Transaction, QAfterWhere> anyId() {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(const IdWhereClause.any());
});
}
}
extension TransactionQueryWhere
on QueryBuilder<Transaction, Transaction, QWhereClause> {
QueryBuilder<Transaction, Transaction, QAfterWhereClause> idEqualTo(Id id) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IdWhereClause.between(lower: id, upper: id));
});
}
QueryBuilder<Transaction, Transaction, QAfterWhereClause> idNotEqualTo(
Id id,
) {
return QueryBuilder.apply(this, (query) {
if (query.whereSort == Sort.asc) {
return query
.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: false),
)
.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: false),
);
} else {
return query
.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: false),
)
.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: false),
);
}
});
}
QueryBuilder<Transaction, Transaction, QAfterWhereClause> idGreaterThan(
Id id, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: include),
);
});
}
QueryBuilder<Transaction, Transaction, QAfterWhereClause> idLessThan(
Id id, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: include),
);
});
}
QueryBuilder<Transaction, Transaction, QAfterWhereClause> idBetween(
Id lowerId,
Id upperId, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.between(
lower: lowerId,
includeLower: includeLower,
upper: upperId,
includeUpper: includeUpper,
),
);
});
}
}
extension TransactionQueryFilter
on QueryBuilder<Transaction, Transaction, QFilterCondition> {
QueryBuilder<Transaction, Transaction, QAfterFilterCondition> amountEqualTo(
double value, {
double epsilon = Query.epsilon,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.equalTo(
property: r'amount',
value: value,
epsilon: epsilon,
),
);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition>
amountGreaterThan(
double value, {
bool include = false,
double epsilon = Query.epsilon,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.greaterThan(
include: include,
property: r'amount',
value: value,
epsilon: epsilon,
),
);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition> amountLessThan(
double value, {
bool include = false,
double epsilon = Query.epsilon,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.lessThan(
include: include,
property: r'amount',
value: value,
epsilon: epsilon,
),
);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition> amountBetween(
double lower,
double upper, {
bool includeLower = true,
bool includeUpper = true,
double epsilon = Query.epsilon,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.between(
property: r'amount',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
epsilon: epsilon,
),
);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition> dateEqualTo(
DateTime value,
) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.equalTo(property: r'date', value: value),
);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition> dateGreaterThan(
DateTime value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.greaterThan(
include: include,
property: r'date',
value: value,
),
);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition> dateLessThan(
DateTime value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.lessThan(
include: include,
property: r'date',
value: value,
),
);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition> dateBetween(
DateTime lower,
DateTime upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.between(
property: r'date',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
),
);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition> idEqualTo(
Id value,
) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.equalTo(property: r'id', value: value),
);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition> idGreaterThan(
Id value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.greaterThan(
include: include,
property: r'id',
value: value,
),
);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition> idLessThan(
Id value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.lessThan(
include: include,
property: r'id',
value: value,
),
);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition> idBetween(
Id lower,
Id upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.between(
property: r'id',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
),
);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition>
tagsElementEqualTo(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.equalTo(
property: r'tags',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition>
tagsElementGreaterThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.greaterThan(
include: include,
property: r'tags',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition>
tagsElementLessThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.lessThan(
include: include,
property: r'tags',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition>
tagsElementBetween(
String lower,
String upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.between(
property: r'tags',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition>
tagsElementStartsWith(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.startsWith(
property: r'tags',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition>
tagsElementEndsWith(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.endsWith(
property: r'tags',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition>
tagsElementContains(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.contains(
property: r'tags',
value: value,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition>
tagsElementMatches(String pattern, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.matches(
property: r'tags',
wildcard: pattern,
caseSensitive: caseSensitive,
),
);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition>
tagsElementIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.equalTo(property: r'tags', value: ''),
);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition>
tagsElementIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(
FilterCondition.greaterThan(property: r'tags', value: ''),
);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition>
tagsLengthEqualTo(int length) {
return QueryBuilder.apply(this, (query) {
return query.listLength(r'tags', length, true, length, true);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition> tagsIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.listLength(r'tags', 0, true, 0, true);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition>
tagsIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.listLength(r'tags', 0, false, 999999, true);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition>
tagsLengthLessThan(int length, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.listLength(r'tags', 0, true, length, include);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition>
tagsLengthGreaterThan(int length, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.listLength(r'tags', length, include, 999999, true);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition>
tagsLengthBetween(
int lower,
int upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'tags',
lower,
includeLower,
upper,
includeUpper,
);
});
}
}
extension TransactionQueryObject
on QueryBuilder<Transaction, Transaction, QFilterCondition> {}
extension TransactionQueryLinks
on QueryBuilder<Transaction, Transaction, QFilterCondition> {
QueryBuilder<Transaction, Transaction, QAfterFilterCondition> expenseCategory(
FilterQuery<ExpenseCategory> q,
) {
return QueryBuilder.apply(this, (query) {
return query.link(q, r'expenseCategory');
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition>
expenseCategoryIsNull() {
return QueryBuilder.apply(this, (query) {
return query.linkLength(r'expenseCategory', 0, true, 0, true);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition> account(
FilterQuery<Account> q,
) {
return QueryBuilder.apply(this, (query) {
return query.link(q, r'account');
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition>
accountIsNull() {
return QueryBuilder.apply(this, (query) {
return query.linkLength(r'account', 0, true, 0, true);
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition> beneficiary(
FilterQuery<Beneficiary> q,
) {
return QueryBuilder.apply(this, (query) {
return query.link(q, r'beneficiary');
});
}
QueryBuilder<Transaction, Transaction, QAfterFilterCondition>
beneficiaryIsNull() {
return QueryBuilder.apply(this, (query) {
return query.linkLength(r'beneficiary', 0, true, 0, true);
});
}
}
extension TransactionQuerySortBy
on QueryBuilder<Transaction, Transaction, QSortBy> {
QueryBuilder<Transaction, Transaction, QAfterSortBy> sortByAmount() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'amount', Sort.asc);
});
}
QueryBuilder<Transaction, Transaction, QAfterSortBy> sortByAmountDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'amount', Sort.desc);
});
}
QueryBuilder<Transaction, Transaction, QAfterSortBy> sortByDate() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'date', Sort.asc);
});
}
QueryBuilder<Transaction, Transaction, QAfterSortBy> sortByDateDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'date', Sort.desc);
});
}
}
extension TransactionQuerySortThenBy
on QueryBuilder<Transaction, Transaction, QSortThenBy> {
QueryBuilder<Transaction, Transaction, QAfterSortBy> thenByAmount() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'amount', Sort.asc);
});
}
QueryBuilder<Transaction, Transaction, QAfterSortBy> thenByAmountDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'amount', Sort.desc);
});
}
QueryBuilder<Transaction, Transaction, QAfterSortBy> thenByDate() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'date', Sort.asc);
});
}
QueryBuilder<Transaction, Transaction, QAfterSortBy> thenByDateDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'date', Sort.desc);
});
}
QueryBuilder<Transaction, Transaction, QAfterSortBy> thenById() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.asc);
});
}
QueryBuilder<Transaction, Transaction, QAfterSortBy> thenByIdDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.desc);
});
}
}
extension TransactionQueryWhereDistinct
on QueryBuilder<Transaction, Transaction, QDistinct> {
QueryBuilder<Transaction, Transaction, QDistinct> distinctByAmount() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'amount');
});
}
QueryBuilder<Transaction, Transaction, QDistinct> distinctByDate() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'date');
});
}
QueryBuilder<Transaction, Transaction, QDistinct> distinctByTags() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'tags');
});
}
}
extension TransactionQueryProperty
on QueryBuilder<Transaction, Transaction, QQueryProperty> {
QueryBuilder<Transaction, int, QQueryOperations> idProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'id');
});
}
QueryBuilder<Transaction, double, QQueryOperations> amountProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'amount');
});
}
QueryBuilder<Transaction, DateTime, QQueryOperations> dateProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'date');
});
}
QueryBuilder<Transaction, List<String>, QQueryOperations> tagsProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'tags');
});
}
}

207
lib/database/database.dart Normal file
View File

@@ -0,0 +1,207 @@
import 'dart:async';
import 'package:isar/isar.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/collections/account.dart';
import 'package:okane/database/collections/beneficiary.dart';
import 'package:okane/database/collections/expense_category.dart';
import 'package:okane/database/collections/recurrent.dart';
import 'package:okane/database/collections/template.dart';
import 'package:okane/database/collections/transaction.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/utils.dart';
import 'package:path_provider/path_provider.dart';
Future<Isar> openDatabase() async {
final dir = await getApplicationDocumentsDirectory();
return Isar.open([
AccountSchema,
BeneficiarySchema,
TransactionSchema,
TransactionTemplateSchema,
RecurringTransactionSchema,
ExpenseCategorySchema,
], directory: dir.path);
}
Future<List<Account>> getAccounts() {
return GetIt.I.get<Isar>().accounts.where().findAll();
}
Future<double> getTotalBalance(Account account) async {
return GetIt.I
.get<Isar>()
.transactions
.filter()
.account((q) => q.idEqualTo(account.id))
.amountProperty()
.sum();
}
Future<List<Transaction>> getLastTransactions(
Account account,
DateTime today,
int days,
) async {
return GetIt.I
.get<Isar>()
.transactions
.filter()
.account((q) => q.idEqualTo(account.id))
.dateGreaterThan(toMidnight(today.subtract(Duration(days: days))))
.findAll();
}
Future<List<RecurringTransaction>> getRecurringTransactions(Account? account) {
if (account == null) {
return Future.value([]);
}
return GetIt.I
.get<Isar>()
.recurringTransactions
.filter()
.account((q) => q.idEqualTo(account.id))
.findAll();
}
Stream<void> watchRecurringTransactions(Account account) {
final account = GetIt.I.get<CoreCubit>().activeAccount!;
return GetIt.I
.get<Isar>()
.recurringTransactions
.filter()
.account((q) => q.idEqualTo(account.id))
.build()
.watchLazy(fireImmediately: true);
}
Future<void> upsertAccount(Account account) async {
final db = GetIt.I.get<Isar>();
return db.writeTxn(() async {
print("Before account insert");
final id = await db.accounts.put(account);
print("After account insert: $id");
});
}
Future<void> upsertBeneficiary(Beneficiary beneficiary) async {
final db = GetIt.I.get<Isar>();
return db.writeTxn(() async {
await db.beneficiarys.put(beneficiary);
await beneficiary.account.save();
});
}
Future<Beneficiary?> getAccountBeneficiary(Account account) {
return GetIt.I
.get<Isar>()
.beneficiarys
.filter()
.account((q) => q.idEqualTo(account.id))
.findFirst();
}
Future<void> upsertTransactionTemplate(TransactionTemplate template) async {
final db = GetIt.I.get<Isar>();
return db.writeTxn(() async {
await db.transactionTemplates.put(template);
await template.beneficiary.save();
await template.account.save();
await template.expenseCategory.save();
});
}
Future<void> upsertRecurringTransaction(RecurringTransaction template) async {
final db = GetIt.I.get<Isar>();
return db.writeTxn(() async {
await db.recurringTransactions.put(template);
await template.template.save();
await template.account.save();
});
}
Future<void> upsertTransaction(Transaction transaction) async {
final db = GetIt.I.get<Isar>();
return db.writeTxn(() async {
await db.transactions.put(transaction);
await transaction.beneficiary.save();
await transaction.account.save();
await transaction.expenseCategory.save();
});
}
Stream<void> watchAccounts() {
return GetIt.I.get<Isar>().accounts.watchLazy();
}
Stream<void> watchTransactionTemplates(Account account) {
return GetIt.I
.get<Isar>()
.transactionTemplates
.filter()
.account((q) => q.idEqualTo(account.id))
.recurringEqualTo(false)
.watchLazy(fireImmediately: true);
}
Future<List<TransactionTemplate>> getTransactionTemplates(Account? account) {
if (account == null) {
return Future.value([]);
}
return GetIt.I
.get<Isar>()
.transactionTemplates
.filter()
.account((q) => q.idEqualTo(account.id))
.recurringEqualTo(false)
.findAll();
}
Stream<void> watchTransactions(Account account) {
return GetIt.I
.get<Isar>()
.transactions
.filter()
.account((q) => q.idEqualTo(account.id))
.watchLazy(fireImmediately: true);
}
Future<List<Transaction>> getTransactions(Account? account) {
if (account == null) {
return Future.value([]);
}
return GetIt.I
.get<Isar>()
.transactions
.filter()
.account((q) => q.idEqualTo(account.id))
.findAll();
}
Stream<void> watchBeneficiaries() {
return GetIt.I.get<Isar>().beneficiarys.watchLazy(fireImmediately: true);
}
Future<List<Beneficiary>> getBeneficiaries() {
return GetIt.I.get<Isar>().beneficiarys.where().findAll();
}
Stream<Beneficiary?> watchBeneficiaryObject(Id id) {
return GetIt.I.get<Isar>().beneficiarys.watchObject(id);
}
Future<void> upsertExpenseCategory(ExpenseCategory category) {
final db = GetIt.I.get<Isar>();
return db.writeTxn(() => db.expenseCategorys.put(category));
}
Future<List<ExpenseCategory>> getExpenseCategories() {
return GetIt.I.get<Isar>().expenseCategorys.where().findAll();
}
Stream<void> watchExpenseCategory() {
return GetIt.I.get<Isar>().expenseCategorys.watchLazy(fireImmediately: true);
}

107
lib/main.dart Normal file
View File

@@ -0,0 +1,107 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:isar/isar.dart';
import 'package:okane/database/database.dart';
import 'package:okane/screen.dart';
import 'package:okane/ui/navigation.dart';
import 'package:okane/ui/pages/transaction_details.dart';
import 'package:okane/ui/state/core.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
GetIt.I.registerSingleton<CoreCubit>(CoreCubit());
GetIt.I.registerSingleton<Isar>(await openDatabase());
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<CoreCubit>(create: (_) => GetIt.I.get<CoreCubit>()),
],
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const MyHomePage(),
onGenerateRoute:
(settings) => switch (settings.name) {
"/transactions/details" => TransactionDetailsPage.mobileRoute,
_ => MaterialPageRoute<void>(builder: (_) => Text("Unknown!!")),
},
),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final Completer<int> _initCompleter = Completer();
@override
void initState() {
super.initState();
asyncInit();
}
Future<void> asyncInit() async {
print("Starting async init");
await GetIt.I.get<CoreCubit>().init();
_initCompleter.complete(1);
print("Async init done");
}
@override
Widget build(BuildContext context) {
final screenSize = getScreenSize(context);
if (screenSize == ScreenSize.normal) {
SchedulerBinding.instance.addPostFrameCallback((_) {
Navigator.of(context).popUntil((route) => route.isFirst);
});
}
return FutureBuilder(
future: _initCompleter.future,
builder: (context, snapshot) {
print("${snapshot.hasData}");
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
}
return Scaffold(
bottomNavigationBar: switch (screenSize) {
ScreenSize.normal => null,
ScreenSize.small => OkaneNavigationBar(),
},
body: switch (screenSize) {
ScreenSize.normal => const Row(
children: [
OkaneNavigationRail(),
VerticalDivider(thickness: 1, width: 1),
Expanded(child: OkaneNavigationLayout()),
],
),
ScreenSize.small => const OkaneNavigationLayout(),
},
);
},
);
}
}

17
lib/screen.dart Normal file
View File

@@ -0,0 +1,17 @@
import 'package:flutter/cupertino.dart';
enum ScreenSize {
small(300),
normal(400);
final double size;
const ScreenSize(this.size);
}
ScreenSize getScreenSize(BuildContext context) {
final width = MediaQuery.sizeOf(context).shortestSide;
if (width > ScreenSize.normal.size) return ScreenSize.normal;
return ScreenSize.small;
}

138
lib/ui/navigation.dart Normal file
View File

@@ -0,0 +1,138 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:okane/screen.dart';
import 'package:okane/ui/pages/account/account.dart';
import 'package:okane/ui/pages/template_list.dart';
import 'package:okane/ui/pages/transaction_details.dart';
import 'package:okane/ui/pages/transaction_list.dart';
import 'package:okane/ui/state/core.dart';
enum OkanePage { accounts, transactions, beneficiaries, templates, budgets }
typedef OkanePageBuilder = Widget Function(bool);
class OkanePageItem {
final OkanePage page;
final IconData icon;
final String label;
final Widget child;
final OkanePageBuilder? details;
const OkanePageItem(
this.page,
this.icon,
this.label,
this.child,
this.details,
);
NavigationDestination toDestination() =>
NavigationDestination(icon: Icon(icon), label: label);
NavigationRailDestination toRailDestination() =>
NavigationRailDestination(icon: Icon(icon), label: Text(label));
}
final _pages = <OkanePageItem>[
OkanePageItem(
OkanePage.accounts,
Icons.account_box,
"Accounts",
AccountListPage(isPage: false),
null,
),
OkanePageItem(
OkanePage.transactions,
Icons.monetization_on_rounded,
"Transactions",
TransactionListPage(),
(_) => TransactionDetailsPage(),
),
OkanePageItem(
OkanePage.beneficiaries,
Icons.person,
"Beneficiaries",
Container(),
null,
),
OkanePageItem(
OkanePage.templates,
Icons.list,
"Templates",
TemplateListPage(),
null,
),
OkanePageItem(
OkanePage.budgets,
Icons.pie_chart,
"Budgets",
Placeholder(),
null,
),
];
class OkaneNavigationRail extends StatelessWidget {
const OkaneNavigationRail({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<CoreCubit, CoreState>(
builder:
(context, state) => NavigationRail(
onDestinationSelected:
(i) => context.read<CoreCubit>().setPage(_pages[i].page),
destinations: _pages.map((p) => p.toRailDestination()).toList(),
selectedIndex: _pages.indexWhere((p) => p.page == state.activePage),
),
);
}
}
class OkaneNavigationBar extends StatelessWidget {
const OkaneNavigationBar({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<CoreCubit, CoreState>(
builder:
(context, state) => NavigationBar(
onDestinationSelected:
(i) => context.read<CoreCubit>().setPage(_pages[i].page),
destinations: _pages.map((p) => p.toDestination()).toList(),
selectedIndex: _pages.indexWhere((p) => p.page == state.activePage),
),
);
}
}
class OkaneNavigationLayout extends StatelessWidget {
const OkaneNavigationLayout({super.key});
@override
Widget build(BuildContext context) {
final screenSize = getScreenSize(context);
return BlocBuilder<CoreCubit, CoreState>(
builder:
(_, state) => IndexedStack(
index: _pages.indexWhere((p) => p.page == state.activePage),
children:
_pages
.map(
(p) => switch (screenSize) {
ScreenSize.small => p.child,
ScreenSize.normal =>
p.details != null
? Row(
children: [
Expanded(child: p.child),
Expanded(child: p.details!(false)),
],
)
: p.child,
},
)
.toList(),
),
);
}
}

View File

@@ -0,0 +1,191 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/collections/account.dart';
import 'package:okane/database/collections/beneficiary.dart';
import 'package:okane/database/database.dart';
import 'package:okane/ui/pages/account/breakdown_card.dart';
import 'package:okane/ui/pages/account/total_balance_card.dart';
import 'package:okane/ui/pages/account/upcoming_transactions_card.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/utils.dart';
class AccountListPage extends StatefulWidget {
final bool isPage;
const AccountListPage({required this.isPage, super.key});
@override
AccountListPageState createState() => AccountListPageState();
}
class AccountListPageState extends State<AccountListPage> {
final TextEditingController _accountNameController = TextEditingController();
@override
Widget build(BuildContext context) {
return Stack(
children: [
ListView(
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Text(
"Accounts",
style: Theme.of(context).textTheme.titleLarge,
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: BlocBuilder<CoreCubit, CoreState>(
builder:
(context, state) => SizedBox(
child: SizedBox(
height: 100,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: state.accounts.length,
itemBuilder:
(context, index) => SizedBox(
width: 150,
height: 100,
child: Card(
color: colorHash(state.accounts[index].name!),
shape:
index == state.activeAccountIndex
? RoundedRectangleBorder(
side: BorderSide(
color: Colors.black,
width: 2,
),
borderRadius: BorderRadius.circular(
12,
),
)
: null,
child: InkWell(
onTap: () {
GetIt.I
.get<CoreCubit>()
.setActiveAccountIndex(index);
},
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(state.accounts[index].name!),
FutureBuilder(
future: getTotalBalance(
state.accounts[index],
),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Container();
}
return Text(
formatCurrency(snapshot.data!),
);
},
),
],
),
),
),
),
),
),
),
),
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: TotalBalanceCard(),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: UpcomingTransactionsCard(),
),
/*
BlocBuilder<CoreCubit, CoreState>(
builder:
(context, state) => Row(
children: [
Expanded(
child: Padding(
padding: EdgeInsets.all(16),
child: AccountBalanceGraphCard(),
),
),
],
),
),*/
Row(
children: [
Padding(padding: EdgeInsets.all(16), child: BreakdownCard()),
],
),
],
),
Positioned(
right: 16,
bottom: 16,
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
showDialogOrModal(
context: context,
builder:
(ctx) => Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.symmetric(
vertical: 8,
horizontal: 16,
),
child: TextField(
controller: _accountNameController,
decoration: InputDecoration(
hintText: "Account name",
),
),
),
OutlinedButton(
onPressed: () async {
if (_accountNameController.text.isEmpty) return;
final a =
Account()..name = _accountNameController.text;
final b =
Beneficiary()
..name = _accountNameController.text
..account.value =
GetIt.I.get<CoreCubit>().activeAccount
..type = BeneficiaryType.account;
await upsertAccount(a);
await upsertBeneficiary(b);
_accountNameController.text = "";
Navigator.of(context).pop();
},
child: Text("Add"),
),
],
),
),
);
},
),
),
],
);
}
}

View File

@@ -0,0 +1,150 @@
import 'dart:collection';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/database.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/utils.dart';
double getBalanceGraphScaling(double maxBalance) {
if (maxBalance < 100) {
return 10;
} else if (maxBalance < 1000) {
return 200;
} else if (maxBalance < 10000) {
return 1000;
}
return 10000;
}
class AccountBalanceGraphCard extends StatelessWidget {
const AccountBalanceGraphCard({super.key});
Future<List<FlSpot>> getAccountBalance() async {
final coreCubit = GetIt.I.get<CoreCubit>();
final today = toMidnight(DateTime.now());
final transactions = await getLastTransactions(
coreCubit.activeAccount!,
today,
30,
);
final totalBalance = await getTotalBalance(coreCubit.activeAccount!);
// Compute the differences per day
Map<int, double> differences = Map.fromEntries(
List.generate(30, (i) => i).map((i) => MapEntry(i, 0)),
);
for (final item in transactions) {
final diff = today.difference(toMidnight(item.date)).inDays;
final balance = differences[diff]!;
differences[diff] = balance + item.amount;
}
// Compute the balance
final balances = HashMap<int, double>();
balances[0] = totalBalance;
for (final idx in List.generate(29, (i) => i + 1)) {
balances[idx] = balances[idx - 1]! + differences[idx]!;
}
List<FlSpot> result = List.empty(growable: true);
result.add(FlSpot(0, totalBalance));
result.addAll(
List.generate(
28,
(i) => i + 1,
).map((i) => FlSpot(i.toDouble(), balances[i]!)),
);
return result;
}
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Text("Account balance"),
SizedBox(
height: 150,
child: FutureBuilder(
future: getAccountBalance(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
final today = DateTime.now();
final maxBalance = snapshot.data!
.map((spot) => spot.y)
.reduce((acc, y) => y > acc ? y : acc);
return LineChart(
LineChartData(
minY:
snapshot.data!
.map((spot) => spot.y)
.reduce((acc, y) => y < acc ? y : acc) -
20,
maxY: maxBalance + 20,
titlesData: FlTitlesData(
bottomTitles: AxisTitles(
sideTitles: SideTitles(
interval: 4,
showTitles: true,
reservedSize: 40,
getTitlesWidget: (val, meta) {
final day = today.subtract(
Duration(days: val.toInt()),
);
return SideTitleWidget(
meta: meta,
child: Text(
formatDateTime(day, formatYear: false),
),
);
},
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 50,
interval: getBalanceGraphScaling(maxBalance),
getTitlesWidget:
(value, meta) => SideTitleWidget(
meta: meta,
child: Text(
formatCurrency(value, precise: false),
),
),
),
),
rightTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
topTitles: const AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
),
gridData: const FlGridData(show: false),
lineBarsData: [
LineChartBarData(
isCurved: false,
barWidth: 3,
dotData: const FlDotData(show: false),
spots: snapshot.data!,
),
],
),
);
},
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,172 @@
import 'dart:math';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/collections/transaction.dart';
import 'package:okane/database/database.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/utils.dart';
const CATEGORY_INCOME = "Income";
const CATEGORY_OTHER = "Other";
typedef LegendData =
({Map<String, double> expenses, Map<String, Color> colors, double usable});
class LegendItem extends StatelessWidget {
final String text;
final Color color;
const LegendItem({required this.text, required this.color, super.key});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
DecoratedBox(
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(5),
),
child: SizedBox(width: 10, height: 10),
),
Padding(padding: EdgeInsets.only(left: 8), child: Text(text)),
],
);
}
}
class BreakdownCard extends StatelessWidget {
const BreakdownCard({super.key});
LegendData _computeSections(List<Transaction> transactions) {
Map<String, double> expenses = {};
Map<String, Color> colors = {};
double usableMoney = 0;
transactions.forEach((t) {
String category;
if (t.amount > 0) {
category = CATEGORY_INCOME;
colors[CATEGORY_INCOME] = Colors.green;
} else {
if (t.expenseCategory.value?.name == null) {
category = CATEGORY_OTHER;
colors[category] = Colors.red;
} else {
category = t.expenseCategory.value!.name;
colors[category] = colorHash(t.expenseCategory.value!.name);
}
}
expenses.update(
category,
(value) => value + t.amount.abs(),
ifAbsent: () => t.amount.abs(),
);
usableMoney += t.amount;
});
return (expenses: expenses, colors: colors, usable: usableMoney);
}
@override
Widget build(BuildContext context) {
final bloc = GetIt.I.get<CoreCubit>();
return Card(
child: Padding(
padding: const EdgeInsets.all(8),
child: BlocBuilder<CoreCubit, CoreState>(
builder: (context, state) {
if (bloc.activeAccount == null) {
return Text("No active account");
}
return FutureBuilder(
future: getLastTransactions(
bloc.activeAccount!,
DateTime.now(),
30,
),
builder: (context, snapshot) {
final title = Padding(
padding: EdgeInsets.only(bottom: 16),
child: Text("Expense Breakdown"),
);
if (!snapshot.hasData) {
return Column(children: [title, CircularProgressIndicator()]);
}
if (snapshot.data!.isEmpty) {
return Column(children: [title, Text("No transactions")]);
}
final data = _computeSections(snapshot.data!);
final sectionData =
data.expenses.entries
.map(
(entry) => PieChartSectionData(
value: entry.value,
title: formatCurrency(entry.value, precise: false),
titleStyle: TextStyle(fontWeight: FontWeight.bold),
radius: 40,
color: data.colors[entry.key]!,
),
)
.toList();
return Column(
children: [
title,
Row(
children: [
SizedBox(
width: 150,
height: 150,
child: AspectRatio(
aspectRatio: 1,
child: PieChart(
PieChartData(
borderData: FlBorderData(show: false),
sectionsSpace: 0,
centerSpaceRadius: 35,
sections: sectionData,
),
),
),
),
Padding(
padding: EdgeInsets.only(left: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children:
data.expenses.keys
.map(
(key) => LegendItem(
text: key,
color: data.colors[key]!,
),
)
.toList(),
),
),
],
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text(
"Available money: ${formatCurrency(data.usable)}",
),
),
],
);
},
);
},
),
),
);
}
}

View File

@@ -0,0 +1,49 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:okane/database/collections/account.dart';
import 'package:okane/database/database.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/utils.dart';
class TotalBalanceCard extends StatelessWidget {
const TotalBalanceCard({super.key});
Future<double> _getTotalBalance(List<Account> accounts) async {
if (accounts.isEmpty) {
return 0;
}
final results = await Future.wait(accounts.map(getTotalBalance).toList());
return results.reduce((acc, val) => acc + val);
}
@override
Widget build(BuildContext context) {
return BlocBuilder<CoreCubit, CoreState>(
builder: (context, state) {
return Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text("Total balance"),
FutureBuilder(
future: _getTotalBalance(state.accounts),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text("...");
}
return Text(formatCurrency(snapshot.data!));
},
),
],
),
),
);
},
);
}
}

View File

@@ -0,0 +1,69 @@
import 'dart:math';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:okane/database/collections/recurrent.dart';
import 'package:okane/ui/state/core.dart';
class UpcomingTransactionsCard extends StatelessWidget {
const UpcomingTransactionsCard({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<CoreCubit, CoreState>(
builder: (context, state) {
final today = DateTime.now();
final upcomingRaw =
state.recurringTransactions.where((t) {
if (t.lastExecution == null) {
return true;
}
return today.difference(t.lastExecution!).inDays <=
(t.days * 1.5).toInt();
}).toList();
final List<RecurringTransaction> upcoming =
upcomingRaw.isEmpty
? List.empty()
: upcomingRaw.sublist(0, min(upcomingRaw.length, 3));
final transactions =
upcoming.isEmpty
? [Text("No upcoming transactions")]
: upcoming
.map(
(t) => ListTile(
title: Text(
"${t.template.value!.name} (${t.template.value!.amount}€)",
),
subtitle: Text(
"Due in ${today.difference(t.lastExecution ?? today).inDays} days",
),
leading: Icon(
t.template.value!.amount < 0
? Icons.remove
: Icons.add,
color:
t.template.value!.amount < 0
? Colors.red
: Colors.green,
),
trailing: IconButton(
icon: Icon(Icons.play_arrow),
onPressed: () {},
),
),
)
.toList();
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: <Widget>[Text("Upcoming Transactions")] + transactions,
),
),
);
},
);
}
}

View File

@@ -0,0 +1,75 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/utils.dart';
import 'package:okane/ui/widgets/add_template.dart';
class TemplateListPage extends StatefulWidget {
const TemplateListPage({super.key});
@override
State<TemplateListPage> createState() => TemplateListState();
}
class TemplateListState extends State<TemplateListPage> {
@override
Widget build(BuildContext context) {
return BlocBuilder<CoreCubit, CoreState>(
builder: (context, state) {
final account = GetIt.I.get<CoreCubit>().activeAccount;
return Stack(
children: [
Column(
children: [
Padding(
padding: EdgeInsets.only(top: 16),
child: ListView.builder(
itemCount: state.recurringTransactions.length,
shrinkWrap: true,
itemBuilder:
(ctx, idx) => Card(
child: ListTile(
title: Text(
state
.recurringTransactions[idx]
.template
.value!
.name,
),
),
),
),
),
],
),
Positioned(
right: 16,
bottom: 16,
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed:
account == null
? () {}
: () {
showDialogOrModal(
context: context,
builder:
(ctx) => AddTransactionTemplateWidget(
activeAccountItem: account,
onAdd: () {
setState(() {});
Navigator.of(context).pop();
},
),
showDragHandle: true,
);
},
),
),
],
);
},
);
}
}

View File

@@ -0,0 +1,118 @@
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:okane/database/collections/beneficiary.dart';
import 'package:okane/database/database.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/utils.dart';
import 'package:okane/ui/widgets/image_wrapper.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
class TransactionDetailsPage extends StatelessWidget {
final bool isPage;
const TransactionDetailsPage({this.isPage = false, super.key});
static MaterialPageRoute<void> get mobileRoute =>
MaterialPageRoute(builder: (_) => TransactionDetailsPage(isPage: true));
Future<void> _updateBeneficiaryIcon(Beneficiary beneficiary) async {
final pickedFile = await FilePicker.platform.pickFiles(
type: FileType.image,
);
if (pickedFile == null) {
return;
}
final file = pickedFile.files.first;
final suppPath = await getApplicationSupportDirectory();
final imageDir = p.join(suppPath.path, "beneficiaries");
final imagePath = p.join(imageDir, file.name);
print("Copying ${file.path!} to $imagePath");
await Directory(imageDir).create(recursive: true);
if (beneficiary.imagePath != null) {
await File(beneficiary.imagePath!).delete();
}
await File(file.path!).copy(imagePath);
print("Updating DB");
beneficiary.imagePath = imagePath;
await upsertBeneficiary(beneficiary);
}
@override
Widget build(BuildContext context) {
final widget = BlocBuilder<CoreCubit, CoreState>(
builder: (context, state) {
if (state.activeTransaction == null) {
return Text("No transaction selected");
}
return Padding(
padding: const EdgeInsets.all(8.0),
child: ListView(
children: [
Row(
children: [
StreamBuilder(
stream: watchBeneficiaryObject(
state.activeTransaction!.beneficiary.value!.id,
),
builder: (context, snapshot) {
final obj =
snapshot.data ??
state.activeTransaction!.beneficiary.value!;
return ImageWrapper(
title: obj.name,
path: obj.imagePath,
onTap: () => _updateBeneficiaryIcon(obj),
);
},
),
Padding(
padding: EdgeInsets.only(left: 8),
child: Text(
state.activeTransaction!.beneficiary.value!.name,
),
),
Spacer(),
IconButton(
icon: Icon(Icons.edit),
onPressed: () {
// TODO: Implement
},
),
],
),
Wrap(
spacing: 8,
children:
state.activeTransaction!.tags
.map((tag) => Chip(label: Text(tag)))
.toList(),
),
Row(
children: [
state.activeTransaction!.amount > 0
? Icon(Icons.add)
: Icon(Icons.remove),
Text(formatCurrency(state.activeTransaction!.amount)),
],
),
],
),
);
},
);
if (isPage) {
return Scaffold(body: widget);
}
return widget;
}
}

View File

@@ -0,0 +1,163 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:grouped_list/grouped_list.dart';
import 'package:okane/database/collections/transaction.dart';
import 'package:okane/database/database.dart';
import 'package:okane/screen.dart';
import 'package:okane/ui/pages/account/balance_graph_card.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/utils.dart';
import 'package:okane/ui/widgets/add_transaction.dart';
import 'package:okane/ui/widgets/transaction_card.dart';
class TransactionListPage extends StatefulWidget {
const TransactionListPage({super.key});
@override
State<TransactionListPage> createState() => TransactionListState();
}
class TransactionListState extends State<TransactionListPage> {
final _scrollController = ScrollController();
@override
Widget build(BuildContext context) {
return BlocBuilder<CoreCubit, CoreState>(
builder: (context, state) {
final account = GetIt.I.get<CoreCubit>().activeAccount;
return Stack(
children: [
CustomScrollView(
controller: _scrollController,
slivers: [
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: AccountBalanceGraphCard(),
),
),
SliverToBoxAdapter(
child: GroupedListView(
physics: NeverScrollableScrollPhysics(),
elements: state.transactions,
reverse: true,
groupBy: (Transaction item) => formatDateTime(item.date),
groupHeaderBuilder:
(item) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
DecoratedBox(
decoration: BoxDecoration(
color: Colors.black.withAlpha(170),
borderRadius: BorderRadius.circular(8),
),
child: Padding(
padding: EdgeInsets.all(4),
child: Text(
formatDateTime(item.date),
style: TextStyle(color: Colors.white),
),
),
),
],
),
shrinkWrap: true,
indexedItemBuilder:
(ctx, item, idx) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: TransactionCard(
transaction: item,
onTap: () {
GetIt.I.get<CoreCubit>().setActiveTransaction(
item,
);
if (getScreenSize(ctx) == ScreenSize.small) {
Navigator.of(
context,
).pushNamed("/transactions/details");
}
},
),
),
),
),
],
),
/*Column(
children: [
Padding(
padding: EdgeInsets.only(top: 16),
child: GroupedListView(
elements: state.transactions,
reverse: true,
groupBy:
(Transaction item) => formatDateTime(item.date),
groupHeaderBuilder:
(item) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
DecoratedBox(
decoration: BoxDecoration(
color: Colors.black.withAlpha(170),
borderRadius: BorderRadius.circular(8),
),
child: Padding(
padding: EdgeInsets.all(4),
child: Text(
formatDateTime(item.date),
style: TextStyle(color: Colors.white),
),
),
),
],
),
shrinkWrap: true,
indexedItemBuilder:
(ctx, item, idx) => TransactionCard(
transaction: item,
onTap: () {
GetIt.I.get<CoreCubit>().setActiveTransaction(
item,
);
if (getScreenSize(ctx) == ScreenSize.small) {
Navigator.of(
context,
).pushNamed("/transactions/details");
}
},
),
),
),
],
),*/
Positioned(
right: 16,
bottom: 16,
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed:
account == null
? () {}
: () {
showDialogOrModal(
context: context,
builder:
(ctx) => AddTransactionWidget(
activeAccountItem: account,
onAdd: () {
setState(() {});
Navigator.of(context).pop();
},
),
showDragHandle: true,
);
},
),
),
],
);
},
);
}
}

152
lib/ui/state/core.dart Normal file
View File

@@ -0,0 +1,152 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:okane/database/collections/account.dart';
import 'package:okane/database/collections/beneficiary.dart';
import 'package:okane/database/collections/expense_category.dart';
import 'package:okane/database/collections/recurrent.dart';
import 'package:okane/database/collections/template.dart';
import 'package:okane/database/collections/transaction.dart';
import 'package:okane/database/database.dart';
import 'package:okane/ui/navigation.dart';
part 'core.freezed.dart';
@freezed
abstract class CoreState with _$CoreState {
const factory CoreState({
@Default(OkanePage.accounts) OkanePage activePage,
int? activeAccountIndex,
@Default(null) Transaction? activeTransaction,
@Default([]) List<Account> accounts,
@Default([]) List<RecurringTransaction> recurringTransactions,
@Default([]) List<Transaction> transactions,
@Default([]) List<TransactionTemplate> transactionTemplates,
@Default([]) List<Beneficiary> beneficiaries,
@Default([]) List<ExpenseCategory> expenseCategories,
}) = _CoreState;
}
class CoreCubit extends Cubit<CoreState> {
CoreCubit() : super(CoreState());
StreamSubscription<void>? _recurringTransactionStreamSubscription;
StreamSubscription<void>? _transactionTemplatesStreamSubcription;
StreamSubscription<void>? _accountsStreamSubscription;
StreamSubscription<void>? _transactionsStreamSubscription;
StreamSubscription<void>? _beneficiariesStreamSubscription;
StreamSubscription<void>? _expenseCategoryStreamSubscription;
void setupAccountStream() {
_accountsStreamSubscription?.cancel();
_accountsStreamSubscription = watchAccounts().listen((_) async {
final resetStreams = state.activeAccountIndex == null;
final accounts = await getAccounts();
emit(
state.copyWith(
accounts: accounts,
activeAccountIndex: state.activeAccountIndex ?? 0,
),
);
if (resetStreams) {
setupStreams(accounts[0]);
}
});
}
void setupStreams(Account account) {
setupAccountStream();
_recurringTransactionStreamSubscription?.cancel();
_recurringTransactionStreamSubscription = watchRecurringTransactions(
activeAccount!,
).listen((_) async {
emit(
state.copyWith(
recurringTransactions: await getRecurringTransactions(activeAccount!),
),
);
});
_transactionTemplatesStreamSubcription?.cancel();
_transactionTemplatesStreamSubcription = watchTransactionTemplates(
activeAccount!,
).listen((_) async {
emit(
state.copyWith(
transactionTemplates: await getTransactionTemplates(activeAccount!),
),
);
});
_transactionsStreamSubscription?.cancel();
_transactionsStreamSubscription = watchTransactions(activeAccount!).listen((
_,
) async {
emit(state.copyWith(transactions: await getTransactions(activeAccount!)));
});
_beneficiariesStreamSubscription?.cancel();
_beneficiariesStreamSubscription = watchBeneficiaries().listen((_) async {
emit(state.copyWith(beneficiaries: await getBeneficiaries()));
});
_expenseCategoryStreamSubscription?.cancel();
_expenseCategoryStreamSubscription = watchExpenseCategory().listen((
_,
) async {
emit(state.copyWith(expenseCategories: await getExpenseCategories()));
});
}
Future<void> init() async {
final accounts = await getAccounts();
final account = accounts.isEmpty ? null : accounts[0];
emit(
state.copyWith(
accounts: accounts,
activeAccountIndex: accounts.isEmpty ? null : 0,
transactions: await getTransactions(account),
beneficiaries: await getBeneficiaries(),
transactionTemplates: await getTransactionTemplates(account),
recurringTransactions: await getRecurringTransactions(account),
expenseCategories: await getExpenseCategories(),
),
);
if (account != null) {
setupStreams(account);
} else {
setupAccountStream();
}
print("Core init done");
}
void setPage(OkanePage page) {
emit(state.copyWith(activePage: page));
}
Future<void> setActiveAccountIndex(int index) async {
final account = state.accounts[index];
emit(
state.copyWith(
activeAccountIndex: index,
transactions: await getTransactions(account),
beneficiaries: await getBeneficiaries(),
transactionTemplates: await getTransactionTemplates(account),
recurringTransactions: await getRecurringTransactions(account),
),
);
setupStreams(account);
}
void setActiveTransaction(Transaction? item) {
emit(state.copyWith(activeTransaction: item));
}
void setAccounts(List<Account> accounts) {
emit(state.copyWith(accounts: accounts));
}
Account? get activeAccount =>
state.activeAccountIndex == null
? null
: state.accounts[state.activeAccountIndex!];
}

View File

@@ -0,0 +1,408 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'core.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
);
/// @nodoc
mixin _$CoreState {
OkanePage get activePage => throw _privateConstructorUsedError;
int? get activeAccountIndex => throw _privateConstructorUsedError;
Transaction? get activeTransaction => throw _privateConstructorUsedError;
List<Account> get accounts => throw _privateConstructorUsedError;
List<RecurringTransaction> get recurringTransactions =>
throw _privateConstructorUsedError;
List<Transaction> get transactions => throw _privateConstructorUsedError;
List<TransactionTemplate> get transactionTemplates =>
throw _privateConstructorUsedError;
List<Beneficiary> get beneficiaries => throw _privateConstructorUsedError;
List<ExpenseCategory> get expenseCategories =>
throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$CoreStateCopyWith<CoreState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $CoreStateCopyWith<$Res> {
factory $CoreStateCopyWith(CoreState value, $Res Function(CoreState) then) =
_$CoreStateCopyWithImpl<$Res, CoreState>;
@useResult
$Res call({
OkanePage activePage,
int? activeAccountIndex,
Transaction? activeTransaction,
List<Account> accounts,
List<RecurringTransaction> recurringTransactions,
List<Transaction> transactions,
List<TransactionTemplate> transactionTemplates,
List<Beneficiary> beneficiaries,
List<ExpenseCategory> expenseCategories,
});
}
/// @nodoc
class _$CoreStateCopyWithImpl<$Res, $Val extends CoreState>
implements $CoreStateCopyWith<$Res> {
_$CoreStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? activePage = null,
Object? activeAccountIndex = freezed,
Object? activeTransaction = freezed,
Object? accounts = null,
Object? recurringTransactions = null,
Object? transactions = null,
Object? transactionTemplates = null,
Object? beneficiaries = null,
Object? expenseCategories = null,
}) {
return _then(
_value.copyWith(
activePage:
null == activePage
? _value.activePage
: activePage // ignore: cast_nullable_to_non_nullable
as OkanePage,
activeAccountIndex:
freezed == activeAccountIndex
? _value.activeAccountIndex
: activeAccountIndex // ignore: cast_nullable_to_non_nullable
as int?,
activeTransaction:
freezed == activeTransaction
? _value.activeTransaction
: activeTransaction // ignore: cast_nullable_to_non_nullable
as Transaction?,
accounts:
null == accounts
? _value.accounts
: accounts // ignore: cast_nullable_to_non_nullable
as List<Account>,
recurringTransactions:
null == recurringTransactions
? _value.recurringTransactions
: recurringTransactions // ignore: cast_nullable_to_non_nullable
as List<RecurringTransaction>,
transactions:
null == transactions
? _value.transactions
: transactions // ignore: cast_nullable_to_non_nullable
as List<Transaction>,
transactionTemplates:
null == transactionTemplates
? _value.transactionTemplates
: transactionTemplates // ignore: cast_nullable_to_non_nullable
as List<TransactionTemplate>,
beneficiaries:
null == beneficiaries
? _value.beneficiaries
: beneficiaries // ignore: cast_nullable_to_non_nullable
as List<Beneficiary>,
expenseCategories:
null == expenseCategories
? _value.expenseCategories
: expenseCategories // ignore: cast_nullable_to_non_nullable
as List<ExpenseCategory>,
)
as $Val,
);
}
}
/// @nodoc
abstract class _$$CoreStateImplCopyWith<$Res>
implements $CoreStateCopyWith<$Res> {
factory _$$CoreStateImplCopyWith(
_$CoreStateImpl value,
$Res Function(_$CoreStateImpl) then,
) = __$$CoreStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({
OkanePage activePage,
int? activeAccountIndex,
Transaction? activeTransaction,
List<Account> accounts,
List<RecurringTransaction> recurringTransactions,
List<Transaction> transactions,
List<TransactionTemplate> transactionTemplates,
List<Beneficiary> beneficiaries,
List<ExpenseCategory> expenseCategories,
});
}
/// @nodoc
class __$$CoreStateImplCopyWithImpl<$Res>
extends _$CoreStateCopyWithImpl<$Res, _$CoreStateImpl>
implements _$$CoreStateImplCopyWith<$Res> {
__$$CoreStateImplCopyWithImpl(
_$CoreStateImpl _value,
$Res Function(_$CoreStateImpl) _then,
) : super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? activePage = null,
Object? activeAccountIndex = freezed,
Object? activeTransaction = freezed,
Object? accounts = null,
Object? recurringTransactions = null,
Object? transactions = null,
Object? transactionTemplates = null,
Object? beneficiaries = null,
Object? expenseCategories = null,
}) {
return _then(
_$CoreStateImpl(
activePage:
null == activePage
? _value.activePage
: activePage // ignore: cast_nullable_to_non_nullable
as OkanePage,
activeAccountIndex:
freezed == activeAccountIndex
? _value.activeAccountIndex
: activeAccountIndex // ignore: cast_nullable_to_non_nullable
as int?,
activeTransaction:
freezed == activeTransaction
? _value.activeTransaction
: activeTransaction // ignore: cast_nullable_to_non_nullable
as Transaction?,
accounts:
null == accounts
? _value._accounts
: accounts // ignore: cast_nullable_to_non_nullable
as List<Account>,
recurringTransactions:
null == recurringTransactions
? _value._recurringTransactions
: recurringTransactions // ignore: cast_nullable_to_non_nullable
as List<RecurringTransaction>,
transactions:
null == transactions
? _value._transactions
: transactions // ignore: cast_nullable_to_non_nullable
as List<Transaction>,
transactionTemplates:
null == transactionTemplates
? _value._transactionTemplates
: transactionTemplates // ignore: cast_nullable_to_non_nullable
as List<TransactionTemplate>,
beneficiaries:
null == beneficiaries
? _value._beneficiaries
: beneficiaries // ignore: cast_nullable_to_non_nullable
as List<Beneficiary>,
expenseCategories:
null == expenseCategories
? _value._expenseCategories
: expenseCategories // ignore: cast_nullable_to_non_nullable
as List<ExpenseCategory>,
),
);
}
}
/// @nodoc
class _$CoreStateImpl implements _CoreState {
const _$CoreStateImpl({
this.activePage = OkanePage.accounts,
this.activeAccountIndex,
this.activeTransaction = null,
final List<Account> accounts = const [],
final List<RecurringTransaction> recurringTransactions = const [],
final List<Transaction> transactions = const [],
final List<TransactionTemplate> transactionTemplates = const [],
final List<Beneficiary> beneficiaries = const [],
final List<ExpenseCategory> expenseCategories = const [],
}) : _accounts = accounts,
_recurringTransactions = recurringTransactions,
_transactions = transactions,
_transactionTemplates = transactionTemplates,
_beneficiaries = beneficiaries,
_expenseCategories = expenseCategories;
@override
@JsonKey()
final OkanePage activePage;
@override
final int? activeAccountIndex;
@override
@JsonKey()
final Transaction? activeTransaction;
final List<Account> _accounts;
@override
@JsonKey()
List<Account> get accounts {
if (_accounts is EqualUnmodifiableListView) return _accounts;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_accounts);
}
final List<RecurringTransaction> _recurringTransactions;
@override
@JsonKey()
List<RecurringTransaction> get recurringTransactions {
if (_recurringTransactions is EqualUnmodifiableListView)
return _recurringTransactions;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_recurringTransactions);
}
final List<Transaction> _transactions;
@override
@JsonKey()
List<Transaction> get transactions {
if (_transactions is EqualUnmodifiableListView) return _transactions;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_transactions);
}
final List<TransactionTemplate> _transactionTemplates;
@override
@JsonKey()
List<TransactionTemplate> get transactionTemplates {
if (_transactionTemplates is EqualUnmodifiableListView)
return _transactionTemplates;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_transactionTemplates);
}
final List<Beneficiary> _beneficiaries;
@override
@JsonKey()
List<Beneficiary> get beneficiaries {
if (_beneficiaries is EqualUnmodifiableListView) return _beneficiaries;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_beneficiaries);
}
final List<ExpenseCategory> _expenseCategories;
@override
@JsonKey()
List<ExpenseCategory> get expenseCategories {
if (_expenseCategories is EqualUnmodifiableListView)
return _expenseCategories;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_expenseCategories);
}
@override
String toString() {
return 'CoreState(activePage: $activePage, activeAccountIndex: $activeAccountIndex, activeTransaction: $activeTransaction, accounts: $accounts, recurringTransactions: $recurringTransactions, transactions: $transactions, transactionTemplates: $transactionTemplates, beneficiaries: $beneficiaries, expenseCategories: $expenseCategories)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$CoreStateImpl &&
(identical(other.activePage, activePage) ||
other.activePage == activePage) &&
(identical(other.activeAccountIndex, activeAccountIndex) ||
other.activeAccountIndex == activeAccountIndex) &&
(identical(other.activeTransaction, activeTransaction) ||
other.activeTransaction == activeTransaction) &&
const DeepCollectionEquality().equals(other._accounts, _accounts) &&
const DeepCollectionEquality().equals(
other._recurringTransactions,
_recurringTransactions,
) &&
const DeepCollectionEquality().equals(
other._transactions,
_transactions,
) &&
const DeepCollectionEquality().equals(
other._transactionTemplates,
_transactionTemplates,
) &&
const DeepCollectionEquality().equals(
other._beneficiaries,
_beneficiaries,
) &&
const DeepCollectionEquality().equals(
other._expenseCategories,
_expenseCategories,
));
}
@override
int get hashCode => Object.hash(
runtimeType,
activePage,
activeAccountIndex,
activeTransaction,
const DeepCollectionEquality().hash(_accounts),
const DeepCollectionEquality().hash(_recurringTransactions),
const DeepCollectionEquality().hash(_transactions),
const DeepCollectionEquality().hash(_transactionTemplates),
const DeepCollectionEquality().hash(_beneficiaries),
const DeepCollectionEquality().hash(_expenseCategories),
);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$CoreStateImplCopyWith<_$CoreStateImpl> get copyWith =>
__$$CoreStateImplCopyWithImpl<_$CoreStateImpl>(this, _$identity);
}
abstract class _CoreState implements CoreState {
const factory _CoreState({
final OkanePage activePage,
final int? activeAccountIndex,
final Transaction? activeTransaction,
final List<Account> accounts,
final List<RecurringTransaction> recurringTransactions,
final List<Transaction> transactions,
final List<TransactionTemplate> transactionTemplates,
final List<Beneficiary> beneficiaries,
final List<ExpenseCategory> expenseCategories,
}) = _$CoreStateImpl;
@override
OkanePage get activePage;
@override
int? get activeAccountIndex;
@override
Transaction? get activeTransaction;
@override
List<Account> get accounts;
@override
List<RecurringTransaction> get recurringTransactions;
@override
List<Transaction> get transactions;
@override
List<TransactionTemplate> get transactionTemplates;
@override
List<Beneficiary> get beneficiaries;
@override
List<ExpenseCategory> get expenseCategories;
@override
@JsonKey(ignore: true)
_$$CoreStateImplCopyWith<_$CoreStateImpl> get copyWith =>
throw _privateConstructorUsedError;
}

1
lib/ui/transaction.dart Normal file
View File

@@ -0,0 +1 @@
enum TransactionDirection { send, receive }

71
lib/ui/utils.dart Normal file
View File

@@ -0,0 +1,71 @@
import 'package:flutter/material.dart';
import 'package:okane/screen.dart';
Future<T?> showDialogOrModal<T>({
required BuildContext context,
required WidgetBuilder builder,
bool showDragHandle = true,
}) {
final screenSize = getScreenSize(context);
final width = MediaQuery.sizeOf(context).shortestSide;
return switch (screenSize) {
ScreenSize.small => showModalBottomSheet<T>(
context: context,
showDragHandle: showDragHandle,
builder:
(context) => Padding(
padding: EdgeInsets.only(bottom: 32),
child: builder(context),
),
),
ScreenSize.normal => showDialog<T>(
context: context,
builder:
(context) => Dialog(
child: Container(
constraints: BoxConstraints(maxWidth: width * 0.7),
child: Padding(
padding: EdgeInsets.only(bottom: 32),
child: builder(context),
),
),
),
),
};
}
DateTime toMidnight(DateTime t) {
return DateTime(t.year, t.month, t.day);
}
String zeroPad(int i) {
if (i <= 9) {
return "0$i";
}
return i.toString();
}
String formatDateTime(DateTime dt, {bool formatYear = true}) {
if (!formatYear) {
return "${zeroPad(dt.day)}.${zeroPad(dt.month)}";
}
return "${zeroPad(dt.day)}.${zeroPad(dt.month)}.${zeroPad(dt.year)}";
}
Color colorHash(String text) {
final hue =
text.characters
.map((c) => c.codeUnitAt(0).toDouble())
.reduce((acc, c) => c + ((acc.toInt() << 5) - acc)) %
360;
return HSVColor.fromAHSV(1, hue, 0.5, 0.5).toColor();
}
String formatCurrency(double amount, {bool precise = true}) {
if (!precise) {
return "${amount.toInt()}";
}
return "${amount.toStringAsFixed(2)}";
}

View File

@@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:okane/database/collections/expense_category.dart';
import 'package:okane/database/database.dart';
import 'package:okane/ui/state/core.dart';
class AddExpenseCategory extends StatefulWidget {
const AddExpenseCategory({super.key});
@override
AddExpenseCategoryState createState() => AddExpenseCategoryState();
}
class AddExpenseCategoryState extends State<AddExpenseCategory> {
final TextEditingController _categoryNameController = TextEditingController();
@override
Widget build(BuildContext context) {
return BlocBuilder<CoreCubit, CoreState>(
builder:
(context, state) => ConstrainedBox(
constraints: BoxConstraints(maxHeight: 300),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListView.builder(
itemCount: state.expenseCategories.length,
shrinkWrap: true,
itemBuilder:
(context, index) => ListTile(
title: Text(state.expenseCategories[index].name),
onTap: () {
_categoryNameController.text = "";
Navigator.of(
context,
).pop(state.expenseCategories[index]);
},
),
),
TextField(
decoration: InputDecoration(hintText: "Category name"),
controller: _categoryNameController,
),
Row(
children: [
Spacer(),
OutlinedButton(
onPressed: () async {
final category =
ExpenseCategory()
..name = _categoryNameController.text;
await upsertExpenseCategory(category);
_categoryNameController.text = "";
Navigator.of(context).pop(category);
},
child: Text("Add"),
),
],
),
],
),
),
);
}
}

View File

@@ -0,0 +1,284 @@
import 'package:flutter/material.dart';
import 'package:flutter_picker_plus/picker.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/collections/account.dart';
import 'package:okane/database/collections/beneficiary.dart';
import 'package:okane/database/collections/recurrent.dart';
import 'package:okane/database/collections/template.dart';
import 'package:okane/database/database.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/transaction.dart';
import 'package:okane/ui/utils.dart';
import 'package:searchfield/searchfield.dart';
enum Period { days, weeks, months, years }
class AddRecurringTransactionTemplateWidget extends StatefulWidget {
final VoidCallback onAdd;
final Account activeAccountItem;
const AddRecurringTransactionTemplateWidget({
super.key,
required this.activeAccountItem,
required this.onAdd,
});
@override
State<AddRecurringTransactionTemplateWidget> createState() =>
_AddRecurringTransactionTemplateWidgetState();
}
class _AddRecurringTransactionTemplateWidgetState
extends State<AddRecurringTransactionTemplateWidget> {
final TextEditingController _beneficiaryTextController =
TextEditingController();
final TextEditingController _amountTextController = TextEditingController();
final TextEditingController _templateNameController = TextEditingController();
List<Beneficiary> beneficiaries = [];
SearchFieldListItem<Beneficiary>? _selectedBeneficiary;
TransactionDirection _selectedDirection = TransactionDirection.send;
Period _selectedPeriod = Period.months;
int _periodSize = 1;
String getBeneficiaryName(Beneficiary item) {
return switch (item.type) {
BeneficiaryType.account => "${item.name} (Account)",
BeneficiaryType.other => item.name,
};
}
Future<void> _submit(BuildContext context) async {
final beneficiaryName = _beneficiaryTextController.text;
if (_selectedBeneficiary == null && beneficiaryName.isEmpty) {
return;
}
if (_templateNameController.text.isEmpty) {
return;
}
Beneficiary? beneficiary = _selectedBeneficiary?.item;
if (beneficiary == null ||
getBeneficiaryName(beneficiary) != beneficiaryName) {
// Add a new beneficiary, if none was selected
final result = await showDialog<bool>(
context: context,
builder:
(context) => AlertDialog(
title: const Text("Add Beneficiary"),
content: Text(
"The beneficiary '$beneficiaryName' does not exist. Do you want to add it?",
),
actions: [
TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
),
child: const Text('Add'),
onPressed: () => Navigator.of(context).pop(true),
),
TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
),
child: const Text('Cancel'),
onPressed: () => Navigator.of(context).pop(false),
),
],
),
);
if (result == null || !result) {
return;
}
beneficiary =
Beneficiary()
..name = beneficiaryName
..type = BeneficiaryType.other;
await upsertBeneficiary(beneficiary);
}
final days = switch (_selectedPeriod) {
Period.days => _periodSize,
Period.weeks => _periodSize * 7,
Period.months => _periodSize * 31,
Period.years => _periodSize * 365,
};
final factor = switch (_selectedDirection) {
TransactionDirection.send => -1,
TransactionDirection.receive => 1,
};
final amount = factor * double.parse(_amountTextController.text).abs();
final template =
TransactionTemplate()
..name = _templateNameController.text
..beneficiary.value = beneficiary
..account.value = widget.activeAccountItem
..recurring = true
..amount = amount;
await upsertTransactionTemplate(template);
final transaction =
RecurringTransaction()
..lastExecution = null
..template.value = template
..account.value = widget.activeAccountItem
..days = days;
await upsertRecurringTransaction(transaction);
_periodSize = 1;
_selectedPeriod = Period.weeks;
_amountTextController.text = "";
_templateNameController.text = "";
widget.onAdd();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: ListView(
shrinkWrap: true,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: TextField(
controller: _templateNameController,
decoration: InputDecoration(label: Text("Template name")),
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: SearchField<Beneficiary>(
suggestions:
beneficiaries
.where((el) {
final bloc = GetIt.I.get<CoreCubit>();
if (el.type == BeneficiaryType.account) {
return el.account.value?.id != bloc.activeAccount?.id;
}
return true;
})
.map((el) {
return SearchFieldListItem(
getBeneficiaryName(el),
item: el,
);
})
.toList(),
hint: "Beneficiary",
controller: _beneficiaryTextController,
selectedValue: _selectedBeneficiary,
onSuggestionTap: (beneficiary) {
setState(() => _selectedBeneficiary = beneficiary);
},
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: TextField(
controller: _amountTextController,
keyboardType: TextInputType.numberWithOptions(
signed: false,
decimal: false,
),
decoration: InputDecoration(hintText: "Amount"),
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: SegmentedButton<TransactionDirection>(
segments: [
ButtonSegment(
value: TransactionDirection.send,
label: Text("Send"),
icon: Icon(Icons.remove),
),
ButtonSegment(
value: TransactionDirection.receive,
label: Text("Receive"),
icon: Icon(Icons.add),
),
],
selected: <TransactionDirection>{_selectedDirection},
multiSelectionEnabled: false,
onSelectionChanged: (selection) {
setState(() => _selectedDirection = selection.first);
},
),
),
Padding(
padding: EdgeInsets.only(left: 16, right: 16, top: 16),
child: SegmentedButton<Period>(
segments: [
ButtonSegment(value: Period.days, label: Text("Days")),
ButtonSegment(value: Period.weeks, label: Text("Weeks")),
ButtonSegment(value: Period.months, label: Text("Months")),
ButtonSegment(value: Period.years, label: Text("Years")),
],
selected: <Period>{_selectedPeriod},
multiSelectionEnabled: false,
onSelectionChanged: (selection) {
setState(() => _selectedPeriod = selection.first);
},
),
),
Text.rich(
TextSpan(
text: "Repeat every ",
children: [
WidgetSpan(
child: TextButton(
onPressed: () {
Picker(
adapter: NumberPickerAdapter(
data: [
NumberPickerColumn(
begin: 1,
end: 999,
initValue: _periodSize,
),
],
),
hideHeader: true,
selectedTextStyle: TextStyle(color: Colors.blue),
onConfirm: (Picker picker, List value) {
setState(() {
_periodSize = (value.first as int) + 1;
});
},
).showDialog(context);
},
child: Text(_periodSize.toString()),
),
),
TextSpan(
text: switch (_selectedPeriod) {
Period.days => " days",
Period.weeks => " weeks",
Period.months => " months",
Period.years => " years",
},
),
],
),
),
Align(
alignment: Alignment.centerRight,
child: OutlinedButton(
onPressed: () => _submit(context),
child: Text("Add"),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,229 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/collections/account.dart';
import 'package:okane/database/collections/beneficiary.dart';
import 'package:okane/database/collections/expense_category.dart';
import 'package:okane/database/collections/template.dart';
import 'package:okane/database/database.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/transaction.dart';
import 'package:okane/ui/utils.dart';
import 'package:okane/ui/widgets/add_expense_category.dart';
import 'package:searchfield/searchfield.dart';
class AddTransactionTemplateWidget extends StatefulWidget {
final VoidCallback onAdd;
final Account activeAccountItem;
const AddTransactionTemplateWidget({
super.key,
required this.activeAccountItem,
required this.onAdd,
});
@override
State<AddTransactionTemplateWidget> createState() =>
_AddTransactionTemplateWidgetState();
}
class _AddTransactionTemplateWidgetState
extends State<AddTransactionTemplateWidget> {
final TextEditingController _beneficiaryTextController =
TextEditingController();
final TextEditingController _amountTextController = TextEditingController();
final TextEditingController _templateNameController = TextEditingController();
SearchFieldListItem<Beneficiary>? _selectedBeneficiary;
TransactionDirection _selectedDirection = TransactionDirection.send;
ExpenseCategory? _expenseCategory = null;
String getBeneficiaryName(Beneficiary item) {
return switch (item.type) {
BeneficiaryType.account => "${item.name} (Account)",
BeneficiaryType.other => item.name,
};
}
Future<void> _submit(BuildContext context) async {
final beneficiaryName = _beneficiaryTextController.text;
if (_selectedBeneficiary == null && beneficiaryName.isEmpty) {
return;
}
if (_templateNameController.text.isEmpty) {
return;
}
Beneficiary? beneficiary = _selectedBeneficiary?.item;
if (beneficiary == null ||
getBeneficiaryName(beneficiary) != beneficiaryName) {
// Add a new beneficiary, if none was selected
final result = await showDialog<bool>(
context: context,
builder:
(context) => AlertDialog(
title: const Text("Add Beneficiary"),
content: Text(
"The beneficiary '$beneficiaryName' does not exist. Do you want to add it?",
),
actions: [
TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
),
child: const Text('Add'),
onPressed: () => Navigator.of(context).pop(true),
),
TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
),
child: const Text('Cancel'),
onPressed: () => Navigator.of(context).pop(false),
),
],
),
);
if (result == null || !result) {
return;
}
beneficiary =
Beneficiary()
..name = beneficiaryName
..type = BeneficiaryType.other;
await upsertBeneficiary(beneficiary);
}
final factor = switch (_selectedDirection) {
TransactionDirection.send => -1,
TransactionDirection.receive => 1,
};
final amount = factor * double.parse(_amountTextController.text).abs();
final transaction =
TransactionTemplate()
..name = _templateNameController.text
..account.value = widget.activeAccountItem
..beneficiary.value = beneficiary
..expenseCategory.value = _expenseCategory
..recurring = false
..amount = amount;
await upsertTransactionTemplate(transaction);
widget.onAdd();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: ListView(
shrinkWrap: true,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: TextField(
controller: _templateNameController,
decoration: InputDecoration(label: Text("Template name")),
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: BlocBuilder<CoreCubit, CoreState>(
builder:
(context, state) => SearchField<Beneficiary>(
suggestions:
state.beneficiaries
.where((el) {
final bloc = GetIt.I.get<CoreCubit>();
if (el.type == BeneficiaryType.account) {
return el.account.value?.id !=
bloc.activeAccount?.id;
}
return true;
})
.map((el) {
return SearchFieldListItem(
getBeneficiaryName(el),
item: el,
);
})
.toList(),
hint: "Beneficiary",
controller: _beneficiaryTextController,
selectedValue: _selectedBeneficiary,
onSuggestionTap: (beneficiary) {
setState(() => _selectedBeneficiary = beneficiary);
},
),
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: TextField(
controller: _amountTextController,
keyboardType: TextInputType.numberWithOptions(
signed: false,
decimal: false,
),
decoration: InputDecoration(hintText: "Amount"),
),
),
Row(
children: [
Text("Expense category"),
OutlinedButton(
onPressed: () async {
final category = await showDialogOrModal(
context: context,
builder: (_) => AddExpenseCategory(),
);
if (category == null) {
return;
}
setState(() => _expenseCategory = category);
},
child: Text(_expenseCategory?.name ?? "None"),
),
],
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: SegmentedButton<TransactionDirection>(
segments: [
ButtonSegment(
value: TransactionDirection.send,
label: Text("Send"),
icon: Icon(Icons.remove),
),
ButtonSegment(
value: TransactionDirection.receive,
label: Text("Receive"),
icon: Icon(Icons.add),
),
],
selected: <TransactionDirection>{_selectedDirection},
multiSelectionEnabled: false,
onSelectionChanged: (selection) {
setState(() => _selectedDirection = selection.first);
},
),
),
Align(
alignment: Alignment.centerRight,
child: OutlinedButton(
onPressed: () => _submit(context),
child: Text("Add"),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,311 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it/get_it.dart';
import 'package:okane/database/collections/account.dart';
import 'package:okane/database/collections/beneficiary.dart';
import 'package:okane/database/collections/expense_category.dart';
import 'package:okane/database/collections/transaction.dart';
import 'package:okane/database/database.dart';
import 'package:okane/ui/state/core.dart';
import 'package:okane/ui/transaction.dart';
import 'package:okane/ui/utils.dart';
import 'package:okane/ui/widgets/add_expense_category.dart';
import 'package:searchfield/searchfield.dart';
class AddTransactionWidget extends StatefulWidget {
final VoidCallback onAdd;
final Account activeAccountItem;
const AddTransactionWidget({
super.key,
required this.activeAccountItem,
required this.onAdd,
});
@override
State<AddTransactionWidget> createState() => _AddTransactionWidgetState();
}
class _AddTransactionWidgetState extends State<AddTransactionWidget> {
final TextEditingController _beneficiaryTextController =
TextEditingController();
final TextEditingController _amountTextController = TextEditingController();
DateTime _selectedDate = DateTime.now();
SearchFieldListItem<Beneficiary>? _selectedBeneficiary;
TransactionDirection _selectedDirection = TransactionDirection.send;
ExpenseCategory? _expenseCategory = null;
String getBeneficiaryName(Beneficiary item) {
return switch (item.type) {
BeneficiaryType.account => "${item.name} (Account)",
BeneficiaryType.other => item.name,
};
}
Future<void> _submit(BuildContext context) async {
final beneficiaryName = _beneficiaryTextController.text;
if (_selectedBeneficiary == null && beneficiaryName.isEmpty) {
return;
}
Beneficiary? beneficiary = _selectedBeneficiary?.item;
if (beneficiary == null ||
getBeneficiaryName(beneficiary) != beneficiaryName) {
// Add a new beneficiary, if none was selected
final result = await showDialog<bool>(
context: context,
builder:
(context) => AlertDialog(
title: const Text("Add Beneficiary"),
content: Text(
"The beneficiary '$beneficiaryName' does not exist. Do you want to add it?",
),
actions: [
TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
),
child: const Text('Add'),
onPressed: () => Navigator.of(context).pop(true),
),
TextButton(
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge,
),
child: const Text('Cancel'),
onPressed: () => Navigator.of(context).pop(false),
),
],
),
);
if (result == null || !result) {
return;
}
beneficiary =
Beneficiary()
..name = beneficiaryName
..type = BeneficiaryType.other;
await upsertBeneficiary(beneficiary);
}
final factor = switch (_selectedDirection) {
TransactionDirection.send => -1,
TransactionDirection.receive => 1,
};
final amount = factor * double.parse(_amountTextController.text).abs();
final transaction =
Transaction()
..account.value = widget.activeAccountItem
..beneficiary.value = beneficiary
..amount = amount
..tags = []
..expenseCategory.value = _expenseCategory
..date = _selectedDate;
await upsertTransaction(transaction);
if (beneficiary.type == BeneficiaryType.account) {
final otherTransaction =
Transaction()
..account.value = beneficiary.account.value!
..beneficiary.value = await getAccountBeneficiary(
widget.activeAccountItem,
)
..date = _selectedDate
..expenseCategory.value = _expenseCategory
..amount = -1 * amount;
await upsertTransaction(otherTransaction);
}
widget.onAdd();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: ListView(
shrinkWrap: true,
children: [
OutlinedButton(
onPressed: () async {
final template = await showDialogOrModal<Transaction>(
context: context,
builder: (context) {
return BlocBuilder<CoreCubit, CoreState>(
builder: (context, state) {
if (state.transactionTemplates.isEmpty) {
return Text("No templates defined");
}
return ListView.builder(
itemCount: state.transactionTemplates.length,
itemBuilder:
(context, index) => ListTile(
title: Text(
state.transactionTemplates[index].name,
),
onTap: () {
Navigator.of(
context,
).pop(state.transactionTemplates[index]);
},
),
);
},
);
},
);
if (template == null) {
return;
}
_amountTextController.text = template.amount.toString();
_selectedDirection =
template.amount > 0
? TransactionDirection.receive
: TransactionDirection.send;
_selectedBeneficiary = SearchFieldListItem(
getBeneficiaryName(template.beneficiary.value!),
item: template.beneficiary.value!,
);
_beneficiaryTextController.text = getBeneficiaryName(
template.beneficiary.value!,
);
},
child: Text("Use template"),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: BlocBuilder<CoreCubit, CoreState>(
builder:
(context, state) => SearchField<Beneficiary>(
suggestions:
state.beneficiaries
.where((el) {
final bloc = GetIt.I.get<CoreCubit>();
if (el.type == BeneficiaryType.account) {
return el.account.value?.id.toInt() ==
bloc.activeAccount?.id.toInt();
}
return true;
})
.map((el) {
return SearchFieldListItem(
getBeneficiaryName(el),
item: el,
);
})
.toList(),
hint: "Beneficiary",
controller: _beneficiaryTextController,
selectedValue: _selectedBeneficiary,
onSuggestionTap: (beneficiary) {
setState(() => _selectedBeneficiary = beneficiary);
},
),
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: TextField(
controller: _amountTextController,
keyboardType: TextInputType.numberWithOptions(
signed: false,
decimal: false,
),
decoration: InputDecoration(hintText: "Amount"),
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text("Date"),
OutlinedButton(
onPressed: () async {
final dt = await showDatePicker(
context: context,
initialDate: _selectedDate,
firstDate: DateTime(1),
lastDate: DateTime(9999),
);
if (dt == null) return;
setState(() => _selectedDate = dt);
},
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Icon(Icons.date_range),
Text(formatDateTime(_selectedDate)),
],
),
),
],
),
),
Row(
children: [
Text("Expense category"),
OutlinedButton(
onPressed: () async {
final category = await showDialogOrModal(
context: context,
builder: (_) => AddExpenseCategory(),
);
if (category == null) {
return;
}
setState(() => _expenseCategory = category);
},
child: Text(_expenseCategory?.name ?? "None"),
),
],
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: SegmentedButton<TransactionDirection>(
segments: [
ButtonSegment(
value: TransactionDirection.send,
label: Text("Send"),
icon: Icon(Icons.remove),
),
ButtonSegment(
value: TransactionDirection.receive,
label: Text("Receive"),
icon: Icon(Icons.add),
),
],
selected: <TransactionDirection>{_selectedDirection},
multiSelectionEnabled: false,
onSelectionChanged: (selection) {
setState(() => _selectedDirection = selection.first);
},
),
),
Align(
alignment: Alignment.centerRight,
child: OutlinedButton(
onPressed: () => _submit(context),
child: Text("Add"),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,40 @@
import 'dart:io';
import 'package:flutter/material.dart';
class ImageWrapper extends StatelessWidget {
final String title;
final String? path;
final VoidCallback onTap;
const ImageWrapper({
super.key,
required this.title,
required this.onTap,
this.path,
});
@override
Widget build(BuildContext context) {
Widget widget;
if (path == null) {
widget = SizedBox(
width: 45,
height: 45,
child: Container(
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.circular(8),
),
child: Center(child: Text(title[0])),
),
);
} else {
widget = ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.file(File(path!), width: 45, height: 45),
);
}
return InkWell(onTap: onTap, child: widget);
}
}

View File

@@ -0,0 +1,49 @@
import 'package:flutter/material.dart';
import 'package:okane/database/collections/transaction.dart';
import 'package:okane/database/database.dart';
import 'package:okane/ui/utils.dart';
import 'package:okane/ui/widgets/image_wrapper.dart';
class TransactionCard extends StatelessWidget {
final Widget? subtitle;
const TransactionCard({
super.key,
required this.transaction,
required this.onTap,
this.subtitle,
});
final Transaction transaction;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return Card(
child: ListTile(
onTap: onTap,
leading: ImageWrapper(
title: transaction.beneficiary.value!.name,
path: transaction.beneficiary.value!.imagePath,
onTap: () {},
),
trailing: Text(formatDateTime(transaction.date)),
title: Text(transaction.beneficiary.value!.name),
subtitle: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
formatCurrency(transaction.amount),
style: TextStyle(
color: transaction.amount < 0 ? Colors.red : Colors.green,
),
),
if (subtitle != null) subtitle!,
],
),
),
);
}
}