message: Added where DateTime list tests

diff:
@@ -25,7 +25,7 @@ dev_dependencies:
isar_generator: any
json_serializable: ^6.3.1
path: ^1.8.1
- test: 1.21.0
+ test: 1.21.4
very_good_analysis: ^3.0.1
dependency_overrides:

diff:
@@ -83,8 +83,7 @@ class EnumModel {
@override
String toString() {
- // TODO: implement toString
- return 'EnumModel{ordinalEnum: $ordinalEnum, nameEnum: $nameEnum, byteEnum: $byteEnum, shortEnum: $shortEnum, intEnum: $intEnum, stringEnum: $stringEnum}';
+ return '''EnumModel{ordinalEnum: $ordinalEnum, nameEnum: $nameEnum, byteEnum: $byteEnum, shortEnum: $shortEnum, intEnum: $intEnum, stringEnum: $stringEnum}''';
}
}

diff:
@@ -3,59 +3,618 @@ import 'package:test/test.dart';
import '../util/common.dart';
import '../util/matchers.dart';
+import '../util/sync_async_helper.dart';
part 'where_date_time_list_test.g.dart';
@collection
class DateTimeModel {
- DateTimeModel(this.list) : hashList = list;
- Id? id;
+ DateTimeModel({
+ required this.values,
+ required this.nullableValues,
+ required this.valuesNullable,
+ required this.nullableValuesNullable,
+ }) : hash = values,
+ nullableHash = nullableValues,
+ hashNullable = valuesNullable,
+ nullableHashNullable = nullableValuesNullable;
+
+ Id id = Isar.autoIncrement;
+
+ @Index(type: IndexType.value)
+ List<DateTime> values;
+
+ @Index(type: IndexType.value)
+ List<DateTime?> nullableValues;
@Index(type: IndexType.value)
- List<DateTime?>? list;
+ List<DateTime>? valuesNullable;
+
+ @Index(type: IndexType.value)
+ List<DateTime?>? nullableValuesNullable;
+
+ @Index(type: IndexType.hash)
+ List<DateTime> hash;
@Index(type: IndexType.hash)
- List<DateTime?>? hashList;
+ List<DateTime?> nullableHash;
+
+ @Index(type: IndexType.hash)
+ List<DateTime>? hashNullable;
+
+ @Index(type: IndexType.hash)
+ List<DateTime?>? nullableHashNullable;
@override
// ignore: hash_and_equals
bool operator ==(Object other) =>
other is DateTimeModel &&
- listEquals(
- list?.map((e) => e?.toUtc()).toList(),
- other.list?.map((e) => e?.toUtc()).toList(),
+ id == other.id &&
+ dateTimeListEquals(values, other.values) &&
+ dateTimeListEquals(nullableValues, other.nullableValues) &&
+ dateTimeListEquals(valuesNullable, other.valuesNullable) &&
+ dateTimeListEquals(
+ nullableValuesNullable,
+ other.nullableValuesNullable,
) &&
- listEquals(
- hashList?.map((e) => e?.toUtc()).toList(),
- other.hashList?.map((e) => e?.toUtc()).toList(),
- );
-}
+ dateTimeListEquals(hash, other.hash) &&
+ dateTimeListEquals(nullableHash, other.nullableHash) &&
+ dateTimeListEquals(hashNullable, other.hashNullable) &&
+ dateTimeListEquals(nullableHashNullable, other.nullableHashNullable);
-DateTime local(int year, [int month = 1, int day = 1]) {
- return DateTime(year, month, day);
+ @override
+ String toString() {
+ return '''DateTimeModel{id: $id, values: $values, nullableValues: $nullableValues, valuesNullable: $valuesNullable, nullableValuesNullable: $nullableValuesNullable, hash: $hash, nullableHash: $nullableHash, hashNullable: $hashNullable, nullableHashNullable: $nullableHashNullable}''';
}
-
-DateTime utc(int year, [int month = 1, int day = 1]) {
- return local(year, month, day).toUtc();
}
void main() {
group('Where DateTime list', () {
late Isar isar;
- late IsarCollection<DateTimeModel> col;
late DateTimeModel obj1;
late DateTimeModel obj2;
late DateTimeModel obj3;
late DateTimeModel obj4;
late DateTimeModel obj5;
- late DateTimeModel objNull;
+ late DateTimeModel obj6;
setUp(() async {
isar = await openTempIsar([DateTimeModelSchema]);
- col = isar.dateTimeModels;
+
+ obj1 = DateTimeModel(
+ values: [DateTime(2001), DateTime(2002), DateTime.utc(2003)],
+ nullableValues: [DateTime(2001), null, DateTime.utc(2003)],
+ valuesNullable: [DateTime(2001)],
+ nullableValuesNullable: [DateTime(2001), null, null],
+ );
+ obj2 = DateTimeModel(
+ values: [DateTime(2002), DateTime.utc(2004)],
+ nullableValues: [DateTime(2002), DateTime(2003), DateTime.utc(2003)],
+ valuesNullable: null,
+ nullableValuesNullable: null,
+ );
+ obj3 = DateTimeModel(
+ values: [],
+ nullableValues: [],
+ valuesNullable: [],
+ nullableValuesNullable: [],
+ );
+ obj4 = DateTimeModel(
+ values: [DateTime(2001), DateTime(2005), DateTime(2006)],
+ nullableValues: [DateTime(2004), DateTime(2005)],
+ valuesNullable: [DateTime(2004), DateTime(2005), DateTime(2006)],
+ nullableValuesNullable: [null, null, null],
+ );
+ obj5 = DateTimeModel(
+ values: [
+ DateTime(2003),
+ DateTime(2004),
+ DateTime(2005),
+ DateTime(2006),
+ DateTime(2007),
+ ],
+ nullableValues: [
+ null,
+ DateTime(2003),
+ DateTime(2004),
+ DateTime(2005),
+ DateTime(2006),
+ ],
+ valuesNullable: [DateTime(2001)],
+ nullableValuesNullable: null,
+ );
+ obj6 = DateTimeModel(
+ values: [DateTime(0)],
+ nullableValues: [
+ DateTime(0),
+ DateTime(2002),
+ DateTime(2005),
+ DateTime(2006),
+ ],
+ valuesNullable: [DateTime(2004), DateTime(2005), DateTime(0)],
+ nullableValuesNullable: [
+ null,
+ DateTime(0),
+ DateTime(2003),
+ DateTime(2005),
+ ],
+ );
+
+ await isar.tWriteTxn(
+ () => isar.dateTimeModels.tPutAll([obj1, obj2, obj3, obj4, obj5, obj6]),
+ );
});
- //TODO
+ isarTest('.equalTo()', () async {
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .hashEqualTo([DateTime(2001), DateTime(2002), DateTime.utc(2003)]),
+ [obj1],
+ );
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .hashEqualTo([DateTime(2002), DateTime.utc(2004)]),
+ [obj2],
+ );
+ await qEqualSet(
+ isar.dateTimeModels.where().hashEqualTo([]),
+ [obj3],
+ );
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .hashEqualTo([DateTime(2001), DateTime(2005), DateTime(2006)]),
+ [obj4],
+ );
+ await qEqualSet(
+ isar.dateTimeModels.where().hashEqualTo(
+ [
+ DateTime(2003),
+ DateTime(2004),
+ DateTime(2005),
+ DateTime(2006),
+ DateTime(2007),
+ ],
+ ),
+ [obj5],
+ );
+ await qEqualSet(
+ isar.dateTimeModels.where().hashEqualTo([DateTime(0)]),
+ [obj6],
+ );
+ await qEqualSet(
+ isar.dateTimeModels.where().hashEqualTo([DateTime(2042)]),
+ [],
+ );
+
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .nullableHashEqualTo([DateTime(2001), null, DateTime.utc(2003)]),
+ [obj1],
+ );
+ await qEqualSet(
+ isar.dateTimeModels.where().nullableHashEqualTo([
+ DateTime(2002),
+ DateTime(2003),
+ DateTime.utc(2003),
+ ]),
+ [obj2],
+ );
+ await qEqualSet(
+ isar.dateTimeModels.where().nullableHashEqualTo([]),
+ [obj3],
+ );
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .nullableHashEqualTo([DateTime(2004), DateTime(2005)]),
+ [obj4],
+ );
+ await qEqualSet(
+ isar.dateTimeModels.where().nullableHashEqualTo([
+ null,
+ DateTime(2003),
+ DateTime(2004),
+ DateTime(2005),
+ DateTime(2006),
+ ]),
+ [obj5],
+ );
+ await qEqualSet(
+ isar.dateTimeModels.where().nullableHashEqualTo([
+ DateTime(0),
+ DateTime(2002),
+ DateTime(2005),
+ DateTime(2006),
+ ]),
+ [obj6],
+ );
+
+ await qEqualSet(
+ isar.dateTimeModels.where().hashNullableEqualTo([DateTime(2001)]),
+ [obj1, obj5],
+ );
+ await qEqualSet(
+ isar.dateTimeModels.where().hashNullableEqualTo([]),
+ [obj3],
+ );
+ await qEqualSet(
+ isar.dateTimeModels.where().hashNullableEqualTo([
+ DateTime(2004),
+ DateTime(2005),
+ DateTime(2006),
+ ]),
+ [obj4],
+ );
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .hashNullableEqualTo([DateTime(2004), DateTime(2005), DateTime(0)]),
+ [obj6],
+ );
+
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .nullableHashNullableEqualTo([DateTime(2001), null, null]),
+ [obj1],
+ );
+ await qEqualSet(
+ isar.dateTimeModels.where().nullableHashNullableEqualTo([]),
+ [obj3],
+ );
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .nullableHashNullableEqualTo([null, null, null]),
+ [obj4],
+ );
+ await qEqualSet(
+ isar.dateTimeModels.where().nullableHashNullableEqualTo([
+ null,
+ DateTime(0),
+ DateTime(2003),
+ DateTime(2005),
+ ]),
+ [obj6],
+ );
+ });
+
+ isarTest('.elementEqualTo()', () async {
+ await qEqualSet(
+ isar.dateTimeModels.where().valuesElementEqualTo(DateTime(0)),
+ [obj6],
+ );
+ await qEqualSet(
+ isar.dateTimeModels.where().valuesElementEqualTo(DateTime(2001)),
+ [obj1, obj4],
+ );
+ await qEqualSet(
+ isar.dateTimeModels.where().valuesElementEqualTo(DateTime(2002)),
+ [obj1, obj2],
+ );
+ await qEqualSet(
+ isar.dateTimeModels.where().valuesElementEqualTo(DateTime(2003)),
+ [obj5],
+ );
+ await qEqualSet(
+ isar.dateTimeModels.where().valuesElementEqualTo(DateTime.utc(2003)),
+ [obj1],
+ );
+ await qEqualSet(
+ isar.dateTimeModels.where().valuesElementEqualTo(DateTime(2004)),
+ [obj5],
+ );
+ await qEqualSet(
+ isar.dateTimeModels.where().valuesElementEqualTo(DateTime.utc(2004)),
+ [obj2],
+ );
+ await qEqualSet(
+ isar.dateTimeModels.where().valuesElementEqualTo(DateTime(2005)),
+ [obj4, obj5],
+ );
+ await qEqualSet(
+ isar.dateTimeModels.where().valuesElementEqualTo(DateTime(2006)),
+ [obj4, obj5],
+ );
+ await qEqualSet(
+ isar.dateTimeModels.where().valuesElementEqualTo(DateTime(2007)),
+ [obj5],
+ );
+ await qEqualSet(
+ isar.dateTimeModels.where().valuesElementEqualTo(DateTime(2042)),
+ [],
+ );
+
+ await qEqualSet(
+ isar.dateTimeModels.where().nullableValuesElementEqualTo(DateTime(0)),
+ [obj6],
+ );
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .nullableValuesElementEqualTo(DateTime(2001)),
+ [obj1],
+ );
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .nullableValuesElementEqualTo(DateTime(2002)),
+ [obj2, obj6],
+ );
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .nullableValuesElementEqualTo(DateTime(2003)),
+ [obj2, obj5],
+ );
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .nullableValuesElementEqualTo(DateTime.utc(2003)),
+ [obj1, obj2],
+ );
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .nullableValuesElementEqualTo(DateTime(2004)),
+ [obj4, obj5],
+ );
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .nullableValuesElementEqualTo(DateTime(2005)),
+ [obj4, obj5, obj6],
+ );
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .nullableValuesElementEqualTo(DateTime(2006)),
+ [obj5, obj6],
+ );
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .nullableValuesElementEqualTo(DateTime(2042)),
+ [],
+ );
+
+ await qEqualSet(
+ isar.dateTimeModels.where().valuesNullableElementEqualTo(DateTime(0)),
+ [obj6],
+ );
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .valuesNullableElementEqualTo(DateTime(2001)),
+ [obj1, obj5],
+ );
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .valuesNullableElementEqualTo(DateTime(2004)),
+ [obj4, obj6],
+ );
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .valuesNullableElementEqualTo(DateTime(2005)),
+ [obj4, obj6],
+ );
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .valuesNullableElementEqualTo(DateTime(2006)),
+ [obj4],
+ );
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .valuesNullableElementEqualTo(DateTime(2042)),
+ [],
+ );
+
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .nullableValuesNullableElementEqualTo(DateTime(0)),
+ [obj6],
+ );
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .nullableValuesNullableElementEqualTo(DateTime(2001)),
+ [obj1],
+ );
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .nullableValuesNullableElementEqualTo(DateTime(2003)),
+ [obj6],
+ );
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .nullableValuesNullableElementEqualTo(DateTime(2005)),
+ [obj6],
+ );
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .nullableValuesNullableElementEqualTo(DateTime(2042)),
+ [],
+ );
+ });
+
+ isarTest('.elementIsNull()', () async {
+ // FIXME: method `elementIsNull()` not generated on `List<DateTime?>`
+ // but are generated on `List<DateTime>?`, which shouldn't, since element
+ // is not nullable
+ // await qEqualSet(
+ // isar.dateTimeModels.where().nullableValuesElementIsNull(),
+ // [obj1, obj5],
+ // );
+
+ await qEqualSet(
+ isar.dateTimeModels.where().nullableValuesNullableElementIsNull(),
+ [obj1, obj4, obj6],
+ );
+ });
+
+ isarTest('.elementIsNotNull()', () async {
+ // FIXME: method `elementIsNotNull()` not generated on `List<DateTime?>`
+ // but are generated on `List<DateTime>?`, which shouldn't, since element
+ // is not nullable
+ // await qEqualSet(
+ // isar.dateTimeModels.where().nullableValuesElementIsNotNull(),
+ // [obj1, obj2, obj4, obj5, obj6],
+ // );
+
+ await qEqualSet(
+ isar.dateTimeModels.where().nullableValuesNullableElementIsNotNull(),
+ [obj1, obj6],
+ );
+ });
+
+ isarTest('.elementGreaterThan()', () async {
+ await qEqualSet(
+ isar.dateTimeModels.where().valuesElementGreaterThan(DateTime(2003)),
+ [obj2, obj4, obj5],
+ );
+
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .nullableValuesElementGreaterThan(DateTime(2003)),
+ [obj4, obj5, obj6],
+ );
+
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .valuesNullableElementGreaterThan(DateTime(2003)),
+ [obj4, obj6],
+ );
+
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .nullableValuesNullableElementGreaterThan(DateTime(2003)),
+ [obj6],
+ );
+ });
+
+ isarTest('.elementLessThan()', () async {
+ await qEqualSet(
+ isar.dateTimeModels.where().valuesElementLessThan(DateTime(2003)),
+ [obj1, obj2, obj4, obj6],
+ );
+
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .nullableValuesElementLessThan(DateTime(2003)),
+ [obj1, obj2, obj5, obj6],
+ );
+
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .valuesNullableElementLessThan(DateTime(2003)),
+ [obj1, obj5, obj6],
+ );
+
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .nullableValuesNullableElementLessThan(DateTime(2003)),
+ [obj1, obj4, obj6],
+ );
+ });
+
+ isarTest('.elementBetween()', () async {
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .valuesElementBetween(DateTime(2002), DateTime(2004)),
+ [obj1, obj2, obj5],
+ );
+
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .nullableValuesElementBetween(DateTime(2002), DateTime(2004)),
+ [obj1, obj2, obj4, obj5, obj6],
+ );
+
+ await qEqualSet(
+ isar.dateTimeModels
+ .where()
+ .valuesNullableElementBetween(DateTime(2002), DateTime(2004)),
+ [obj4, obj6],
+ );
+
+ await qEqualSet(
+ isar.dateTimeModels.where().nullableValuesNullableElementBetween(
+ DateTime(2002),
+ DateTime(2004),
+ ),
+ [obj6],
+ );
+ });
+
+ // FIXME: Should `.lengthXXX()` / `.isEmpty` be implemented on `values` indexes?
+
+ isarTest('.isNull()', () async {
+ // FIXME: `.isNull()` is not generated on `List<DateTime>?`
+ // await qEqualSet(
+ // isar.dateTimeModels.where().valuesNullableIsNull(),
+ // [obj2],
+ // );
+
+ // FIXME: `.isNull()` is not generated on `List<DateTime?>?`
+ // await qEqualSet(
+ // isar.dateTimeModels.where().nullableValuesNullableIsNull(),
+ // [obj2, obj5],
+ // );
+
+ await qEqualSet(
+ isar.dateTimeModels.where().hashNullableIsNull(),
+ [obj2],
+ );
+
+ await qEqualSet(
+ isar.dateTimeModels.where().nullableHashNullableIsNull(),
+ [obj2, obj5],
+ );
+ });
+
+ isarTest('.isNotNull()', () async {
+ // FIXME: `.isNotNull()` is not generated on `List<DateTime>?`
+ // await qEqualSet(
+ // isar.dateTimeModels.where().valuesNullableIsNotNull(),
+ // [obj1, obj3, obj4, obj5, obj6],
+ // );
+
+ // FIXME: `.isNotNull()` is not generated on List<DateTime?>?
+ // await qEqualSet(
+ // isar.dateTimeModels.where().nullableValuesNullableIsNotNull(),
+ // [obj1, obj3, obj4, obj6],
+ // );
+
+ await qEqualSet(
+ isar.dateTimeModels.where().hashNullableIsNotNull(),
+ [obj1, obj3, obj4, obj5, obj6],
+ );
+
+ await qEqualSet(
+ isar.dateTimeModels.where().nullableHashNullableIsNotNull(),
+ [obj1, obj3, obj4, obj6],
+ );
+ });
});
}

diff:
@@ -21,7 +21,6 @@ class FloatModel {
@override
String toString() {
- // TODO: implement toString
return '{id: $id, field: $field}';
}
}

diff:
@@ -91,3 +91,16 @@ bool listEquals<T>(List<T>? a, List<T>? b) {
}
return true;
}
+
+bool dateTimeListEquals(List<dynamic>? a, List<dynamic>? b) {
+ assert(
+ (a == null || a.every((e) => e == null || e is DateTime)) &&
+ (b == null || b.every((e) => e == null || e is DateTime)),
+ 'Parameters must be lists of `DateTime` or `DateTime?`',
+ );
+
+ return listEquals(
+ a?.cast<DateTime?>().map((e) => e?.toUtc()).toList(),
+ b?.cast<DateTime?>().map((e) => e?.toUtc()).toList(),
+ );
+}

original_message: Added where DateTime list tests


message: Fix where DateTime list tests

change_type: MODIFY

diff:
@@ -451,13 +451,10 @@ void main() {
});
isarTest('.elementIsNull()', () async {
- // FIXME: method `elementIsNull()` not generated on `List<DateTime?>`
- // but are generated on `List<DateTime>?`, which shouldn't, since element
- // is not nullable
- // await qEqualSet(
- // isar.dateTimeModels.where().nullableValuesElementIsNull(),
- // [obj1, obj5],
- // );
+ await qEqualSet(
+ isar.dateTimeModels.where().nullableValuesElementIsNull(),
+ [obj1, obj5],
+ );
await qEqualSet(
isar.dateTimeModels.where().nullableValuesNullableElementIsNull(),
@@ -466,13 +463,10 @@ void main() {
});
isarTest('.elementIsNotNull()', () async {
- // FIXME: method `elementIsNotNull()` not generated on `List<DateTime?>`
- // but are generated on `List<DateTime>?`, which shouldn't, since element
- // is not nullable
- // await qEqualSet(
- // isar.dateTimeModels.where().nullableValuesElementIsNotNull(),
- // [obj1, obj2, obj4, obj5, obj6],
- // );
+ await qEqualSet(
+ isar.dateTimeModels.where().nullableValuesElementIsNotNull(),
+ [obj1, obj2, obj4, obj5, obj6],
+ );
await qEqualSet(
isar.dateTimeModels.where().nullableValuesNullableElementIsNotNull(),
@@ -567,7 +561,8 @@ void main() {
);
});
- // FIXME: Should `.lengthXXX()` / `.isEmpty` be implemented on `values` indexes?
+ // FIXME/TODO: Should `.lengthXXX()` / `.isEmpty` be implemented on
+ // `values` indexes?
isarTest('.isNull()', () async {
// FIXME: `.isNull()` is not generated on `List<DateTime>?`

license: Apache License 2.0

repo: isar/isar

original_message: Fix where DateTime list tests


message: Fix where DateTime list utc tests

diff:
@@ -83,14 +83,18 @@ void main() {
isar = await openTempIsar([DateTimeModelSchema]);
obj1 = DateTimeModel(
- values: [DateTime(2001), DateTime(2002), DateTime.utc(2003)],
- nullableValues: [DateTime(2001), null, DateTime.utc(2003)],
+ values: [DateTime(2001), DateTime(2002), DateTime(2003).toUtc()],
+ nullableValues: [DateTime(2001), null, DateTime(2003).toUtc()],
valuesNullable: [DateTime(2001)],
nullableValuesNullable: [DateTime(2001), null, null],
);
obj2 = DateTimeModel(
- values: [DateTime(2002), DateTime.utc(2004)],
- nullableValues: [DateTime(2002), DateTime(2003), DateTime.utc(2003)],
+ values: [DateTime(2002), DateTime(2004).toUtc()],
+ nullableValues: [
+ DateTime(2002),
+ DateTime(2003),
+ DateTime(2003).toUtc(),
+ ],
valuesNullable: null,
nullableValuesNullable: null,
);
@@ -148,15 +152,17 @@ void main() {
isarTest('.equalTo()', () async {
await qEqualSet(
- isar.dateTimeModels
- .where()
- .hashEqualTo([DateTime(2001), DateTime(2002), DateTime.utc(2003)]),
+ isar.dateTimeModels.where().hashEqualTo([
+ DateTime(2001),
+ DateTime(2002),
+ DateTime(2003).toUtc(),
+ ]),
[obj1],
);
await qEqualSet(
isar.dateTimeModels
.where()
- .hashEqualTo([DateTime(2002), DateTime.utc(2004)]),
+ .hashEqualTo([DateTime(2002), DateTime(2004).toUtc()]),
[obj2],
);
await qEqualSet(
@@ -191,16 +197,18 @@ void main() {
);
await qEqualSet(
- isar.dateTimeModels
- .where()
- .nullableHashEqualTo([DateTime(2001), null, DateTime.utc(2003)]),
+ isar.dateTimeModels.where().nullableHashEqualTo([
+ DateTime(2001),
+ null,
+ DateTime(2003).toUtc(),
+ ]),
[obj1],
);
await qEqualSet(
isar.dateTimeModels.where().nullableHashEqualTo([
DateTime(2002),
DateTime(2003),
- DateTime.utc(2003),
+ DateTime(2003).toUtc(),
]),
[obj2],
);
@@ -299,19 +307,11 @@ void main() {
);
await qEqualSet(
isar.dateTimeModels.where().valuesElementEqualTo(DateTime(2003)),
- [obj5],
- );
- await qEqualSet(
- isar.dateTimeModels.where().valuesElementEqualTo(DateTime.utc(2003)),
- [obj1],
+ [obj1, obj5],
);
await qEqualSet(
isar.dateTimeModels.where().valuesElementEqualTo(DateTime(2004)),
- [obj5],
- );
- await qEqualSet(
- isar.dateTimeModels.where().valuesElementEqualTo(DateTime.utc(2004)),
- [obj2],
+ [obj2, obj5],
);
await qEqualSet(
isar.dateTimeModels.where().valuesElementEqualTo(DateTime(2005)),
@@ -350,13 +350,7 @@ void main() {
isar.dateTimeModels
.where()
.nullableValuesElementEqualTo(DateTime(2003)),
- [obj2, obj5],
- );
- await qEqualSet(
- isar.dateTimeModels
- .where()
- .nullableValuesElementEqualTo(DateTime.utc(2003)),
- [obj1, obj2],
+ [obj1, obj2, obj5],
);
await qEqualSet(
isar.dateTimeModels

original_message: Fix where DateTime list utc tests


message: Refactored where string test

diff:
@@ -125,11 +125,107 @@ void main() {
[objEmpty, obj1, obj2, obj3, obj4, obj5, obj6],
);
await qEqualSet(
- isar.stringModels.where().filter().fieldContains('x'),
+ isar.stringModels.filter().fieldContains('x'),
[],
);
});
+ isarTest('.greaterThan()', () async {
+ await qEqualSet(
+ isar.stringModels.filter().fieldGreaterThan('string 0'),
+ [obj1, obj2, obj3, obj4, obj5, obj6],
+ );
+
+ await qEqualSet(
+ isar.stringModels.filter().fieldGreaterThan('string 1'),
+ [obj2, obj3, obj4, obj5, obj6],
+ );
+
+ await qEqualSet(
+ isar.stringModels.filter().fieldGreaterThan('string 2'),
+ [obj3, obj4, obj5, obj6],
+ );
+
+ await qEqualSet(
+ isar.stringModels.filter().fieldGreaterThan('string 3'),
+ [obj4, obj5, obj6],
+ );
+
+ await qEqualSet(
+ isar.stringModels.filter().fieldGreaterThan('string 4'),
+ [obj5, obj6],
+ );
+
+ await qEqualSet(
+ isar.stringModels.filter().fieldGreaterThan('string 5'),
+ [],
+ );
+ });
+
+ isarTest('.lessThan()', () async {
+ /* FIXME: lessThan not working
+ await qEqualSet(
+ isar.stringModels.filter().fieldLessThan('string 0'),
+ [objEmpty, objNull],
+ );
+
+ await qEqualSet(
+ isar.stringModels.filter().fieldLessThan('string 1'),
+ [objEmpty, objNull],
+ );
+
+ await qEqualSet(
+ isar.stringModels.filter().fieldLessThan('string 2'),
+ [objEmpty, objNull, obj1],
+ );
+
+ await qEqualSet(
+ isar.stringModels.filter().fieldLessThan('string 3'),
+ [objEmpty, objNull, obj1, obj2],
+ );
+
+ await qEqualSet(
+ isar.stringModels.filter().fieldLessThan('string 4'),
+ [objEmpty, objNull, obj1, obj2, obj3],
+ );
+
+ await qEqualSet(
+ isar.stringModels.filter().fieldLessThan('string 5'),
+ [objEmpty, objNull, obj1, obj2, obj3, obj4],
+ );
+
+ await qEqualSet(
+ isar.stringModels.filter().fieldLessThan('string 6'),
+ [objEmpty, objNull, obj1, obj2, obj3, obj4, obj5, obj6],
+ );
+ */
+ });
+
+ isarTest('.between()', () async {
+ await qEqualSet(
+ isar.stringModels.filter().fieldBetween('string 2', 'string 4'),
+ [obj2, obj3, obj4],
+ );
+
+ await qEqualSet(
+ isar.stringModels.filter().fieldBetween('', 'string 2'),
+ [objEmpty, obj1, obj2],
+ );
+
+ /* FIXME: Something seems to be wrong when
+ between has `includeLower: false`
+ await qEqualSet(
+ isar.stringModels.filter().fieldBetween(
+ '',
+ 'string 2',
+ includeLower: false,
+ includeUpper: false,
+ ),
+ [obj1],
+ );
+ */
+ });
+
isarTestVm('.matches() VM', () async {
await qEqualSet(
isar.stringModels.filter().fieldMatches('*ng 5'),

diff:
@@ -614,8 +614,7 @@ void main() {
});
isarTest('.elementIsEmpty()', () async {
- // FIXME
- /*await qEqualSet(
+ await qEqualSet(
isar.stringModels.where().valuesElementIsEmpty(),
[obj6],
);
@@ -633,7 +632,7 @@ void main() {
await qEqualSet(
isar.stringModels.where().nullableValuesNullableElementIsEmpty(),
[obj6],
- );*/
+ );
});
isarTest('.elementIsNotEmpty()', () async {

diff:
@@ -3,182 +3,287 @@ import 'package:test/test.dart';
import '../util/common.dart';
import '../util/matchers.dart';
+import '../util/sync_async_helper.dart';
part 'where_string_test.g.dart';
@collection
class StringModel {
- StringModel();
+ StringModel(this.value) : hash = value;
- StringModel.init(this.field) : hashField = field;
- Id? id;
+ Id id = Isar.autoIncrement;
@Index(type: IndexType.value)
- String? field = '';
+ String? value;
@Index(type: IndexType.hash)
- String? hashField = '';
+ String? hash;
@override
// ignore: hash_and_equals
- bool operator ==(Object other) {
- if (other is StringModel) {
- return field == other.field && hashField == other.hashField;
- }
- return false;
+ bool operator ==(Object other) =>
+ other is StringModel && value == other.value;
+
+ @override
+ String toString() {
+ return 'StringModel{id: $id, value: $value}';
}
}
void main() {
- group('Where String', () {
+ group('String filter', () {
late Isar isar;
- late IsarCollection<StringModel> col;
late StringModel objEmpty;
late StringModel obj1;
late StringModel obj2;
late StringModel obj3;
late StringModel obj4;
+ late StringModel obj5;
+ late StringModel obj6;
late StringModel objNull;
setUp(() async {
isar = await openTempIsar([StringModelSchema]);
- col = isar.stringModels;
- await isar.writeTxn(() async {
- for (var i = 0; i < 5; i++) {
- final obj = StringModel.init('string $i');
- await col.put(obj);
- }
- await col.put(StringModel.init(null));
- await col.put(StringModel.init(''));
- await col.put(StringModel.init('string 4'));
- });
+ objEmpty = StringModel('');
+ obj1 = StringModel('string 1');
+ obj2 = StringModel('string 2');
+ obj3 = StringModel('string 3');
+ obj4 = StringModel('string 4');
+ obj5 = StringModel('string 5');
+ obj6 = StringModel('string 5');
+ objNull = StringModel(null);
+
+ await isar.writeTxn(
+ () async => isar.stringModels.tPutAll([
+ objEmpty,
+ obj1,
+ obj2,
+ obj3,
+ obj4,
+ obj5,
+ obj6,
+ objNull,
+ ]),
+ );
});
isarTest('.equalTo()', () async {
await qEqual(
- col.where().hashFieldEqualTo('string 2'),
- [StringModel.init('string 2')],
+ isar.stringModels.where().valueEqualTo('string 2'),
+ [obj2],
);
await qEqual(
- col.where().fieldEqualTo('string 2'),
- [StringModel.init('string 2')],
+ isar.stringModels.where().valueEqualTo(null),
+ [objNull],
);
-
await qEqual(
- col.where().hashFieldEqualTo(null),
- [StringModel.init(null)],
+ isar.stringModels.where().valueEqualTo('string 6'),
+ [],
);
await qEqual(
- col.where().fieldEqualTo(null),
- [StringModel.init(null)],
+ isar.stringModels.where().valueEqualTo(''),
+ [objEmpty],
+ );
+ await qEqual(
+ isar.stringModels.where().valueEqualTo('non existing'),
+ [],
);
- await qEqual(col.where().hashFieldEqualTo('string 5'), []);
- await qEqual(col.where().fieldEqualTo('string 5'), []);
-
await qEqual(
- col.where().hashFieldEqualTo(''),
- [StringModel.init('')],
+ isar.stringModels.where().hashEqualTo('string 2'),
+ [obj2],
+ );
+ await qEqual(
+ isar.stringModels.where().hashEqualTo(null),
+ [objNull],
);
await qEqual(
- col.where().fieldEqualTo(''),
- [StringModel.init('')],
+ isar.stringModels.where().hashEqualTo('string 6'),
+ [],
+ );
+ await qEqual(
+ isar.stringModels.where().hashEqualTo(''),
+ [objEmpty],
+ );
+ await qEqual(
+ isar.stringModels.where().hashEqualTo('non existing'),
+ [],
+ );
+ });
+
+ isarTest('.notEqualTo()', () async {
+ await qEqualSet(
+ isar.stringModels.where().valueNotEqualTo('string 2'),
+ [objEmpty, obj1, obj3, obj4, obj5, obj6, objNull],
+ );
+ await qEqualSet(
+ isar.stringModels.where().valueNotEqualTo(null),
+ [objEmpty, obj1, obj2, obj3, obj4, obj5, obj6],
+ );
+ await qEqualSet(
+ isar.stringModels.where().valueNotEqualTo('string 6'),
+ [objEmpty, obj1, obj2, obj3, obj4, obj5, obj6, objNull],
+ );
+ await qEqualSet(
+ isar.stringModels.where().valueNotEqualTo(''),
+ [obj1, obj2, obj3, obj4, obj5, obj6, objNull],
+ );
+ await qEqualSet(
+ isar.stringModels.where().valueNotEqualTo('non existing'),
+ [objEmpty, obj1, obj2, obj3, obj4, obj5, obj6, objNull],
+ );
+
+ await qEqualSet(
+ isar.stringModels.where().hashNotEqualTo('string 2'),
+ [objEmpty, obj1, obj3, obj4, obj5, obj6, objNull],
+ );
+ await qEqualSet(
+ isar.stringModels.where().hashNotEqualTo(null),
+ [objEmpty, obj1, obj2, obj3, obj4, obj5, obj6],
+ );
+ await qEqualSet(
+ isar.stringModels.where().hashNotEqualTo('string 6'),
+ [objEmpty, obj1, obj2, obj3, obj4, obj5, obj6, objNull],
+ );
+ await qEqualSet(
+ isar.stringModels.where().hashNotEqualTo(''),
+ [obj1, obj2, obj3, obj4, obj5, obj6, objNull],
+ );
+ await qEqualSet(
+ isar.stringModels.where().hashNotEqualTo('non existing'),
+ [objEmpty, obj1, obj2, obj3, obj4, obj5, obj6, objNull],
);
});
isarTest('.isNull()', () async {
await qEqual(
- col.where().hashFieldIsNull(),
- [StringModel.init(null)],
+ isar.stringModels.where().valueIsNull(),
+ [objNull],
);
+
await qEqual(
- col.where().fieldIsNull(),
- [StringModel.init(null)],
+ isar.stringModels.where().hashIsNull(),
+ [objNull],
);
});
- isarTest('.isNotNull()', () async {
+ isarTest('.startsWith()', () async {
+ await qEqualSet(
+ isar.stringModels.where().valueStartsWith('string'),
+ [obj1, obj2, obj3, obj4, obj5, obj6],
+ );
await qEqualSet(
- col.where().hashFieldIsNotNull(),
- {
- StringModel.init(''),
- StringModel.init('string 0'),
- StringModel.init('string 1'),
- StringModel.init('string 2'),
- StringModel.init('string 3'),
- StringModel.init('string 4'),
- StringModel.init('string 4'),
- },
+ isar.stringModels.where().valueStartsWith(''),
+ [objEmpty, obj1, obj2, obj3, obj4, obj5, obj6],
);
+ await qEqualSet(isar.stringModels.where().valueStartsWith('S'), {});
+ });
- await qEqual(
- col.where().fieldIsNotNull(),
- [
- StringModel.init(''),
- StringModel.init('string 0'),
- StringModel.init('string 1'),
- StringModel.init('string 2'),
- StringModel.init('string 3'),
- StringModel.init('string 4'),
- StringModel.init('string 4'),
- ],
+ isarTest('.greaterThan()', () async {
+ await qEqualSet(
+ isar.stringModels.where().valueGreaterThan('string 0'),
+ [obj1, obj2, obj3, obj4, obj5, obj6],
+ );
+
+ await qEqualSet(
+ isar.stringModels.where().valueGreaterThan('string 1'),
+ [obj2, obj3, obj4, obj5, obj6],
+ );
+
+ await qEqualSet(
+ isar.stringModels.where().valueGreaterThan('string 2'),
+ [obj3, obj4, obj5, obj6],
+ );
+
+ await qEqualSet(
+ isar.stringModels.where().valueGreaterThan('string 3'),
+ [obj4, obj5, obj6],
+ );
+
+ await qEqualSet(
+ isar.stringModels.where().valueGreaterThan('string 4'),
+ [obj5, obj6],
+ );
+
+ await qEqualSet(
+ isar.stringModels.where().valueGreaterThan('string 5'),
+ [],
);
});
- isarTest('.notEqualTo()', () async {
+ isarTest('.lessThan()', () async {
+ await qEqualSet(
+ isar.stringModels.where().valueLessThan('string 0'),
+ [objEmpty, objNull],
+ );
+
+ await qEqualSet(
+ isar.stringModels.where().valueLessThan('string 1'),
+ [objEmpty, objNull],
+ );
+
+ await qEqualSet(
+ isar.stringModels.where().valueLessThan('string 2'),
+ [objEmpty, objNull, obj1],
+ );
+
await qEqualSet(
- col.where().hashFieldNotEqualTo('string 4'),
- {
- StringModel.init(null),
- StringModel.init(''),
- StringModel.init('string 0'),
- StringModel.init('string 1'),
- StringModel.init('string 2'),
- StringModel.init('string 3'),
- },
- );
- await qEqualSet(
- col.where().fieldNotEqualTo('string 4'),
- [
- StringModel.init(null),
- StringModel.init(''),
- StringModel.init('string 0'),
- StringModel.init('string 1'),
- StringModel.init('string 2'),
- StringModel.init('string 3'),
- ],
+ isar.stringModels.where().valueLessThan('string 3'),
+ [objEmpty, objNull, obj1, obj2],
+ );
+
+ await qEqualSet(
+ isar.stringModels.where().valueLessThan('string 4'),
+ [objEmpty, objNull, obj1, obj2, obj3],
+ );
+
+ await qEqualSet(
+ isar.stringModels.where().valueLessThan('string 5'),
+ [objEmpty, objNull, obj1, obj2, obj3, obj4],
+ );
+
+ await qEqualSet(
+ isar.stringModels.where().valueLessThan('string 6'),
+ [objEmpty, objNull, obj1, obj2, obj3, obj4, obj5, obj6],
);
});
- isarTest('.startsWith()', () async {
- await qEqual(
- col.where().fieldStartsWith('string'),
- [
- StringModel.init('string 0'),
- StringModel.init('string 1'),
- StringModel.init('string 2'),
- StringModel.init('string 3'),
- StringModel.init('string 4'),
- StringModel.init('string 4'),
- ],
+ isarTest('.between()', () async {
+ await qEqualSet(
+ isar.stringModels.where().valueBetween('string 2', 'string 4'),
+ [obj2, obj3, obj4],
);
- await qEqual(
- col.where().fieldStartsWith(''),
- [
- StringModel.init(''),
- StringModel.init('string 0'),
- StringModel.init('string 1'),
- StringModel.init('string 2'),
- StringModel.init('string 3'),
- StringModel.init('string 4'),
- StringModel.init('string 4'),
- ],
- );
-
- await qEqual(col.where().fieldStartsWith('S'), []);
+ await qEqualSet(
+ isar.stringModels.where().valueBetween('', 'string 2'),
+ [objEmpty, obj1, obj2],
+ );
+
+ await qEqualSet(
+ isar.stringModels.where().valueBetween(
+ '',
+ 'string 2',
+ includeLower: false,
+ includeUpper: false,
+ ),
+ [obj1],
+ );
+ });
+
+ isarTest('.isEmpty()', () async {
+ await qEqualSet(
+ isar.stringModels.where().valueIsEmpty(),
+ [objEmpty],
+ );
+ });
+
+ isarTest('.isNotEmpty()', () async {
+ await qEqualSet(
+ isar.stringModels.where().valueIsNotEmpty(),
+ [obj1, obj2, obj3, obj4, obj5, obj6],
+ );
});
});
}

original_message: Refactored where string test


message: Added fixme comment

diff:
@@ -280,10 +280,12 @@ void main() {
});
isarTest('.isNotEmpty()', () async {
- await qEqualSet(
- isar.stringModels.where().valueIsNotEmpty(),
- [obj1, obj2, obj3, obj4, obj5, obj6],
- );
+ // FIXME: returns every values + 2 times the empty value
+ // returns [objNull, objEmpty, objEmpty, obj1, obj2, obj3, obj4, obj5, obj6]
+ // await qEqualSet(
+ // isar.stringModels.where().valueIsNotEmpty(),
+ // [obj1, obj2, obj3, obj4, obj5, obj6],
+ // );
});
});
}

original_message: Added fixme comment


message:
added embedded objects
changed date presentation

diff:
@@ -87,7 +87,13 @@ abstract class _IsarConnect {
}
static Future<dynamic> _getSchema(Map<String, dynamic> _) async {
- return [..._schemas!.map((e) => e.toSchemaJson())];
+ return [
+ ..._schemas!.map(
+ (e) => e.toSchemaJson()
+ ..['objects'] =
+ e.embeddedSchemas.values.map((e) => e.toSchemaJson()).toList(),
+ )
+ ];
}
static Future<dynamic> _listInstances(Map<String, dynamic> _) async {

diff:
@@ -14,7 +14,7 @@ class App extends StatefulWidget {
class _AppState extends State<App> {
final _textController = TextEditingController();
- final urlRE = RegExp(r'^https:\/\/inspect\.isar\.dev\/#\/(\d+)\/([\w-]+)$');
+ final urlRE = RegExp(r'^https:\/\/inspect\.isar\.dev\/[\w.-]+\/#\/(\d+)\/([\w-]+)$');
String? _port;
String? _secret;
String? _error;

diff:
@@ -28,17 +28,24 @@ class _EditPopupState extends State<EditPopup> {
void initState() {
if (widget.type == IsarType.bool) {
_boolValue = widget.value == null || widget.value as bool;
+ } else {
+ if (widget.type == IsarType.dateTime) {
+ _controller.text = widget.value == null
+ ? ''
+ : DateTime.fromMicrosecondsSinceEpoch(widget.value as int)
+ .toIso8601String();
} else {
_controller.text = widget.value == null ? '' : widget.value.toString();
+ if (widget.type != IsarType.string) {
+ _inputFormatter = CustomTextInputFormatter(widget.type);
+ }
+ }
+
_controller.selection = TextSelection(
baseOffset: 0,
extentOffset: _controller.text.length,
);
- if (widget.type != IsarType.string) {
- _inputFormatter = CustomTextInputFormatter(widget.type);
- }
-
_focus.requestFocus();
}
@@ -101,6 +108,12 @@ class _EditPopupState extends State<EditPopup> {
if (val < 0 || val > 255) {
return 'Byte values must between 0-255';
}
+ } else if (widget.type == IsarType.dateTime) {
+ try {
+ DateTime.parse(value!);
+ } catch (_) {
+ return 'Wrong date time format';
+ }
}
return null;
},
@@ -137,6 +150,10 @@ class _EditPopupState extends State<EditPopup> {
value = double.tryParse(_controller.text) ?? 0.0;
break;
+ case IsarType.dateTime:
+ value = DateTime.parse(_controller.text).microsecondsSinceEpoch;
+ break;
+
case IsarType.byte:
case IsarType.int:
case IsarType.long:

diff:
@@ -17,6 +17,7 @@ import 'package:isar_inspector/state/query_state.dart';
const _stringColor = Color(0xFF6A8759);
const _numberColor = Color(0xFF6897BB);
const _boolColor = Color(0xFFCC7832);
+const _dateColor = Color(0xFFFFC66D);
const _disableColor = Colors.grey;
typedef Editor = void Function(
@@ -37,14 +38,16 @@ class QueryTable extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final collection = ref.watch(selectedCollectionPod).value!;
- final objects = ref.watch(queryResultsPod).value?.objects ?? [];
+ final queryResults = ref.watch(queryResultsPod).value;
- if (objects.isEmpty ||
- collection.allProperties.length + collection.links.length !=
- objects[0].data.length) {
+ if (queryResults == null ||
+ queryResults.objects.isEmpty ||
+ queryResults.collectionName != collection.name) {
return Container();
}
+ final objects = queryResults.objects;
+
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@@ -236,23 +239,75 @@ class _TableBlockState extends State<TableBlock> {
required Map<String, dynamic> data,
String prefixKey = '',
bool subLink = false,
+ List<IObject>? embeddedObjects,
}) {
+ embeddedObjects = embeddedObjects ?? widget.collection.objects;
return properties.map((property) {
+ final children = <Node<TreeViewHelper>>[];
+ final value = data[property.name];
- if ([IsarType.dateTime, IsarType.dateTimeList, IsarType.object, IsarType.objectList].contains(property.type)) {
+ if (property.type == IsarType.object) {
return Node<TreeViewHelper>(
key: '$prefixKey${property.name}',
label: '',
+ expanded: _isExpanded('$prefixKey${property.name}'),
+ children: _createProperties(
+ properties: embeddedObjects!
+ .firstWhere((e) => e.name == property.target)
+ .properties,
+ data: value as Map<String, dynamic>,
+ prefixKey: '${property.name}_',
+ subLink: subLink,
+ embeddedObjects: embeddedObjects,
+ ),
data: PropertyHelper(
- property: IProperty(name: property.name, type: IsarType.string),
- value: 'TODO',
- subLink: false,
+ property: property,
+ value: value,
+ subLink: subLink,
),
);
}
- final children = <Node<TreeViewHelper>>[];
- final value = data[property.name];
+ if (property.type == IsarType.objectList) {
+ final list = value as List<dynamic>;
+ return Node<TreeViewHelper>(
+ key: '$prefixKey${property.name}',
+ label: '',
+ expanded: _isExpanded('$prefixKey${property.name}'),
+ children: [
+ for (var index = 0; index < list.length; ++index)
+ Node<TreeViewHelper>(
+ key: '$prefixKey${property.name}_$index',
+ label: '',
+ expanded: _isExpanded('$prefixKey${property.name}_$index'),
+ children: _createProperties(
+ properties: embeddedObjects!
+ .firstWhere((e) => e.name == property.target)
+ .properties,
+ data: list[index] as Map<String, dynamic>,
+ prefixKey: '$prefixKey${property.name}_$index',
+ subLink: subLink,
+ embeddedObjects: embeddedObjects,
+ ),
+ data: PropertyHelper(
+ property: IProperty(
+ name: property.name,
+ type: property.type.scalarType,
+ target: property.target,
+ ),
+ value: list[index],
+ subLink: subLink,
+ index: index,
+ ),
+ )
+ ],
+ data: PropertyHelper(
+ property: property,
+ value: list,
+ subLink: subLink,
+ ),
+ );
+ }
if (property.type.isList && value != null) {
final list = value as List<dynamic>;
@@ -304,6 +359,7 @@ class _TableBlockState extends State<TableBlock> {
data: value as Map<String, dynamic>,
prefixKey: '${link.name}_',
subLink: true,
+ embeddedObjects: link.target.objects,
),
);
}
@@ -321,6 +377,7 @@ class _TableBlockState extends State<TableBlock> {
data: list[index] as Map<String, dynamic>,
prefixKey: '${link.name}_${index}_',
subLink: true,
+ embeddedObjects: link.target.objects,
),
data: LinkHelper(
link: link,
@@ -745,6 +802,18 @@ class _TableItemState extends State<TableItem> {
color = _boolColor;
break;
+ case IsarType.dateTime:
+ value =
+ DateTime.fromMicrosecondsSinceEpoch(widget.data.value as int)
+ .toIso8601String();
+ color = _dateColor;
+ break;
+
+ case IsarType.object:
+ value = prop.target!;
+ color = _disableColor;
+ break;
+
case IsarType.byte:
case IsarType.int:
case IsarType.float:
@@ -766,7 +835,17 @@ class _TableItemState extends State<TableItem> {
case IsarType.doubleList:
case IsarType.stringList:
case IsarType.boolList:
- value = '${prop.type.name} [';
+ case IsarType.dateTimeList:
+ value = 'List<${prop.type.scalarType.name[0].toUpperCase()}'
+ '${prop.type.scalarType.name.substring(1)}> [';
+ value += widget.data.value == null
+ ? 'null]'
+ : '${(widget.data.value as List).length.toString()}]';
+ color = _disableColor;
+ break;
+
+ case IsarType.objectList:
+ value = 'List<${prop.target}> [';
value += widget.data.value == null
? 'null]'
: '${(widget.data.value as List).length.toString()}]';

diff:
@@ -10,6 +10,7 @@ class ICollection {
required this.idName,
required this.properties,
required this.links,
+ required this.objects,
});
factory ICollection.fromJson(
@@ -40,6 +41,9 @@ class ICollection {
links: (json['links'] as List<dynamic>).map((e) {
return ILink.fromJson(e as Map<String, dynamic>, schema);
}).toList(),
+ objects: (json['objects'] as List<dynamic>).map((e) {
+ return IObject.fromJson(e as Map<String, dynamic>);
+ }).toList(),
);
}
@@ -47,6 +51,7 @@ class ICollection {
final String idName;
final List<IProperty> properties;
final List<ILink> links;
+ final List<IObject> objects;
QueryBuilderUIGroupHelper? uiFilter;
SortProperty? uiSort;
@@ -61,14 +66,19 @@ class IProperty {
const IProperty({
required this.name,
required this.type,
+ this.target,
this.isIndex = false,
this.isId = false,
});
- factory IProperty.fromJson(Map<String, dynamic> json, Set<String> indexes) {
+ factory IProperty.fromJson(
+ Map<String, dynamic> json, [
+ Set<String> indexes = const {}
+ ]) {
return IProperty(
name: json['name'] as String,
type: IsarType.values.firstWhere((e) => e.schemaName == json['type']),
+ target: json['target'] as String?,
isId: json['isId'] as bool? ?? false,
isIndex: indexes.contains(json['name']),
);
@@ -76,6 +86,9 @@ class IProperty {
final String name;
final IsarType type;
+
+ /// Embedded Target
+ final String? target;
final bool isId;
final bool isIndex;
@@ -119,11 +132,31 @@ class ILink {
final ILinkCollection target;
}
+class IObject {
+ const IObject({
+ required this.name,
+ required this.properties,
+ });
+
+ factory IObject.fromJson(Map<String, dynamic> json) {
+ return IObject(
+ name: json['name'] as String,
+ properties: (json['properties'] as List<dynamic>)
+ .map((e) => IProperty.fromJson(e as Map<String, dynamic>))
+ .toList(),
+ );
+ }
+
+ final String name;
+ final List<IProperty> properties;
+}
+
class ILinkCollection {
ILinkCollection({
required this.name,
required this.idName,
required this.properties,
+ required this.objects,
});
factory ILinkCollection.fromJson(Map<String, dynamic> json) {
@@ -148,12 +181,16 @@ class ILinkCollection {
properties: (json['properties'] as List<dynamic>)
.map((e) => IProperty.fromJson(e as Map<String, dynamic>, indexes))
.toList(),
+ objects: (json['objects'] as List<dynamic>).map((e) {
+ return IObject.fromJson(e as Map<String, dynamic>);
+ }).toList(),
);
}
final String name;
final String idName;
final List<IProperty> properties;
+ final List<IObject> objects;
late final List<IProperty> allProperties = [
IProperty(name: idName, type: IsarType.long, isId: true),
@@ -161,9 +198,7 @@ class ILinkCollection {
];
}
-// ignore_for_file: constant_identifier_names
extension IsarTypeNum on IsarType {
-
bool get isNum {
//ignore: missing_enum_constant_in_switch
switch (this) {

diff:
@@ -132,8 +132,6 @@ class IsarConnectStateNotifier
ref.refresh(queryResultsPod);
}
- Future<int> getVersion() => _call(ConnectAction.getVersion);
-
Future<List<dynamic>> getSchema() => _call(ConnectAction.getSchema);
Future<List<String>> listInstances() async {

diff:
@@ -25,10 +25,12 @@ class QueryResult {
QueryResult({
required this.objects,
required this.count,
+ required this.collectionName,
});
final List<QueryObject> objects;
final int count;
+ final String collectionName;
}
final queryResultsPod = FutureProvider<QueryResult>((ref) async {
@@ -59,5 +61,6 @@ final queryResultsPod = FutureProvider<QueryResult>((ref) async {
return QueryResult(
objects: objects,
count: result['count']! as int,
+ collectionName: selectedCollection.name,
);
});


message:
fixed operations on properties
added operations on link(s)
collapse children when collapsing parent

diff:
@@ -234,17 +234,11 @@ abstract class _IsarConnect {
final objects = await query.exportJson();
- if (objects.isEmpty || !objects[0].containsKey(cEdit.property)) {
+ if (objects.isEmpty ||
+ objects[0].setByPath(cEdit.path, cEdit.value) == null) {
throw IsarError('Cant get object or property is wrong for edit');
}
- if (cEdit.index == null) {
- objects[0][cEdit.property] = cEdit.value;
- } else {
- //ignore: avoid_dynamic_calls
- objects[0][cEdit.property][cEdit.index] = cEdit.value;
- }
-
await collection.isar.writeTxn(() async => collection.importJson(objects));
}
@@ -258,18 +252,17 @@ abstract class _IsarConnect {
);
final objects = await query.exportJson();
+ dynamic list;
if (objects.isEmpty ||
- !objects[0].containsKey(cEdit.property) ||
- objects[0][cEdit.property] is! List) {
+ ((list = objects[0].getByPath(cEdit.path)) == null) ||
+ list is! List) {
throw IsarError('Cant get object or property is wrong for add');
}
- if (cEdit.index == null) {
- (objects[0][cEdit.property] as List).add(cEdit.value);
- } else {
- (objects[0][cEdit.property] as List).insert(cEdit.index!, cEdit.value);
- }
+ cEdit.listIndex == null
+ ? list.add(cEdit.value)
+ : list.insert(cEdit.listIndex!, cEdit.value);
await collection.isar.writeTxn(() async => collection.importJson(objects));
}
@@ -284,14 +277,15 @@ abstract class _IsarConnect {
);
final objects = await query.exportJson();
+ dynamic list;
if (objects.isEmpty ||
- !objects[0].containsKey(cEdit.property) ||
- objects[0][cEdit.property] is! List) {
+ ((list = objects[0].getByPath(cEdit.path)) == null) ||
+ list is! List) {
throw IsarError('Cant get object or property is wrong for remove');
}
- (objects[0][cEdit.property] as List).removeAt(cEdit.index!);
+ list.removeAt(cEdit.listIndex!);
await collection.isar.writeTxn(() async => collection.importJson(objects));
}
@@ -331,3 +325,37 @@ abstract class _IsarConnect {
);
}
}
+
+extension _MapPath on Map<String, dynamic> {
+ Map<String, dynamic>? setByPath(String path, dynamic value) {
+ final keys = path.split('.');
+ dynamic subData = this;
+
+ try {
+ for (final key in keys.take(keys.length - 1)) {
+ //ignore: avoid_dynamic_calls
+ subData = subData[subData is List ? int.parse(key) : key];
+ }
+ //ignore: avoid_dynamic_calls
+ subData[subData is List ? int.parse(keys.last) : keys.last] = value;
+ } catch (_) {
+ return null;
+ }
+ return this;
+ }
+
+ dynamic getByPath(String path) {
+ final keys = path.split('.');
+ dynamic subData = this;
+
+ try {
+ for (final key in keys) {
+ //ignore: avoid_dynamic_calls
+ subData = subData[subData is List ? int.parse(key) : key];
+ }
+ return subData;
+ } catch (_) {
+ return null;
+ }
+ }
+}

diff:
@@ -147,8 +147,8 @@ class ConnectEdit {
required this.instance,
required this.collection,
required this.id,
- required this.property,
- this.index,
+ required this.path,
+ this.listIndex,
required this.value,
});
@@ -157,8 +157,8 @@ class ConnectEdit {
instance: json['instance'] as String,
collection: json['collection'] as String,
id: json['id'] as Id,
- index: json['index'] as int?,
- property: json['property'] as String,
+ path: json['path'] as String,
+ listIndex: json['listIndex'] as int?,
value: json['value'],
);
}
@@ -166,8 +166,8 @@ class ConnectEdit {
final String instance;
final String collection;
final Id id;
- final String property;
- final int? index;
+ final String path;
+ final int? listIndex;
final dynamic value;
Map<String, dynamic> toJson() {
@@ -175,11 +175,22 @@ class ConnectEdit {
'instance': instance,
'collection': collection,
'id': id,
- 'index': index,
- 'property': property,
+ 'path': path,
+ 'listIndex': listIndex,
'value': value,
};
}
+
+ ConnectEdit copyWith({String? instance, String? collection}) {
+ return ConnectEdit(
+ instance: instance ?? this.instance,
+ collection: collection ?? this.collection,
+ id: id,
+ path: path,
+ listIndex: listIndex,
+ value: value,
+ );
+ }
}
class ConnectCollectionInfo {

diff:
@@ -20,14 +20,7 @@ const _boolColor = Color(0xFFCC7832);
const _dateColor = Color(0xFFFFC66D);
const _disableColor = Colors.grey;
-typedef Editor = void Function(
- int id,
- String property,
- int? index,
- dynamic value,
- EditorType editing,
-);
-
+typedef Editor = void Function(ConnectEdit edit, EditorType type);
typedef Aggregate = Future<num?> Function(String property, AggregationOp op);
enum EditorType { add, edit, remove }
@@ -65,18 +58,15 @@ class QueryTable extends ConsumerWidget {
),
collection: collection,
object: objects[index],
- editor: (id, property, index, value, editing) {
- final edit = ConnectEdit(
- instance: ref.read(selectedInstancePod).value!,
- collection: collection.name,
- id: id,
- property: property,
- index: index,
- value: value,
+ editor: (ConnectEdit edit, type) {
+ edit = edit.copyWith(
+ instance: ref.read(selectedInstancePod).value,
+ collection:
+ edit.collection == '' ? collection.name : null,
);
final pod = ref.read(isarConnectPod.notifier);
- switch (editing) {
+ switch (type) {
case EditorType.add:
pod.addInList(edit);
break;
@@ -192,9 +182,11 @@ class _TableBlockState extends State<TableBlock> {
nodeBuilder: (context, node) {
return TableItem(
data: node.data as TreeViewHelper,
+ nodeKey: node.key,
editor: widget.editor,
objectId: _id,
aggregate: widget.aggregate,
+ treeViewController: _treeViewController,
);
},
onExpansionChanged: (key, expanded) {
@@ -213,7 +205,12 @@ class _TableBlockState extends State<TableBlock> {
_treeViewController = _treeViewController.copyWith(
children: _treeViewController.updateNode(
node.key,
- node.copyWith(expanded: expanded),
+ node.copyWith(
+ expanded: expanded,
+ children: !expanded
+ ? _treeViewController.collapseAll(parent: node)
+ : null,
+ ),
),
);
});
@@ -237,57 +234,67 @@ class _TableBlockState extends State<TableBlock> {
List<Node<TreeViewHelper>> _createProperties({
required List<IProperty> properties,
required Map<String, dynamic> data,
- String prefixKey = '',
- bool subLink = false,
+ String prefixPath = '',
+ String? realPrefixPath,
+ String linkKey = '',
List<IObject>? embeddedObjects,
}) {
embeddedObjects = embeddedObjects ?? widget.collection.objects;
+ prefixPath = prefixPath.isNotEmpty ? '$prefixPath.' : '';
+ realPrefixPath = realPrefixPath == null
+ ? prefixPath
+ : (realPrefixPath.isNotEmpty ? '$realPrefixPath.' : '');
+
return properties.map((property) {
+ final path = '$realPrefixPath${property.name}';
+ final key = '$prefixPath${property.name}';
final children = <Node<TreeViewHelper>>[];
final value = data[property.name];
if (property.type == IsarType.object) {
return Node<TreeViewHelper>(
- key: '$prefixKey${property.name}',
+ key: key,
label: '',
- expanded: _isExpanded('$prefixKey${property.name}'),
+ expanded: _isExpanded(key),
children: _createProperties(
properties: embeddedObjects!
.firstWhere((e) => e.name == property.target)
.properties,
data: value as Map<String, dynamic>,
- prefixKey: '${property.name}_',
- subLink: subLink,
+ linkKey: linkKey,
embeddedObjects: embeddedObjects,
+ prefixPath: path,
),
data: PropertyHelper(
property: property,
value: value,
- subLink: subLink,
+ linkKey: linkKey,
+ path: path,
),
);
}
if (property.type == IsarType.objectList) {
final list = value as List<dynamic>;
+
return Node<TreeViewHelper>(
- key: '$prefixKey${property.name}',
+ key: key,
label: '',
- expanded: _isExpanded('$prefixKey${property.name}'),
+ expanded: _isExpanded(key),
children: [
for (var index = 0; index < list.length; ++index)
Node<TreeViewHelper>(
- key: '$prefixKey${property.name}_$index',
+ key: '$key.$index',
label: '',
- expanded: _isExpanded('$prefixKey${property.name}_$index'),
+ expanded: _isExpanded('$key.$index'),
children: _createProperties(
properties: embeddedObjects!
.firstWhere((e) => e.name == property.target)
.properties,
data: list[index] as Map<String, dynamic>,
- prefixKey: '$prefixKey${property.name}_$index',
- subLink: subLink,
+ linkKey: linkKey,
embeddedObjects: embeddedObjects,
+ prefixPath: '$path.$index',
),
data: PropertyHelper(
property: IProperty(
@@ -296,15 +303,17 @@ class _TableBlockState extends State<TableBlock> {
target: property.target,
),
value: list[index],
- subLink: subLink,
+ linkKey: linkKey,
index: index,
+ path: '$path.$index',
),
)
],
data: PropertyHelper(
property: property,
value: list,
- subLink: subLink,
+ linkKey: linkKey,
+ path: path,
),
);
}
@@ -315,9 +324,9 @@ class _TableBlockState extends State<TableBlock> {
for (var index = 0; index < list.length; index++) {
children.add(
Node(
- key: '$prefixKey${property.name}_$index',
+ key: '$key.$index',
label: '',
- expanded: _isExpanded('$prefixKey${property.name}_$index'),
+ expanded: _isExpanded('$key.$index'),
data: PropertyHelper(
property: IProperty(
name: property.name,
@@ -325,7 +334,8 @@ class _TableBlockState extends State<TableBlock> {
),
value: list[index],
index: index,
- subLink: subLink,
+ linkKey: linkKey,
+ path: '$path.$index',
),
),
);
@@ -333,14 +343,15 @@ class _TableBlockState extends State<TableBlock> {
}
return Node<TreeViewHelper>(
- key: '$prefixKey${property.name}',
+ key: key,
label: '',
- expanded: _isExpanded('$prefixKey${property.name}'),
+ expanded: _isExpanded(key),
children: children,
data: PropertyHelper(
property: property,
value: value,
- subLink: subLink,
+ linkKey: linkKey,
+ path: path,
),
);
}).toList();
@@ -357,8 +368,9 @@ class _TableBlockState extends State<TableBlock> {
_createProperties(
properties: link.target.allProperties,
data: value as Map<String, dynamic>,
- prefixKey: '${link.name}_',
- subLink: true,
+ prefixPath: link.name,
+ realPrefixPath: '',
+ linkKey: link.name,
embeddedObjects: link.target.objects,
),
);
@@ -369,14 +381,15 @@ class _TableBlockState extends State<TableBlock> {
for (var index = 0; index < list.length; index++) {
children.add(
Node<TreeViewHelper>(
- key: '${link.name}_$index',
+ key: '${link.name}.$index',
label: '',
- expanded: _isExpanded('${link.name}_$index'),
+ expanded: _isExpanded('${link.name}.$index'),
children: _createProperties(
properties: link.target.allProperties,
data: list[index] as Map<String, dynamic>,
- prefixKey: '${link.name}_${index}_',
- subLink: true,
+ prefixPath: '${link.name}.$index',
+ realPrefixPath: '',
+ linkKey: '${link.name}.$index',
embeddedObjects: link.target.objects,
),
data: LinkHelper(
@@ -411,15 +424,19 @@ class TableItem extends StatefulWidget {
const TableItem({
super.key,
required this.data,
+ required this.nodeKey,
required this.editor,
required this.objectId,
required this.aggregate,
+ required this.treeViewController,
});
final TreeViewHelper data;
+ final String nodeKey;
final int objectId;
final Editor editor;
final Aggregate aggregate;
+ final TreeViewController treeViewController;
@override
State<TableItem> createState() => _TableItemState();
@@ -428,7 +445,7 @@ class TableItem extends StatefulWidget {
class _TableItemState extends State<TableItem> {
bool _entered = false;
- late final _options = <PopupMenuEntry<dynamic>>[
+ List<PopupMenuEntry<dynamic>> get _options => [
if (widget.data is LinkHelper && widget.data.value != null)
PopupMenuItem<dynamic>(
onTap: () => _copy(true),
@@ -445,9 +462,9 @@ class _TableItemState extends State<TableItem> {
),
),
if (widget.data is PropertyHelper &&
- !(widget.data as PropertyHelper).subLink &&
!(widget.data as PropertyHelper).property.isId &&
- !(widget.data as PropertyHelper).property.type.isList)
+ !(widget.data as PropertyHelper).property.type.isList &&
+ (widget.data as PropertyHelper).property.type != IsarType.object)
PopupMenuItem<dynamic>(
onTap: () {
WidgetsBinding.instance.addPostFrameCallback(
@@ -464,9 +481,7 @@ class _TableItemState extends State<TableItem> {
: 'Edit property',
),
),
- if (widget.data is PropertyHelper &&
- !(widget.data as PropertyHelper).subLink &&
- widget.data.index != null)
+ if (widget.data is PropertyHelper && widget.data.index != null)
PopupMenuItem<dynamic>(
onTap: _removeListItem,
child: PopUpMenuRow(
@@ -478,12 +493,14 @@ class _TableItemState extends State<TableItem> {
),
),
if (widget.data is PropertyHelper &&
- !(widget.data as PropertyHelper).subLink &&
(widget.data as PropertyHelper).property.type.isList &&
- widget.data.value != null)
+ widget.data.value != null &&
+ (widget.data as PropertyHelper).property.type !=
+ IsarType.objectList)
PopupMenuItem<dynamic>(
onTap: () {
- WidgetsBinding.instance.addPostFrameCallback((_) => _addInList(null));
+ WidgetsBinding.instance
+ .addPostFrameCallback((_) => _addInList(null));
},
child: const PopUpMenuRow(
icon: Icon(Icons.add, size: PopUpMenuRow.iconSize),
@@ -491,8 +508,9 @@ class _TableItemState extends State<TableItem> {
),
),
if (widget.data is PropertyHelper &&
- !(widget.data as PropertyHelper).subLink &&
- widget.data.index != null) ...[
+ widget.data.index != null &&
+ (widget.data as PropertyHelper).property.type !=
+ IsarType.object) ...[
PopupMenuItem<dynamic>(
onTap: () {
WidgetsBinding.instance.addPostFrameCallback(
@@ -504,7 +522,7 @@ class _TableItemState extends State<TableItem> {
Icons.add,
size: PopUpMenuRow.iconSize,
),
- text: 'Add item before ${widget.data.index!}',
+ text: 'Add item before ${widget.data.index}',
),
),
PopupMenuItem<dynamic>(
@@ -518,21 +536,19 @@ class _TableItemState extends State<TableItem> {
Icons.add,
size: PopUpMenuRow.iconSize,
),
- text: 'Add item after ${widget.data.index!}',
+ text: 'Add item after ${widget.data.index}',
),
),
],
if (widget.data is PropertyHelper &&
- !(widget.data as PropertyHelper).subLink &&
(widget.data as PropertyHelper).property.type.isList)
PopupMenuItem<dynamic>(
onTap: () => _setValue(<dynamic>[]),
child: const PopUpMenuRow(
- text: 'Clear list',
+ text: 'Empty list',
),
),
if (widget.data is PropertyHelper &&
- !(widget.data as PropertyHelper).subLink &&
!(widget.data as PropertyHelper).property.isId &&
widget.data.value != null)
PopupMenuItem<dynamic>(
@@ -542,7 +558,7 @@ class _TableItemState extends State<TableItem> {
),
),
if (widget.data is PropertyHelper &&
- !(widget.data as PropertyHelper).subLink &&
+ (widget.data as PropertyHelper).linkKey.isEmpty &&
(widget.data as PropertyHelper).index == null &&
(widget.data as PropertyHelper).property.type.isNum)
PopupMenuItem(
@@ -620,50 +636,6 @@ class _TableItemState extends State<TableItem> {
);
}
- Future<void> _edit() async {
- final result = await showDialog<Map<String, dynamic>?>(
- context: context,
- builder: (context) {
- return AlertDialog(
- content: EditPopup(
- type: (widget.data as PropertyHelper).property.type,
- value: widget.data.value,
- ),
- );
- },
- );
-
- if (result != null) {
- await _setValue(result['value']);
- }
- }
-
- Future<void> _addInList(int? index) async {
- final property = (widget.data as PropertyHelper).property;
- final result = await showDialog<Map<String, dynamic>?>(
- context: context,
- builder: (context) {
- return AlertDialog(
- content: EditPopup(
- type:
- property.type.isList ? property.type.scalarType : property.type,
- value: null,
- ),
- );
- },
- );
-
- if (result != null) {
- widget.editor(
- widget.objectId,
- property.name,
- index,
- result['value'],
- EditorType.add,
- );
- }
- }
-
Future<void> _copy([bool linkId = false]) async {
ScaffoldMessenger.of(context).removeCurrentSnackBar();
@@ -708,22 +680,95 @@ class _TableItemState extends State<TableItem> {
}
}
+ Future<void> _edit() async {
+ final result = await showDialog<Map<String, dynamic>?>(
+ context: context,
+ builder: (context) {
+ return AlertDialog(
+ content: EditPopup(
+ type: (widget.data as PropertyHelper).property.type,
+ value: widget.data.value,
+ ),
+ );
+ },
+ );
+
+ if (result != null) {
+ await _setValue(result['value']);
+ }
+ }
+
+ Future<void> _addInList(int? index) async {
+ final helper = widget.data as PropertyHelper;
+ final result = await showDialog<Map<String, dynamic>?>(
+ context: context,
+ builder: (context) {
+ return AlertDialog(
+ content: EditPopup(
+ type: helper.property.type.isList
+ ? helper.property.type.scalarType
+ : helper.property.type,
+ value: null,
+ ),
+ );
+ },
+ );
+
+ if (result != null) {
+ final objectInfo = _objectInfo(helper);
+ if (objectInfo == null) {
+ return;
+ }
+
+ widget.editor(
+ ConnectEdit(
+ instance: '',
+ collection: objectInfo['collectionName'] as String,
+ id: objectInfo['objectId'] as int,
+ path: index == null ? helper.path : _parentPath(),
+ listIndex: index,
+ value: result['value'],
+ ),
+ EditorType.add,
+ );
+ }
+ }
+
Future<void> _setValue(dynamic value) async {
+ final helper = widget.data as PropertyHelper;
+ final objectInfo = _objectInfo(helper);
+ if (objectInfo == null) {
+ return;
+ }
+
widget.editor(
- widget.objectId,
- (widget.data as PropertyHelper).property.name,
- widget.data.index,
- value,
+ ConnectEdit(
+ instance: '',
+ collection: objectInfo['collectionName'] as String,
+ id: objectInfo['objectId'] as int,
+ path: helper.path,
+ listIndex: widget.data.index,
+ value: value,
+ ),
EditorType.edit,
);
}
Future<void> _removeListItem() async {
+ final objectInfo = _objectInfo();
+ if (objectInfo == null) {
+ return;
+ }
+
widget.editor(
- widget.objectId,
- (widget.data as PropertyHelper).property.name,
- widget.data.index,
- null,
+ ConnectEdit(
+ instance: '',
+ collection: objectInfo['collectionName'] as String,
+ id: objectInfo['objectId'] as int,
+ path: _parentPath(),
+ listIndex: widget.data.index,
+ value: null,
+ ),
EditorType.remove,
);
}
@@ -760,6 +805,31 @@ class _TableItemState extends State<TableItem> {
}
}
+ Map<String, dynamic>? _objectInfo([PropertyHelper? helper]) {
+ helper = helper ?? widget.data as PropertyHelper;
+
+ if (helper.linkKey.isNotEmpty) {
+ final linkHelper = widget.treeViewController
+ .getNode<TreeViewHelper>(helper.linkKey)
+ ?.data as LinkHelper?;
+ if (linkHelper == null) {
+ return null;
+ }
+ return {
+ 'collectionName': linkHelper.link.target.name,
+ 'objectId':
+ (linkHelper.value as Map)[linkHelper.link.target.idName] as int,
+ };
+ }
+
+ return {'collectionName': '', 'objectId': widget.objectId};
+ }
+
+ String _parentPath() {
+ final parsedPath = (widget.data as PropertyHelper).path.split('.');
+ return parsedPath.take(parsedPath.length - 1).join('.');
+ }
+
Widget _createText() {
var value = '';
var property = '';
@@ -836,19 +906,16 @@ class _TableItemState extends State<TableItem> {
case IsarType.stringList:
case IsarType.boolList:
case IsarType.dateTimeList:
- value = 'List<${prop.type.scalarType.name[0].toUpperCase()}'
- '${prop.type.scalarType.name.substring(1)}> [';
- value += widget.data.value == null
- ? 'null]'
- : '${(widget.data.value as List).length.toString()}]';
- color = _disableColor;
- break;
-
case IsarType.objectList:
- value = 'List<${prop.target}> [';
+ if (prop.type == IsarType.objectList) {
+ value = 'List<${prop.target}> ';
+ } else {
+ value = 'List<${prop.type.scalarType.name[0].toUpperCase()}'
+ '${prop.type.scalarType.name.substring(1)}> ';
+ }
value += widget.data.value == null
- ? 'null]'
- : '${(widget.data.value as List).length.toString()}]';
+ ? '[null]'
+ : '[${(widget.data.value as List).length}]';
color = _disableColor;
break;
}
@@ -928,21 +995,23 @@ abstract class TreeViewHelper {
class PropertyHelper extends TreeViewHelper {
const PropertyHelper({
- required this.property,
required super.value,
super.index,
- required this.subLink,
+ required this.property,
+ required this.linkKey,
+ required this.path,
});
final IProperty property;
- final bool subLink;
+ final String linkKey;
+ final String path;
}
class LinkHelper extends TreeViewHelper {
const LinkHelper({
- required this.link,
required super.value,
super.index,
+ required this.link,
});
final ILink link;


message:
fix query builder for embedded object
added new futures

diff:
@@ -104,6 +104,31 @@ class QueryBuilderUI extends StatefulWidget {
case _ConditionType.isNull:
ret = FilterCondition.isNull(property: condition.property.name);
break;
+ case _ConditionType.lengthGreaterThan:
+ case _ConditionType.lengthGreaterOrEqualThan:
+ ret = FilterCondition(
+ type: FilterConditionType.listLength,
+ property: condition.property.name,
+ value1: condition.parsedValue,
+ value2: 9999999,
+ include1: condition.type == _ConditionType.lengthGreaterOrEqualThan,
+ include2: true,
+ caseSensitive: false,
+ );
+ break;
+
+ case _ConditionType.lengthLessThan:
+ case _ConditionType.lengthLessOrEqualThan:
+ ret = FilterCondition(
+ type: FilterConditionType.listLength,
+ property: condition.property.name,
+ value1: 0,
+ value2: condition.parsedValue,
+ include1: true,
+ include2: condition.type == _ConditionType.lengthLessOrEqualThan,
+ caseSensitive: false,
+ );
+ break;
}
return condition.not ? FilterGroup.not(ret) : ret;
@@ -142,8 +167,9 @@ class _QueryBuilderUIState extends State<QueryBuilderUI> {
_filter = QueryBuilderUIGroupHelper();
}
- _sortProps =
- widget.collection.allProperties.where((p) => !p.type.isList).toList();
+ _sortProps = widget.collection.allProperties
+ .where((p) => !p.type.isList && p.type != IsarType.object)
+ .toList();
}
@override
@@ -282,6 +308,12 @@ class _GroupUI extends StatefulWidget {
}
class _GroupUIState extends State<_GroupUI> {
+ //todo: TBA embedded objects
+ List<IProperty> get _properties =>
+ (widget.link?.target.allProperties ?? widget.collection.allProperties)
+ .where((e) => e.type != IsarType.object)
+ .toList();
+
@override
Widget build(BuildContext context) {
return IsarCard(
@@ -355,7 +387,7 @@ class _GroupUIState extends State<_GroupUI> {
onPressed: () {
setState(() {
widget.helper.children.add(
- _ConditionHelper(widget.collection.allProperties.first),
+ _ConditionHelper(_properties.first),
);
});
},
@@ -411,8 +443,7 @@ class _GroupUIState extends State<_GroupUI> {
children.add(
_ConditionUI(
helper: widget.helper.children[index] as _ConditionHelper,
- collection: widget.collection,
- link: widget.link,
+ properties: _properties,
removeItem: _removeItem,
index: index,
),
@@ -458,6 +489,11 @@ class _LinkUI extends StatefulWidget {
}
class _LinkUIState extends State<_LinkUI> {
+ //todo: TBA embedded objects
+ List<IProperty> get _properties => widget.helper.link.target.allProperties
+ .where((e) => e.type != IsarType.object)
+ .toList();
+
@override
Widget build(BuildContext context) {
return IsarCard(
@@ -586,8 +622,7 @@ class _LinkUIState extends State<_LinkUI> {
children.add(
_ConditionUI(
helper: widget.helper.children[index] as _ConditionHelper,
- collection: widget.collection,
- link: widget.helper.link,
+ properties: _properties,
removeItem: _removeItem,
index: index,
),
@@ -611,24 +646,19 @@ class _ConditionUI extends StatefulWidget {
required this.helper,
required this.index,
required this.removeItem,
- required this.collection,
- this.link,
+ required this.properties,
});
final _ConditionHelper helper;
final int index;
final void Function(int index) removeItem;
- final ICollection collection;
- final ILink? link;
+ final List<IProperty> properties;
@override
State<_ConditionUI> createState() => _ConditionUIState();
}
class _ConditionUIState extends State<_ConditionUI> {
- late final properties =
- widget.link?.target.allProperties ?? widget.collection.allProperties;
-
@override
Widget build(BuildContext context) {
return Row(
@@ -636,7 +666,7 @@ class _ConditionUIState extends State<_ConditionUI> {
DropdownButtonHideUnderline(
child: DropdownButton<IProperty>(
value: widget.helper.property,
- items: properties
+ items: widget.properties
.map((e) => DropdownMenuItem(value: e, child: Text(e.name)))
.toList(),
onChanged: (property) {
@@ -660,12 +690,19 @@ class _ConditionUIState extends State<_ConditionUI> {
),
const SizedBox(width: 20),
SizedBox(
- width: 190,
+ width: 150,
child: DropdownButtonHideUnderline(
child: DropdownButton<_ConditionType>(
value: widget.helper.type,
items: widget.helper.types
- .map((e) => DropdownMenuItem(value: e, child: Text(e.name)))
+ .map(
+ (e) => DropdownMenuItem(
+ value: e,
+ child: Center(
+ child: Text(e.showName),
+ ),
+ ),
+ )
.toList(),
onChanged: (type) {
if (type != null) {
@@ -804,17 +841,26 @@ class QueryBuilderUIGroupHelper extends QueryBuilderUIHelper {
}
}
+
enum _ConditionType {
- equalTo,
- greaterThan,
- greaterOrEqualThan,
- lessThan,
- lessOrEqualThan,
- startsWith,
- endsWith,
- contains,
- matches,
- isNull;
+ equalTo('=='),
+ greaterThan('>'),
+ greaterOrEqualThan('>='),
+ lessThan('<'),
+ lessOrEqualThan('<='),
+ startsWith('startsWith'),
+ endsWith('endsWith'),
+ contains('contains'),
+ matches('matches'),
+ isNull('isNull'),
+ lengthGreaterThan('ListLength >'),
+ lengthGreaterOrEqualThan('ListLength >='),
+ lengthLessThan('ListLength <'),
+ lengthLessOrEqualThan('ListLength <=');
+
+ const _ConditionType(this.showName);
+
+ final String showName;
}
enum GenericType { int, double, string, bool }
@@ -873,23 +919,43 @@ class _ConditionHelper extends QueryBuilderUIHelper {
case IsarType.bool:
return const [_ConditionType.equalTo, _ConditionType.isNull];
- //todo
case IsarType.object:
+ throw IsarError('unexpected error');
+
case IsarType.objectList:
- case IsarType.dateTime:
- case IsarType.dateTimeList:
- return const [_ConditionType.equalTo, _ConditionType.isNull];
+ return const [
+ _ConditionType.lengthGreaterThan,
+ _ConditionType.lengthGreaterOrEqualThan,
+ _ConditionType.lengthLessThan,
+ _ConditionType.lengthLessOrEqualThan,
+ _ConditionType.isNull,
+ ];
case IsarType.byteList:
case IsarType.intList:
case IsarType.floatList:
case IsarType.longList:
case IsarType.doubleList:
+ case IsarType.dateTimeList:
+ return const [
+ _ConditionType.equalTo,
+ _ConditionType.greaterThan,
+ _ConditionType.greaterOrEqualThan,
+ _ConditionType.lessThan,
+ _ConditionType.lessOrEqualThan,
+ _ConditionType.lengthGreaterThan,
+ _ConditionType.lengthGreaterOrEqualThan,
+ _ConditionType.lengthLessThan,
+ _ConditionType.lengthLessOrEqualThan,
+ _ConditionType.isNull,
+ ];
+
case IsarType.int:
case IsarType.float:
case IsarType.long:
case IsarType.byte:
case IsarType.double:
+ case IsarType.dateTime:
return const [
_ConditionType.equalTo,
_ConditionType.greaterThan,
@@ -900,6 +966,15 @@ class _ConditionHelper extends QueryBuilderUIHelper {
];
case IsarType.string:
+ return const [
+ _ConditionType.equalTo,
+ _ConditionType.contains,
+ _ConditionType.startsWith,
+ _ConditionType.endsWith,
+ _ConditionType.matches,
+ _ConditionType.isNull
+ ];
+
case IsarType.stringList:
return const [
_ConditionType.equalTo,
@@ -907,6 +982,10 @@ class _ConditionHelper extends QueryBuilderUIHelper {
_ConditionType.startsWith,
_ConditionType.endsWith,
_ConditionType.matches,
+ _ConditionType.lengthGreaterThan,
+ _ConditionType.lengthGreaterOrEqualThan,
+ _ConditionType.lengthLessThan,
+ _ConditionType.lengthLessOrEqualThan,
_ConditionType.isNull
];
}
@@ -914,6 +993,9 @@ class _ConditionHelper extends QueryBuilderUIHelper {
CustomTextInputFormatter? get textFormatter {
switch (property.type) {
+ case IsarType.object:
+ throw IsarError('unexpected error');
+
case IsarType.string:
case IsarType.stringList:
case IsarType.boolList:
@@ -922,8 +1004,11 @@ class _ConditionHelper extends QueryBuilderUIHelper {
case IsarType.intList:
case IsarType.longList:
+ case IsarType.dateTimeList:
+ case IsarType.objectList:
case IsarType.int:
case IsarType.long:
+ case IsarType.dateTime:
return CustomTextInputFormatter(IsarType.int);
case IsarType.byteList:
@@ -940,8 +1025,9 @@ class _ConditionHelper extends QueryBuilderUIHelper {
GenericType get generic {
switch (property.type) {
- //todo
case IsarType.object:
+ throw IsarError('unexpected error');
+
case IsarType.dateTime:
case IsarType.objectList:
case IsarType.dateTimeList:


message: query aggregation disabled in inspector

diff:
@@ -554,6 +554,8 @@ class _TableItemState extends State<TableItem> {
text: 'Set value to null',
),
),
+ // todo: TBA we need to filter object's properties.
+ /*
if ( widget.data is PropertyHelper &&
(widget.data as PropertyHelper).linkKey.isEmpty &&
(widget.data as PropertyHelper).index == null &&
@@ -601,6 +603,7 @@ class _TableItemState extends State<TableItem> {
),
),
),
+ */
];
@override
@@ -770,6 +773,8 @@ class _TableItemState extends State<TableItem> {
);
}
+ // todo: query aggregation.
+ /*
Future<void> _aggregate(AggregationOp op) async {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
final property = (widget.data as PropertyHelper).property;
@@ -801,6 +806,7 @@ class _TableItemState extends State<TableItem> {
);
}
}
+ */
Map<String, dynamic>? _objectInfo([PropertyHelper? helper]) {
helper = helper ?? widget.data as PropertyHelper;

original_message: query aggregation disabled in inspector


message: Add CI for tutorials code

diff:
@@ -182,3 +182,18 @@ matrix:
- swift build --verbose
script:
- swift test
+
+ - language: swift
+ name: "Swift - Tutorial - Cocoapods (Xcode 10.2)"
+ os: osx
+ osx_image: xcode10.2
+ xcode_workspace: Tutorial.xcworkspace
+ xcode_scheme: Tutorial
+ xcode_destination: platform=iOS Simulator,OS=12.2,name=iPad Pro (9.7-inch)
+ before_install:
+ - gem update --system
+ - gem install bundler
+ - bundle install
+ - cd swift/Samples/Tutorial
+ - bundle exec pod repo update
+ - bundle exec pod install

repo: square/workflow-swift

original_message: Add CI for tutorials code


message: Update Cocoapods version

old_path: Gemfile

new_path: Gemfile

diff:
source 'https://rubygems.org'
-gem 'cocoapods', '1.7.0.beta.2'
+gem 'cocoapods', '1.8.4'
gem 'cocoapods-generate', '~> 1.0'

diff:
GEM
remote: https://rubygems.org/
specs:
- CFPropertyList (3.0.0)
+ CFPropertyList (3.0.1)
activesupport (4.2.11.1)
i18n (~> 0.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
+ algoliasearch (1.27.1)
+ httpclient (~> 2.8, >= 2.8.3)
+ json (>= 1.5.1)
atomos (0.1.3)
- claide (1.0.2)
- cocoapods (1.7.0.beta.2)
+ claide (1.0.3)
+ cocoapods (1.8.4)
activesupport (>= 4.0.2, < 5)
claide (>= 1.0.2, < 2.0)
- cocoapods-core (= 1.7.0.beta.2)
+ cocoapods-core (= 1.8.4)
cocoapods-deintegrate (>= 1.0.3, < 2.0)
cocoapods-downloader (>= 1.2.2, < 2.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
cocoapods-search (>= 1.0.0, < 2.0)
cocoapods-stats (>= 1.0.0, < 2.0)
- cocoapods-trunk (>= 1.3.1, < 2.0)
+ cocoapods-trunk (>= 1.4.0, < 2.0)
cocoapods-try (>= 1.1.0, < 2.0)
colored2 (~> 3.1)
escape (~> 0.0.4)
- fourflusher (>= 2.2.0, < 3.0)
+ fourflusher (>= 2.3.0, < 3.0)
gh_inspector (~> 1.0)
molinillo (~> 0.6.6)
nap (~> 1.0)
ruby-macho (~> 1.4)
- xcodeproj (>= 1.8.1, < 2.0)
- cocoapods-core (1.7.0.beta.2)
+ xcodeproj (>= 1.11.1, < 2.0)
+ cocoapods-core (1.8.4)
activesupport (>= 4.0.2, < 6)
+ algoliasearch (~> 1.0)
+ concurrent-ruby (~> 1.1)
fuzzy_match (~> 2.0.4)
nap (~> 1.0)
- cocoapods-deintegrate (1.0.3)
- cocoapods-downloader (1.2.2)
+ cocoapods-deintegrate (1.0.4)
+ cocoapods-downloader (1.3.0)
cocoapods-generate (1.4.0)
cocoapods-plugins (1.0.0)
nap
cocoapods-search (1.0.0)
cocoapods-stats (1.1.0)
- cocoapods-trunk (1.3.1)
+ cocoapods-trunk (1.4.1)
nap (>= 0.8, < 2.0)
netrc (~> 0.11)
cocoapods-try (1.1.0)
colored2 (3.1.2)
concurrent-ruby (1.1.5)
escape (0.0.4)
- fourflusher (2.2.0)
+ fourflusher (2.3.1)
fuzzy_match (2.0.4)
gh_inspector (1.1.3)
+ httpclient (2.8.3)
i18n (0.9.5)
concurrent-ruby (~> 1.0)
- minitest (5.11.3)
+ json (2.2.0)
+ minitest (5.13.0)
molinillo (0.6.6)
nanaimo (0.2.6)
nap (1.1.0)
@@ -60,7 +67,7 @@ GEM
thread_safe (0.3.6)
tzinfo (1.2.5)
thread_safe (~> 0.1)
- xcodeproj (1.8.1)
+ xcodeproj (1.13.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
@@ -71,8 +78,8 @@ PLATFORMS
ruby
DEPENDENCIES
- cocoapods (= 1.7.0.beta.2)
+ cocoapods (= 1.8.4)
cocoapods-generate (~> 1.0)
BUNDLED WITH
- 2.0.1
+ 2.0.2

original_message: Update Cocoapods version


message:
Remove Podfile `source`
-  CocoaPods will use CDN by default. Resulting in a blazing fast `pod install`.

old_path: swift/Samples/SampleApp/Podfile

new_path: swift/Samples/SampleApp/Podfile

diff:
-source 'https://github.com/CocoaPods/Specs.git'
project 'SampleApp.xcodeproj'
platform :ios, '9.3'

old_path: swift/Samples/TicTacToe/Podfile

new_path: swift/Samples/TicTacToe/Podfile

diff:
-source 'https://github.com/CocoaPods/Specs.git'
project 'TicTacToe.xcodeproj'
platform :ios, '9.3'

old_path: swift/Samples/Tutorial/Podfile

new_path: swift/Samples/Tutorial/Podfile

diff:
-source 'https://github.com/CocoaPods/Specs.git'
project 'Tutorial.xcodeproj'
platform :ios, '11.0'


message: Add SplitScreenContainer

change_type: ADD

diff:
+Pod::Spec.new do |s|
+ s.name = 'WorkflowSplitScreenContainer'
+ s.version = '0.22.4' #TODO: Change to latest version number.
+ s.summary = 'See the README.'
+ s.homepage = 'https://www.github.com/square/workflow'
+ s.license = 'Apache License, Version 2.0'
+ s.author = 'Square'
+ s.source = { :git => 'https://github.com/square/workflow.git', :tag => "v#{s.version}" }
+
+ # 1.7 is needed for `swift_versions` support
+ s.cocoapods_version = '>= 1.7.0.beta.1'
+
+ s.swift_versions = ['5.0']
+ s.ios.deployment_target = '9.3'
+ s.osx.deployment_target = '10.12'
+
+ s.source_files = 'swift/WorkflowSplitScreenContainer/Sources/**/*.swift'
+
+ s.dependency 'Workflow', "#{s.version}"
+ s.dependency 'WorkflowUI', "#{s.version}"
+
+ s.app_spec 'DemoApp' do |app_spec|
+ app_spec.source_files = 'swift/WorkflowSplitScreenContainer/DemoApp/**/*.swift'
+ end
+
+end

diff:
+/*
+ * Copyright 2019 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import UIKit
+import Workflow
+import WorkflowUI
+
+
+@UIApplicationMain
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+ var window: UIWindow?
+
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+ let window = UIWindow(frame: UIScreen.main.bounds)
+
+ var viewRegistry = ViewRegistry()
+ viewRegistry.registerSplitScreenContainer()
+ viewRegistry.registerBaseScreen()
+
+ let container = ContainerViewController(
+ workflow: DemoWorkflow(),
+ viewRegistry: viewRegistry
+ )
+
+ window.rootViewController = container
+ self.window = window
+ window.makeKeyAndVisible()
+ return true
+ }
+
+}

diff:
+/*
+ * Copyright 2019 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import Workflow
+import WorkflowUI
+
+
+struct BaseScreen: Screen {
+ var title: String
+ var backgroundColor: UIColor
+}
+
+
+extension ViewRegistry {
+
+ public mutating func registerBaseScreen() {
+ self.register(screenViewControllerType: BaseScreenViewController.self)
+ }
+
+}
+
+
+fileprivate final class BaseScreenViewController: ScreenViewController<BaseScreen> {
+
+ private let titleLabel: UILabel
+
+ required init(screen: BaseScreen, viewRegistry: ViewRegistry) {
+ titleLabel = UILabel(frame: .zero)
+ super.init(screen: screen, viewRegistry: viewRegistry)
+
+ update(with: screen)
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ view.addSubview(titleLabel)
+ }
+
+ override func viewDidLayoutSubviews() {
+ super.viewDidLayoutSubviews()
+
+ let height: CGFloat = 44.0
+
+ var (top, bottom) = view.bounds.divided(atDistance: view.bounds.height / 2, from: CGRectEdge.minYEdge)
+
+ top.size.height -= (height / 2.0)
+ bottom.origin.y += (height)
+ bottom.size.height -= (height / 2.0)
+
+ titleLabel.frame = top
+ }
+
+ override func screenDidChange(from previousScreen: BaseScreen) {
+ update(with: screen)
+ }
+
+ private func update(with screen: BaseScreen) {
+ view.backgroundColor = screen.backgroundColor
+ titleLabel.text = screen.title
+ }
+}
+

diff:
+/*
+ * Copyright 2019 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import Workflow
+import WorkflowUI
+import WorkflowSplitScreenContainer
+
+
+// MARK: Input and Output
+
+struct DemoWorkflow: Workflow {
+
+ typealias Output = Never
+
+}
+
+
+// MARK: State and Initialization
+
+extension DemoWorkflow {
+
+ typealias State = Void
+
+ func makeInitialState() -> State {
+ return ()
+ }
+
+ func workflowDidChange(from previousWorkflow: DemoWorkflow, state: inout State) {
+ }
+
+}
+
+
+// MARK: Rendering
+
+extension DemoWorkflow {
+
+ typealias Rendering = SplitScreenContainerScreen
+
+ func render(state: State, context: RenderContext<DemoWorkflow>) -> Rendering {
+
+ return SplitScreenContainerScreen(
+ leftScreen: BaseScreen(title: "Left screen", backgroundColor: .red),
+ rightScreen: BaseScreen(title: "Right screen", backgroundColor: .green),
+ ratio: .third
+ )
+
+ }
+
+}

diff:
+# WorkflowSplitScreenContainer
+
+Container to display two screens side by side.

diff:
+/*
+ * Copyright 2019 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import WorkflowUI
+
+
+/// A `SplitScreenContainerScreen` displays two screens side by side with a separator in between.
+public struct SplitScreenContainerScreen: Screen {
+
+ /// The screen displayed to the left of the separator.
+ public var leftScreen: AnyScreen
+
+ /// The screen displayed to the right of the separator.
+ public var rightScreen: AnyScreen
+
+ /// The ratio of `leftScreen`'s width relative to that of `rightScreen`. Defaults to `.third`.
+ public var ratio: Ratio
+
+ public init<LeftScreenType: Screen, RightScreenType: Screen>(
+ leftScreen: LeftScreenType,
+ rightScreen: RightScreenType,
+ ratio: Ratio = .third
+ ) {
+ self.leftScreen = AnyScreen(leftScreen)
+ self.rightScreen = AnyScreen(rightScreen)
+ self.ratio = ratio
+ }
+
+}
+
+
+extension SplitScreenContainerScreen {
+
+ public struct Ratio {
+
+ public var value: CGFloat
+
+ public init(_ value: CGFloat) {
+ self.value = value
+ }
+
+ public static let quarter = Ratio(1.0 / 4.0)
+ public static let third = Ratio(1.0 / 3.0)
+ public static let half = Ratio(1.0 / 2.0)
+
+ }
+
+}

diff:
+/*
+ * Copyright 2019 Square Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import Workflow
+import WorkflowUI
+
+extension ViewRegistry {
+
+ public mutating func registerSplitScreenContainer() {
+ self.register(screenViewControllerType: SplitScreenContainerViewController.self)
+ }
+
+}
+
+
+final class SplitScreenContainerViewController: ScreenViewController<SplitScreenContainerScreen> {
+
+ var leftContentViewController: ScreenViewController<AnyScreen>? = nil
+ var leftContainerView: UIView
+
+ var separatorView: UIView
+
+ var rightContentViewController: ScreenViewController<AnyScreen>? = nil
+ var rightContainerView: UIView
+
+ required init(screen: SplitScreenContainerScreen, viewRegistry: ViewRegistry) {
+ self.leftContainerView = UIView()
+ self.leftContainerView.translatesAutoresizingMaskIntoConstraints = false
+
+ self.separatorView = UIView()
+ self.separatorView.translatesAutoresizingMaskIntoConstraints = false
+ self.separatorView.backgroundColor = .black
+
+ self.rightContainerView = UIView()
+ self.rightContainerView.translatesAutoresizingMaskIntoConstraints = false
+
+ super.init(screen: screen, viewRegistry: viewRegistry)
+
+ update(with: screen)
+ }
+
+ override func screenDidChange(from previousScreen: SplitScreenContainerScreen) {
+ update(with: screen)
+ }
+
+ private func update(with screen: SplitScreenContainerScreen) {
+
+ func embed(_ screen: AnyScreen, in containerView: UIView) -> ScreenViewController<AnyScreen> {
+ let viewController = viewRegistry.provideView(for: screen)
+ addChild(viewController)
+ containerView.addSubview(viewController.view)
+ viewController.didMove(toParent: self)
+
+ return viewController
+ }
+
+ if let leftContentViewController = leftContentViewController {
+ if leftContentViewController.screenType == type(of: screen.leftScreen) {
+ leftContentViewController.update(screen: screen.leftScreen)
+ } else {
+ leftContentViewController.willMove(toParent: nil)
+ leftContentViewController.view.removeFromSuperview()
+ leftContentViewController.removeFromParent()
+
+ self.leftContentViewController = embed(screen.leftScreen, in: leftContainerView)
+ }
+ } else {
+ self.leftContentViewController = embed(screen.leftScreen, in: leftContainerView)
+ }
+
+ if let rightContentViewController = rightContentViewController {
+ if rightContentViewController.screenType == type(of: screen.rightScreen) {
+ rightContentViewController.update(screen: screen.rightScreen)
+ } else {
+ rightContentViewController.willMove(toParent: nil)
+ rightContentViewController.view.removeFromSuperview()
+ rightContentViewController.removeFromParent()
+
+ self.rightContentViewController = embed(screen.rightScreen, in: rightContainerView)
+ }
+ } else {
+ self.rightContentViewController = embed(screen.rightScreen, in: rightContainerView)
+ }
+
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ view.addSubview(leftContainerView)
+ view.addSubview(separatorView)
+ view.addSubview(rightContainerView)
+
+ if let leftContentViewController = leftContentViewController {
+ leftContainerView.addSubview(leftContentViewController.view)
+ }
+
+ if let rightContentViewController = rightContentViewController {
+ rightContainerView.addSubview(rightContentViewController.view)
+ }
+
+ NSLayoutConstraint.activate([
+ leftContainerView.topAnchor.constraint(equalTo: view.topAnchor),
+ leftContainerView.leftAnchor.constraint(equalTo: view.leftAnchor),
+ leftContainerView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+ leftContainerView.rightAnchor.constraint(equalTo: separatorView.leftAnchor),
+ leftContainerView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: screen.ratio.value),
+
+ separatorView.topAnchor.constraint(equalTo: view.topAnchor),
+ separatorView.widthAnchor.constraint(equalToConstant: 1.0),
+ separatorView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+
+ rightContainerView.topAnchor.constraint(equalTo: view.topAnchor),
+ rightContainerView.leftAnchor.constraint(equalTo: separatorView.rightAnchor),
+ rightContainerView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+ rightContainerView.rightAnchor.constraint(equalTo: view.rightAnchor),
+ ])
+ }
+
+ override func viewDidLayoutSubviews() {
+ super.viewDidLayoutSubviews()
+
+ leftContentViewController?.view.frame = leftContainerView.bounds
+ rightContentViewController?.view.frame = rightContainerView.bounds
+ }
+}

original_message: Add SplitScreenContainer


message: Add Snapshots tests

diff:
@@ -260,3 +260,17 @@ matrix:
- cd swift/Samples/TicTacToe
- bundle exec pod repo update
- bundle exec pod install
+
+ - language: swift
+ name: "Swift - WorkflowSplitScreenContainer Snapshot Tests - Cocoapods (Xcode 10.2)"
+ os: osx
+ osx_image: xcode10.2
+ xcode_workspace: WorkflowSplitScreenContainer.xcworkspace
+ xcode_scheme: WorkflowSplitScreenContainer-Unit-SnapshotTests
+ xcode_destination: platform=iOS Simulator,OS=12.2,name=iPad Pro (9.7-inch)
+ before_install:
+ - yes | gem update --system
+ - gem install bundler
+ - bundle install
+ - WORKFLOW_VERSION=$(git describe --tags $(git rev-list --tags --max-count=1) | sed s/v//) pod gen WorkflowSplitScreenContainer.podspec
+ - cd gen/WorkflowSplitScreenContainer

diff:
@@ -12,15 +12,25 @@ Pod::Spec.new do |s|
s.swift_versions = ['5.0']
s.ios.deployment_target = '9.3'
- s.osx.deployment_target = '10.12'
s.source_files = 'swift/WorkflowSplitScreenContainer/Sources/**/*.swift'
- s.dependency 'Workflow', "#{s.version}"
- s.dependency 'WorkflowUI', "#{s.version}"
+ s.dependency 'Workflow', "#{ENV['WORKFLOW_VERSION'] || s.version}"
+ s.dependency 'WorkflowUI', "#{ENV['WORKFLOW_VERSION'] || s.version}"
s.app_spec 'DemoApp' do |app_spec|
app_spec.source_files = 'swift/WorkflowSplitScreenContainer/DemoApp/**/*.swift'
end
+ s.test_spec 'SnapshotTests' do |test_spec|
+ test_spec.requires_app_host = true
+ test_spec.source_files = 'swift/WorkflowSplitScreenContainer/SnapshotTests/**/*.swift'
+
+ test_spec.framework = 'XCTest'
+
+ test_spec.dependency 'iOSSnapshotTestCase'
+
+ test_spec.scheme = { environment_variables: { 'FB_REFERENCE_IMAGE_DIR' => '$PODS_TARGET_SRCROOT/swift/WorkflowSplitScreenContainer/SnapshotTests/ReferenceImages' } }
+ end
+
end

diff:
@@ -37,7 +37,9 @@ fileprivate final class BaseScreenViewController: ScreenViewController<BaseScree
private let titleLabel: UILabel
required init(screen: BaseScreen, viewRegistry: ViewRegistry) {
- titleLabel = UILabel(frame: .zero)
+ titleLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 20))
+ titleLabel.textAlignment = .center
+
super.init(screen: screen, viewRegistry: viewRegistry)
update(with: screen)
@@ -52,15 +54,7 @@ fileprivate final class BaseScreenViewController: ScreenViewController<BaseScree
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
- let height: CGFloat = 44.0
-
- var (top, bottom) = view.bounds.divided(atDistance: view.bounds.height / 2, from: CGRectEdge.minYEdge)
-
- top.size.height -= (height / 2.0)
- bottom.origin.y += (height)
- bottom.size.height -= (height / 2.0)
-
- titleLabel.frame = top
+ titleLabel.center = view.center
}
override func screenDidChange(from previousScreen: BaseScreen) {

diff:
+import Workflow
+import WorkflowUI
+import XCTest
+import FBSnapshotTestCase
+@testable import WorkflowSplitScreenContainer
+
+
+class SplitScreenContainerScreenSnapshotTests: FBSnapshotTestCase {
+ override func setUp() {
+ super.setUp()
+ recordMode = false
+ }
+
+ func test_splitRatio() {
+ var viewRegistry = ViewRegistry()
+ viewRegistry.register(screenViewControllerType: BaseScreenViewController.self)
+
+ let ratios: [String : SplitScreenContainerScreen.Ratio] = [
+ "third" : .third,
+ "quarter" : .quarter,
+ "half" : .half,
+ "custom" : .init(0.3125)
+ ]
+
+ for (name, ratio) in ratios {
+ let splitScreenContainerScreen = SplitScreenContainerScreen(
+ leftScreen: BaseScreen(title: "Left screen", backgroundColor: .green),
+ rightScreen: BaseScreen(title: "Right screen", backgroundColor: .red),
+ ratio: ratio
+ )
+
+ let viewController = SplitScreenContainerViewController(
+ screen: splitScreenContainerScreen,
+ viewRegistry: viewRegistry
+ )
+ viewController.view.layoutIfNeeded()
+
+ FBSnapshotVerifyView(viewController.view, identifier: name)
+ }
+ }
+}
+
+
+struct BaseScreen: Screen {
+ var title: String
+ var backgroundColor: UIColor
+}
+
+
+fileprivate final class BaseScreenViewController: ScreenViewController<BaseScreen> {
+
+ private let titleLabel: UILabel
+
+ required init(screen: BaseScreen, viewRegistry: ViewRegistry) {
+ titleLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 20))
+ titleLabel.textAlignment = .center
+
+ super.init(screen: screen, viewRegistry: viewRegistry)
+
+ update(with: screen)
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ view.addSubview(titleLabel)
+ }
+
+ override func viewDidLayoutSubviews() {
+ super.viewDidLayoutSubviews()
+
+ titleLabel.center = view.center
+ }
+
+ override func screenDidChange(from previousScreen: BaseScreen) {
+ update(with: screen)
+ }
+
+ private func update(with screen: BaseScreen) {
+ view.backgroundColor = screen.backgroundColor
+ titleLabel.text = screen.title
+ }
+}
+

original_message: Add Snapshots tests


message: Take separatorColor as part of the `SplitScreenContainerScreen` model

old_path: .gitignore

new_path: .gitignore

diff:
@@ -55,3 +55,6 @@ SampleApp.xcworkspace
# Special Mkdocs files
docs/kotlin/api/
docs/swift/api/
+
+# ios-snapshot-test-case Failure Diffs
+FailureDiffs/

diff:
@@ -30,7 +30,12 @@ Pod::Spec.new do |s|
test_spec.dependency 'iOSSnapshotTestCase'
- test_spec.scheme = { environment_variables: { 'FB_REFERENCE_IMAGE_DIR' => '$PODS_TARGET_SRCROOT/swift/WorkflowSplitScreenContainer/SnapshotTests/ReferenceImages' } }
+ test_spec.scheme = {
+ environment_variables: {
+ 'FB_REFERENCE_IMAGE_DIR' => '$PODS_TARGET_SRCROOT/swift/WorkflowSplitScreenContainer/SnapshotTests/ReferenceImages',
+ 'IMAGE_DIFF_DIR' => '$PODS_TARGET_SRCROOT/swift/WorkflowSplitScreenContainer/SnapshotTests/FailureDiffs'
+ }
+ }
end
end

diff:
@@ -28,14 +28,19 @@ public struct SplitScreenContainerScreen: Screen {
/// The ratio of `leftScreen`'s width relative to that of `rightScreen`. Defaults to `.third`.
public var ratio: Ratio
+ /// The color of the `separatorView` displayed between `leftScreen`'s and `rightScreen`'s views.
+ public var separatorColor: UIColor
+
public init<LeftScreenType: Screen, RightScreenType: Screen>(
leftScreen: LeftScreenType,
rightScreen: RightScreenType,
- ratio: Ratio = .third
+ ratio: Ratio = .third,
+ separatorColor: UIColor = .black
) {
self.leftScreen = AnyScreen(leftScreen)
self.rightScreen = AnyScreen(rightScreen)
self.ratio = ratio
+ self.separatorColor = separatorColor
}
}

diff:
@@ -41,7 +41,7 @@ final class SplitScreenContainerViewController: ScreenViewController<SplitScreen
self.separatorView = UIView()
self.separatorView.translatesAutoresizingMaskIntoConstraints = false
- self.separatorView.backgroundColor = .black
+ self.separatorView.backgroundColor = screen.separatorColor
self.rightContainerView = UIView()
self.rightContainerView.translatesAutoresizingMaskIntoConstraints = false
@@ -66,6 +66,8 @@ final class SplitScreenContainerViewController: ScreenViewController<SplitScreen
return viewController
}
+ self.separatorView.backgroundColor = screen.separatorColor
+
if let leftContentViewController = leftContentViewController {
if leftContentViewController.screenType == type(of: screen.leftScreen) {
leftContentViewController.update(screen: screen.leftScreen)


message: Move to Samples

diff:
@@ -262,15 +262,16 @@ matrix:
- bundle exec pod install
- language: swift
- name: "Swift - WorkflowSplitScreenContainer Snapshot Tests - Cocoapods (Xcode 10.2)"
+ name: "Swift - SplitScreenContainer Snapshot Tests - Cocoapods (Xcode 10.2)"
os: osx
osx_image: xcode10.2
- xcode_workspace: WorkflowSplitScreenContainer.xcworkspace
- xcode_scheme: WorkflowSplitScreenContainer-Unit-SnapshotTests
+ xcode_workspace: SplitScreenContainer.xcworkspace
+ xcode_scheme: SplitScreenContainer-Unit-SnapshotTests
xcode_destination: platform=iOS Simulator,OS=12.2,name=iPad Pro (9.7-inch)
before_install:
- yes | gem update --system
- gem install bundler
+ - cd swift/Samples/SplitScreenContainer
- bundle install
- - WORKFLOW_VERSION=$(git describe --tags $(git rev-list --tags --max-count=1) | sed s/v//) pod gen WorkflowSplitScreenContainer.podspec
- - cd gen/WorkflowSplitScreenContainer
+ - pod gen SplitScreenContainer.podspec
+ - cd gen/SplitScreenContainer

change_type: DELETE

diff:
-Pod::Spec.new do |s|
- s.name = 'WorkflowSplitScreenContainer'
- s.version = '0.23.0'
- s.summary = 'See the README.'
- s.homepage = 'https://www.github.com/square/workflow'
- s.license = 'Apache License, Version 2.0'
- s.author = 'Square'
- s.source = { :git => 'https://github.com/square/workflow.git', :tag => "v#{s.version}" }
-
- # 1.7 is needed for `swift_versions` support
- s.cocoapods_version = '>= 1.7.0.beta.1'
-
- s.swift_versions = ['5.0']
- s.ios.deployment_target = '9.3'
-
- s.source_files = 'swift/WorkflowSplitScreenContainer/Sources/**/*.swift'
-
- s.dependency 'Workflow', "#{ENV['WORKFLOW_VERSION'] || s.version}"
- s.dependency 'WorkflowUI', "#{ENV['WORKFLOW_VERSION'] || s.version}"
-
- s.app_spec 'DemoApp' do |app_spec|
- app_spec.source_files = 'swift/WorkflowSplitScreenContainer/DemoApp/**/*.swift'
- end
-
- s.test_spec 'SnapshotTests' do |test_spec|
- test_spec.requires_app_host = true
- test_spec.source_files = 'swift/WorkflowSplitScreenContainer/SnapshotTests/**/*.swift'
-
- test_spec.framework = 'XCTest'
-
- test_spec.dependency 'iOSSnapshotTestCase'
-
- test_spec.scheme = {
- environment_variables: {
- 'FB_REFERENCE_IMAGE_DIR' => '$PODS_TARGET_SRCROOT/swift/WorkflowSplitScreenContainer/SnapshotTests/ReferenceImages',
- 'IMAGE_DIFF_DIR' => '$PODS_TARGET_SRCROOT/swift/WorkflowSplitScreenContainer/SnapshotTests/FailureDiffs'
- }
- }
- end
-
-end

change_type: RENAME

diff:
*/
import Workflow
import WorkflowUI
-import WorkflowSplitScreenContainer
+import SplitScreenContainer
// MARK: Input and Output

diff:
-# WorkflowSplitScreenContainer
+# SplitScreenContainer
Container to display two screens side by side.

diff:
@@ -2,7 +2,7 @@ import Workflow
import WorkflowUI
import XCTest
import FBSnapshotTestCase
-@testable import WorkflowSplitScreenContainer
+@testable import SplitScreenContainer
class SplitScreenContainerScreenSnapshotTests: FBSnapshotTestCase {

diff:
+Pod::Spec.new do |s|
+ s.name = 'SplitScreenContainer'
+ s.version = '1.0.0.LOCAL'
+ s.summary = 'See the README.'
+ s.homepage = 'https://www.github.com/square/workflow'
+ s.license = 'Apache License, Version 2.0'
+ s.author = 'Square'
+ s.source = { git: 'Not Published', tag: "podify/#{s.version}" }
+
+ # 1.7 is needed for `swift_versions` support
+ s.cocoapods_version = '>= 1.7.0.beta.1'
+
+ s.swift_versions = ['5.0']
+ s.ios.deployment_target = '9.3'
+
+ s.source_files = 'Sources/**/*.swift'
+
+ s.dependency 'Workflow'
+ s.dependency 'WorkflowUI'
+
+ s.app_spec 'DemoApp' do |app_spec|
+ app_spec.source_files = 'DemoApp/**/*.swift'
+ end
+
+ s.test_spec 'SnapshotTests' do |test_spec|
+ test_spec.requires_app_host = true
+ test_spec.source_files = 'SnapshotTests/**/*.swift'
+
+ test_spec.framework = 'XCTest'
+
+ test_spec.dependency 'iOSSnapshotTestCase'
+
+ test_spec.scheme = {
+ environment_variables: {
+ 'FB_REFERENCE_IMAGE_DIR' => '$PODS_TARGET_SRCROOT/SnapshotTests/ReferenceImages',
+ 'IMAGE_DIFF_DIR' => '$PODS_TARGET_SRCROOT/SnapshotTests/FailureDiffs'
+ }
+ }
+ end
+
+end

original_message: Move to Samples