From 67aef064c41efefc57cf864029b560fdd0fc1510 Mon Sep 17 00:00:00 2001 From: zin Date: Fri, 20 Sep 2024 00:39:54 +0900 Subject: [PATCH] Refactor/377 repository (#378) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * rename for refactoring wip #377 * add: Entity, DatabaseTupleEntity wip #377 * refactor: `Equals` wip #377 * add: count & receive wip add: DatabaseTupleRepository wip #377 * add: ship wip add: DatabaseTupleRepository wip #377 * add: maxRetryCount = 5 fix: failed test * add: replace wip add: DatabaseTupleRepository wip #377 * add: archive $ unarchive wip add: DatabaseTupleRepository wip #377 * add: waste wip add: DatabaseTupleRepository wip #377 * add: close add: DatabaseTupleRepository wip #377 * wip refactor: ActRepository wip #377 * drop: on ActService wip refactor: ActRepository wip #377 * wip refactor: ActRepository wip #377 * refactor: ActRepository wip refactor: Act wip #377 * add: MemRepository V2 wip refactor: MemRepository wip #377 * drop: MemRepository V1 refactor: MemRepository wip #377 * add: MemItemRepository V2 wip refactor: MemItemRepository wip #377 * add: archiveBy add: MemItemRepository V2 wip refactor: MemItemRepository wip #377 * add: unarchiveBy add: MemItemRepository V2 wip refactor: MemItemRepository wip #377 * refactor: MemItemRepository wip #377 * add: MemNotificationRepositoryV2 wip refactor: MemNotificationRepository wip #377 * coverage wip #377 * refactor: MemNotificationRepository wip #377 * drop: openDatabase wip drop: DatabaseTupleRepositoryV1 wip refactor: DatabaseTupleRepository wip #377 * drop: DatabaseTupleRepositoryV1 wip refactor: DatabaseTupleRepository wip #377 * refactor: HomeWidgetRepository wip #377 * coverage wip #377 * add: FIXME wip #377 * drop: `RepositoryV1` wip #377 * coverage wip #377 * drop: on `DatabaseRepository` wip drop: `RepositoryV2` wip #377 * coverage wip #377 * wip add: NotificationRepositoryV2 wip add: `KeyWithValueRepositoryV2` wip drop: on `KeyWithValueRepository` wip drop: `RepositoryV2` wip #377 * drop: on `NotificationClient` wip add: NotificationRepositoryV2 wip add: `KeyWithValueRepositoryV2` wip drop: on `KeyWithValueRepository` wip drop: `RepositoryV2` wip #377 * replace: `NotificationRepository` wip add: `KeyWithValueRepositoryV2` wip drop: on `KeyWithValueRepository` wip drop: `RepositoryV2` wip #377 * replace: `KeyWithValueRepository` wip drop: `RepositoryV2` wip #377 * replace: on `LogRepository` wip drop: `RepositoryV2` wip #377 * drop: `RepositoryV2` wip #377 * extract: `Copyable` wip #377 * coverage wip #377 * fix: `Analyze Test code` wip #377 * refactor: rename wip #377 * drop: Act v1 wip replace: `Act` wip drop: EntityV1 wip #377 * drop: MemNotification v1 wip drop: EntityV1 * fix: failed test on local StartOfDayの後片付けができてなかった * drop: Mem v1 drop: EntityV1 * rename: v2 * move * fixed --- .../app/src/main/res/xml/act_counter_info.xml | 4 + .../database_tuple_repository_tests.dart | 410 ++++++-- .../scenarios/habit/act_counter_scenario.dart | 16 +- .../habit/act_list_page_scenario.dart | 734 +++++++-------- .../after_act_started_habit_scenario.dart | 2 +- .../habit/mem_list_page_scenario.dart | 6 +- .../habit/repeat_by_day_of_week_scenario.dart | 2 +- .../habit/repeat_by_n_day_habit_scenario.dart | 886 +++++++++--------- .../habit/repeated_habit_scenario.dart | 486 +++++----- integration_test/scenarios/helpers.dart | 1 + .../scenarios/memo/detail_scenarios.dart | 295 +++--- .../scenarios/memo/list_scenarios.dart | 2 +- .../scenarios/notification_scenario.dart | 2 +- .../scenarios/settings/backup_scenario.dart | 2 +- .../scenarios/settings/settings_scenario.dart | 67 +- integration_test/scenarios/task_scenario.dart | 100 +- integration_test/scenarios/todo_scenario.dart | 3 - lib/act_counter/act_counter.dart | 77 +- lib/act_counter/act_counter_client.dart | 31 +- lib/act_counter/act_counter_entity.dart | 45 + lib/act_counter/act_counter_repository.dart | 63 +- lib/act_counter/home_widget.dart | 7 + .../single_selectable_mem_list_item.dart | 4 +- lib/{core => acts}/act.dart | 25 +- lib/acts/act_entity.dart | 69 ++ lib/acts/act_repository.dart | 123 +-- lib/acts/act_service.dart | 58 +- lib/acts/actions.dart | 12 +- lib/acts/acts_summary.dart | 2 +- lib/acts/client.dart | 16 +- lib/acts/line_chart/line_chart_page.dart | 8 +- lib/acts/list/act_list.dart | 15 +- lib/acts/list/add_act_fab.dart | 10 +- lib/acts/list/item/actions.dart | 2 +- lib/acts/list/item/builder.dart | 9 +- lib/acts/list/item/editing_act_dialog.dart | 10 +- lib/acts/list/item/states.dart | 4 +- lib/acts/list/item/total_act_time_item.dart | 6 +- lib/acts/list/item/view.dart | 4 +- lib/acts/list/sub_header.dart | 2 +- lib/acts/states.dart | 10 +- .../created_and_updated_at_texts.dart | 6 +- .../date_and_time_period_view.dart | 4 +- .../date_and_time_text_form_field.dart | 4 +- lib/components/mem/list/actions.dart | 17 +- lib/components/mem/list/states.dart | 68 +- lib/components/mem/list/view.dart | 4 +- lib/components/mem/mem_done_checkbox.dart | 8 +- lib/components/mem/mem_name.dart | 4 +- lib/core/mem_detail.dart | 18 - lib/core/mem_item.dart | 33 - .../definition/database_definition.dart | 12 +- .../repository/condition/conditions.dart | 11 +- .../repository/database_repository.dart | 4 +- .../repository/database_tuple_entity.dart | 36 +- .../repository/database_tuple_repository.dart | 228 ++--- lib/framework/repository/entity.dart | 23 +- .../repository/home_widget_entity.dart | 14 + .../repository/home_widget_repository.dart | 59 ++ lib/framework/repository/key_with_value.dart | 14 +- .../repository/key_with_value_repository.dart | 15 +- lib/framework/repository/order_by.dart | 12 +- lib/framework/repository/repository.dart | 16 +- lib/logger/log.dart | 8 +- lib/logger/log_repository.dart | 3 +- lib/main.dart | 16 +- lib/mems/actions.dart | 14 +- lib/mems/detail/actions.dart | 53 +- lib/mems/detail/body.dart | 6 +- lib/mems/detail/mem_items_view.dart | 10 +- .../after_act_started_notification_view.dart | 11 +- .../notifications/mem_notifications_text.dart | 2 +- .../notifications/mem_notifications_view.dart | 12 +- ...peat_by_day_of_week_notification_view.dart | 20 +- ...mem_repeat_by_n_day_notification_view.dart | 5 +- .../mem_repeated_notification_view.dart | 14 +- lib/mems/detail/page.dart | 8 +- lib/mems/detail/states.dart | 48 +- lib/mems/list/item/actions.dart | 33 +- lib/mems/list/item/subtitle.dart | 8 +- lib/mems/list/item/view.dart | 25 +- lib/{core => mems}/mem.dart | 28 +- lib/mems/mem_client.dart | 41 +- lib/mems/mem_detail.dart | 23 + lib/mems/mem_entity.dart | 82 ++ lib/mems/mem_item.dart | 25 +- lib/mems/mem_item_entity.dart | 58 ++ lib/mems/mem_item_repository.dart | 147 ++- lib/{core => mems}/mem_notification.dart | 122 ++- lib/mems/mem_notification_entity.dart | 76 ++ lib/mems/mem_notification_repository.dart | 109 +++ lib/mems/mem_repository.dart | 82 ++ lib/mems/mem_service.dart | 121 ++- lib/mems/states.dart | 40 +- lib/notifications/mem_notifications.dart | 14 +- .../notification/notification.dart | 27 +- lib/notifications/notification_channels.dart | 12 +- lib/notifications/notification_client.dart | 61 +- .../notification_repository.dart | 59 +- lib/notifications/schedule.dart | 22 +- lib/notifications/schedule_client.dart | 7 +- lib/repositories/mem.dart | 19 - lib/repositories/mem_notification.dart | 23 - .../mem_notification_repository.dart | 131 --- lib/repositories/mem_repository.dart | 123 --- lib/settings/actions.dart | 20 +- lib/settings/client.dart | 24 +- lib/settings/preference.dart | 9 +- pubspec.yaml | 2 +- test/act_counter/act_counter_test.dart | 51 +- ...and_time_period_text_form_fields_test.dart | 8 +- .../date_and_time_text_form_field_test.dart | 2 +- test/core/act_test.dart | 2 +- test/core/date_and_time_period_test.dart | 8 +- test/core/mem_notification_test.dart | 294 +++--- .../database_tuple_entity_test.dart | 67 ++ test/framework/repository/entity_test.dart | 74 ++ test/helpers.dart | 20 +- test/logger/logger_service_test.dart | 130 ++- test/mems/mem_list_body_test.dart | 8 +- 120 files changed, 3547 insertions(+), 3160 deletions(-) create mode 100644 lib/act_counter/act_counter_entity.dart create mode 100644 lib/act_counter/home_widget.dart rename lib/{core => acts}/act.dart (56%) create mode 100644 lib/acts/act_entity.dart delete mode 100644 lib/core/mem_detail.dart delete mode 100644 lib/core/mem_item.dart create mode 100644 lib/framework/repository/home_widget_entity.dart create mode 100644 lib/framework/repository/home_widget_repository.dart rename lib/{core => mems}/mem.dart (73%) create mode 100644 lib/mems/mem_detail.dart create mode 100644 lib/mems/mem_entity.dart create mode 100644 lib/mems/mem_item_entity.dart rename lib/{core => mems}/mem_notification.dart (72%) create mode 100644 lib/mems/mem_notification_entity.dart create mode 100644 lib/mems/mem_notification_repository.dart create mode 100644 lib/mems/mem_repository.dart delete mode 100644 lib/repositories/mem.dart delete mode 100644 lib/repositories/mem_notification.dart delete mode 100644 lib/repositories/mem_notification_repository.dart delete mode 100644 lib/repositories/mem_repository.dart create mode 100644 test/framework/repository/database_tuple_entity_test.dart create mode 100644 test/framework/repository/entity_test.dart diff --git a/android/app/src/main/res/xml/act_counter_info.xml b/android/app/src/main/res/xml/act_counter_info.xml index 98f3d4fbf..7b65bdcc9 100644 --- a/android/app/src/main/res/xml/act_counter_info.xml +++ b/android/app/src/main/res/xml/act_counter_info.xml @@ -1,4 +1,8 @@ + {} - -class TestRepository - extends DatabaseTupleRepository { - TestRepository(super.tableDefinition); - - @override - SavedTestEntity pack(Map tuple) { - // TODO: implement pack - throw UnimplementedError(); - } +class _TestObjectRepository extends DatabaseTupleRepository { + _TestObjectRepository() : super(_defDbTest, _defTableTestObject); @override - Map unpack(TestEntity entity) { - // TODO: implement unpack - throw UnimplementedError(); - } + TestObjectDatabaseTupleEntity pack(Map map) => + TestObjectDatabaseTupleEntity.fromMap(map); } void main() => group( _name, () { - setUpAll(() async { - DatabaseFactory.onTest = true; - for (final testDefDatabase in [ - sampleDefDb, - sampleDefDBAddedTable, - sampleDefDBAddedColumn, - ]) { + late String databasePath; + setUpAll( + () async { + databasePath = + await DatabaseFactory.buildDatabasePath(_defDbTest.name); + await DatabaseFactory // ignore: deprecated_member_use_from_same_package .nativeFactory - .deleteDatabase( - await DatabaseFactory.buildDatabasePath(testDefDatabase.name), - ); - } - - DatabaseTupleRepository.databaseAccessor = - await DatabaseRepository().receive(sampleDefDBAddedColumn); - }); - tearDownAll(() { - DatabaseFactory.onTest = false; - DatabaseTupleRepository.databaseAccessor = null; - }); + .deleteDatabase(databasePath); + }, + ); test( - ": count.", + '#new', () async { - final repository = TestRepository(sampleDefTable); - - final count = await repository.count( - condition: Equals(sampleDefPk.name, 1), - ); + _TestObjectRepository(); - expect(count, equals(0)); + expect( + await DatabaseFactory + // ignore: deprecated_member_use_from_same_package + .nativeFactory + .databaseExists(databasePath), + false); }, ); group( - 'ship', + 'operations', () { - test( - 'group by', - () async { - final repository = TestRepository(sampleDefTable); + final repository = _TestObjectRepository(); + + group( + '#count', + () { + setUpAll( + () async { + final falseSample = TestObjectEntity(false); + final trueSample = TestObjectEntity(true); + final now = DateTime.now(); + + final dbA = await DatabaseFactory.open(_defDbTest); + + await dbA.delete(_defTableTestObject); + await dbA.insert(_defTableTestObject, + falseSample.toMap..addAll({defColCreatedAt.name: now})); + await dbA.insert(_defTableTestObject, + falseSample.toMap..addAll({defColCreatedAt.name: now})); + await dbA.insert(_defTableTestObject, + trueSample.toMap..addAll({defColCreatedAt.name: now})); + }, + ); - final shipped = await repository.ship( - groupBy: GroupBy( - [sampleDefColBoolean], - extraColumns: [Max(sampleDefPk)], - ), + test( + ': all.', + () async { + final count = await repository.count(); + + expect(count, 3); + }, ); + test( + ': condition.', + () async { + final count = await repository.count( + condition: Equals(_defColA, false)); - expect(shipped, hasLength(0)); + expect(count, 2); + }, + ); }, ); - test( - 'order by', - () async { - final repository = TestRepository(sampleDefTable); + group( + '#receive', + () { + setUpAll( + () async { + final dbA = await DatabaseFactory.open(_defDbTest); - final shipped = await repository.ship( - orderBy: [ - Ascending(sampleDefPk), - Descending(sampleDefColInteger), - ], - offset: 1, - limit: 1, + await dbA.delete(_defTableTestObject); + }, ); - expect(shipped, hasLength(0)); + test( + ': received.', + () async { + final now = DateTime.now(); + final entity = TestObjectEntity(false); + + final received = + await repository.receive(entity, createdAt: now); + + expect( + received, + equals(TestObjectDatabaseTupleEntity(entity.a).withMap( + {defPkId.name: 1, defColCreatedAt.name: now}))); + }, + ); + }, + ); + + group( + '#ship', + () { + final falseSample = TestObjectEntity(false); + final trueSample = TestObjectEntity(true); + final now = DateTime.now(); + final later = now.add(const Duration(seconds: 1)); + + late int sampleId1; + late int sampleId2; + late int sampleId3; + setUpAll( + () async { + final dbA = await DatabaseFactory.open(_defDbTest); + + await dbA.delete(_defTableTestObject); + sampleId1 = await dbA.insert(_defTableTestObject, + falseSample.toMap..addAll({defColCreatedAt.name: now})); + sampleId2 = await dbA.insert(_defTableTestObject, + trueSample.toMap..addAll({defColCreatedAt.name: now})); + sampleId3 = await dbA.insert( + _defTableTestObject, + falseSample.toMap + ..addAll({defColCreatedAt.name: later})); + }, + ); + + test( + ': all.', + () async { + final shipped = await repository.ship(); + + expect(shipped, hasLength(3)); + expect(shipped[0].id, equals(sampleId1)); + expect(shipped[0].createdAt, equals(now)); + }, + ); + test( + ': condition.', + () async { + final shipped = await repository.ship( + condition: Equals(_defColA, false), + ); + + expect(shipped, hasLength(2)); + }, + ); + test( + ': groupBy.', + () async { + final shipped = await repository.ship( + groupBy: GroupBy([_defColA], + extraColumns: [Max(defColCreatedAt)])); + + expect(shipped, hasLength(2)); + expect(shipped[0].id, equals(sampleId3)); + expect(shipped[0].createdAt, equals(later)); + }, + ); + test( + ': orderBy.', + () async { + final shipped = + await repository.ship(orderBy: [Descending(defPkId)]); + + expect(shipped, hasLength(3)); + expect(shipped[0].id, sampleId3); + }, + ); + test( + ': offset.', + () async { + final shipped = await repository.ship(offset: 1); + + expect(shipped, hasLength(2)); + expect(shipped[0].id, sampleId2); + }, + ); + test( + ': limit.', + () async { + final shipped = await repository.ship(limit: 2); + + expect(shipped, hasLength(2)); + expect(shipped[1].id, sampleId2); + }, + ); + }, + ); + + group( + '#replace', + () { + late TestObjectDatabaseTupleEntity savedFalseSample; + setUpAll( + () async { + final falseSample = TestObjectEntity(false); + final now = DateTime.now(); + + final dbA = await DatabaseFactory.open(_defDbTest); + + await dbA.delete(_defTableTestObject); + savedFalseSample = + TestObjectDatabaseTupleEntity.fromMap(falseSample.toMap + ..addAll({ + defPkId.name: await dbA.insert( + _defTableTestObject, + falseSample.toMap + ..addAll({defColCreatedAt.name: now})), + defColCreatedAt.name: now + })); + }, + ); + + test(': updated.', () async { + final updatedAt = DateTime.now(); + final updating = TestObjectDatabaseTupleEntity.fromMap( + savedFalseSample.toMap + ..update( + TestObjectEntity.fieldNames[0], (value) => false)); + + final updated = + await repository.replace(updating, updatedAt: updatedAt); + + expect(updated.a, equals(updating.a)); + expect(updated.updatedAt, equals(updatedAt)); + }); + }, + ); + + group( + '#archive', + () { + late TestObjectDatabaseTupleEntity savedFalseSample; + setUpAll( + () async { + final falseSample = TestObjectEntity(false); + final now = DateTime.now(); + + final dbA = await DatabaseFactory.open(_defDbTest); + + await dbA.delete(_defTableTestObject); + savedFalseSample = + TestObjectDatabaseTupleEntity.fromMap(falseSample.toMap + ..addAll({ + defPkId.name: await dbA.insert( + _defTableTestObject, + falseSample.toMap + ..addAll({defColCreatedAt.name: now})), + defColCreatedAt.name: now + })); + }, + ); + + test( + ': archived.', + () async { + final archivedAt = DateTime.now(); + + final archived = await repository.archive(savedFalseSample, + archivedAt: archivedAt); + + expect(archived.archivedAt, equals(archivedAt)); + }, + ); + }, + ); + + group( + '#unarchive', + () { + late TestObjectDatabaseTupleEntity savedArchivedSample; + setUpAll( + () async { + final falseSample = TestObjectEntity(false); + final now = DateTime.now(); + + final dbA = await DatabaseFactory.open(_defDbTest); + + await dbA.delete(_defTableTestObject); + savedArchivedSample = + TestObjectDatabaseTupleEntity.fromMap(falseSample.toMap + ..addAll({ + defPkId.name: await dbA.insert( + _defTableTestObject, + falseSample.toMap + ..addAll({ + defColCreatedAt.name: now, + defColArchivedAt.name: now + })), + defColCreatedAt.name: now, + defColArchivedAt.name: now + })); + }, + ); + + test( + ': unarchived.', + () async { + final updatedAt = DateTime.now(); + + final archived = await repository + .unarchive(savedArchivedSample, updatedAt: updatedAt); + + expect(archived.updatedAt, equals(updatedAt)); + expect(archived.archivedAt, isNull); + }, + ); + }, + ); + + group( + '#waste', + () { + late TestObjectDatabaseTupleEntity savedFalseSample; + setUpAll( + () async { + final falseSample = TestObjectEntity(false); + final now = DateTime.now(); + + final dbA = await DatabaseFactory.open(_defDbTest); + + await dbA.delete(_defTableTestObject); + savedFalseSample = + TestObjectDatabaseTupleEntity.fromMap(falseSample.toMap + ..addAll({ + defPkId.name: await dbA.insert( + _defTableTestObject, + falseSample.toMap + ..addAll({defColCreatedAt.name: now})), + defColCreatedAt.name: now + })); + }, + ); + + test( + ': wasted.', + () async { + final wastedList = await repository.waste( + condition: Equals(defPkId, savedFalseSample.id)); + + expect(wastedList, hasLength(equals(1))); + expect(wastedList[0], equals(savedFalseSample)); + }, + ); }, ); }, diff --git a/integration_test/scenarios/habit/act_counter_scenario.dart b/integration_test/scenarios/habit/act_counter_scenario.dart index 098d9cf7d..23e612594 100644 --- a/integration_test/scenarios/habit/act_counter_scenario.dart +++ b/integration_test/scenarios/habit/act_counter_scenario.dart @@ -2,8 +2,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; - -import 'package:mem/act_counter/act_counter_repository.dart'; +import 'package:mem/act_counter/act_counter_entity.dart'; import 'package:mem/databases/definition.dart'; import 'package:mem/databases/table_definitions/acts.dart'; import 'package:mem/databases/table_definitions/base.dart'; @@ -22,7 +21,7 @@ void main() => group( const insertedMemName2 = '$_name: inserted - mem name - 2'; late int insertedMemId; late int insertedMemId2; - late DateTime actPeriod; + late DateTime actStart; late final DatabaseAccessor dbA; @@ -59,7 +58,7 @@ void main() => group( defTableActs, { defFkActsMemId.name: insertedMemId, - defColActsStart.name: actPeriod = DateTime.now(), + defColActsStart.name: actStart = DateTime.now(), defColActsStartIsAllDay.name: 0, defColCreatedAt.name: zeroDate, }, @@ -73,11 +72,14 @@ void main() => group( var saveWidgetDataCount = 0; var updateWidgetCount = 0; final homeWidgetId = randomInt(); + final actCounter = + ActCounterEntity(insertedMemId, insertedMemName, 1, actStart); + widgetTester.binding.defaultBinaryMessenger .setMockMethodCallHandler( - const MethodChannel(methodChannelName), + MethodChannel(actCounter.methodChannelName), (message) { - expect(message.method, initializeMethodName); + expect(message.method, actCounter.initializeMethodName); expect(message.arguments, null); initializeCount++; @@ -96,7 +98,7 @@ void main() => group( }, 2: { 'id': "lastUpdatedAtSeconds-$insertedMemId", - 'data': actPeriod.millisecondsSinceEpoch.toDouble(), + 'data': actStart.millisecondsSinceEpoch.toDouble(), }, 3: { 'id': "memId-$homeWidgetId", diff --git a/integration_test/scenarios/habit/act_list_page_scenario.dart b/integration_test/scenarios/habit/act_list_page_scenario.dart index 14cb07168..062da87cc 100644 --- a/integration_test/scenarios/habit/act_list_page_scenario.dart +++ b/integration_test/scenarios/habit/act_list_page_scenario.dart @@ -14,439 +14,367 @@ import '../helpers.dart'; const _name = "ActListPage scenario"; -void main() => group( - _name, - () { - const oneMin = Duration(minutes: 1); - const insertedMemName = '$_name: inserted mem - name'; - - final oneMinDate = zeroDate.add(oneMin); - - late final DatabaseAccessor dbA; - late final int insertedMemId; - - setUpAll(() async { - dbA = await openTestDatabase(databaseDefinition); - await clearAllTestDatabaseRows(databaseDefinition); - - insertedMemId = await dbA.insert( - defTableMems, - { - defColMemsName.name: insertedMemName, - defColCreatedAt.name: zeroDate, - }, - ); - }); - setUp(() async { - await dbA.delete(defTableActs); - - await dbA.insert( - defTableActs, - { - defFkActsMemId.name: insertedMemId, - defColActsStart.name: zeroDate, - defColActsStartIsAllDay.name: 0, - defColActsEnd.name: oneMinDate, - defColActsEndIsAllDay.name: 0, - defColCreatedAt.name: zeroDate, - }, - ); - }); - - group( - ": show inserted acts", - () { - testWidgets( - ": by Mem.", - (widgetTester) async { - await runApplication(); - await widgetTester.pumpAndSettle(); - await widgetTester.tap(find.text(insertedMemName)); - await widgetTester.pumpAndSettle(); - await widgetTester.tap(startIconFinder); - await widgetTester.pumpAndSettle(); - await widgetTester.tap(find.byIcon(Icons.numbers)); - await widgetTester.pumpAndSettle(); - - expect(widgetTester.textAt(0).data, equals(insertedMemName)); - expect(widgetTester.textAt(1).data, equals(dateText(zeroDate))); - expect(widgetTester.textAt(2).data, equals("1")); - expect(widgetTester.textAt(3).data, equals(oneMin.format())); - expect(widgetTester.textAt(4).data, equals(timeText(zeroDate))); - expect(widgetTester.textAt(5).data, equals("~")); - expect( - widgetTester.textAt(6).data, equals(timeText(oneMinDate))); - - expect(startIconFinder, findsOneWidget); - expect(stopIconFinder, findsNothing); - }, - ); - - group( - ": All", - () { - testWidgets( - ': time.', - (widgetTester) async { - await runApplication(); - await widgetTester.pumpAndSettle(); - - await widgetTester.tap(find.byIcon(Icons.playlist_play)); - await widgetTester.pumpAndSettle(); - - expect(startIconFinder, findsNothing); - expect(stopIconFinder, findsNothing); - expect(widgetTester.textAt(0).data, equals("All")); - expect(widgetTester.textAt(1).data, - equals(dateText(zeroDate))); - expect(widgetTester.textAt(2).data, equals("1")); - expect( - widgetTester.textAt(3).data, equals(oneMin.format())); - expect( - widgetTester.textAt(4).data, equals(oneMin.format())); - expect(widgetTester.textAt(5).data, equals("1")); - expect( - widgetTester.textAt(6).data, equals(insertedMemName)); - }, - ); - - testWidgets( - ': count.', - (widgetTester) async { - await runApplication(); - await widgetTester.pumpAndSettle(); - await widgetTester.tap(find.byIcon(Icons.playlist_play)); - await widgetTester.pumpAndSettle(); - - await widgetTester.tap(find.byIcon(Icons.numbers)); - await widgetTester.pumpAndSettle(); - - expect(startIconFinder, findsNothing); - expect(stopIconFinder, findsNothing); - expect(find.byIcon(Icons.access_time), findsOneWidget); - expect(widgetTester.textAt(0).data, equals("All")); - expect(widgetTester.textAt(1).data, - equals(dateText(zeroDate))); - expect(widgetTester.textAt(2).data, equals("1")); - expect( - widgetTester.textAt(3).data, equals(oneMin.format())); - expect(widgetTester.textAt(4).data, - equals(timeText(zeroDate))); - expect(widgetTester.textAt(5).data, equals("~")); - expect(widgetTester.textAt(6).data, - equals(timeText(oneMinDate))); - expect( - widgetTester.textAt(7).data, equals(insertedMemName)); - }, - ); - - testWidgets( - ': month view.', - (widgetTester) async { - await runApplication(); - await widgetTester.pumpAndSettle(); - await widgetTester.tap(find.byIcon(Icons.playlist_play)); - await widgetTester.pumpAndSettle(); - - await widgetTester - .tap(find.byIcon(Icons.calendar_view_month)); - await widgetTester.pumpAndSettle(); - - expect(widgetTester.textAt(0).data, equals("All")); - expect(widgetTester.textAt(1).data, equals("January 0")); - expect(widgetTester.textAt(2).data, equals("1")); - expect( - widgetTester.textAt(3).data, equals(oneMin.format())); - expect( - widgetTester.textAt(4).data, equals(oneMin.format())); - expect(widgetTester.textAt(5).data, equals("1")); - expect( - widgetTester.textAt(6).data, equals(insertedMemName)); - }, - ); - }, - ); - - group( - ": many acts", - () { - const days = 30; - const numberOfActsByDate = 3; - - setUp(() async { - await dbA.delete(defTableActs); - - for (var j = 0; j < days; j++) { - for (var i = 0; i < numberOfActsByDate; i++) { - final start = zeroDate - .add(Duration(days: j)) - .add(Duration(minutes: i)); - final end = start.add(oneMin); - - await dbA.insert( - defTableActs, - { - defFkActsMemId.name: insertedMemId, - defColActsStart.name: start, - defColActsStartIsAllDay.name: 0, - defColActsEnd.name: end, - defColActsEndIsAllDay.name: 0, - defColCreatedAt.name: zeroDate, - }, - ); - } - } - }); - - testWidgets( - ': infinite scroll.', - (widgetTester) async { - await runApplication(); - await widgetTester.pumpAndSettle(); - await widgetTester.tap(find.byIcon(Icons.playlist_play)); - await widgetTester.pumpAndSettle(); - - final noOnInitialItemFinder = find.text("1/15/0"); - final earliestItemFinder = find.text("1/1/0"); - - expect(noOnInitialItemFinder, findsNothing); - expect(earliestItemFinder, findsNothing); - - final listFinder = find.byType(Scrollable); - - await widgetTester.scrollUntilVisible( - noOnInitialItemFinder, - 500.0, - scrollable: listFinder, - ); - - expect(noOnInitialItemFinder, findsOneWidget); - expect(earliestItemFinder, findsNothing); - - await widgetTester.scrollUntilVisible( - earliestItemFinder, - 500.0, - scrollable: listFinder, - ); - - expect(noOnInitialItemFinder, findsNothing); - expect(earliestItemFinder, findsOneWidget); - }, - ); - }, - ); - }, - ); +void main() => group(_name, () { + const oneMin = Duration(minutes: 1); + const insertedMemName = '$_name: inserted mem - name'; - group(": by Mem", () { - Future showMemListPage(WidgetTester widgetTester) async { - await runApplication(); - await widgetTester.pumpAndSettle(); - } + final oneMinDate = zeroDate.add(oneMin); - Future showActListPage(WidgetTester widgetTester) async { - await showMemListPage(widgetTester); - - await widgetTester.tap(find.text(insertedMemName)); - await widgetTester.pumpAndSettle(); + late final DatabaseAccessor dbA; + late final int insertedMemId; - await widgetTester.tap(startIconFinder); - await widgetTester.pumpAndSettle(); - - await widgetTester.tap(find.byIcon(Icons.numbers)); - await widgetTester.pumpAndSettle(); - } + setUpAll(() async { + dbA = await openTestDatabase(databaseDefinition); + await clearAllTestDatabaseRows(databaseDefinition); - testWidgets( - ': start & finish act.', - (widgetTester) async { - widgetTester.ignoreMockMethodCallHandler( - MethodChannelMock.flutterLocalNotifications); + insertedMemId = await dbA.insert(defTableMems, { + defColMemsName.name: insertedMemName, + defColCreatedAt.name: zeroDate + }); + }); + setUp(() async { + await dbA.delete(defTableActs); + + await dbA.insert(defTableActs, { + defFkActsMemId.name: insertedMemId, + defColActsStart.name: zeroDate, + defColActsStartIsAllDay.name: 0, + defColActsEnd.name: oneMinDate, + defColActsEndIsAllDay.name: 0, + defColCreatedAt.name: zeroDate + }); + }); + + group(": show inserted acts", () { + testWidgets(": by Mem.", (widgetTester) async { + await runApplication(); + await widgetTester.pumpAndSettle(); + await widgetTester.tap(find.text(insertedMemName)); + await widgetTester.pumpAndSettle(); + await widgetTester.tap(startIconFinder); + await widgetTester.pumpAndSettle(); + await widgetTester.tap(find.byIcon(Icons.numbers)); + await widgetTester.pumpAndSettle(); + + expect(widgetTester.textAt(0).data, equals(insertedMemName)); + expect(widgetTester.textAt(1).data, equals(dateText(zeroDate))); + expect(widgetTester.textAt(2).data, equals("1")); + expect(widgetTester.textAt(3).data, equals(oneMin.format())); + expect(widgetTester.textAt(4).data, equals(timeText(zeroDate))); + expect(widgetTester.textAt(5).data, equals("~")); + expect(widgetTester.textAt(6).data, equals(timeText(oneMinDate))); + + expect(startIconFinder, findsOneWidget); + expect(stopIconFinder, findsNothing); + }); - await showActListPage(widgetTester); + group( + ": All", + () { + testWidgets(': time.', (widgetTester) async { + await runApplication(); + await widgetTester.pumpAndSettle(); - expect(stopIconFinder, findsNothing); - final startTime = DateTime.now(); - await widgetTester.tap(startIconFinder); - await Future.delayed(defaultTransitionDuration); - await widgetTester.pumpAndSettle(waitSideEffectDuration); + await widgetTester.tap(find.byIcon(Icons.playlist_play)); + await widgetTester.pumpAndSettle(); expect(startIconFinder, findsNothing); - expect(widgetTester.textAt(0).data, equals(insertedMemName)); - expect(widgetTester.textAt(1).data, equals(dateText(startTime))); - expect(widgetTester.textAt(2).data, equals("1")); - expect( - widgetTester.textAt(3).data, equals(Duration.zero.format())); - expect(widgetTester.textAt(4).data, equals(timeText(startTime))); - expect(widgetTester.textAt(5).data, equals("~")); - expect(widgetTester.textAt(6).data, equals(dateText(zeroDate))); - expect(widgetTester.textAt(7).data, equals("1")); - expect(widgetTester.textAt(8).data, equals(oneMin.format())); - expect(widgetTester.textAt(9).data, equals(timeText(zeroDate))); - expect(widgetTester.textAt(10).data, equals("~")); - expect( - widgetTester.textAt(11).data, equals(timeText(oneMinDate))); - - final stopTime = DateTime.now(); - await widgetTester.tap(stopIconFinder); - await Future.delayed(defaultTransitionDuration); - await widgetTester.pumpAndSettle(waitSideEffectDuration); - - expect(widgetTester.textAt(0).data, equals(insertedMemName)); - expect(widgetTester.textAt(1).data, equals(dateText(startTime))); - expect(widgetTester.textAt(2).data, equals("1")); - expect(widgetTester.textAt(3).data, isNotNull); - expect(widgetTester.textAt(4).data, equals(timeText(startTime))); - expect(widgetTester.textAt(5).data, equals("~")); - expect(widgetTester.textAt(6).data, equals(timeText(stopTime))); - expect(widgetTester.textAt(7).data, equals(dateText(zeroDate))); - expect(widgetTester.textAt(8).data, equals("1")); - expect(widgetTester.textAt(9).data, equals(oneMin.format())); - expect(widgetTester.textAt(10).data, equals(timeText(zeroDate))); - expect(widgetTester.textAt(11).data, equals("~")); - expect( - widgetTester.textAt(12).data, equals(timeText(oneMinDate))); expect(stopIconFinder, findsNothing); - - final startTime2 = DateTime.now(); - await widgetTester.tap(startIconFinder); - await Future.delayed(defaultTransitionDuration); - await widgetTester.pumpAndSettle(waitSideEffectDuration); - - expect(widgetTester.textAt(0).data, equals(insertedMemName)); - expect(widgetTester.textAt(1).data, equals(dateText(startTime2))); - expect(widgetTester.textAt(2).data, equals("2")); - expect(widgetTester.textAt(3).data, isNotNull); - expect(widgetTester.textAt(4).data, equals(timeText(startTime2))); - expect(widgetTester.textAt(5).data, equals("~")); - expect(widgetTester.textAt(6).data, equals(timeText(startTime))); - expect(widgetTester.textAt(7).data, equals("~")); - expect(widgetTester.textAt(8).data, equals(timeText(stopTime))); - expect(widgetTester.textAt(9).data, equals(dateText(zeroDate))); - expect(widgetTester.textAt(10).data, equals("1")); - expect(widgetTester.textAt(11).data, equals(oneMin.format())); - expect(widgetTester.textAt(12).data, equals(timeText(zeroDate))); - expect(widgetTester.textAt(13).data, equals("~")); - expect( - widgetTester.textAt(14).data, equals(timeText(oneMinDate))); - }, - ); - - group(': Edit act', () { - setUp(() async { - await dbA.insert( - defTableActs, - { - defFkActsMemId.name: insertedMemId, - defColActsStart.name: zeroDate, - defColActsStartIsAllDay.name: 0, - defColCreatedAt.name: zeroDate, - }, - ); + expect(widgetTester.textAt(0).data, equals("All")); + expect(widgetTester.textAt(1).data, equals(dateText(zeroDate))); + expect(widgetTester.textAt(2).data, equals("1")); + expect(widgetTester.textAt(3).data, equals(oneMin.format())); + expect(widgetTester.textAt(4).data, equals(oneMin.format())); + expect(widgetTester.textAt(5).data, equals("1")); + expect(widgetTester.textAt(6).data, equals(insertedMemName)); }); - testWidgets( - ': save.', - (widgetTester) async { - widgetTester.ignoreMockMethodCallHandler( - MethodChannelMock.flutterLocalNotifications); - - await showActListPage(widgetTester); - - await widgetTester - .longPress(find.text(timeText(zeroDate)).at(0)); - await widgetTester.pumpAndSettle(); - - await widgetTester.tap(find.byType(Switch).at(1)); - await widgetTester.pump(); - - final pickedDate = DateTime.now(); - await widgetTester.tap(find.text('OK')); - await widgetTester.pump(); - - await widgetTester.tap(find.byIcon(Icons.save_alt)); - await widgetTester.pumpAndSettle(waitSideEffectDuration); - - expect(widgetTester.textAt(0).data, equals(insertedMemName)); - expect(widgetTester.textAt(1).data, equals(dateText(zeroDate))); - expect(widgetTester.textAt(2).data, equals("2")); - expect(widgetTester.textAt(3).data, isNotNull); - expect(widgetTester.textAt(4).data, equals(timeText(zeroDate))); - expect(widgetTester.textAt(5).data, equals("~")); - expect( - widgetTester.textAt(6).data, equals(timeText(pickedDate))); - expect(widgetTester.textAt(7).data, equals(timeText(zeroDate))); - expect(widgetTester.textAt(8).data, equals("~")); - expect( - widgetTester.textAt(9).data, equals(timeText(oneMinDate))); - - await widgetTester - .longPress(find.text(timeText(zeroDate)).at(1)); - await widgetTester.pumpAndSettle(defaultTransitionDuration); - - await widgetTester.tap(find.byIcon(Icons.clear).at(1)); - await widgetTester.pumpAndSettle(); - - await widgetTester.tap(find.byIcon(Icons.save_alt)); - await widgetTester.pumpAndSettle(waitSideEffectDuration); - - expect(widgetTester.textAt(0).data, equals(insertedMemName)); - expect(widgetTester.textAt(1).data, equals(dateText(zeroDate))); - expect(widgetTester.textAt(2).data, equals("2")); - expect(widgetTester.textAt(3).data, isNotNull); - expect(widgetTester.textAt(4).data, equals(timeText(zeroDate))); - expect(widgetTester.textAt(5).data, equals("~")); - expect(widgetTester.textAt(6).data, equals(timeText(zeroDate))); - expect(widgetTester.textAt(7).data, equals("~")); - expect( - widgetTester.textAt(8).data, equals(timeText(pickedDate))); - }, - ); - - testWidgets(': delete.', (widgetTester) async { - widgetTester.ignoreMockMethodCallHandler( - MethodChannelMock.flutterLocalNotifications); - - await showActListPage(widgetTester); - - await widgetTester.longPress(find.text(timeText(zeroDate)).at(0)); + testWidgets(': count.', (widgetTester) async { + await runApplication(); + await widgetTester.pumpAndSettle(); + await widgetTester.tap(find.byIcon(Icons.playlist_play)); await widgetTester.pumpAndSettle(); - await widgetTester.tap(find.byIcon(Icons.delete)); + await widgetTester.tap(find.byIcon(Icons.numbers)); await widgetTester.pumpAndSettle(); - expect(widgetTester.textAt(0).data, equals(insertedMemName)); + expect(startIconFinder, findsNothing); + expect(stopIconFinder, findsNothing); + expect(find.byIcon(Icons.access_time), findsOneWidget); + expect(widgetTester.textAt(0).data, equals("All")); expect(widgetTester.textAt(1).data, equals(dateText(zeroDate))); expect(widgetTester.textAt(2).data, equals("1")); expect(widgetTester.textAt(3).data, equals(oneMin.format())); expect(widgetTester.textAt(4).data, equals(timeText(zeroDate))); expect(widgetTester.textAt(5).data, equals("~")); expect(widgetTester.textAt(6).data, equals(timeText(oneMinDate))); + expect(widgetTester.textAt(7).data, equals(insertedMemName)); + }); + + testWidgets(': month view.', (widgetTester) async { + await runApplication(); + await widgetTester.pumpAndSettle(); + await widgetTester.tap(find.byIcon(Icons.playlist_play)); + await widgetTester.pumpAndSettle(); + + await widgetTester.tap(find.byIcon(Icons.calendar_view_month)); + await widgetTester.pumpAndSettle(); + + expect(widgetTester.textAt(0).data, equals("All")); + expect(widgetTester.textAt(1).data, equals("January 0")); + expect(widgetTester.textAt(2).data, equals("1")); + expect(widgetTester.textAt(3).data, equals(oneMin.format())); + expect(widgetTester.textAt(4).data, equals(oneMin.format())); + expect(widgetTester.textAt(5).data, equals("1")); + expect(widgetTester.textAt(6).data, equals(insertedMemName)); }); + }, + ); + + group(": many acts", () { + const days = 30; + const numberOfActsByDate = 3; + + setUp(() async { + await dbA.delete(defTableActs); + + for (var j = 0; j < days; j++) { + for (var i = 0; i < numberOfActsByDate; i++) { + final start = + zeroDate.add(Duration(days: j)).add(Duration(minutes: i)); + final end = start.add(oneMin); + + await dbA.insert(defTableActs, { + defFkActsMemId.name: insertedMemId, + defColActsStart.name: start, + defColActsStartIsAllDay.name: 0, + defColActsEnd.name: end, + defColActsEndIsAllDay.name: 0, + defColCreatedAt.name: zeroDate + }); + } + } + }); + + testWidgets(': infinite scroll.', (widgetTester) async { + await runApplication(); + await widgetTester.pumpAndSettle(); + await widgetTester.tap(find.byIcon(Icons.playlist_play)); + await widgetTester.pumpAndSettle(); + + final noOnInitialItemFinder = find.text("1/15/0"); + final earliestItemFinder = find.text("1/1/0"); + + expect(noOnInitialItemFinder, findsNothing); + expect(earliestItemFinder, findsNothing); + + final listFinder = find.byType(Scrollable); + + await widgetTester.scrollUntilVisible(noOnInitialItemFinder, 500.0, + scrollable: listFinder); + + expect(noOnInitialItemFinder, findsOneWidget); + expect(earliestItemFinder, findsNothing); + + await widgetTester.scrollUntilVisible(earliestItemFinder, 500.0, + scrollable: listFinder); + + expect(noOnInitialItemFinder, findsNothing); + expect(earliestItemFinder, findsOneWidget); }); }); + }); + + group(": by Mem", () { + Future showMemListPage(WidgetTester widgetTester) async { + await runApplication(); + await widgetTester.pumpAndSettle(); + } + + Future showActListPage(WidgetTester widgetTester) async { + await showMemListPage(widgetTester); + + await widgetTester.tap(find.text(insertedMemName)); + await widgetTester.pumpAndSettle(); + + await widgetTester.tap(startIconFinder); + await widgetTester.pumpAndSettle(); + + await widgetTester.tap(find.byIcon(Icons.numbers)); + await widgetTester.pumpAndSettle(); + } + + testWidgets(': start & finish act.', + // 時間に関するテストなのでリトライ可能とする + retry: maxRetryCount, (widgetTester) async { + widgetTester + .ignoreMockMethodCallHandler(MethodChannelMock.permissionHandler); + widgetTester.ignoreMockMethodCallHandler( + MethodChannelMock.flutterLocalNotifications); + + await showActListPage(widgetTester); + + expect(stopIconFinder, findsNothing); + final startTime = DateTime.now(); + await widgetTester.tap(startIconFinder); + await Future.delayed(defaultTransitionDuration); + await widgetTester.pumpAndSettle(waitSideEffectDuration); + + expect(startIconFinder, findsNothing); + expect(widgetTester.textAt(0).data, equals(insertedMemName)); + expect(widgetTester.textAt(1).data, equals(dateText(startTime))); + expect(widgetTester.textAt(2).data, equals("1")); + expect(widgetTester.textAt(3).data, equals(Duration.zero.format())); + expect(widgetTester.textAt(4).data, equals(timeText(startTime))); + expect(widgetTester.textAt(5).data, equals("~")); + expect(widgetTester.textAt(6).data, equals(dateText(zeroDate))); + expect(widgetTester.textAt(7).data, equals("1")); + expect(widgetTester.textAt(8).data, equals(oneMin.format())); + expect(widgetTester.textAt(9).data, equals(timeText(zeroDate))); + expect(widgetTester.textAt(10).data, equals("~")); + expect(widgetTester.textAt(11).data, equals(timeText(oneMinDate))); + + final stopTime = DateTime.now(); + await widgetTester.tap(stopIconFinder); + await Future.delayed(defaultTransitionDuration); + await widgetTester.pumpAndSettle(waitSideEffectDuration); + + expect(widgetTester.textAt(0).data, equals(insertedMemName)); + expect(widgetTester.textAt(1).data, equals(dateText(startTime))); + expect(widgetTester.textAt(2).data, equals("1")); + expect(widgetTester.textAt(3).data, isNotNull); + expect(widgetTester.textAt(4).data, equals(timeText(startTime))); + expect(widgetTester.textAt(5).data, equals("~")); + expect(widgetTester.textAt(6).data, equals(timeText(stopTime))); + expect(widgetTester.textAt(7).data, equals(dateText(zeroDate))); + expect(widgetTester.textAt(8).data, equals("1")); + expect(widgetTester.textAt(9).data, equals(oneMin.format())); + expect(widgetTester.textAt(10).data, equals(timeText(zeroDate))); + expect(widgetTester.textAt(11).data, equals("~")); + expect(widgetTester.textAt(12).data, equals(timeText(oneMinDate))); + expect(stopIconFinder, findsNothing); + + final startTime2 = DateTime.now(); + await widgetTester.tap(startIconFinder); + await Future.delayed(defaultTransitionDuration); + await widgetTester.pumpAndSettle(waitSideEffectDuration); + + expect(widgetTester.textAt(0).data, equals(insertedMemName)); + expect(widgetTester.textAt(1).data, equals(dateText(startTime2))); + expect(widgetTester.textAt(2).data, equals("2")); + expect(widgetTester.textAt(3).data, isNotNull); + expect(widgetTester.textAt(4).data, equals(timeText(startTime2))); + expect(widgetTester.textAt(5).data, equals("~")); + expect(widgetTester.textAt(6).data, equals(timeText(startTime))); + expect(widgetTester.textAt(7).data, equals("~")); + expect(widgetTester.textAt(8).data, equals(timeText(stopTime))); + expect(widgetTester.textAt(9).data, equals(dateText(zeroDate))); + expect(widgetTester.textAt(10).data, equals("1")); + expect(widgetTester.textAt(11).data, equals(oneMin.format())); + expect(widgetTester.textAt(12).data, equals(timeText(zeroDate))); + expect(widgetTester.textAt(13).data, equals("~")); + expect(widgetTester.textAt(14).data, equals(timeText(oneMinDate))); + }); - testWidgets( - ": show MemDetailPage.", - (widgetTester) async { + group(': Edit act', () { + setUp(() async { + await dbA.insert(defTableActs, { + defFkActsMemId.name: insertedMemId, + defColActsStart.name: zeroDate, + defColActsStartIsAllDay.name: 0, + defColCreatedAt.name: zeroDate + }); + }); + + testWidgets(': save.', (widgetTester) async { widgetTester.ignoreMockMethodCallHandler( MethodChannelMock.flutterLocalNotifications); - await runApplication(); + await showActListPage(widgetTester); + + await widgetTester.longPress(find.text(timeText(zeroDate)).at(0)); await widgetTester.pumpAndSettle(); - await widgetTester.tap(find.byIcon(Icons.playlist_play)); + + await widgetTester.tap(find.byType(Switch).at(1)); + await widgetTester.pump(); + + final pickedDate = DateTime.now(); + await widgetTester.tap(find.text('OK')); + await widgetTester.pump(); + + await widgetTester.tap(find.byIcon(Icons.save_alt)); + await widgetTester.pumpAndSettle(waitSideEffectDuration); + + expect(widgetTester.textAt(0).data, equals(insertedMemName)); + expect(widgetTester.textAt(1).data, equals(dateText(zeroDate))); + expect(widgetTester.textAt(2).data, equals("2")); + expect(widgetTester.textAt(3).data, isNotNull); + expect(widgetTester.textAt(4).data, equals(timeText(zeroDate))); + expect(widgetTester.textAt(5).data, equals("~")); + expect(widgetTester.textAt(6).data, equals(timeText(pickedDate))); + expect(widgetTester.textAt(7).data, equals(timeText(zeroDate))); + expect(widgetTester.textAt(8).data, equals("~")); + expect(widgetTester.textAt(9).data, equals(timeText(oneMinDate))); + + await widgetTester.longPress(find.text(timeText(zeroDate)).at(1)); + await widgetTester.pumpAndSettle(defaultTransitionDuration); + + await widgetTester.tap(find.byIcon(Icons.clear).at(1)); await widgetTester.pumpAndSettle(); - await widgetTester.tap(find.byIcon(Icons.arrow_forward)); + await widgetTester.tap(find.byIcon(Icons.save_alt)); + await widgetTester.pumpAndSettle(waitSideEffectDuration); + + expect(widgetTester.textAt(0).data, equals(insertedMemName)); + expect(widgetTester.textAt(1).data, equals(dateText(zeroDate))); + expect(widgetTester.textAt(2).data, equals("2")); + expect(widgetTester.textAt(3).data, isNotNull); + expect(widgetTester.textAt(4).data, equals(timeText(zeroDate))); + expect(widgetTester.textAt(5).data, equals("~")); + expect(widgetTester.textAt(6).data, equals(timeText(zeroDate))); + expect(widgetTester.textAt(7).data, equals("~")); + expect(widgetTester.textAt(8).data, equals(timeText(pickedDate))); + }); + + testWidgets(': delete.', (widgetTester) async { + widgetTester.ignoreMockMethodCallHandler( + MethodChannelMock.flutterLocalNotifications); + + await showActListPage(widgetTester); + + await widgetTester.longPress(find.text(timeText(zeroDate)).at(0)); await widgetTester.pumpAndSettle(); - expect( - widgetTester - .widget(find.byKey(keyMemName)) - .initialValue, - insertedMemName, - ); - }, - ); - }, - ); + await widgetTester.tap(find.byIcon(Icons.delete)); + await widgetTester.pumpAndSettle(); + + expect(widgetTester.textAt(0).data, equals(insertedMemName)); + expect(widgetTester.textAt(1).data, equals(dateText(zeroDate))); + expect(widgetTester.textAt(2).data, equals("1")); + expect(widgetTester.textAt(3).data, equals(oneMin.format())); + expect(widgetTester.textAt(4).data, equals(timeText(zeroDate))); + expect(widgetTester.textAt(5).data, equals("~")); + expect(widgetTester.textAt(6).data, equals(timeText(oneMinDate))); + }); + }); + }); + + testWidgets(": show MemDetailPage.", (widgetTester) async { + widgetTester.ignoreMockMethodCallHandler( + MethodChannelMock.flutterLocalNotifications); + + await runApplication(); + await widgetTester.pumpAndSettle(); + await widgetTester.tap(find.byIcon(Icons.playlist_play)); + await widgetTester.pumpAndSettle(); + + await widgetTester.tap(find.byIcon(Icons.arrow_forward)); + await widgetTester.pumpAndSettle(); + + expect( + widgetTester + .widget(find.byKey(keyMemName)) + .initialValue, + insertedMemName); + }); + }); diff --git a/integration_test/scenarios/habit/after_act_started_habit_scenario.dart b/integration_test/scenarios/habit/after_act_started_habit_scenario.dart index 93fd76445..ad5502b58 100644 --- a/integration_test/scenarios/habit/after_act_started_habit_scenario.dart +++ b/integration_test/scenarios/habit/after_act_started_habit_scenario.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mem/components/mem/mem_name.dart'; import 'package:mem/components/time_text_form_field.dart'; -import 'package:mem/core/mem_notification.dart'; +import 'package:mem/mems/mem_notification.dart'; import 'package:mem/databases/definition.dart'; import 'package:mem/databases/table_definitions/base.dart'; import 'package:mem/databases/table_definitions/mem_notifications.dart'; diff --git a/integration_test/scenarios/habit/mem_list_page_scenario.dart b/integration_test/scenarios/habit/mem_list_page_scenario.dart index 70db5680e..bcde6c7ef 100644 --- a/integration_test/scenarios/habit/mem_list_page_scenario.dart +++ b/integration_test/scenarios/habit/mem_list_page_scenario.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mem/components/timer.dart'; -import 'package:mem/core/mem_notification.dart'; +import 'package:mem/mems/mem_notification.dart'; import 'package:mem/databases/definition.dart'; import 'package:mem/databases/table_definitions/acts.dart'; import 'package:mem/databases/table_definitions/base.dart'; @@ -175,8 +175,8 @@ void main() => group( () { testWidgets( 'start.', - // 時間に関するテストなので3回までリトライ可能とする - retry: 3, + // 時間に関するテストなのでリトライ可能とする + retry: maxRetryCount, (widgetTester) async { widgetTester.ignoreMockMethodCallHandler( MethodChannelMock.flutterLocalNotifications); diff --git a/integration_test/scenarios/habit/repeat_by_day_of_week_scenario.dart b/integration_test/scenarios/habit/repeat_by_day_of_week_scenario.dart index 2b41267fe..ef2c177cf 100644 --- a/integration_test/scenarios/habit/repeat_by_day_of_week_scenario.dart +++ b/integration_test/scenarios/habit/repeat_by_day_of_week_scenario.dart @@ -4,7 +4,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:intl/intl.dart'; -import 'package:mem/core/mem_notification.dart'; +import 'package:mem/mems/mem_notification.dart'; import 'package:mem/databases/definition.dart'; import 'package:mem/databases/table_definitions/base.dart'; import 'package:mem/databases/table_definitions/mem_notifications.dart'; diff --git a/integration_test/scenarios/habit/repeat_by_n_day_habit_scenario.dart b/integration_test/scenarios/habit/repeat_by_n_day_habit_scenario.dart index d22dad4b3..ae5a4dd53 100644 --- a/integration_test/scenarios/habit/repeat_by_n_day_habit_scenario.dart +++ b/integration_test/scenarios/habit/repeat_by_n_day_habit_scenario.dart @@ -3,7 +3,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mem/components/mem/mem_name.dart'; -import 'package:mem/core/mem_notification.dart'; +import 'package:mem/mems/mem_notification.dart'; import 'package:mem/databases/definition.dart'; import 'package:mem/databases/table_definitions/acts.dart'; import 'package:mem/databases/table_definitions/base.dart'; @@ -25,146 +25,138 @@ import '../helpers.dart'; const _name = 'Repeat by n day habit scenario'; -void main() => group( - _name, - () { - const insertedMemName = "$_name - mem name - inserted"; - const insertedMemRepeatByNDay = 2; - const withoutActMemName = "$insertedMemName - without act"; - const withOldActMemName = "$insertedMemName - with old act"; - const withCurrentActMemName = "$insertedMemName - with current act"; - const insertedRepeatNotificationMessage = - "$_name - inserted - mem notification message - repeat"; - - late final DatabaseAccessor dbA; - - int insertedMemId = 0; - int withoutActMemId = 0; - int withOldActMemId = 0; - int withCurrentActMemId = 0; - - setUpAll(() async { - dbA = await openTestDatabase(databaseDefinition); - - await clearAllTestDatabaseRows(databaseDefinition); - - insertedMemId = await dbA.insert( - defTableMems, - { - defColMemsName.name: insertedMemName, - defColCreatedAt.name: zeroDate, - }, - ); - await dbA.insert( - defTableMemNotifications, - { - defFkMemNotificationsMemId.name: insertedMemId, - defColMemNotificationsType.name: MemNotificationType.repeat.name, - defColMemNotificationsTime.name: 1, - defColMemNotificationsMessage.name: "never", - defColCreatedAt.name: zeroDate, - }, - ); - await dbA.insert( - defTableMemNotifications, - { - defFkMemNotificationsMemId.name: insertedMemId, - defColMemNotificationsType.name: - MemNotificationType.repeatByNDay.name, - defColMemNotificationsTime.name: insertedMemRepeatByNDay, - defColMemNotificationsMessage.name: "never", - defColCreatedAt.name: zeroDate, - }, - ); +void main() => group(': $_name', () { + const insertedMemName = "$_name - mem name - inserted"; + const insertedMemRepeatByNDay = 2; + const withoutActMemName = "$insertedMemName - without act"; + const withOldActMemName = "$insertedMemName - with old act"; + const withCurrentActMemName = "$insertedMemName - with current act"; + const insertedRepeatNotificationMessage = + "$_name - inserted - mem notification message - repeat"; - withoutActMemId = await dbA.insert(defTableMems, { - defColMemsName.name: withoutActMemName, - defColMemsDoneAt.name: null, - defColCreatedAt.name: zeroDate, - }); - withOldActMemId = await dbA.insert(defTableMems, { - defColMemsName.name: withOldActMemName, - defColMemsDoneAt.name: null, + late final DatabaseAccessor dbA; + + int insertedMemId = 0; + int withoutActMemId = 0; + int withOldActMemId = 0; + int withCurrentActMemId = 0; + + setUpAll(() async { + dbA = await openTestDatabase(databaseDefinition); + + await clearAllTestDatabaseRows(databaseDefinition); + + insertedMemId = await dbA.insert( + defTableMems, + { + defColMemsName.name: insertedMemName, defColCreatedAt.name: zeroDate, - }); - withCurrentActMemId = await dbA.insert(defTableMems, { - defColMemsName.name: withCurrentActMemName, - defColMemsDoneAt.name: null, + }, + ); + await dbA.insert( + defTableMemNotifications, + { + defFkMemNotificationsMemId.name: insertedMemId, + defColMemNotificationsType.name: MemNotificationType.repeat.name, + defColMemNotificationsTime.name: 1, + defColMemNotificationsMessage.name: "never", defColCreatedAt.name: zeroDate, - }); + }, + ); + await dbA.insert(defTableMemNotifications, { + defFkMemNotificationsMemId.name: insertedMemId, + defColMemNotificationsType.name: + MemNotificationType.repeatByNDay.name, + defColMemNotificationsTime.name: insertedMemRepeatByNDay, + defColMemNotificationsMessage.name: "never", + defColCreatedAt.name: zeroDate + }); - [withoutActMemId, withOldActMemId, withCurrentActMemId] - .forEachIndexed((index, insertedMemId) async { - await dbA.insert(defTableMemNotifications, { - defFkMemNotificationsMemId.name: insertedMemId, - defColMemNotificationsTime.name: 0, - defColMemNotificationsType.name: MemNotificationType.repeat.name, - defColMemNotificationsMessage.name: - insertedRepeatNotificationMessage, - defColCreatedAt.name: zeroDate, - }); - await dbA.insert(defTableMemNotifications, { - defFkMemNotificationsMemId.name: insertedMemId, - defColMemNotificationsTime.name: - index + insertedMemRepeatByNDay + 1, - defColMemNotificationsType.name: - MemNotificationType.repeatByNDay.name, - defColMemNotificationsMessage.name: - "$_name - inserted - mem notification message - after act started", - defColCreatedAt.name: zeroDate, - }); - }); + withoutActMemId = await dbA.insert(defTableMems, { + defColMemsName.name: withoutActMemName, + defColMemsDoneAt.name: null, + defColCreatedAt.name: zeroDate, + }); + withOldActMemId = await dbA.insert(defTableMems, { + defColMemsName.name: withOldActMemName, + defColMemsDoneAt.name: null, + defColCreatedAt.name: zeroDate + }); + withCurrentActMemId = await dbA.insert(defTableMems, { + defColMemsName.name: withCurrentActMemName, + defColMemsDoneAt.name: null, + defColCreatedAt.name: zeroDate + }); - await dbA.insert(defTableActs, { - defFkActsMemId.name: withOldActMemId, - defColActsStart.name: zeroDate.toIso8601String(), - defColActsStartIsAllDay.name: 0, - defColCreatedAt.name: zeroDate, + [withoutActMemId, withOldActMemId, withCurrentActMemId] + .forEachIndexed((index, insertedMemId) async { + await dbA.insert(defTableMemNotifications, { + defFkMemNotificationsMemId.name: insertedMemId, + defColMemNotificationsTime.name: 0, + defColMemNotificationsType.name: MemNotificationType.repeat.name, + defColMemNotificationsMessage.name: + insertedRepeatNotificationMessage, + defColCreatedAt.name: zeroDate }); - await dbA.insert(defTableActs, { - defFkActsMemId.name: withCurrentActMemId, - defColActsStart.name: DateTime.now().toIso8601String(), - defColActsStartIsAllDay.name: 0, - defColCreatedAt.name: zeroDate, + await dbA.insert(defTableMemNotifications, { + defFkMemNotificationsMemId.name: insertedMemId, + defColMemNotificationsTime.name: + index + insertedMemRepeatByNDay + 1, + defColMemNotificationsType.name: + MemNotificationType.repeatByNDay.name, + defColMemNotificationsMessage.name: + "$_name - inserted - mem notification message - after act started", + defColCreatedAt.name: zeroDate }); }); - setUp(() async { - NotificationClient.resetSingleton(); + await dbA.insert(defTableActs, { + defFkActsMemId.name: withOldActMemId, + defColActsStart.name: zeroDate.toIso8601String(), + defColActsStartIsAllDay.name: 0, + defColCreatedAt.name: zeroDate }); + await dbA.insert(defTableActs, { + defFkActsMemId.name: withCurrentActMemId, + defColActsStart.name: DateTime.now().toIso8601String(), + defColActsStartIsAllDay.name: 0, + defColCreatedAt.name: zeroDate + }); + }); - testWidgets( - 'show saved.', - (widgetTester) async { - widgetTester.ignoreMockMethodCallHandler( - MethodChannelMock.flutterLocalNotifications); + setUp(() async { + NotificationClient.resetSingleton(); + }); - const repeatText = "12:00 AM every $insertedMemRepeatByNDay days"; + testWidgets( + 'show saved.', + (widgetTester) async { + widgetTester.ignoreMockMethodCallHandler( + MethodChannelMock.flutterLocalNotifications); - await runApplication(); - await widgetTester.pumpAndSettle(defaultTransitionDuration); + const repeatText = "12:00 AM every $insertedMemRepeatByNDay days"; - expect(find.text(repeatText), findsOneWidget); - await widgetTester.tap(find.text(insertedMemName)); - await widgetTester.pumpAndSettle(defaultTransitionDuration); + await runApplication(); + await widgetTester.pumpAndSettle(defaultTransitionDuration); - expect( - widgetTester - .widget(find.descendant( - of: find.byKey(keyMemNotificationsView), - matching: find.byType(Text))) - .data, - repeatText); - - await widgetTester.tap( - find.descendant( - of: find.byKey(keyMemNotificationsView), - matching: find.byIcon(Icons.edit), - ), - ); - await widgetTester.pumpAndSettle(defaultTransitionDuration); + expect(find.text(repeatText), findsOneWidget); + await widgetTester.tap(find.text(insertedMemName)); + await widgetTester.pumpAndSettle(defaultTransitionDuration); - expect( + expect( + widgetTester + .widget(find.descendant( + of: find.byKey(keyMemNotificationsView), + matching: find.byType(Text))) + .data, + repeatText); + + await widgetTester.tap(find.descendant( + of: find.byKey(keyMemNotificationsView), + matching: find.byIcon(Icons.edit))); + await widgetTester.pumpAndSettle(defaultTransitionDuration); + + expect( widgetTester .widget( find.descendant( @@ -173,349 +165,311 @@ void main() => group( ), ) .initialValue, - insertedMemRepeatByNDay.toString(), - ); + insertedMemRepeatByNDay.toString()); + }, + ); + + testWidgets('save.', (widgetTester) async { + widgetTester.ignoreMockMethodCallHandler( + MethodChannelMock.flutterLocalNotifications); + + final testStart = DateTime.now(); + var expectedSavedMemId = + ((await dbA.select(defTableMems, orderBy: "id DESC", limit: 1)) + .single[defPkId.name] as int) + + 1; + + int checkPermissionStatusCount = 0; + widgetTester.setMockMethodCallHandler( + MethodChannelMock.permissionHandler, + List.generate( + 3, + (i) => (m) async { + expect(m.method, 'checkPermissionStatus'); + checkPermissionStatusCount++; + return 1; + })); + int alarmServiceStartCount = 0; + int alarmCancelCount = 0; + int alarmPeriodicCount = 0; + widgetTester + .setMockMethodCallHandler(MethodChannelMock.androidAlarmManager, [ + (message) async { + expect(message.method, equals('AlarmService.start')); + expect(message.arguments, orderedEquals([isNotNull])); + alarmServiceStartCount++; + return true; }, - ); + (message) async { + expect(message.method, equals('Alarm.cancel')); + expect( + message.arguments, + orderedEquals( + [equals(memStartNotificationId(expectedSavedMemId))])); + alarmCancelCount++; + return false; + }, + (message) async { + expect(message.method, equals('Alarm.cancel')); + expect( + message.arguments, + orderedEquals( + [equals(memEndNotificationId(expectedSavedMemId))])); + alarmCancelCount++; + return false; + }, + (message) async { + expect(message.method, equals('Alarm.periodic')); + expect(message.arguments[0], + equals(memRepeatedNotificationId(expectedSavedMemId))); + expect(message.arguments[1], isFalse); + expect(message.arguments[2], isFalse); + expect(message.arguments[3], isFalse); + expect( + DateTime.fromMillisecondsSinceEpoch(message.arguments[4]), + equals(testStart + .copyWith( + hour: defaultStartOfDay.hour, + minute: defaultStartOfDay.minute, + second: 0, + millisecond: 0, + microsecond: 0) + .add(const Duration(days: 1)))); + expect( + message.arguments[5], const Duration(days: 1).inMilliseconds); + expect(message.arguments[6], isFalse); + expect(message.arguments[7], isNotNull); + expect( + message.arguments[8], + equals({ + memIdKey: expectedSavedMemId, + notificationTypeKey: NotificationType.repeat.name + })); + alarmPeriodicCount++; + return false; + }, + ]); + + await runApplication(); + await widgetTester.pumpAndSettle(); + await widgetTester.tap(newMemFabFinder); + await widgetTester.pumpAndSettle(); + const enteringMemName = "$_name: Save - entering - mem name"; + await widgetTester.enterText(find.byKey(keyMemName), enteringMemName); + + final notificationAddFinder = find.descendant( + of: find.byKey(keyMemNotificationsView), + matching: find.byIcon(Icons.notification_add)); + await widgetTester.tap(notificationAddFinder); + await widgetTester.pumpAndSettle(defaultTransitionDuration); + + await widgetTester.tap(timeIconFinder); + await widgetTester.pump(); + await widgetTester.tap(okFinder); + await widgetTester.pump(); + + const enteringNDay = 3; + await widgetTester.enterText( + find.descendant( + of: find.byKey(keyMemRepeatByNDayNotification), + matching: find.byType(TextFormField)), + enteringNDay.toString()); + + await widgetTester.pageBack(); + await widgetTester.pumpAndSettle(); + await widgetTester.tap(find.byKey(keySaveMemFab)); + await widgetTester.pumpAndSettle(); + + final savedMem = (await dbA.select( + defTableMems, + where: "${defColMemsName.name} = ?", + whereArgs: [enteringMemName], + )) + .single; + final savedMemNotification = (await dbA.select(defTableMemNotifications, + where: "${defFkMemNotificationsMemId.name} = ?" + " AND ${defColMemNotificationsType.name} = ?" + " AND ${defColMemNotificationsTime.name} = ?", + whereArgs: [ + savedMem[defPkId.name], + MemNotificationType.repeatByNDay.name, + enteringNDay + ])) + .single; + expect(savedMemNotification[defColMemNotificationsTime.name], + equals(enteringNDay), + reason: 'enteringNDay'); + + if (defaultTargetPlatform == TargetPlatform.android) { + expect(checkPermissionStatusCount, equals(3), + reason: 'checkPermissionStatusCount'); + expect(alarmServiceStartCount, equals(1), + reason: 'alarmServiceStartCount'); + expect(alarmCancelCount, equals(2), reason: 'alarmCancelCount'); + expect(alarmPeriodicCount, equals(1), reason: 'alarmPeriodicCount'); + } else { + expect(checkPermissionStatusCount, equals(3), + reason: 'checkPermissionStatusCount'); + expect(alarmServiceStartCount, equals(0), + reason: 'alarmServiceStartCount'); + expect(alarmCancelCount, equals(0), reason: 'alarmCancelCount'); + expect(alarmPeriodicCount, equals(0), reason: 'alarmPeriodicCount'); + } + + widgetTester.clearAllMockMethodCallHandler(); + }); + + group('notify repeatByNDay', () { + testWidgets('withoutAct.', (widgetTester) async { + int checkPermissionStatusCount = 0; + widgetTester + .setMockMethodCallHandler(MethodChannelMock.permissionHandler, [ + (m) async { + expect(m.method, 'checkPermissionStatus'); + checkPermissionStatusCount++; + return 1; + } + ]); + int initializeCount = 0; + int cancelCount = 0; + int showCount = 0; + widgetTester.setMockMethodCallHandler( + MethodChannelMock.flutterLocalNotifications, [ + (message) async { + expect(message.method, equals('initialize')); + initializeCount++; + return true; + }, + ...[ + memStartNotificationId(withoutActMemId), + memEndNotificationId(withoutActMemId), + pausedActNotificationId(withoutActMemId), + afterActStartedNotificationId(withoutActMemId) + ].map((e) => (message) async { + expect(message.method, equals('cancel')); + expect(message.arguments['id'], equals(e)); + cancelCount++; + return false; + }), + (message) async { + expect(message.method, equals('show')); + expect(message.arguments['id'], + equals(memRepeatedNotificationId(withoutActMemId))); + expect(message.arguments['title'], equals(withoutActMemName)); + expect(message.arguments['body'], + equals(insertedRepeatNotificationMessage)); + expect(message.arguments['payload'], + equals("{\"$memIdKey\":$withoutActMemId}")); + showCount++; + return false; + } + ]); + + final params = { + memIdKey: withoutActMemId, + notificationTypeKey: NotificationType.repeat.name, + }; + + await scheduleCallback(0, params); + + if (defaultTargetPlatform == TargetPlatform.android) { + expect(checkPermissionStatusCount, equals(1), + reason: 'checkPermissionStatusCount'); + expect(initializeCount, equals(1), reason: 'initializeCount'); + expect(cancelCount, equals(4), reason: 'cancelCount'); + expect(showCount, equals(1), reason: 'showCount'); + } else { + expect(checkPermissionStatusCount, equals(1), + reason: 'checkPermissionStatusCount'); + expect(initializeCount, equals(0), reason: 'initializeCount'); + expect(cancelCount, equals(0), reason: 'cancelCount'); + expect(showCount, equals(0), reason: 'showCount'); + } + + widgetTester.clearAllMockMethodCallHandler(); + }); - testWidgets( - 'save.', - (widgetTester) async { - widgetTester.ignoreMockMethodCallHandler( - MethodChannelMock.flutterLocalNotifications); - - final testStart = DateTime.now(); - var expectedSavedMemId = - ((await dbA.select(defTableMems, orderBy: "id DESC", limit: 1)) - .single[defPkId.name] as int) + - 1; - - int checkPermissionStatusCount = 0; - widgetTester.setMockMethodCallHandler( - MethodChannelMock.permissionHandler, - List.generate( - 3, - (i) => (m) async { - expect(m.method, 'checkPermissionStatus'); - checkPermissionStatusCount++; - return 1; - })); - int alarmServiceStartCount = 0; - int alarmCancelCount = 0; - int alarmPeriodicCount = 0; - widgetTester.setMockMethodCallHandler( - MethodChannelMock.androidAlarmManager, [ - (message) async { - expect(message.method, equals('AlarmService.start')); - expect( - message.arguments, - orderedEquals([ - isNotNull, - ])); - alarmServiceStartCount++; - return true; - }, - (message) async { - expect(message.method, equals('Alarm.cancel')); - expect( - message.arguments, - orderedEquals( - [equals(memStartNotificationId(expectedSavedMemId))])); - alarmCancelCount++; - return false; - }, - (message) async { - expect(message.method, equals('Alarm.cancel')); - expect( - message.arguments, - orderedEquals( - [equals(memEndNotificationId(expectedSavedMemId))])); - alarmCancelCount++; - return false; - }, - (message) async { - expect(message.method, equals('Alarm.periodic')); - expect(message.arguments[0], - equals(memRepeatedNotificationId(expectedSavedMemId))); - expect(message.arguments[1], isFalse); - expect(message.arguments[2], isFalse); - expect(message.arguments[3], isFalse); - expect( - message.arguments[4], - equals(testStart - .copyWith( - hour: defaultStartOfDay.hour, - minute: defaultStartOfDay.minute, - second: 0, - millisecond: 0, - microsecond: 0) - .add(const Duration(days: 1)) - .millisecondsSinceEpoch)); - expect(message.arguments[5], - const Duration(days: 1).inMilliseconds); - expect(message.arguments[6], isFalse); - expect(message.arguments[7], isNotNull); - expect( - message.arguments[8], - equals({ - memIdKey: expectedSavedMemId, - notificationTypeKey: NotificationType.repeat.name - })); - alarmPeriodicCount++; - return false; - }, - ]); - - await runApplication(); - await widgetTester.pumpAndSettle(); - await widgetTester.tap(newMemFabFinder); - await widgetTester.pumpAndSettle(); - const enteringMemName = "$_name: Save - entering - mem name"; - await widgetTester.enterText( - find.byKey(keyMemName), enteringMemName); - - final notificationAddFinder = find.descendant( - of: find.byKey(keyMemNotificationsView), - matching: find.byIcon(Icons.notification_add)); - await widgetTester.tap(notificationAddFinder); - await widgetTester.pumpAndSettle(defaultTransitionDuration); - - await widgetTester.tap(timeIconFinder); - await widgetTester.pump(); - await widgetTester.tap(okFinder); - await widgetTester.pump(); - - const enteringNDay = 3; - await widgetTester.enterText( - find.descendant( - of: find.byKey(keyMemRepeatByNDayNotification), - matching: find.byType(TextFormField)), - enteringNDay.toString(), - ); - - await widgetTester.pageBack(); - await widgetTester.pumpAndSettle(); - await widgetTester.tap(find.byKey(keySaveMemFab)); - await widgetTester.pumpAndSettle(); - - final savedMem = (await dbA.select( - defTableMems, - where: "${defColMemsName.name} = ?", - whereArgs: [enteringMemName], - )) - .single; - final savedMemNotification = (await dbA.select( - defTableMemNotifications, - where: "${defFkMemNotificationsMemId.name} = ?" - " AND ${defColMemNotificationsType.name} = ?" - " AND ${defColMemNotificationsTime.name} = ?", - whereArgs: [ - savedMem[defPkId.name], - MemNotificationType.repeatByNDay.name, - enteringNDay, - ], - )) - .single; - expect(savedMemNotification[defColMemNotificationsTime.name], - equals(enteringNDay), - reason: 'enteringNDay'); - - if (defaultTargetPlatform == TargetPlatform.android) { - expect(checkPermissionStatusCount, equals(3), - reason: 'checkPermissionStatusCount'); - expect(alarmServiceStartCount, equals(1), - reason: 'alarmServiceStartCount'); - expect(alarmCancelCount, equals(2), reason: 'alarmCancelCount'); - expect(alarmPeriodicCount, equals(1), - reason: 'alarmPeriodicCount'); - } else { - expect(checkPermissionStatusCount, equals(3), - reason: 'checkPermissionStatusCount'); - expect(alarmServiceStartCount, equals(0), - reason: 'alarmServiceStartCount'); - expect(alarmCancelCount, equals(0), reason: 'alarmCancelCount'); - expect(alarmPeriodicCount, equals(0), - reason: 'alarmPeriodicCount'); + testWidgets('withOldAct', (widgetTester) async { + int checkPermissionStatusCount = 0; + widgetTester + .setMockMethodCallHandler(MethodChannelMock.permissionHandler, [ + (m) async { + expect(m.method, 'checkPermissionStatus'); + checkPermissionStatusCount++; + return 1; + } + ]); + int initializeCount = 0; + int cancelCount = 0; + int showCount = 0; + widgetTester.setMockMethodCallHandler( + MethodChannelMock.flutterLocalNotifications, [ + (message) async { + expect(message.method, equals('initialize')); + initializeCount++; + return true; + }, + ...[ + memStartNotificationId(withOldActMemId), + memEndNotificationId(withOldActMemId), + pausedActNotificationId(withOldActMemId), + afterActStartedNotificationId(withOldActMemId), + ].map((e) => (message) async { + expect(message.method, equals('cancel')); + expect(message.arguments['id'], equals(e)); + cancelCount++; + return false; + }), + (message) async { + expect(message.method, equals('show')); + expect(message.arguments['id'], + equals(memRepeatedNotificationId(withOldActMemId))); + expect(message.arguments['title'], equals(withOldActMemName)); + expect(message.arguments['body'], + equals(insertedRepeatNotificationMessage)); + expect(message.arguments['payload'], + equals("{\"$memIdKey\":$withOldActMemId}")); + showCount++; + return false; } + ]); + + final params = { + memIdKey: withOldActMemId, + notificationTypeKey: NotificationType.repeat.name + }; + + await scheduleCallback(0, params); + + if (defaultTargetPlatform == TargetPlatform.android) { + expect(checkPermissionStatusCount, equals(1), + reason: 'checkPermissionStatusCount'); + expect(initializeCount, equals(1), reason: 'initializeCount'); + expect(cancelCount, equals(4), reason: 'cancelCount'); + expect(showCount, equals(1), reason: 'showCount'); + } else { + expect(checkPermissionStatusCount, equals(1), + reason: 'checkPermissionStatusCount'); + expect(initializeCount, equals(0), reason: 'initializeCount'); + expect(cancelCount, equals(0), reason: 'cancelCount'); + expect(showCount, equals(0), reason: 'showCount'); + } + + widgetTester.clearAllMockMethodCallHandler(); + }); - widgetTester.clearAllMockMethodCallHandler(); - }, - ); + testWidgets('withCurrentAct', (widgetTester) async { + widgetTester.setMockMethodCallHandler( + MethodChannelMock.flutterLocalNotifications, []); - group( - 'notify repeatByNDay', - () { - testWidgets( - 'withoutAct.', - (widgetTester) async { - int checkPermissionStatusCount = 0; - widgetTester.setMockMethodCallHandler( - MethodChannelMock.permissionHandler, [ - (m) async { - expect(m.method, 'checkPermissionStatus'); - checkPermissionStatusCount++; - return 1; - } - ]); - int initializeCount = 0; - int cancelCount = 0; - int showCount = 0; - widgetTester.setMockMethodCallHandler( - MethodChannelMock.flutterLocalNotifications, - [ - (message) async { - expect(message.method, equals('initialize')); - initializeCount++; - return true; - }, - ...[ - memStartNotificationId(withoutActMemId), - memEndNotificationId(withoutActMemId), - pausedActNotificationId(withoutActMemId), - afterActStartedNotificationId(withoutActMemId), - ].map( - (e) => (message) async { - expect(message.method, equals('cancel')); - expect(message.arguments['id'], equals(e)); - cancelCount++; - return false; - }, - ), - (message) async { - expect(message.method, equals('show')); - expect(message.arguments['id'], - equals(memRepeatedNotificationId(withoutActMemId))); - expect(message.arguments['title'], - equals(withoutActMemName)); - expect(message.arguments['body'], - equals(insertedRepeatNotificationMessage)); - expect(message.arguments['payload'], - equals("{\"$memIdKey\":$withoutActMemId}")); - showCount++; - return false; - }, - ], - ); - - final params = { - memIdKey: withoutActMemId, - notificationTypeKey: NotificationType.repeat.name, - }; - - await scheduleCallback(0, params); - - if (defaultTargetPlatform == TargetPlatform.android) { - expect(checkPermissionStatusCount, equals(1), - reason: 'checkPermissionStatusCount'); - expect(initializeCount, equals(1), reason: 'initializeCount'); - expect(cancelCount, equals(4), reason: 'cancelCount'); - expect(showCount, equals(1), reason: 'showCount'); - } else { - expect(checkPermissionStatusCount, equals(1), - reason: 'checkPermissionStatusCount'); - expect(initializeCount, equals(0), reason: 'initializeCount'); - expect(cancelCount, equals(0), reason: 'cancelCount'); - expect(showCount, equals(0), reason: 'showCount'); - } - - widgetTester.clearAllMockMethodCallHandler(); - }, - ); - - testWidgets( - 'withOldAct', - (widgetTester) async { - int checkPermissionStatusCount = 0; - widgetTester.setMockMethodCallHandler( - MethodChannelMock.permissionHandler, [ - (m) async { - expect(m.method, 'checkPermissionStatus'); - checkPermissionStatusCount++; - return 1; - } - ]); - int initializeCount = 0; - int cancelCount = 0; - int showCount = 0; - widgetTester.setMockMethodCallHandler( - MethodChannelMock.flutterLocalNotifications, - [ - (message) async { - expect(message.method, equals('initialize')); - initializeCount++; - return true; - }, - ...[ - memStartNotificationId(withOldActMemId), - memEndNotificationId(withOldActMemId), - pausedActNotificationId(withOldActMemId), - afterActStartedNotificationId(withOldActMemId), - ].map( - (e) => (message) async { - expect(message.method, equals('cancel')); - expect(message.arguments['id'], equals(e)); - cancelCount++; - return false; - }, - ), - (message) async { - expect(message.method, equals('show')); - expect(message.arguments['id'], - equals(memRepeatedNotificationId(withOldActMemId))); - expect(message.arguments['title'], - equals(withOldActMemName)); - expect(message.arguments['body'], - equals(insertedRepeatNotificationMessage)); - expect(message.arguments['payload'], - equals("{\"$memIdKey\":$withOldActMemId}")); - showCount++; - return false; - }, - ], - ); - - final params = { - memIdKey: withOldActMemId, - notificationTypeKey: NotificationType.repeat.name, - }; - - await scheduleCallback(0, params); - - if (defaultTargetPlatform == TargetPlatform.android) { - expect(checkPermissionStatusCount, equals(1), - reason: 'checkPermissionStatusCount'); - expect(initializeCount, equals(1), reason: 'initializeCount'); - expect(cancelCount, equals(4), reason: 'cancelCount'); - expect(showCount, equals(1), reason: 'showCount'); - } else { - expect(checkPermissionStatusCount, equals(1), - reason: 'checkPermissionStatusCount'); - expect(initializeCount, equals(0), reason: 'initializeCount'); - expect(cancelCount, equals(0), reason: 'cancelCount'); - expect(showCount, equals(0), reason: 'showCount'); - } - - widgetTester.clearAllMockMethodCallHandler(); - }, - ); - - testWidgets( - 'withCurrentAct', - (widgetTester) async { - widgetTester.setMockMethodCallHandler( - MethodChannelMock.flutterLocalNotifications, []); - - final params = { - memIdKey: withCurrentActMemId, - notificationTypeKey: NotificationType.repeat.name, - }; - - await scheduleCallback(0, params); - - widgetTester.clearAllMockMethodCallHandler(); - }, - ); - }, - ); - }, - ); + final params = { + memIdKey: withCurrentActMemId, + notificationTypeKey: NotificationType.repeat.name + }; + + await scheduleCallback(0, params); + + widgetTester.clearAllMockMethodCallHandler(); + }); + }); + }); diff --git a/integration_test/scenarios/habit/repeated_habit_scenario.dart b/integration_test/scenarios/habit/repeated_habit_scenario.dart index 5c6647ad4..2994f3f2d 100644 --- a/integration_test/scenarios/habit/repeated_habit_scenario.dart +++ b/integration_test/scenarios/habit/repeated_habit_scenario.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mem/components/date_and_time/time_of_day_view.dart'; import 'package:mem/components/mem/mem_name.dart'; -import 'package:mem/core/mem_notification.dart'; +import 'package:mem/mems/mem_notification.dart'; import 'package:mem/databases/definition.dart'; import 'package:mem/databases/table_definitions/acts.dart'; import 'package:mem/databases/table_definitions/base.dart'; @@ -19,279 +19,237 @@ import '../helpers.dart'; const _name = 'Repeated habit scenario'; -void main() => group( - _name, - () { - late final DatabaseAccessor dbA; - setUpAll(() async { - dbA = await openTestDatabase(databaseDefinition); - }); +void main() => group(_name, () { + late final DatabaseAccessor dbA; + setUpAll(() async { + dbA = await openTestDatabase(databaseDefinition); + }); - const baseMemName = "$_name - mem - name"; - const insertedMemName = "$baseMemName - inserted"; - const insertedMemName2 = "$insertedMemName - 2"; + const baseMemName = "$_name - mem - name"; + const insertedMemName = "$baseMemName - inserted"; + const insertedMemName2 = "$insertedMemName - 2"; - final now = DateTime.now(); + final now = DateTime.now(); - late int insertedMemId; + late int insertedMemId; - setUp(() async { - await clearAllTestDatabaseRows(databaseDefinition); - - insertedMemId = await dbA.insert( - defTableMems, - { - defColMemsName.name: insertedMemName, - defColCreatedAt.name: zeroDate, - }, - ); - await dbA.insert( - defTableMemNotifications, - { - defFkMemNotificationsMemId.name: insertedMemId, - defColMemNotificationsType.name: MemNotificationType.repeat.name, - defColMemNotificationsTime.name: 2, - defColMemNotificationsMessage.name: "never", - defColCreatedAt.name: zeroDate, - }, - ); - await dbA.insert( - defTableMemNotifications, - { - defFkMemNotificationsMemId.name: insertedMemId, - defColMemNotificationsType.name: - MemNotificationType.repeatByNDay.name, - defColMemNotificationsTime.name: 1, - defColMemNotificationsMessage.name: "never", - defColCreatedAt.name: zeroDate, - }, - ); - await dbA.insert( - defTableActs, - { - defFkActsMemId.name: insertedMemId, - defColActsStart.name: now.toIso8601String(), - defColActsStartIsAllDay.name: 0, - defColActsEnd.name: now.toIso8601String(), - defColActsEndIsAllDay.name: 0, - defColCreatedAt.name: zeroDate, - }, - ); - await dbA.insert( - defTableMems, - { - defColMemsName.name: insertedMemName2, - defColMemsStartOn.name: now.toIso8601String(), - defColCreatedAt.name: zeroDate, - }, - ); + setUp(() async { + await clearAllTestDatabaseRows(databaseDefinition); + + insertedMemId = await dbA.insert(defTableMems, { + defColMemsName.name: insertedMemName, + defColCreatedAt.name: zeroDate + }); + await dbA.insert(defTableMemNotifications, { + defFkMemNotificationsMemId.name: insertedMemId, + defColMemNotificationsType.name: MemNotificationType.repeat.name, + defColMemNotificationsTime.name: 2, + defColMemNotificationsMessage.name: "never", + defColCreatedAt.name: zeroDate + }); + await dbA.insert(defTableMemNotifications, { + defFkMemNotificationsMemId.name: insertedMemId, + defColMemNotificationsType.name: + MemNotificationType.repeatByNDay.name, + defColMemNotificationsTime.name: 1, + defColMemNotificationsMessage.name: "never", + defColCreatedAt.name: zeroDate + }); + await dbA.insert(defTableActs, { + defFkActsMemId.name: insertedMemId, + defColActsStart.name: now.toIso8601String(), + defColActsStartIsAllDay.name: 0, + defColActsEnd.name: now.toIso8601String(), + defColActsEndIsAllDay.name: 0, + defColCreatedAt.name: zeroDate + }); + await dbA.insert(defTableMems, { + defColMemsName.name: insertedMemName2, + defColMemsStartOn.name: now.toIso8601String(), + defColCreatedAt.name: zeroDate + }); + }); + + group('show', () { + testWidgets('on new.', (widgetTester) async { + await runApplication(); + await widgetTester.pumpAndSettle(); + await widgetTester.tap(newMemFabFinder); + await widgetTester.pumpAndSettle(defaultTransitionDuration); + + expect( + widgetTester + .widget(find.descendant( + of: find.byKey(keyMemNotificationsView), + matching: find.byType(Text))) + .data, + l10n.noNotifications); + final notificationAddFinder = find.descendant( + of: find.byKey(keyMemNotificationsView), + matching: find.byIcon(Icons.notification_add)); + expect(notificationAddFinder, findsOneWidget); + + await widgetTester.dragUntilVisible(notificationAddFinder, + find.byType(SingleChildScrollView), const Offset(0, 50)); + await widgetTester.tap(notificationAddFinder); + await widgetTester.pumpAndSettle(defaultTransitionDuration); + + expect( + widgetTester + .widget(find.descendant( + of: find.byKey(keyMemRepeatedNotification), + matching: find.byType(TimeOfDayTextFormField))) + .timeOfDay, + defaultStartOfDay); + expect( + find.descendant( + of: find.byKey(keyMemRepeatedNotification), + matching: find.byIcon(Icons.clear)), + findsNothing); }); - group( - 'show', - () { - testWidgets( - 'on new.', - (widgetTester) async { - await runApplication(); - await widgetTester.pumpAndSettle(); - await widgetTester.tap(newMemFabFinder); - await widgetTester.pumpAndSettle(defaultTransitionDuration); - - expect( - widgetTester - .widget(find.descendant( - of: find.byKey(keyMemNotificationsView), - matching: find.byType(Text))) - .data, - l10n.noNotifications); - final notificationAddFinder = find.descendant( - of: find.byKey(keyMemNotificationsView), - matching: find.byIcon(Icons.notification_add)); - expect(notificationAddFinder, findsOneWidget); - - await widgetTester.dragUntilVisible(notificationAddFinder, - find.byType(SingleChildScrollView), const Offset(0, 50)); - await widgetTester.tap(notificationAddFinder); - await widgetTester.pumpAndSettle(defaultTransitionDuration); - - expect( - widgetTester - .widget(find.descendant( - of: find.byKey(keyMemRepeatedNotification), - matching: find.byType(TimeOfDayTextFormField))) - .timeOfDay, - defaultStartOfDay); - expect( - find.descendant( - of: find.byKey(keyMemRepeatedNotification), - matching: find.byIcon(Icons.clear)), - findsNothing); - }, - ); - - testWidgets( - 'saved.', - (widgetTester) async { - const repeatText = "12:00 AM every day"; - - await runApplication(); - await widgetTester.pumpAndSettle(); - - expect(find.text(repeatText), findsOneWidget); - - await widgetTester.tap(find.text(insertedMemName)); - await widgetTester.pumpAndSettle(defaultTransitionDuration); - - expect( - widgetTester - .widget(find.descendant( - of: find.byKey(keyMemNotificationsView), - matching: find.byType(Text))) - .data, - repeatText); - - await widgetTester.tap(find.descendant( - of: find.byKey(keyMemNotificationsView), - matching: find.byIcon(Icons.edit))); - await widgetTester.pumpAndSettle(defaultTransitionDuration); - - expect( - widgetTester - .widget( - find.descendant( - of: find.byKey(keyMemRepeatedNotification), - matching: find.byType(TimeOfDayTextFormField)), - ) - .timeOfDay, - defaultStartOfDay); - expect( - find.descendant( + testWidgets( + 'saved.', + (widgetTester) async { + const repeatText = "12:00 AM every day"; + + await runApplication(); + await widgetTester.pumpAndSettle(); + + expect(find.text(repeatText), findsOneWidget); + + await widgetTester.tap(find.text(insertedMemName)); + await widgetTester.pumpAndSettle(defaultTransitionDuration); + + expect( + widgetTester + .widget(find.descendant( + of: find.byKey(keyMemNotificationsView), + matching: find.byType(Text))) + .data, + repeatText); + + await widgetTester.tap(find.descendant( + of: find.byKey(keyMemNotificationsView), + matching: find.byIcon(Icons.edit))); + await widgetTester.pumpAndSettle(defaultTransitionDuration); + + expect( + widgetTester + .widget(find.descendant( of: find.byKey(keyMemRepeatedNotification), - matching: find.byIcon(Icons.clear)), - findsOneWidget); - }, - ); + matching: find.byType(TimeOfDayTextFormField))) + .timeOfDay, + defaultStartOfDay); + expect( + find.descendant( + of: find.byKey(keyMemRepeatedNotification), + matching: find.byIcon(Icons.clear)), + findsOneWidget); }, ); + }); - group('save', () { - setUp(() async { - await dbA.insert(defTableActs, { - defFkActsMemId.name: insertedMemId, - defColActsStart.name: zeroDate, - defColActsStartIsAllDay.name: 0, - defColCreatedAt.name: zeroDate - }); + group('save', () { + setUp(() async { + await dbA.insert(defTableActs, { + defFkActsMemId.name: insertedMemId, + defColActsStart.name: zeroDate, + defColActsStartIsAllDay.name: 0, + defColCreatedAt.name: zeroDate }); + }); - testWidgets( - 'create.', - (widgetTester) async { - widgetTester.ignoreMockMethodCallHandler( - MethodChannelMock.permissionHandler); - - await runApplication(); - await widgetTester.pumpAndSettle(); - await widgetTester.tap(newMemFabFinder); - await widgetTester.pumpAndSettle(); - const enteringMemName = "$baseMemName - entering"; - await widgetTester.enterText( - find.byKey(keyMemName), enteringMemName); - - final notificationAddFinder = find.descendant( - of: find.byKey(keyMemNotificationsView), - matching: find.byIcon(Icons.notification_add)); - await widgetTester.tap(notificationAddFinder); - await widgetTester.pumpAndSettle(defaultTransitionDuration); - - await widgetTester.tap(timeIconFinder); - await widgetTester.pump(); - - await widgetTester.tap(okFinder); - await widgetTester.pump(); - - await widgetTester.pageBack(); - await widgetTester.pumpAndSettle(); - await widgetTester.tap(find.byKey(keySaveMemFab)); - await widgetTester.pumpAndSettle(); - - final savedMem = (await dbA.select(defTableMems, - where: "${defColMemsName.name} = ?", - whereArgs: [enteringMemName])) - .single; - final savedMemNotifications = (await dbA.select( - defTableMemNotifications, - where: "${defFkMemNotificationsMemId.name} = ?", - whereArgs: [savedMem[defPkId.name]])); - final repeat = savedMemNotifications.singleWhere((e) => - e[defColMemNotificationsType.name] == - MemNotificationType.repeat.name); - expect(repeat[defColMemNotificationsTime.name], 0); - }, - ); - - testWidgets( - ": update.", - (widgetTester) async { - await runApplication(); - await widgetTester.pumpAndSettle(); - await widgetTester.tap(find.text(insertedMemName)); - await widgetTester.pumpAndSettle(); - - final notificationAddFinder = find.descendant( - of: find.byKey(keyMemNotificationsView), - matching: find.byIcon(Icons.edit), - ); - await widgetTester.dragUntilVisible( - notificationAddFinder, - find.byType(SingleChildScrollView), - const Offset(0, 50), - ); - await widgetTester.tap( - notificationAddFinder, - ); - await widgetTester.pumpAndSettle(defaultTransitionDuration); - - await widgetTester.tap( - find.descendant( - of: find.byKey(keyMemRepeatedNotification), - matching: find.byIcon(Icons.clear), - ), - ); - await widgetTester.pump(); - - await widgetTester.tap(timeIconFinder); - await widgetTester.pump(); - await widgetTester.tap(okFinder); - await widgetTester.pump(); - - await widgetTester.pageBack(); - await widgetTester.pumpAndSettle(); - await widgetTester.tap(find.byKey(keySaveMemFab)); - await widgetTester.pump(waitSideEffectDuration); - - final savedMem = (await dbA.select( - defTableMems, - where: "${defColMemsName.name} = ?", - whereArgs: [insertedMemName], - )) - .single; - final savedMemNotification = (await dbA.select( - defTableMemNotifications, - where: "${defFkMemNotificationsMemId.name} = ?", - whereArgs: [savedMem[defPkId.name]], - )); - expect( - savedMemNotification[0][defColMemNotificationsTime.name], - 0, - ); - expect( - savedMemNotification[1][defColMemNotificationsTime.name], - 1, - ); - }, - ); + testWidgets('create.', (widgetTester) async { + widgetTester + .ignoreMockMethodCallHandler(MethodChannelMock.permissionHandler); + + await runApplication(); + await widgetTester.pumpAndSettle(); + await widgetTester.tap(newMemFabFinder); + await widgetTester.pumpAndSettle(); + const enteringMemName = "$baseMemName - entering"; + await widgetTester.enterText(find.byKey(keyMemName), enteringMemName); + + final notificationAddFinder = find.descendant( + of: find.byKey(keyMemNotificationsView), + matching: find.byIcon(Icons.notification_add)); + await widgetTester.tap(notificationAddFinder); + await widgetTester.pumpAndSettle(defaultTransitionDuration); + + await widgetTester.tap(timeIconFinder); + await widgetTester.pump(); + + await widgetTester.tap(okFinder); + await widgetTester.pump(); + + await widgetTester.pageBack(); + await widgetTester.pumpAndSettle(); + await widgetTester.tap(find.byKey(keySaveMemFab)); + await widgetTester.pumpAndSettle(); + + final savedMem = (await dbA.select(defTableMems, + where: "${defColMemsName.name} = ?", + whereArgs: [enteringMemName])) + .single; + final savedMemNotifications = (await dbA.select( + defTableMemNotifications, + where: "${defFkMemNotificationsMemId.name} = ?", + whereArgs: [savedMem[defPkId.name]])); + final repeat = savedMemNotifications.singleWhere((e) => + e[defColMemNotificationsType.name] == + MemNotificationType.repeat.name); + expect(repeat[defColMemNotificationsTime.name], 0); + }); + + testWidgets(": update.", (widgetTester) async { + widgetTester + .ignoreMockMethodCallHandler(MethodChannelMock.permissionHandler); + + await runApplication(); + await widgetTester.pumpAndSettle(); + await widgetTester.tap(find.text(insertedMemName)); + await widgetTester.pumpAndSettle(); + + final notificationAddFinder = find.descendant( + of: find.byKey(keyMemNotificationsView), + matching: find.byIcon(Icons.edit)); + await widgetTester.dragUntilVisible(notificationAddFinder, + find.byType(SingleChildScrollView), const Offset(0, 50)); + await widgetTester.tap(notificationAddFinder); + await widgetTester.pumpAndSettle(defaultTransitionDuration); + + await widgetTester.tap(find.descendant( + of: find.byKey(keyMemRepeatedNotification), + matching: find.byIcon(Icons.clear))); + await widgetTester.pump(); + + await widgetTester.tap(timeIconFinder); + await widgetTester.pump(); + await widgetTester.tap(okFinder); + await widgetTester.pump(); + + await widgetTester.pageBack(); + await widgetTester.pumpAndSettle(); + await widgetTester.tap(find.byKey(keySaveMemFab)); + await widgetTester.pump(waitSideEffectDuration); + + final savedMemId = (await dbA.select(defTableMems, + where: "${defColMemsName.name} = ?", + whereArgs: [insertedMemName])) + .single[defPkId.name]; + final savedMemNotifications = (await dbA.select( + defTableMemNotifications, + where: "${defFkMemNotificationsMemId.name} = ?", + whereArgs: [savedMemId], + orderBy: "id ASC")); + expect(savedMemNotifications, hasLength(2)); + expect(savedMemNotifications[0][defColMemNotificationsType.name], + MemNotificationType.repeat.name); + expect(savedMemNotifications[0][defColMemNotificationsTime.name], 0); + expect(savedMemNotifications[1][defColMemNotificationsType.name], + MemNotificationType.repeatByNDay.name); + expect(savedMemNotifications[1][defColMemNotificationsTime.name], 1); }); - }, - ); + }); + }); diff --git a/integration_test/scenarios/helpers.dart b/integration_test/scenarios/helpers.dart index 1732d0c59..c40d226b1 100644 --- a/integration_test/scenarios/helpers.dart +++ b/integration_test/scenarios/helpers.dart @@ -76,6 +76,7 @@ const waitShowSoftwareKeyboardDuration = Duration(milliseconds: 400); const waitSideEffectDuration = Duration(milliseconds: 700); final zeroDate = DateTime(0); const datePlaceHolder = "M/d/y"; +const maxRetryCount = 5; // Value builder int randomInt([int max = 42949671]) => Random().nextInt(max); diff --git a/integration_test/scenarios/memo/detail_scenarios.dart b/integration_test/scenarios/memo/detail_scenarios.dart index 58cf4dab1..e2653078f 100644 --- a/integration_test/scenarios/memo/detail_scenarios.dart +++ b/integration_test/scenarios/memo/detail_scenarios.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:mem/components/mem/mem_name.dart'; -import 'package:mem/core/mem_item.dart'; +import 'package:mem/mems/mem_item.dart'; import 'package:mem/databases/definition.dart'; import 'package:mem/databases/table_definitions/base.dart'; import 'package:mem/databases/table_definitions/mem_items.dart'; @@ -91,6 +91,11 @@ void main() => group( testWidgets( ": create.", (widgetTester) async { + widgetTester.ignoreMockMethodCallHandler( + MethodChannelMock.permissionHandler); + widgetTester.ignoreMockMethodCallHandler( + MethodChannelMock.flutterLocalNotifications); + await runApplication(); await widgetTester.pumpAndSettle(); await widgetTester.tap(newMemFabFinder); @@ -103,27 +108,22 @@ void main() => group( enteringMemName, ); await widgetTester.enterText( - find.byKey(keyMemMemo), - enteringMemMemo, - ); + find.byKey(keyMemMemo), enteringMemMemo); await widgetTester.tap(find.byKey(keySaveMemFab)); await widgetTester.pump(); - expect( - find.text(l10n.saveMemSuccessMessage(enteringMemName)), - findsOneWidget, - ); + expect(find.text(l10n.saveMemSuccessMessage(enteringMemName)), + findsOneWidget); - final getCreatedMem = - Equals(defColMemsName.name, enteringMemName); + final getCreatedMem = Equals(defColMemsName, enteringMemName); final mems = await dbA.select(defTableMems, where: getCreatedMem.where(), whereArgs: getCreatedMem.whereArgs()); expect(mems.length, 1); final getCreatedMemItem = And([ - Equals(defFkMemItemsMemId.name, mems[0][defPkId.name]), - Equals(defColMemItemsType.name, MemItemType.memo.name), - Equals(defColMemItemsValue.name, enteringMemMemo) + Equals(defFkMemItemsMemId, mems[0][defPkId.name]), + Equals(defColMemItemsType, MemItemType.memo.name), + Equals(defColMemItemsValue, enteringMemMemo) ]); final memItems = await dbA.select(defTableMemItems, where: getCreatedMemItem.where(), @@ -140,37 +140,29 @@ void main() => group( testWidgets( ': update.', (widgetTester) async { + widgetTester.ignoreMockMethodCallHandler( + MethodChannelMock.permissionHandler); + widgetTester.ignoreMockMethodCallHandler( + MethodChannelMock.flutterLocalNotifications); + await runApplication(); await widgetTester.pumpAndSettle(); - await widgetTester.tap(find.text(insertedMemName)); await widgetTester.pumpAndSettle(); await widgetTester.tap(memNameOnDetailPageFinder); await widgetTester.pump(waitShowSoftwareKeyboardDuration); - const enteringMemNameText = - '$_scenarioName: Save: Update - mem name - entering'; + "$_scenarioName: Save: Update - mem name - entering"; await widgetTester.enterText( - memNameOnDetailPageFinder, - enteringMemNameText, - ); + memNameOnDetailPageFinder, enteringMemNameText); await widgetTester.pumpAndSettle(); expect(find.text(enteringMemNameText), findsOneWidget); await widgetTester.tap(saveMemFabFinder); await widgetTester.pumpAndSettle(); - const saveSuccessText = 'Save success. $enteringMemNameText'; - expect( - find.text(saveSuccessText), - findsOneWidget, - ); - - await widgetTester.pumpAndSettle(defaultDismissDuration); - expect( - find.text(saveSuccessText), - findsNothing, - ); + const saveSuccessText = "Save success. $enteringMemNameText"; + expect(find.text(saveSuccessText), findsOneWidget); await widgetTester.pageBack(); await widgetTester.pumpAndSettle(); @@ -184,58 +176,49 @@ void main() => group( }, ); - testWidgets( - 'twice on create.', - retry: 3, - (widgetTester) async { - await runApplication(); - await widgetTester.pumpAndSettle(); - await widgetTester.tap(newMemFabFinder); - await widgetTester.pumpAndSettle(); - const enteringMemName = - "$saveMemName: twice on create - entering"; - const enteringMemMemo = - "$saveMemMemo: twice on create - entering"; - await widgetTester.enterText( - find.byKey(keyMemName), - enteringMemName, - ); - await widgetTester.enterText( - find.byKey(keyMemMemo), - enteringMemMemo, - ); - await widgetTester.tap(find.byKey(keySaveMemFab)); - await widgetTester.pumpAndSettle(waitSideEffectDuration); - - const enteringMemMemo2 = "$enteringMemMemo - 2"; - await widgetTester.enterText( - find.byKey(keyMemMemo), - enteringMemMemo2, - ); - await widgetTester.tap(find.byKey(keySaveMemFab)); - await widgetTester.pumpAndSettle(waitSideEffectDuration); - - final getCreatedMem = - Equals(defColMemsName.name, enteringMemName); - final mems = await dbA.select( - defTableMems, + testWidgets(': twice on create.', retry: maxRetryCount, + (widgetTester) async { + widgetTester.ignoreMockMethodCallHandler( + MethodChannelMock.permissionHandler); + widgetTester.ignoreMockMethodCallHandler( + MethodChannelMock.flutterLocalNotifications); + + await runApplication(); + await widgetTester.pumpAndSettle(); + await widgetTester.tap(newMemFabFinder); + await widgetTester.pumpAndSettle(); + const enteringMemName = + "$saveMemName: twice on create - entering"; + const enteringMemMemo = + "$saveMemMemo: twice on create - entering"; + await widgetTester.enterText( + find.byKey(keyMemName), enteringMemName); + await widgetTester.enterText( + find.byKey(keyMemMemo), enteringMemMemo); + await widgetTester.tap(find.byKey(keySaveMemFab)); + await widgetTester.pumpAndSettle(waitSideEffectDuration); + + const enteringMemMemo2 = "$enteringMemMemo - 2"; + await widgetTester.enterText( + find.byKey(keyMemMemo), enteringMemMemo2); + await widgetTester.tap(find.byKey(keySaveMemFab)); + await widgetTester.pumpAndSettle(waitSideEffectDuration); + + final getCreatedMem = Equals(defColMemsName, enteringMemName); + final mems = await dbA.select(defTableMems, where: getCreatedMem.where(), - whereArgs: getCreatedMem.whereArgs(), - ); - expect(mems.length, 1); - final getCreatedMemItem = And([ - Equals(defFkMemItemsMemId.name, mems[0][defPkId.name]), - Equals(defColMemItemsType.name, MemItemType.memo.name), - ]); - final memItems = await dbA.select(defTableMemItems, - where: getCreatedMemItem.where(), - whereArgs: getCreatedMemItem.whereArgs()); - expect( - memItems.single[defColMemItemsValue.name], - enteringMemMemo2, - ); - }, - ); + whereArgs: getCreatedMem.whereArgs()); + expect(mems.length, 1); + final getCreatedMemItem = And([ + Equals(defFkMemItemsMemId, mems[0][defPkId.name]), + Equals(defColMemItemsType, MemItemType.memo.name) + ]); + final memItems = await dbA.select(defTableMemItems, + where: getCreatedMemItem.where(), + whereArgs: getCreatedMemItem.whereArgs()); + expect( + memItems.single[defColMemItemsValue.name], enteringMemMemo2); + }); group(': Archive', () { const insertedMemName = "$saveMemName: Archive: inserted"; @@ -243,94 +226,84 @@ void main() => group( const archivedMemName = "$insertedMemName - archived"; setUp(() async { - await dbA.insert( - defTableMems, - { - defColMemsName.name: unarchivedMemName, - defColCreatedAt.name: DateTime.now(), - }, - ); - await dbA.insert( - defTableMems, - { - defColMemsName.name: archivedMemName, - defColCreatedAt.name: DateTime.now(), - defColArchivedAt.name: DateTime.now(), - }, - ); + final unarchivedMemId = await dbA.insert(defTableMems, { + defColMemsName.name: unarchivedMemName, + defColCreatedAt.name: DateTime.now(), + }); + await dbA.insert(defTableMemItems, { + defFkMemItemsMemId.name: unarchivedMemId, + defColMemItemsType.name: MemItemType.memo.name, + defColMemItemsValue.name: insertedMemMemo, + defColCreatedAt.name: zeroDate, + }); + final archivedMemId = await dbA.insert(defTableMems, { + defColMemsName.name: archivedMemName, + defColCreatedAt.name: DateTime.now(), + defColArchivedAt.name: DateTime.now(), + }); + await dbA.insert(defTableMemItems, { + defFkMemItemsMemId.name: archivedMemId, + defColMemItemsType.name: MemItemType.memo.name, + defColMemItemsValue.name: insertedMemMemo, + defColCreatedAt.name: zeroDate, + }); }); - testWidgets( - ": archive.", - (widgetTester) async { - await runApplication(); - await widgetTester.pumpAndSettle(); - await widgetTester.tap(find.text(unarchivedMemName)); - await widgetTester.pumpAndSettle(); - - await widgetTester.tap(find.byIcon(Icons.more_vert)); - await widgetTester.pumpAndSettle(); - - await widgetTester.tap(find.byIcon(Icons.archive)); - await widgetTester.pumpAndSettle(); - - expect( - find.text(unarchivedMemName), - findsNothing, - ); - expect( + testWidgets(": archive.", (widgetTester) async { + await runApplication(); + await widgetTester.pumpAndSettle(); + await widgetTester.tap(find.text(unarchivedMemName)); + await widgetTester.pumpAndSettle(); + + await widgetTester.tap(find.byIcon(Icons.more_vert)); + await widgetTester.pumpAndSettle(); + + await widgetTester.tap(find.byIcon(Icons.archive)); + await widgetTester.pumpAndSettle(); + + expect(find.text(unarchivedMemName), findsNothing); + expect( find.text(l10n.archiveMemSuccessMessage(unarchivedMemName)), - findsOneWidget, - ); + findsOneWidget); - final findUnarchivedMem = - Equals(defColMemsName.name, unarchivedMemName); - final mems = await dbA.select( - defTableMems, + final findUnarchivedMem = + Equals(defColMemsName, unarchivedMemName); + final mems = await dbA.select(defTableMems, where: findUnarchivedMem.where(), - whereArgs: findUnarchivedMem.whereArgs(), - ); - expect(mems.length, 1); - expect(mems.single[defColArchivedAt.name], isNotNull); - }, - ); + whereArgs: findUnarchivedMem.whereArgs()); + expect(mems.length, 1); + expect(mems.single[defColArchivedAt.name], isNotNull); + }); - testWidgets( - ": unarchive.", - (widgetTester) async { - await runApplication(); - await widgetTester.pumpAndSettle(); - await widgetTester.tap(filterListIconFinder); - await widgetTester.pumpAndSettle(); - await widgetTester.tap(showArchiveSwitchFinder); - await widgetTester.pumpAndSettle(); - await closeMemListFilter(widgetTester); - await widgetTester.pumpAndSettle(); - await widgetTester.tap(find.text(archivedMemName)); - await widgetTester.pumpAndSettle(); - - await widgetTester.tap(find.byIcon(Icons.more_vert)); - await widgetTester.pumpAndSettle(); - - await widgetTester.tap(find.byIcon(Icons.unarchive)); - await widgetTester.pumpAndSettle(); - - expect( + testWidgets(": unarchive.", (widgetTester) async { + await runApplication(); + await widgetTester.pumpAndSettle(); + await widgetTester.tap(filterListIconFinder); + await widgetTester.pumpAndSettle(); + await widgetTester.tap(showArchiveSwitchFinder); + await widgetTester.pumpAndSettle(); + await closeMemListFilter(widgetTester); + await widgetTester.pumpAndSettle(); + await widgetTester.tap(find.text(archivedMemName)); + await widgetTester.pumpAndSettle(); + + await widgetTester.tap(find.byIcon(Icons.more_vert)); + await widgetTester.pumpAndSettle(); + + await widgetTester.tap(find.byIcon(Icons.unarchive)); + await widgetTester.pumpAndSettle(); + + expect( find.text(l10n.unarchiveMemSuccessMessage(archivedMemName)), - findsOneWidget, - ); + findsOneWidget); - final findArchivedMem = - Equals(defColMemsName.name, archivedMemName); - final mems = await dbA.select( - defTableMems, + final findArchivedMem = Equals(defColMemsName, archivedMemName); + final mems = await dbA.select(defTableMems, where: findArchivedMem.where(), - whereArgs: findArchivedMem.whereArgs(), - ); - expect(mems.length, 1); - expect(mems.single[defColArchivedAt.name], isNull); - }, - ); + whereArgs: findArchivedMem.whereArgs()); + expect(mems.length, 1); + expect(mems.single[defColArchivedAt.name], isNull); + }); }); }, ); @@ -341,7 +314,7 @@ void main() => group( Future>> selectFromMemsWhereName( String name, ) async { - final where = Equals(defColMemsName.name, name); + final where = Equals(defColMemsName, name); return await dbA.select( defTableMems, where: where.where(), diff --git a/integration_test/scenarios/memo/list_scenarios.dart b/integration_test/scenarios/memo/list_scenarios.dart index e33bbd96f..5589923f1 100644 --- a/integration_test/scenarios/memo/list_scenarios.dart +++ b/integration_test/scenarios/memo/list_scenarios.dart @@ -4,7 +4,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:mem/components/mem/mem_name.dart'; -import 'package:mem/core/mem_item.dart'; +import 'package:mem/mems/mem_item.dart'; import 'package:mem/databases/definition.dart'; import 'package:mem/databases/table_definitions/base.dart'; import 'package:mem/databases/table_definitions/mem_items.dart'; diff --git a/integration_test/scenarios/notification_scenario.dart b/integration_test/scenarios/notification_scenario.dart index 0a840c8b6..6987bc85e 100644 --- a/integration_test/scenarios/notification_scenario.dart +++ b/integration_test/scenarios/notification_scenario.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; -import 'package:mem/core/mem_notification.dart'; +import 'package:mem/mems/mem_notification.dart'; import 'package:mem/databases/definition.dart'; import 'package:mem/databases/table_definitions/acts.dart'; import 'package:mem/databases/table_definitions/base.dart'; diff --git a/integration_test/scenarios/settings/backup_scenario.dart b/integration_test/scenarios/settings/backup_scenario.dart index 85c490721..cf609d099 100644 --- a/integration_test/scenarios/settings/backup_scenario.dart +++ b/integration_test/scenarios/settings/backup_scenario.dart @@ -48,7 +48,7 @@ void main() => group( testWidgets( "create.", - retry: 3, + retry: maxRetryCount, (widgetTester) async { String? result; switch (defaultTargetPlatform) { diff --git a/integration_test/scenarios/settings/settings_scenario.dart b/integration_test/scenarios/settings/settings_scenario.dart index 08b82e0af..32995b26a 100644 --- a/integration_test/scenarios/settings/settings_scenario.dart +++ b/integration_test/scenarios/settings/settings_scenario.dart @@ -88,7 +88,7 @@ void main() => group( "Start of day", () { setUp(() async { - await PreferenceClient().discard(startOfDayKey); + await PreferenceClientRepository().discard(startOfDayKey); }); testWidgets( @@ -120,49 +120,40 @@ void main() => group( timeText(zeroDate), ); expect( - (await PreferenceClient().shipByKey(startOfDayKey)).value, + (await PreferenceClientRepository().shipByKey(startOfDayKey)) + .value, TimeOfDay.fromDateTime(zeroDate), ); }, ); - group( - "with saved", - () { - final now = DateTime.now(); - setUp(() async { - await PreferenceClient().receive(Preference( - startOfDayKey, - TimeOfDay.fromDateTime(now), - )); - }); - - testWidgets( - "show saved.", - (widgetTester) async { - await runApplication(); - await widgetTester.pumpAndSettle(); - - await widgetTester.tap(drawerIconFinder); - await widgetTester.pumpAndSettle(); - await widgetTester.tap(find.text(l10n.settingsPageTitle)); - await widgetTester.pumpAndSettle(); - - expect( - widgetTester - .widget(find - .descendant( + group('with saved', () { + final now = DateTime.now(); + setUp(() async { + await PreferenceClientRepository().receive(PreferenceEntity( + startOfDayKey, TimeOfDay.fromDateTime(now))); + }); + + testWidgets('show saved.', (widgetTester) async { + await runApplication(); + await widgetTester.pumpAndSettle(); + + await widgetTester.tap(drawerIconFinder); + await widgetTester.pumpAndSettle(); + await widgetTester.tap(find.text(l10n.settingsPageTitle)); + await widgetTester.pumpAndSettle(); + + expect( + widgetTester + .widget(find + .descendant( of: find.byType(SettingsTile), - matching: find.byType(Text), - ) - .at(1)) - .data, - timeText(now), - ); - }, - ); - }, - ); + matching: find.byType(Text)) + .at(1)) + .data, + timeText(now)); + }); + }); }, ); diff --git a/integration_test/scenarios/task_scenario.dart b/integration_test/scenarios/task_scenario.dart index b4878f0b6..999c72781 100644 --- a/integration_test/scenarios/task_scenario.dart +++ b/integration_test/scenarios/task_scenario.dart @@ -9,7 +9,6 @@ import 'package:mem/databases/table_definitions/base.dart'; import 'package:mem/databases/table_definitions/mems.dart'; import 'package:mem/framework/database/accessor.dart'; import 'package:mem/databases/definition.dart'; -import 'package:mem/framework/repository/database_tuple_repository.dart'; import 'package:mem/logger/log.dart'; import 'package:mem/logger/log_service.dart'; import 'package:mem/notifications/notification_client.dart'; @@ -341,70 +340,63 @@ void testTaskScenario() => group(': $_scenarioName', () { }, ); - testWidgets( - ": start is not all day, end is all day.", - (widgetTester) async { - await PreferenceClient().receive(Preference( - startOfDayKey, - const TimeOfDay(hour: 1, minute: 0), - )); + testWidgets(': start is not all day, end is all day.', + (widgetTester) async { + widgetTester.ignoreMockMethodCallHandler( + MethodChannelMock.permissionHandler); - await runApplication(); - await widgetTester.pumpAndSettle(); - await widgetTester.tap(newMemFabFinder); - await widgetTester.pumpAndSettle(); - await widgetTester.tap(calendarIconFinder.at(0)); - await widgetTester.pumpAndSettle(); - await widgetTester.tap(find.byIcon(Icons.chevron_right).at(0)); - await widgetTester.pumpAndSettle(); - const pickingDate = 1; - await widgetTester.tap(find.text("$pickingDate")); - await widgetTester.tap(okFinder); - await widgetTester.pumpAndSettle(); + await PreferenceClientRepository().receive(PreferenceEntity( + startOfDayKey, const TimeOfDay(hour: 1, minute: 0))); - await widgetTester.tap(find.byType(Switch).at(0)); - await widgetTester.pumpAndSettle(); + await runApplication(); + await widgetTester.pumpAndSettle(); + await widgetTester.tap(newMemFabFinder); + await widgetTester.pumpAndSettle(); + await widgetTester.tap(calendarIconFinder.at(0)); + await widgetTester.pumpAndSettle(); + await widgetTester.tap(find.byIcon(Icons.chevron_right).at(0)); + await widgetTester.pumpAndSettle(); + const pickingDate = 1; + await widgetTester.tap(find.text("$pickingDate")); + await widgetTester.tap(okFinder); + await widgetTester.pumpAndSettle(); - await widgetTester.tap(okFinder); - await widgetTester.pumpAndSettle(); + await widgetTester.tap(find.byType(Switch).at(0)); + await widgetTester.pumpAndSettle(); - await widgetTester.tap(calendarIconFinder.at(1)); - await widgetTester.pumpAndSettle(); + await widgetTester.tap(okFinder); + await widgetTester.pumpAndSettle(); - await widgetTester.tap(find.text("$pickingDate")); - await widgetTester.tap(okFinder); - await widgetTester.pumpAndSettle(); + await widgetTester.tap(calendarIconFinder.at(1)); + await widgetTester.pumpAndSettle(); - final now = DateTime.now(); - final endDate = DateTime(now.year, now.month + 1, pickingDate); - expect( + await widgetTester.tap(find.text("$pickingDate")); + await widgetTester.tap(okFinder); + await widgetTester.pumpAndSettle(); + + final now = DateTime.now(); + final endDate = DateTime(now.year, now.month + 1, pickingDate); + expect( (widgetTester.widget(find.byType(TextFormField).at(3)) as TextFormField) .initialValue, - dateText(endDate), - ); + dateText(endDate)); - const enteringMemName = - "$_scenarioName: Save Period: start is not all day, end is all day - mem name - entering"; - await widgetTester.enterText( - memNameOnDetailPageFinder, - enteringMemName, - ); - await widgetTester.tap(saveMemFabFinder); - await widgetTester.pumpAndSettle(); + const enteringMemName = + "$_scenarioName: Save Period: start is not all day, end is all day - mem name - entering"; + await widgetTester.enterText( + memNameOnDetailPageFinder, enteringMemName); + await widgetTester.tap(saveMemFabFinder); + await widgetTester.pumpAndSettle(); - final savedMems = (await dbA.select( - defTableMems, + await PreferenceClientRepository().discard(startOfDayKey); + + final savedMems = (await dbA.select(defTableMems, where: "${defColMemsName.name} = ?", - whereArgs: [enteringMemName], - )); - expect(savedMems, hasLength(1)); - expect( - savedMems.single[defColMemsEndOn.name], - equals(endDate), - ); - }, - ); + whereArgs: [enteringMemName])); + expect(savedMems, hasLength(1)); + expect(savedMems.single[defColMemsEndOn.name], equals(endDate)); + }); }, ); @@ -425,8 +417,6 @@ void testTaskScenario() => group(': $_scenarioName', () { testWidgets( 'not notify on active act.', (widgetTester) async { - DatabaseTupleRepository.databaseAccessor = dbA; - widgetTester.setMockMethodCallHandler( MethodChannelMock.flutterLocalNotifications, [], diff --git a/integration_test/scenarios/todo_scenario.dart b/integration_test/scenarios/todo_scenario.dart index 9b75398da..efad822bd 100644 --- a/integration_test/scenarios/todo_scenario.dart +++ b/integration_test/scenarios/todo_scenario.dart @@ -6,7 +6,6 @@ import 'package:mem/databases/table_definitions/base.dart'; import 'package:mem/databases/table_definitions/mems.dart'; import 'package:mem/framework/database/accessor.dart'; import 'package:mem/databases/definition.dart'; -import 'package:mem/framework/repository/database_tuple_repository.dart'; import 'package:mem/logger/log.dart'; import 'package:mem/logger/log_service.dart'; import 'package:mem/notifications/notification_client.dart'; @@ -189,8 +188,6 @@ void testTodoScenario() => group(': $_scenarioName', () { testWidgets( 'not notify on done mem.', (widgetTester) async { - DatabaseTupleRepository.databaseAccessor = dbA; - int initializeCount = 0; int cancelCount = 0; widgetTester.setMockMethodCallHandler( diff --git a/lib/act_counter/act_counter.dart b/lib/act_counter/act_counter.dart index 5837e21a6..2d3c66478 100644 --- a/lib/act_counter/act_counter.dart +++ b/lib/act_counter/act_counter.dart @@ -1,39 +1,50 @@ import 'package:collection/collection.dart'; +import 'package:mem/act_counter/home_widget.dart'; -import 'package:mem/core/act.dart'; import 'package:mem/core/date_and_time/date_and_time.dart'; import 'package:mem/core/date_and_time/date_and_time_period.dart'; -import 'package:mem/framework/repository/entity.dart'; -import 'package:mem/repositories/mem.dart'; +import 'package:mem/acts/act_entity.dart'; +import 'package:mem/mems/mem_entity.dart'; + +class ActCounter implements HomeWidget { + @override + String get methodChannelName => 'zin.playground.mem/act_counter'; + + @override + String get initializeMethodName => 'initialize'; + + @override + String get widgetProviderName => 'ActCounterProvider'; -class ActCounter extends EntityV1 { - final SavedMem _mem; - final Iterable _acts; final int memId; final String? name; final int? actCount; - final SavedAct? lastAct; + final DateTime? updatedAt; - ActCounter(this._mem, this._acts) - : memId = _mem.id, - name = _mem.name, - actCount = _acts.length, - lastAct = _acts - .sorted( - (a, b) => (a.updatedAt ?? a.createdAt) - .compareTo(b.updatedAt ?? b.createdAt), - ) - .lastOrNull; + ActCounter(this.memId, this.name, this.actCount, this.updatedAt); - Map widgetData() => { - "memName-$memId": name, - "actCount-$memId": actCount, - "lastUpdatedAtSeconds-$memId": lastAct == null - ? null - : (lastAct?.period.end ?? lastAct?.period.start!) - ?.millisecondsSinceEpoch - .toDouble(), - }; + ActCounter.from( + SavedMemEntity savedMem, + Iterable savedActs, + ) : memId = savedMem.id, + name = savedMem.name, + actCount = savedActs.length, + updatedAt = savedActs + .sorted( + (a, b) => (a.updatedAt ?? a.createdAt) + .compareTo(b.updatedAt ?? b.createdAt), + ) + .lastOrNull + ?.period + .end ?? + savedActs + .sorted( + (a, b) => (a.updatedAt ?? a.createdAt) + .compareTo(b.updatedAt ?? b.createdAt), + ) + .lastOrNull + ?.period + .start; static DateAndTimePeriod period(DateAndTime startDate) { int startHour = 5; @@ -59,17 +70,3 @@ class ActCounter extends EntityV1 { ); } } - -class InitializedActCounter extends ActCounter { - final int homeWidgetId; - - InitializedActCounter(this.homeWidgetId, ActCounter actCounter) - : super(actCounter._mem, actCounter._acts); - - @override - Map widgetData() => super.widgetData() - ..putIfAbsent( - "memId-$homeWidgetId", - () => memId, - ); -} diff --git a/lib/act_counter/act_counter_client.dart b/lib/act_counter/act_counter_client.dart index 45dcb47ee..9f711073c 100644 --- a/lib/act_counter/act_counter_client.dart +++ b/lib/act_counter/act_counter_client.dart @@ -1,8 +1,9 @@ -import 'package:mem/acts/act_repository.dart'; +import 'package:mem/act_counter/act_counter_entity.dart'; import 'package:mem/acts/client.dart'; import 'package:mem/core/date_and_time/date_and_time.dart'; import 'package:mem/logger/log_service.dart'; -import 'package:mem/repositories/mem_repository.dart'; +import 'package:mem/acts/act_repository.dart'; +import 'package:mem/mems/mem_repository.dart'; import 'act_counter.dart'; import 'act_counter_repository.dart'; @@ -14,16 +15,20 @@ class ActCounterClient { final ActCounterRepository _actCounterRepository; Future createNew(int memId) => v( - () async => await _actCounterRepository.receive( - ActCounter( - await _memRepository.shipById(memId), - await _actRepository.ship( - memId: memId, - period: ActCounter.period(DateAndTime.now()), + () async { + await _actCounterRepository.receive( + ActCounterEntity.from( + await _memRepository.ship(id: memId).then((v) => v.single), + await _actRepository.ship( + memId: memId, + period: ActCounter.period(DateAndTime.now()), + ), ), - ), - ), - {'memId': memId}, + ); + }, + { + 'memId': memId, + }, ); Future increment( @@ -38,8 +43,8 @@ class ActCounterClient { ); await _actCounterRepository.replace( - ActCounter( - await _memRepository.shipById(memId), + ActCounterEntity.from( + await _memRepository.ship(id: memId).then((v) => v.single), await _actRepository.ship( memId: memId, period: ActCounter.period(when), diff --git a/lib/act_counter/act_counter_entity.dart b/lib/act_counter/act_counter_entity.dart new file mode 100644 index 000000000..a219c519c --- /dev/null +++ b/lib/act_counter/act_counter_entity.dart @@ -0,0 +1,45 @@ +import 'package:mem/act_counter/act_counter.dart'; +import 'package:mem/framework/repository/entity.dart'; +import 'package:mem/framework/repository/home_widget_entity.dart'; + +class ActCounterEntity extends ActCounter with Entity, HomeWidgetEntity { + ActCounterEntity(super.memId, super.name, super.actCount, super.updatedAt) + : super(); + + @override + Map get toMap => { + 'initializeMethodName': initializeMethodName, + 'methodChannelName': methodChannelName, + 'memId': memId, + 'name': name, + 'actCount': actCount, + 'updatedAt': updatedAt, + }; + + @override + Map get toWidgetData => { + "memName-$memId": name, + "actCount-$memId": actCount, + "lastUpdatedAtSeconds-$memId": + updatedAt?.millisecondsSinceEpoch.toDouble(), + }; + + ActCounterEntity.from(super.savedMem, super.savedActs) : super.from(); +} + +class SavedActCounterEntity extends ActCounterEntity + with SavedHomeWidgetEntity { + SavedActCounterEntity.fromMap(Map map) + : super( + map['memId'], + map['name'], + map['actCount'], + map['updatedAt'], + ); + + @override + Map get toWidgetData => super.toWidgetData + ..addAll({ + "memId-$homeWidgetId": memId, + }); +} diff --git a/lib/act_counter/act_counter_repository.dart b/lib/act_counter/act_counter_repository.dart index 9a6ccd809..d8e2fa402 100644 --- a/lib/act_counter/act_counter_repository.dart +++ b/lib/act_counter/act_counter_repository.dart @@ -1,60 +1,9 @@ -import 'package:flutter/foundation.dart'; -import 'package:mem/framework/repository/repository.dart'; -import 'package:mem/logger/log_service.dart'; - -import 'act_counter.dart'; -import 'home_widget_accessor.dart'; - -// TODO constantsとして別で定義する -// see android\app\src\main\kotlin\zin\playground\mem\ActCounterConfigure.kt -const methodChannelName = 'zin.playground.mem/act_counter'; -const initializeMethodName = 'initialize'; -const widgetProviderName = 'ActCounterProvider'; - -class ActCounterRepository extends RepositoryV1 { - final HomeWidgetAccessor? _homeWidgetAccessor; +import 'package:mem/act_counter/act_counter_entity.dart'; +import 'package:mem/framework/repository/home_widget_repository.dart'; +class ActCounterRepository + extends HomeWidgetRepository { @override - Future receive(ActCounter entity) => v( - () async { - final homeWidgetId = await _homeWidgetAccessor?.initialize( - methodChannelName, - initializeMethodName, - ); - - if (homeWidgetId != null) { - InitializedActCounter(homeWidgetId, entity) - .widgetData() - .forEach((key, value) async { - await _homeWidgetAccessor?.saveWidgetData(key, value); - }); - - await _homeWidgetAccessor?.updateWidget(widgetProviderName); - } - }, - entity, - ); - - Future replace(ActCounter entity) => v( - () async { - entity.widgetData().forEach((key, value) async { - await _homeWidgetAccessor?.saveWidgetData(key, value); - }); - - await _homeWidgetAccessor?.updateWidget(widgetProviderName); - - return entity; - }, - entity, - ); - - ActCounterRepository._(this._homeWidgetAccessor); - - static ActCounterRepository? _instance; - - factory ActCounterRepository() => _instance ??= ActCounterRepository._( - defaultTargetPlatform == TargetPlatform.android - ? HomeWidgetAccessor() - : null, - ); + SavedActCounterEntity pack(Map map) => + SavedActCounterEntity.fromMap(map)..homeWidgetId = map['homeWidgetId']; } diff --git a/lib/act_counter/home_widget.dart b/lib/act_counter/home_widget.dart new file mode 100644 index 000000000..7cdb9f1f8 --- /dev/null +++ b/lib/act_counter/home_widget.dart @@ -0,0 +1,7 @@ +abstract class HomeWidget { + String get methodChannelName; + + String get initializeMethodName; + + String get widgetProviderName; +} diff --git a/lib/act_counter/single_selectable_mem_list_item.dart b/lib/act_counter/single_selectable_mem_list_item.dart index d6af0b86e..079819bac 100644 --- a/lib/act_counter/single_selectable_mem_list_item.dart +++ b/lib/act_counter/single_selectable_mem_list_item.dart @@ -4,7 +4,7 @@ import 'package:mem/components/mem/list/states.dart'; import 'package:mem/components/mem/mem_name.dart'; import 'package:mem/components/mem/mem_period.dart'; import 'package:mem/logger/log_service.dart'; -import 'package:mem/repositories/mem.dart'; +import 'package:mem/mems/mem_entity.dart'; import 'states.dart'; @@ -31,7 +31,7 @@ class SingleSelectableMemListItem extends ConsumerWidget { class _SingleSelectableMemListItemComponent extends ListTile { _SingleSelectableMemListItemComponent( - SavedMem mem, + SavedMemEntity mem, bool isSelected, void Function(int? memId) onSelected, ) : super( diff --git a/lib/core/act.dart b/lib/acts/act.dart similarity index 56% rename from lib/core/act.dart rename to lib/acts/act.dart index 275417125..4dad2248a 100644 --- a/lib/core/act.dart +++ b/lib/acts/act.dart @@ -1,12 +1,10 @@ import 'dart:core'; -import 'package:mem/framework/repository/entity.dart'; -import 'package:mem/framework/repository/database_tuple_entity.dart'; import 'package:mem/logger/log_service.dart'; -import 'date_and_time/date_and_time_period.dart'; +import '../core/date_and_time/date_and_time_period.dart'; -class Act extends EntityV1 { +class Act { final int memId; final DateAndTimePeriod period; @@ -32,23 +30,4 @@ class Act extends EntityV1 { }, {'a': a, 'b': b}, ); - - @override - String toString() => "${super.toString()}: ${{ - "memId": memId, - "period": period, - }}"; -} - -class SavedAct extends Act with SavedDatabaseTupleMixin { - SavedAct(super.memId, super.period); - - SavedAct copiedWith(DateAndTimePeriod Function()? period) => SavedAct( - memId, - period == null ? this.period : period(), - ) - ..id = id - ..createdAt = createdAt - ..updatedAt = updatedAt - ..archivedAt = archivedAt; } diff --git a/lib/acts/act_entity.dart b/lib/acts/act_entity.dart new file mode 100644 index 000000000..d8b3f3150 --- /dev/null +++ b/lib/acts/act_entity.dart @@ -0,0 +1,69 @@ +import 'package:mem/acts/act.dart'; +import 'package:mem/core/date_and_time/date_and_time.dart'; +import 'package:mem/core/date_and_time/date_and_time_period.dart'; +import 'package:mem/databases/table_definitions/acts.dart'; +import 'package:mem/framework/repository/database_tuple_entity.dart'; +import 'package:mem/framework/repository/entity.dart'; + +class ActEntity extends Act with Entity, Copyable { + ActEntity(super.memId, super.period); + + ActEntity.fromMap(Map map) + : super( + map[defFkActsMemId.name], + DateAndTimePeriod( + start: DateAndTime.from( + map[defColActsStart.name], + timeOfDay: map[defColActsStartIsAllDay.name] + ? null + : map[defColActsStart.name], + ), + end: map[defColActsEnd.name] == null + ? null + : DateAndTime.from( + map[defColActsEnd.name], + timeOfDay: map[defColActsEndIsAllDay.name] + ? null + : map[defColActsEnd.name], + ), + ), + ); + + @override + ActEntity copiedWith({ + int Function()? memId, + DateAndTimePeriod Function()? period, + }) => + ActEntity( + memId == null ? this.memId : memId(), + period == null ? this.period : period(), + ); + + @override + Map get toMap => { + defFkActsMemId.name: memId, + defColActsStart.name: period.start, + defColActsStartIsAllDay.name: period.start?.isAllDay, + defColActsEnd.name: period.end, + defColActsEndIsAllDay.name: period.end?.isAllDay, + }; +} + +class SavedActEntity extends ActEntity with DatabaseTupleEntity { + SavedActEntity(super.memId, super.period, Map map) + : super() { + withMap(map); + } + + SavedActEntity.fromMap(Map map) : super.fromMap(map) { + withMap(map); + } + + @override + SavedActEntity copiedWith({ + int Function()? memId, + DateAndTimePeriod Function()? period, + }) => + SavedActEntity.fromMap( + toMap..addAll(super.copiedWith(memId: memId, period: period).toMap)); +} diff --git a/lib/acts/act_repository.dart b/lib/acts/act_repository.dart index 01ed42e52..00049571f 100644 --- a/lib/acts/act_repository.dart +++ b/lib/acts/act_repository.dart @@ -1,14 +1,15 @@ -import 'package:mem/core/act.dart'; -import 'package:mem/core/date_and_time/date_and_time.dart'; import 'package:mem/core/date_and_time/date_and_time_period.dart'; +import 'package:mem/databases/definition.dart'; import 'package:mem/databases/table_definitions/acts.dart'; +import 'package:mem/databases/table_definitions/base.dart'; +import 'package:mem/framework/repository/condition/conditions.dart'; import 'package:mem/framework/repository/condition/in.dart'; +import 'package:mem/framework/repository/database_tuple_repository.dart'; import 'package:mem/framework/repository/extra_column.dart'; import 'package:mem/framework/repository/group_by.dart'; import 'package:mem/framework/repository/order_by.dart'; import 'package:mem/logger/log_service.dart'; -import 'package:mem/framework/repository/database_tuple_repository.dart'; -import 'package:mem/framework/repository/condition/conditions.dart'; +import 'package:mem/acts/act_entity.dart'; enum ActOrderBy { descStart } @@ -24,32 +25,11 @@ extension _ActOrderByExt on ActOrderBy { } } -class ActRepository extends DatabaseTupleRepository { +class ActRepository extends DatabaseTupleRepository { + ActRepository() : super(databaseDefinition, defTableActs); + @override - Future findOneBy({ - int? memId, - bool? latest, - Condition? condition, - List? orderBy, - }) => - v( - () async => await super.findOneBy( - condition: And([ - if (memId != null) Equals(defFkActsMemId.name, memId), - if (condition != null) condition, // coverage:ignore-line - ]), - orderBy: [ - if (latest == true) ActOrderBy.descStart.toQuery, - if (orderBy != null) ...orderBy, // coverage:ignore-line - ], - ), - { - "memId": memId, - "latest": latest, - "condition": condition, - "orderBy": orderBy, - }, - ); + SavedActEntity pack(Map map) => SavedActEntity.fromMap(map); @override Future count({ @@ -60,26 +40,27 @@ class ActRepository extends DatabaseTupleRepository { () => super.count( condition: And( [ - if (memId != null) Equals(defFkActsMemId.name, memId), + if (memId != null) Equals(defFkActsMemId, memId), if (condition != null) condition, // coverage:ignore-line ], ), ), { - "memId": memId, - "condition": condition, + 'memId': memId, + 'condition': condition, }, ); @override - Future> ship({ + Future> ship({ int? memId, Iterable? memIdsIn, DateAndTimePeriod? period, bool? latestByMemIds, - ActOrderBy? actOrderBy, + bool? isActive, Condition? condition, GroupBy? groupBy, + ActOrderBy? actOrderBy, List? orderBy, int? offset, int? limit, @@ -88,11 +69,12 @@ class ActRepository extends DatabaseTupleRepository { () => super.ship( condition: And( [ - if (memId != null) Equals(defFkActsMemId.name, memId), + if (memId != null) Equals(defFkActsMemId, memId), if (memIdsIn != null) In(defFkActsMemId.name, memIdsIn), if (period != null) GraterThanOrEqual(defColActsStart, period.start), if (period != null) LessThan(defColActsStart, period.end), + if (isActive != null) IsNull(defColActsEnd.name), if (condition != null) condition, ], ), @@ -113,73 +95,30 @@ class ActRepository extends DatabaseTupleRepository { 'memId': memId, 'memIds': memIdsIn, 'period': period, - 'actOrderBy': actOrderBy, + 'latestByMemIds': latestByMemIds, + 'isActive': isActive, 'condition': condition, 'groupBy': groupBy, + 'actOrderBy': actOrderBy, 'orderBy': orderBy, 'offset': offset, 'limit': limit, }, ); - Future> shipActiveByMemId( - int memId, - ) => - v( - () async => await ship( - condition: And([ - Equals(defFkActsMemId.name, memId), - IsNull(defColActsEnd.name), - ]), - ), - { - "memId": memId, - }, - ); - @override - SavedAct pack(Map tuple) => SavedAct( - tuple[defFkActsMemId.name], - DateAndTimePeriod( - start: DateAndTime.from( - tuple[defColActsStart.name], - timeOfDay: tuple[defColActsStartIsAllDay.name] - ? null - : tuple[defColActsStart.name], + Future> waste({int? id, Condition? condition}) => v( + () => super.waste( + condition: And( + [ + if (id != null) Equals(defPkId, id), + if (condition != null) condition, // coverage:ignore-line + ], ), - end: tuple[defColActsEnd.name] == null - ? null - : DateAndTime.from( - tuple[defColActsEnd.name], - timeOfDay: tuple[defColActsEndIsAllDay.name] - ? null - : tuple[defColActsEnd.name], - ), ), - )..pack(tuple); - - @override - Map unpack(Act entity) { - final map = { - defFkActsMemId.name: entity.memId, - defColActsStart.name: entity.period.start, - defColActsStartIsAllDay.name: entity.period.start?.isAllDay, - defColActsEnd.name: entity.period.end, - defColActsEndIsAllDay.name: entity.period.end?.isAllDay, - }; - - if (entity is SavedAct) { - map.addAll(entity.unpack()); - } - - return map; - } - - ActRepository._() : super(defTableActs); - - static ActRepository? _instance; - - factory ActRepository() => v( - () => _instance ??= ActRepository._(), + { + 'id': id, + 'condition': condition, + }, ); } diff --git a/lib/acts/act_service.dart b/lib/acts/act_service.dart index 022f7f2ea..93bf73feb 100644 --- a/lib/acts/act_service.dart +++ b/lib/acts/act_service.dart @@ -1,10 +1,9 @@ import 'package:collection/collection.dart'; -import 'package:mem/core/act.dart'; import 'package:mem/core/date_and_time/date_and_time.dart'; import 'package:mem/core/date_and_time/date_and_time_period.dart'; import 'package:mem/logger/log_service.dart'; - -import 'act_repository.dart'; +import 'package:mem/acts/act_entity.dart'; +import 'package:mem/acts/act_repository.dart'; class ListWithTotalCount { final List list; @@ -16,57 +15,58 @@ class ListWithTotalCount { class ActService { final ActRepository _actRepository; - Future> fetch( + Future> fetch( int? memId, int offset, int limit, ) => v( - () async { - final acts = await _actRepository.ship( + () async => ListWithTotalCount( + (await _actRepository.ship( memId: memId, actOrderBy: ActOrderBy.descStart, offset: offset, limit: limit, - ); - final count = await _actRepository.count(memId: memId); - - return ListWithTotalCount(acts, count); - }, + )), + await _actRepository.count(memId: memId), + ), { - "memId": memId, - "offset": offset, - "limit": limit, + 'memId': memId, + 'offset': offset, + 'limit': limit, }, ); - Future start( + Future start( int memId, DateAndTime when, ) => i( () async => await _actRepository.receive( - Act(memId, DateAndTimePeriod(start: when)), + ActEntity(memId, DateAndTimePeriod(start: when)), ), { - "memId": memId, - "when": when, + 'memId': memId, + 'when': when, }, ); - Future finish( + Future finish( int memId, DateAndTime when, ) => i( () async { - final active = (await _actRepository.shipActiveByMemId(memId)) + final active = (await _actRepository.ship( + memId: memId, + isActive: true, + )) .sorted((a, b) => a.createdAt.compareTo(b.createdAt)) .firstOrNull; if (active == null) { return await _actRepository.receive( - Act( + ActEntity( memId, DateAndTimePeriod( start: when, @@ -77,28 +77,28 @@ class ActService { } else { return await _actRepository.replace( active.copiedWith( - () => active.period.copiedWith(when), + period: () => active.period.copiedWith(when), ), ); } }, { - "memId": memId, - "when": when, + 'memId': memId, + 'when': when, }, ); - Future edit(SavedAct savedAct) => i( + Future edit(SavedActEntity savedAct) => i( () async => await _actRepository.replace(savedAct), { - "savedAct": savedAct, + 'savedAct': savedAct, }, ); - Future delete(int actId) => i( - () async => await _actRepository.wasteById(actId), + Future delete(int actId) => i( + () async => (await _actRepository.waste(id: actId)).single, { - "actId": actId, + 'actId': actId, }, ); diff --git a/lib/acts/actions.dart b/lib/acts/actions.dart index fb36c79f6..8f82cbf2f 100644 --- a/lib/acts/actions.dart +++ b/lib/acts/actions.dart @@ -1,10 +1,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:mem/acts/act_repository.dart'; import 'package:mem/components/mem/list/states.dart'; -import 'package:mem/core/act.dart'; +import 'package:mem/acts/act.dart'; import 'package:mem/core/date_and_time/date_and_time.dart'; import 'package:mem/core/date_and_time/date_and_time_period.dart'; import 'package:mem/logger/log_service.dart'; +import 'package:mem/acts/act_entity.dart'; +import 'package:mem/acts/act_repository.dart'; import 'client.dart'; import 'states.dart'; @@ -12,7 +13,8 @@ import 'states.dart'; final _actsClient = ActsClient(); final _actRepository = ActRepository(); -final loadActList = FutureProvider.autoDispose.family, int?>( +final loadActList = + FutureProvider.autoDispose.family, int?>( (ref, memId) => v( () async { // TODO 全件取得する場合、件数的な不安がある @@ -27,7 +29,7 @@ final loadActList = FutureProvider.autoDispose.family, int?>( return acts; }, { - "memId": memId, + 'memId': memId, }, ), ); @@ -53,7 +55,7 @@ final startActBy = Provider.autoDispose.family( ), ); -final finishActBy = Provider.autoDispose.family( +final finishActBy = Provider.autoDispose.family( (ref, memId) => v( () { final now = DateAndTime.now(); diff --git a/lib/acts/acts_summary.dart b/lib/acts/acts_summary.dart index 230a5e06d..86e85703b 100644 --- a/lib/acts/acts_summary.dart +++ b/lib/acts/acts_summary.dart @@ -1,5 +1,5 @@ import 'package:collection/collection.dart'; -import 'package:mem/core/act.dart'; +import 'package:mem/acts/act.dart'; import 'package:mem/core/date_and_time/date_and_time.dart'; import 'package:mem/logger/log_service.dart'; diff --git a/lib/acts/client.dart b/lib/acts/client.dart index 109521c12..82be2ae50 100644 --- a/lib/acts/client.dart +++ b/lib/acts/client.dart @@ -1,8 +1,8 @@ import 'package:mem/acts/act_service.dart'; -import 'package:mem/core/act.dart'; import 'package:mem/core/date_and_time/date_and_time.dart'; import 'package:mem/logger/log_service.dart'; import 'package:mem/notifications/notification_client.dart'; +import 'package:mem/acts/act_entity.dart'; class ListWithTotalPage { final List list; @@ -16,7 +16,7 @@ class ActsClient { final NotificationClient _notificationClient; - Future> fetch( + Future> fetch( int? memId, int page, ) => @@ -43,7 +43,7 @@ class ActsClient { }, ); - Future start( + Future start( int memId, DateAndTime when, ) => @@ -61,8 +61,8 @@ class ActsClient { }, ); - Future edit( - SavedAct savedAct, + Future edit( + SavedActEntity savedAct, ) => i( () async { @@ -81,7 +81,7 @@ class ActsClient { }, ); - Future pause( + Future pause( int memId, DateAndTime when, ) => @@ -97,7 +97,7 @@ class ActsClient { }, ); - Future finish( + Future finish( int memId, DateAndTime when, ) => @@ -117,7 +117,7 @@ class ActsClient { }, ); - Future delete(int actId) => i( + Future delete(int actId) => i( () async { final deleted = await _actService.delete(actId); diff --git a/lib/acts/line_chart/line_chart_page.dart b/lib/acts/line_chart/line_chart_page.dart index dcff3e182..d8d5faf35 100644 --- a/lib/acts/line_chart/line_chart_page.dart +++ b/lib/acts/line_chart/line_chart_page.dart @@ -29,11 +29,9 @@ class ActLineChartPage extends ConsumerWidget { child: AsyncValueView( loadActList(_memId), (loaded) => LineChartWrapper( - ActsSummary( - ref - .watch(actListProvider(_memId)) - .where((element) => element.memId == _memId), - ), + ActsSummary(ref + .watch(actListProvider(_memId)) + .where((element) => element.memId == _memId)), ), ), ), diff --git a/lib/acts/list/act_list.dart b/lib/acts/list/act_list.dart index b742e998e..fbc8667ca 100644 --- a/lib/acts/list/act_list.dart +++ b/lib/acts/list/act_list.dart @@ -4,9 +4,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_sticky_header/flutter_sticky_header.dart'; import 'package:mem/acts/states.dart'; import 'package:mem/components/mem/list/states.dart'; -import 'package:mem/core/act.dart'; +import 'package:mem/acts/act.dart'; import 'package:mem/logger/log_service.dart'; -import 'package:mem/repositories/mem.dart'; +import 'package:mem/mems/mem_entity.dart'; import 'app_bar.dart'; import 'item/builder.dart'; @@ -50,7 +50,7 @@ class ActList extends ConsumerWidget { _memId, ref.watch(dateViewProvider), ref.watch(timeViewProvider), - ref.watch(actListProvider(_memId)) ?? [], + ref.watch(actListProvider(_memId)), (_memId == null ? ref.watch(memListProvider) : []), _scrollController, ); @@ -66,7 +66,7 @@ class _ActList extends StatelessWidget { final bool _isDateView; final bool _isTimeView; final List _actList; - final List _memList; + final List _memList; final ScrollController? _scrollController; const _ActList( @@ -100,8 +100,11 @@ class _ActList extends StatelessWidget { header: ActListSubHeader(e, _isDateView), sliver: SliverList( delegate: () { - final builder = - ActListItemBuilder(e, _memList, _isTimeView); + final builder = ActListItemBuilder( + e, + _memList, + _isTimeView, + ); return SliverChildBuilderDelegate( builder.build, diff --git a/lib/acts/list/add_act_fab.dart b/lib/acts/list/add_act_fab.dart index 0db98e0a6..b6bb43023 100644 --- a/lib/acts/list/add_act_fab.dart +++ b/lib/acts/list/add_act_fab.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mem/acts/actions.dart'; import 'package:mem/acts/states.dart'; -import 'package:mem/core/act.dart'; +import 'package:mem/acts/act_entity.dart'; class ActFab extends ConsumerWidget { final int _memId; @@ -19,14 +19,18 @@ class ActFab extends ConsumerWidget { () => ref.read(actListProvider(_memId).notifier).upsertAll( [ref.read(startActBy(_memId))], (tmp, item) => - tmp is SavedAct && item is SavedAct && tmp.id == item.id, + tmp is SavedActEntity && + item is SavedActEntity && +// coverage:ignore-start + tmp.id == item.id, +// coverage:ignore-end ), ); } else { return _FinishActFab( () => ref.read(actListProvider(_memId).notifier).removeWhere( (element) => - element is SavedAct && + element is SavedActEntity && element.id == ref.read(finishActBy(activeActList.last.memId)).id, ), diff --git a/lib/acts/list/item/actions.dart b/lib/acts/list/item/actions.dart index 0b16fd6bb..6c039cae6 100644 --- a/lib/acts/list/item/actions.dart +++ b/lib/acts/list/item/actions.dart @@ -1,7 +1,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mem/acts/client.dart'; import 'package:mem/acts/states.dart'; -import 'package:mem/core/act.dart'; +import 'package:mem/acts/act.dart'; import 'package:mem/logger/log_service.dart'; import 'states.dart'; diff --git a/lib/acts/list/item/builder.dart b/lib/acts/list/item/builder.dart index 9d5ebb566..e82563992 100644 --- a/lib/acts/list/item/builder.dart +++ b/lib/acts/list/item/builder.dart @@ -1,15 +1,16 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; -import 'package:mem/core/act.dart'; +import 'package:mem/acts/act.dart'; import 'package:mem/logger/log_service.dart'; -import 'package:mem/repositories/mem.dart'; +import 'package:mem/acts/act_entity.dart'; +import 'package:mem/mems/mem_entity.dart'; import 'total_act_time_item.dart'; import 'view.dart'; class ActListItemBuilder { final MapEntry> _actListWithDatetime; - final List _memList; + final List _memList; final bool _isTimeView; late final Map _actListGroupedByMemId; @@ -36,7 +37,7 @@ class ActListItemBuilder { ); } else { final act = _actListWithDatetime.value[index]; - if (act is SavedAct) { + if (act is SavedActEntity) { return ActListItemView( act, _memList diff --git a/lib/acts/list/item/editing_act_dialog.dart b/lib/acts/list/item/editing_act_dialog.dart index 14aba888f..c2ffd2018 100644 --- a/lib/acts/list/item/editing_act_dialog.dart +++ b/lib/acts/list/item/editing_act_dialog.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mem/acts/states.dart'; import 'package:mem/components/date_and_time/date_and_time_period_view.dart'; -import 'package:mem/core/act.dart'; import 'package:mem/core/date_and_time/date_and_time_period.dart'; import 'package:mem/logger/log_service.dart'; +import 'package:mem/acts/act_entity.dart'; import 'actions.dart'; import 'states.dart'; @@ -23,7 +23,7 @@ class EditingActDialog extends ConsumerWidget { (pickedPeriod) => v( () => ref.read(editingActProvider(_actId).notifier).updatedBy( editingAct.copiedWith( - pickedPeriod == null ? null : () => pickedPeriod, + period: pickedPeriod == null ? null : () => pickedPeriod, ), ), pickedPeriod, @@ -33,14 +33,16 @@ class EditingActDialog extends ConsumerWidget { () => ref.read(actListProvider(editingAct.memId).notifier).upsertAll( [ref.read(editAct(_actId))], (tmp, item) => - tmp is SavedAct && item is SavedAct && tmp.id == item.id, + tmp is SavedActEntity && + item is SavedActEntity && + tmp.id == item.id, )), ); } } class _EditingActDialogComponent extends StatelessWidget { - final SavedAct _editingAct; + final SavedActEntity _editingAct; final Function(DateAndTimePeriod? picked) _onPeriodChanged; final Function() _onDeleteTapped; final Function() _onSaveTapped; diff --git a/lib/acts/list/item/states.dart b/lib/acts/list/item/states.dart index 6c604766f..153d2cdbe 100644 --- a/lib/acts/list/item/states.dart +++ b/lib/acts/list/item/states.dart @@ -1,11 +1,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mem/acts/states.dart'; import 'package:mem/components/value_state_notifier.dart'; -import 'package:mem/core/act.dart'; import 'package:mem/logger/log_service.dart'; +import 'package:mem/acts/act_entity.dart'; final editingActProvider = StateNotifierProvider.autoDispose - .family, SavedAct, int>( + .family, SavedActEntity, int>( (ref, actId) => v( () => ValueStateNotifier( ref.read(actsProvider).singleWhere((act) => act.id == actId), diff --git a/lib/acts/list/item/total_act_time_item.dart b/lib/acts/list/item/total_act_time_item.dart index ca50a6656..791b3b9b7 100644 --- a/lib/acts/list/item/total_act_time_item.dart +++ b/lib/acts/list/item/total_act_time_item.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; -import 'package:mem/core/act.dart'; +import 'package:mem/acts/act.dart'; import 'package:mem/core/date_and_time/duration.dart'; import 'package:mem/logger/log_service.dart'; import 'package:mem/mems/transitions.dart'; -import 'package:mem/repositories/mem.dart'; +import 'package:mem/mems/mem_entity.dart'; class TotalActTimeListItem extends StatelessWidget { final List _actList; - final SavedMem? _mem; + final SavedMemEntity? _mem; const TotalActTimeListItem(this._actList, this._mem, {super.key}); diff --git a/lib/acts/list/item/view.dart b/lib/acts/list/item/view.dart index fa84ed08f..4b3b1d572 100644 --- a/lib/acts/list/item/view.dart +++ b/lib/acts/list/item/view.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:mem/components/date_and_time/date_and_time_period_view.dart'; -import 'package:mem/core/act.dart'; import 'package:mem/logger/log_service.dart'; +import 'package:mem/acts/act_entity.dart'; import 'editing_act_dialog.dart'; class ActListItemView extends StatelessWidget { - final SavedAct _act; + final SavedActEntity _act; // Act一覧の要素に対してMemがあったら名前を表示するという実装は合っているだろうか? final String? _memName; diff --git a/lib/acts/list/sub_header.dart b/lib/acts/list/sub_header.dart index e7726f699..02b122dfc 100644 --- a/lib/acts/list/sub_header.dart +++ b/lib/acts/list/sub_header.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:mem/components/date_and_time/date_and_time_view.dart'; -import 'package:mem/core/act.dart'; +import 'package:mem/acts/act.dart'; import 'package:mem/core/date_and_time/date_and_time.dart'; import 'package:mem/core/date_and_time/duration.dart'; import 'package:mem/logger/log_service.dart'; diff --git a/lib/acts/states.dart b/lib/acts/states.dart index e9edc9796..ec97c2aa2 100644 --- a/lib/acts/states.dart +++ b/lib/acts/states.dart @@ -3,13 +3,14 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mem/acts/client.dart'; import 'package:mem/components/list_value_state_notifier.dart'; import 'package:mem/components/value_state_notifier.dart'; -import 'package:mem/core/act.dart'; +import 'package:mem/acts/act.dart'; import 'package:mem/logger/log_service.dart'; +import 'package:mem/acts/act_entity.dart'; final _actsClient = ActsClient(); -final actsProvider = - StateNotifierProvider, List>( +final actsProvider = StateNotifierProvider< + ListValueStateNotifier, List>( (ref) => v(() => ListValueStateNotifier([])), ); @@ -55,7 +56,7 @@ final actListProvider = StateNotifierProvider.autoDispose final latest = await _actsClient.fetch(memId, 1); final c = ref.read(currentPage(memId)); - ListWithTotalPage? byPage; + ListWithTotalPage? byPage; if (c != 1) { byPage = await _actsClient.fetch(memId, c); } @@ -74,7 +75,6 @@ final actListProvider = StateNotifierProvider.autoDispose ref .watch(actsProvider) .where((act) => memId == null || act.memId == memId) - .toList() .sorted((a, b) => b.period.compareTo(a.period)), ); }, diff --git a/lib/components/created_and_updated_at_texts.dart b/lib/components/created_and_updated_at_texts.dart index de837e6fe..209eba7a7 100644 --- a/lib/components/created_and_updated_at_texts.dart +++ b/lib/components/created_and_updated_at_texts.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:mem/components/date_and_time/date_and_time_view.dart'; import 'package:mem/core/date_and_time/date_and_time.dart'; -import 'package:mem/core/mem.dart'; +import 'package:mem/mems/mem.dart'; import 'package:mem/logger/log_service.dart'; -import 'package:mem/repositories/mem.dart'; +import 'package:mem/mems/mem_entity.dart'; import 'package:mem/values/colors.dart'; class CreatedAndUpdatedAtTexts extends StatelessWidget { @@ -14,7 +14,7 @@ class CreatedAndUpdatedAtTexts extends StatelessWidget { @override Widget build(BuildContext context) => v( () { - if (_entity is SavedMem) { + if (_entity is SavedMemEntity) { return Wrap( direction: Axis.horizontal, children: [ diff --git a/lib/components/date_and_time/date_and_time_period_view.dart b/lib/components/date_and_time/date_and_time_period_view.dart index d867fb883..1c5aad530 100644 --- a/lib/components/date_and_time/date_and_time_period_view.dart +++ b/lib/components/date_and_time/date_and_time_period_view.dart @@ -58,7 +58,7 @@ class DateAndTimePeriodTextFormFields extends StatelessWidget { direction: Axis.vertical, mainAxisSize: MainAxisSize.min, children: [ - DateAndTimeTextFormFieldV2( + DateAndTimeTextFormField( _dateAndTimePeriod?.start, (pickedDateAndTime) => v( () => @@ -76,7 +76,7 @@ class DateAndTimePeriodTextFormFields extends StatelessWidget { end: _dateAndTimePeriod?.end, ), ), - DateAndTimeTextFormFieldV2( + DateAndTimeTextFormField( _dateAndTimePeriod?.end, (pickedDateAndTime) => v( () => _dateAndTimePeriod?.start == null && diff --git a/lib/components/date_and_time/date_and_time_text_form_field.dart b/lib/components/date_and_time/date_and_time_text_form_field.dart index 3ed632499..a0a879408 100644 --- a/lib/components/date_and_time/date_and_time_text_form_field.dart +++ b/lib/components/date_and_time/date_and_time_text_form_field.dart @@ -5,12 +5,12 @@ import 'package:mem/core/date_and_time/date_and_time.dart'; import 'package:mem/core/date_and_time/date_and_time_period.dart'; import 'package:mem/logger/log_service.dart'; -class DateAndTimeTextFormFieldV2 extends StatelessWidget { +class DateAndTimeTextFormField extends StatelessWidget { final DateAndTime? _dateAndTime; final void Function(DateAndTime? pickedDateAndTime) _onChanged; final DateAndTimePeriod? _selectableRange; - const DateAndTimeTextFormFieldV2( + const DateAndTimeTextFormField( this._dateAndTime, this._onChanged, { super.key, diff --git a/lib/components/mem/list/actions.dart b/lib/components/mem/list/actions.dart index 8fb04d1b4..731959206 100644 --- a/lib/components/mem/list/actions.dart +++ b/lib/components/mem/list/actions.dart @@ -1,10 +1,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mem/components/mem/list/states.dart'; import 'package:mem/logger/log_service.dart'; -import 'package:mem/mems/mem_item.dart'; import 'package:mem/mems/mem_item_repository.dart'; -import 'package:mem/repositories/mem.dart'; -import 'package:mem/repositories/mem_repository.dart'; +import 'package:mem/mems/mem_entity.dart'; +import 'package:mem/mems/mem_item_entity.dart'; +import 'package:mem/mems/mem_repository.dart'; import 'package:mem/mems/states.dart'; final loadMemList = FutureProvider( @@ -30,15 +30,16 @@ final loadMemList = FutureProvider( ref.read(memsProvider.notifier).upsertAll( mems, - (tmp, item) => - tmp is SavedMem && item is SavedMem ? tmp.id == item.id : false, + (tmp, item) => tmp is SavedMemEntity && item is SavedMemEntity + ? tmp.id == item.id + : false, ); for (var mem in mems) { ref.read(memItemsProvider.notifier).upsertAll( - await MemItemRepository().shipByMemId(mem.id), + await MemItemRepository().ship(memId: mem.id), (current, updating) => - current is SavedMemItem && - updating is SavedMemItem && + current is SavedMemItemEntity && + updating is SavedMemItemEntity && current.id == updating.id, ); } diff --git a/lib/components/mem/list/states.dart b/lib/components/mem/list/states.dart index aa9a3551f..3a68d8464 100644 --- a/lib/components/mem/list/states.dart +++ b/lib/components/mem/list/states.dart @@ -1,8 +1,7 @@ import 'package:collection/collection.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:mem/acts/act_repository.dart'; import 'package:mem/acts/states.dart'; -import 'package:mem/core/act.dart'; +import 'package:mem/acts/act.dart'; import 'package:mem/core/date_and_time/date_and_time_period.dart'; import 'package:mem/components/list_value_state_notifier.dart'; import 'package:mem/components/value_state_notifier.dart'; @@ -10,9 +9,11 @@ import 'package:mem/logger/log_service.dart'; import 'package:mem/mems/list/states.dart'; import 'package:mem/mems/states.dart'; import 'package:mem/notifications/mem_notifications.dart'; -import 'package:mem/repositories/mem.dart'; -import 'package:mem/repositories/mem_notification.dart'; -import 'package:mem/repositories/mem_notification_repository.dart'; +import 'package:mem/acts/act_entity.dart'; +import 'package:mem/acts/act_repository.dart'; +import 'package:mem/mems/mem_entity.dart'; +import 'package:mem/mems/mem_notification_entity.dart'; +import 'package:mem/mems/mem_notification_repository.dart'; import 'package:mem/settings/states.dart'; final showNotArchivedProvider = @@ -39,9 +40,9 @@ final showDoneProvider = StateNotifierProvider, bool>( ), ); final _filteredMemsProvider = StateNotifierProvider.autoDispose< - ListValueStateNotifier, List>( + ListValueStateNotifier, List>( (ref) { - final savedMems = ref.watch(memsProvider).map((e) => e as SavedMem); + final savedMems = ref.watch(memsProvider).whereType(); final showNotArchived = ref.watch(showNotArchivedProvider); final showArchived = ref.watch(showArchivedProvider); @@ -85,7 +86,7 @@ final _filteredMemsProvider = StateNotifierProvider.autoDispose< ); final memListProvider = StateNotifierProvider.autoDispose< - ValueStateNotifier>, List>((ref) { + ValueStateNotifier>, List>((ref) { final filtered = ref.watch(_filteredMemsProvider); final latestActsByMem = ref.watch(latestActsByMemProvider); final savedMemNotifications = ref.watch(savedMemNotificationsProvider); @@ -97,8 +98,10 @@ final memListProvider = StateNotifierProvider.autoDispose< latestActsByMem.singleWhereOrNull((act) => act.memId == a.id); final latestActOfB = latestActsByMem.singleWhereOrNull((act) => act.memId == b.id); - final comparedByActiveAct = - Act.activeCompare(latestActOfA, latestActOfB); + final comparedByActiveAct = Act.activeCompare( + latestActOfA, + latestActOfB, + ); if (comparedByActiveAct != 0) { return comparedByActiveAct; } @@ -180,7 +183,7 @@ int _compareTime( ); final latestActsByMemProvider = StateNotifierProvider.autoDispose< - ListValueStateNotifier, List>( + ListValueStateNotifier, List>( (ref) => v( () => ListValueStateNotifier( ref.watch( @@ -196,15 +199,15 @@ final latestActsByMemProvider = StateNotifierProvider.autoDispose< initializer: (current, notifier) => v( () async { if (current.isEmpty) { - final memIds = - ref.read(memsProvider).whereType().map((e) => e.id); - - final actsByMemIds = await ActRepository().ship( - memIdsIn: memIds, - latestByMemIds: true, - ); - - ref.read(actsProvider.notifier).addAll(actsByMemIds); + ref.read(actsProvider.notifier).addAll( + await ActRepository().ship( + memIdsIn: ref + .read(memsProvider) + .whereType() + .map((e) => e.id), + latestByMemIds: true, + ), + ); } }, {'current': current}, @@ -213,18 +216,21 @@ final latestActsByMemProvider = StateNotifierProvider.autoDispose< ), ); final savedMemNotificationsProvider = StateNotifierProvider.autoDispose< - ListValueStateNotifier, List>( + ListValueStateNotifier, + List>( (ref) => v( () => ListValueStateNotifier( ref.watch( memNotificationsProvider.select( - (value) => value.whereType().toList()), + (value) => value.whereType().toList()), ), initializer: (current, notifier) => v( () async { if (current.isEmpty) { - final memIds = - ref.read(memsProvider).whereType().map((e) => e.id); + final memIds = ref + .read(memsProvider) + .whereType() + .map((e) => e.id); final actsByMemIds = await MemNotificationRepository().ship( memIdsIn: memIds, @@ -233,8 +239,8 @@ final savedMemNotificationsProvider = StateNotifierProvider.autoDispose< ref.read(memNotificationsProvider.notifier).upsertAll( actsByMemIds, (current, updating) => - current is SavedMemNotification && - updating is SavedMemNotification && + current is SavedMemNotificationEntity && + updating is SavedMemNotificationEntity && current.id == updating.id, ); } @@ -246,8 +252,10 @@ final savedMemNotificationsProvider = StateNotifierProvider.autoDispose< ); final activeActsProvider = StateNotifierProvider.autoDispose< - ListValueStateNotifier, List>( - (ref) => v(() => ListValueStateNotifier( - ref.watch(actsProvider).where((act) => act.period.end == null).toList(), - )), + ListValueStateNotifier, List>( + (ref) => v( + () => ListValueStateNotifier( + ref.watch(actsProvider).where((act) => act.isActive).toList(), + ), + ), ); diff --git a/lib/components/mem/list/view.dart b/lib/components/mem/list/view.dart index 13a8cbd32..838dbde66 100644 --- a/lib/components/mem/list/view.dart +++ b/lib/components/mem/list/view.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mem/components/async_value_view.dart'; import 'package:mem/logger/log_service.dart'; -import 'package:mem/repositories/mem.dart'; +import 'package:mem/mems/mem_entity.dart'; import 'actions.dart'; import 'states.dart'; @@ -32,7 +32,7 @@ class MemListView extends ConsumerWidget { } class _MemListViewComponent extends StatelessWidget { - final List _memList; + final List _memList; final Widget _appBar; final Widget Function(int memId) _itemBuilder; final ScrollController? _scrollController; diff --git a/lib/components/mem/mem_done_checkbox.dart b/lib/components/mem/mem_done_checkbox.dart index b40fc87e4..6b6f6a798 100644 --- a/lib/components/mem/mem_done_checkbox.dart +++ b/lib/components/mem/mem_done_checkbox.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:mem/core/mem.dart'; +import 'package:mem/mems/mem.dart'; import 'package:mem/components/hero_view.dart'; import 'package:mem/logger/log_service.dart'; -import 'package:mem/repositories/mem.dart'; +import 'package:mem/mems/mem_entity.dart'; class MemDoneCheckbox extends StatelessWidget { final Mem _mem; @@ -19,11 +19,11 @@ class MemDoneCheckbox extends StatelessWidget { () => HeroView( heroTag( 'mem-done', - _mem is SavedMem ? _mem.id : null, + _mem is SavedMemEntity ? _mem.id : null, ), Checkbox( value: _mem.isDone, - onChanged: (_mem is SavedMem ? _mem.isArchived : false) + onChanged: (_mem is SavedMemEntity ? _mem.isArchived : false) ? null : _onChanged, ), diff --git a/lib/components/mem/mem_name.dart b/lib/components/mem/mem_name.dart index 34219108a..7db60a2d3 100644 --- a/lib/components/mem/mem_name.dart +++ b/lib/components/mem/mem_name.dart @@ -4,14 +4,14 @@ import 'package:mem/components/hero_view.dart'; import 'package:mem/components/l10n.dart'; import 'package:mem/logger/log_service.dart'; import 'package:mem/mems/detail/states.dart'; -import 'package:mem/repositories/mem.dart'; +import 'package:mem/mems/mem_entity.dart'; Key keyMemName = const Key("mem-name"); String _memNameTag(int? memId) => heroTag('mem-name', memId); class MemNameText extends StatelessWidget { - final SavedMem _mem; + final SavedMemEntity _mem; const MemNameText(this._mem, {super.key}); diff --git a/lib/core/mem_detail.dart b/lib/core/mem_detail.dart deleted file mode 100644 index 7cf993f59..000000000 --- a/lib/core/mem_detail.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:mem/core/mem.dart'; -import 'package:mem/core/mem_item.dart'; -import 'package:mem/core/mem_notification.dart'; - -class MemDetail { - final Mem mem; - final List memItems; - final List? notifications; - - MemDetail(this.mem, this.memItems, [this.notifications]); - - @override - String toString() => { - 'mem': mem, - 'memItems': memItems, - 'notifications': notifications, - }.toString(); -} diff --git a/lib/core/mem_item.dart b/lib/core/mem_item.dart deleted file mode 100644 index 0845ebbe7..000000000 --- a/lib/core/mem_item.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:mem/framework/repository/entity.dart'; - -enum MemItemType { - memo, -} - -class MemItem extends EntityV1 { - // 未保存のMemに紐づくMemItemはmemIdをintで持つことができないため暫定的にnullableにしている - final int? memId; - final MemItemType type; - final dynamic value; - - MemItem(this.memId, this.type, this.value); - - factory MemItem.memo(int? memId) => MemItem(memId, MemItemType.memo, ""); - - MemItem copiedWith({ - int Function()? memId, - dynamic Function()? value, - }) => - MemItem( - memId == null ? this.memId : memId(), - type, - value == null ? this.value : value(), - ); - - @override - String toString() => "${super.toString()}: ${{ - "memId": memId, - "type": type, - "value": value, - }}"; -} diff --git a/lib/framework/database/definition/database_definition.dart b/lib/framework/database/definition/database_definition.dart index df614c5b0..88138b6eb 100644 --- a/lib/framework/database/definition/database_definition.dart +++ b/lib/framework/database/definition/database_definition.dart @@ -2,7 +2,7 @@ import 'package:mem/framework/database/definition/exceptions.dart'; import 'package:mem/framework/database/definition/table_definition.dart'; import 'package:mem/framework/repository/entity.dart'; -class DatabaseDefinition extends Entity { +class DatabaseDefinition with Entity { final String name; final int version; final List tableDefinitions; @@ -22,9 +22,9 @@ class DatabaseDefinition extends Entity { } @override - String toString() => "${super.toString()}: ${{ - "name": name, - "version": version, - "tableDefinitions": tableDefinitions.map((e) => e.toString()) - }}"; + Map get toMap => { + 'name': name, + 'version': version, + 'tableDefinitions': tableDefinitions.map((e) => e.toString()) + }; } diff --git a/lib/framework/repository/condition/conditions.dart b/lib/framework/repository/condition/conditions.dart index 44bcb7a9c..7ebc3b8e9 100644 --- a/lib/framework/repository/condition/conditions.dart +++ b/lib/framework/repository/condition/conditions.dart @@ -1,3 +1,4 @@ +import 'package:mem/framework/database/converter.dart'; import 'package:mem/framework/database/definition/column/column_definition.dart'; abstract class Condition { @@ -7,19 +8,19 @@ abstract class Condition { } class Equals extends Condition { - final String _key; + final ColumnDefinition _columnDefinition; final dynamic _value; - Equals(this._key, this._value); + Equals(this._columnDefinition, this._value); @override - String where() => '$_key = ?'; + String? where() => "${_columnDefinition.name} = ?"; @override - List? whereArgs() => [_value]; + List? whereArgs() => [DatabaseConverter().to(_value)]; @override - String toString() => '$_key = $_value'; + String toString() => "${_columnDefinition.name} = $_value"; } class IsNull extends Condition { diff --git a/lib/framework/repository/database_repository.dart b/lib/framework/repository/database_repository.dart index 382314796..e9f76112d 100644 --- a/lib/framework/repository/database_repository.dart +++ b/lib/framework/repository/database_repository.dart @@ -8,8 +8,7 @@ import 'package:mem/framework/database/factory.dart'; import 'package:mem/framework/repository/repository.dart'; import 'package:mem/logger/log_service.dart'; -class DatabaseRepository extends Repository - with Receiver { +class DatabaseRepository extends Repository { static DatabaseRepository? _instance; final _cache = {}; @@ -18,7 +17,6 @@ class DatabaseRepository extends Repository factory DatabaseRepository() => _instance ??= DatabaseRepository._(); - @override Future receive(DatabaseDefinition entity) => v( () async => _cache[entity.name] ?? diff --git a/lib/framework/repository/database_tuple_entity.dart b/lib/framework/repository/database_tuple_entity.dart index ba4cab577..5040c28ea 100644 --- a/lib/framework/repository/database_tuple_entity.dart +++ b/lib/framework/repository/database_tuple_entity.dart @@ -1,36 +1,28 @@ import 'package:mem/databases/table_definitions/base.dart'; import 'package:mem/framework/repository/entity.dart'; -mixin SavedDatabaseTupleMixin on EntityV1 { - late T id; +mixin DatabaseTupleEntity on Entity { + late PrimaryKey id; late DateTime createdAt; - DateTime? updatedAt; - DateTime? archivedAt; + late DateTime? updatedAt; + late DateTime? archivedAt; bool get isArchived => archivedAt != null; - void pack(Map tuple) { - id = tuple[defPkId.name]; - createdAt = tuple[defColCreatedAt.name]; - updatedAt = tuple[defColUpdatedAt.name]; - archivedAt = tuple[defColArchivedAt.name]; + DatabaseTupleEntity withMap(Map map) { + id = map[defPkId.name]; + createdAt = map[defColCreatedAt.name]; + updatedAt = map[defColUpdatedAt.name]; + archivedAt = map[defColArchivedAt.name]; + return this; } - Map unpack() { - return { + @override + Map get toMap => super.toMap + ..addAll({ defPkId.name: id, defColCreatedAt.name: createdAt, defColUpdatedAt.name: updatedAt, defColArchivedAt.name: archivedAt, - }; - } - - SavedDatabaseTupleMixin copiedFrom(SavedDatabaseTupleMixin origin) => this - ..id = origin.id - ..createdAt = origin.createdAt - ..updatedAt = origin.updatedAt - ..archivedAt = origin.archivedAt; - - @override - String toString() => "${super.toString()}${unpack()}"; + }); } diff --git a/lib/framework/repository/database_tuple_repository.dart b/lib/framework/repository/database_tuple_repository.dart index aaeede53c..4c534df6c 100644 --- a/lib/framework/repository/database_tuple_repository.dart +++ b/lib/framework/repository/database_tuple_repository.dart @@ -1,58 +1,78 @@ import 'package:mem/databases/table_definitions/base.dart'; import 'package:mem/framework/database/accessor.dart'; +import 'package:mem/framework/database/definition/database_definition.dart'; import 'package:mem/framework/database/definition/table_definition.dart'; +import 'package:mem/framework/repository/condition/conditions.dart'; +import 'package:mem/framework/repository/database_repository.dart'; +import 'package:mem/framework/repository/database_tuple_entity.dart'; import 'package:mem/framework/repository/entity.dart'; import 'package:mem/framework/repository/group_by.dart'; import 'package:mem/framework/repository/order_by.dart'; import 'package:mem/framework/repository/repository.dart'; import 'package:mem/logger/log_service.dart'; -import 'package:mem/framework/repository/condition/conditions.dart'; - -// FIXME byIdの引数の型のためにSavedEntityの型以外にIが必要になっている -// Rにidの型情報が含まれているのに改めて渡す必要があるのはおかしい -// DatabaseTupleに型情報を付与することでズレは発生しなくなった -// ただ、これだと未保存のDatabaseTupleが -// FIXME SavedEntityはSavedDatabaseTupleをmixinしている必要があるが型制約を定義できていない -abstract class DatabaseTupleRepository implements RepositoryV1 { - Map unpack(E entity); - SavedEntity pack(Map tuple); - - @override - Future receive(E entity) => v( - () async { - final entityMap = unpack(entity); +abstract class DatabaseTupleRepository extends Repository { + final DatabaseDefinition _databaseDefinition; + final TableDefinition _tableDefinition; - entityMap[defColCreatedAt.name] = DateTime.now(); + // FIXME DatabaseDefinitionの中にTableDefinitionがあるのでEから取得できるのでは? + DatabaseTupleRepository(this._databaseDefinition, this._tableDefinition); - final id = await _databaseAccessor!.insert( - _tableDefinition, - entityMap, - ); + static DatabaseAccessor? _databaseAccessor; - entityMap[defPkId.name] = id; + late final Future _dbA = (() async => _databaseAccessor ??= + await DatabaseRepository().receive(_databaseDefinition))(); - return pack(entityMap); + static Future close() => v( + () async { + await _databaseAccessor?.close(); + _databaseAccessor = null; }, - entity, ); Future count({ Condition? condition, }) => v( - () async => await _databaseAccessor!.count( + () async => (await _dbA).count( _tableDefinition, where: condition?.where(), whereArgs: condition?.whereArgs(), ), { - "condition": condition, + 'condition': condition, }, ); - Future> ship({ + SAVED pack(Map map); + + Future receive( + ENTITY entity, { + DateTime? createdAt, + }) => + v( + () async { + final entityMap = entity.toMap; + + entityMap[defColCreatedAt.name] = createdAt ?? DateTime.now(); + + final id = await (await _dbA).insert( + _tableDefinition, + entityMap, + ); + + entityMap[defPkId.name] = id; + + return pack(entityMap); + }, + { + 'entity': entity, + 'createdAt': createdAt, + }, + ); + + Future> ship({ Condition? condition, GroupBy? groupBy, List? orderBy, @@ -60,7 +80,7 @@ abstract class DatabaseTupleRepository v( - () async => (await _databaseAccessor!.select( + () async => (await (await _dbA).select( _tableDefinition, groupBy: groupBy?.toQuery, extraColumns: groupBy?.toExtraColumns, @@ -83,150 +103,104 @@ abstract class DatabaseTupleRepository shipById(Id id) => v( - () async { - final condition = Equals(defPkId.name, id); - return pack( - (await _databaseAccessor!.select( - _tableDefinition, - where: condition.where(), - whereArgs: condition.whereArgs(), - )) - .single, - ); - }, - id, - ); - - Future findOneBy({ - Condition? condition, - List? orderBy, + Future replace( + SAVED savedEntity, { + DateTime? updatedAt, }) => v( - () async => await ship( - condition: condition, - orderBy: orderBy, - limit: 1, - ).then((value) => value.length == 1 ? value.single : null), - { - "condition": condition, - "orderBy": orderBy, - }, - ); - - Future replace(SavedEntity payload) => v( () async { - final entityMap = unpack(payload); + final entityMap = savedEntity.toMap; - entityMap[defColUpdatedAt.name] = DateTime.now(); + entityMap[defColUpdatedAt.name] = updatedAt ?? DateTime.now(); - final condition = Equals(defPkId.name, entityMap[defPkId.name]); - await _databaseAccessor!.update( + final byId = Equals(defPkId, entityMap[defPkId.name]); + await (await _dbA).update( _tableDefinition, entityMap, - where: condition.where(), - whereArgs: condition.whereArgs(), + where: byId.where(), + whereArgs: byId.whereArgs(), ); return pack(entityMap); }, - payload, + { + 'savedEntity': savedEntity, + 'updatedAt': updatedAt, + }, ); - Future archive(SavedEntity payload) => v( + Future archive( + SAVED savedEntity, { + DateTime? archivedAt, + }) => + v( () async { - final entityMap = unpack(payload); + final entityMap = savedEntity.toMap; - entityMap[defColArchivedAt.name] = DateTime.now(); + entityMap[defColArchivedAt.name] = archivedAt ?? DateTime.now(); - final condition = Equals(defPkId.name, entityMap[defPkId.name]); - await _databaseAccessor!.update( + final byId = Equals(defPkId, entityMap[defPkId.name]); + await (await _dbA).update( _tableDefinition, entityMap, - where: condition.where(), - whereArgs: condition.whereArgs(), + where: byId.where(), + whereArgs: byId.whereArgs(), ); return pack(entityMap); }, - payload, + { + 'savedEntity': savedEntity, + 'archivedAt': archivedAt, + }, ); - Future unarchive(SavedEntity payload) => v( + Future unarchive( + SAVED savedEntity, { + DateTime? updatedAt, + }) => + v( () async { - final entityMap = unpack(payload); + final entityMap = savedEntity.toMap; - entityMap[defColUpdatedAt.name] = DateTime.now(); + entityMap[defColUpdatedAt.name] = updatedAt ?? DateTime.now(); entityMap[defColArchivedAt.name] = null; - final condition = Equals(defPkId.name, entityMap[defPkId.name]); - await _databaseAccessor!.update( + final byId = Equals(defPkId, entityMap[defPkId.name]); + await (await _dbA).update( _tableDefinition, entityMap, - where: condition.where(), - whereArgs: condition.whereArgs(), + where: byId.where(), + whereArgs: byId.whereArgs(), ); return pack(entityMap); }, - payload, + { + 'savedEntity': savedEntity, + 'updatedAt': updatedAt, + }, ); - Future> waste([Condition? condition]) => v( + Future> waste({ + Condition? condition, + }) => + v( () async { - final targets = (await _databaseAccessor!.select( - _tableDefinition, - where: condition?.where(), - whereArgs: condition?.whereArgs(), - )) - .map((e) => pack(e)) - .toList(); + final targets = await ship( + condition: condition, + ); - final count = await _databaseAccessor!.delete( + await _databaseAccessor!.delete( _tableDefinition, where: condition?.where(), whereArgs: condition?.whereArgs(), ); - assert(count == targets.length); - return targets; }, - condition, - ); - - Future wasteById(Id id) => v( - () async { - final condition = Equals(defPkId.name, id); - final payload = (await _databaseAccessor!.select( - _tableDefinition, - where: condition.where(), - whereArgs: condition.whereArgs(), - )) - .single; - await _databaseAccessor!.delete( - _tableDefinition, - where: condition.where(), - whereArgs: condition.whereArgs(), - ); - return pack(payload); - }, - id, - ); - - static Future close() => v( - () async { - await _databaseAccessor?.close(); - _databaseAccessor = null; + { + 'condition': condition, }, ); - - static DatabaseAccessor? _databaseAccessor; - - static set databaseAccessor(DatabaseAccessor? databaseAccessor) => - _databaseAccessor = databaseAccessor; - - final TableDefinition _tableDefinition; - - DatabaseTupleRepository(this._tableDefinition); } diff --git a/lib/framework/repository/entity.dart b/lib/framework/repository/entity.dart index de1e74306..7e4fa82ab 100644 --- a/lib/framework/repository/entity.dart +++ b/lib/framework/repository/entity.dart @@ -4,9 +4,28 @@ // # 語源 // // 「存在するもの」、「実体」 -abstract class EntityV1 {} +mixin Entity { + Map get toMap; -abstract class Entity {} + @override + String toString() => "${super.toString()}: $toMap"; + + @override + int get hashCode => toMap.entries.fold( + 0, + (value, element) => + value ^ element.key.hashCode ^ element.value.hashCode, + ); + + @override + bool operator ==(Object other) => + identical(this, other) || + (runtimeType == other.runtimeType && hashCode == other.hashCode); +} + +mixin Copyable on Entity { + T copiedWith(); +} // memo // - view, domain, dataのそれぞれの領域で似た内容でも型が変わることになるはず // これをしっかりと定義したい diff --git a/lib/framework/repository/home_widget_entity.dart b/lib/framework/repository/home_widget_entity.dart new file mode 100644 index 000000000..d4edc0539 --- /dev/null +++ b/lib/framework/repository/home_widget_entity.dart @@ -0,0 +1,14 @@ +import 'package:mem/framework/repository/entity.dart'; + +mixin HomeWidgetEntity on Entity { + String get methodChannelName; + + String get initializeMethodName; + + String get widgetProviderName; + + Map get toWidgetData; +} +mixin SavedHomeWidgetEntity on HomeWidgetEntity { + late final int homeWidgetId; +} diff --git a/lib/framework/repository/home_widget_repository.dart b/lib/framework/repository/home_widget_repository.dart new file mode 100644 index 000000000..b74619fe9 --- /dev/null +++ b/lib/framework/repository/home_widget_repository.dart @@ -0,0 +1,59 @@ +import 'package:flutter/foundation.dart'; +import 'package:mem/act_counter/home_widget_accessor.dart'; +import 'package:mem/framework/repository/home_widget_entity.dart'; +import 'package:mem/framework/repository/repository.dart'; +import 'package:mem/logger/log_service.dart'; + +abstract class HomeWidgetRepository extends Repository { + static final _homeWidgetAccessor = + defaultTargetPlatform == TargetPlatform.android + ? HomeWidgetAccessor() + : null; + + SAVED pack(Map map); + + Future receive( + ENTITY entity, + ) => + v( + () async { + final homeWidgetId = await _homeWidgetAccessor?.initialize( + entity.methodChannelName, + entity.initializeMethodName, + ); + + if (homeWidgetId != null) { + final saved = pack(entity.toMap + ..addAll({ + 'homeWidgetId': homeWidgetId, + })); + + saved.toWidgetData.forEach( + (key, value) async => + await _homeWidgetAccessor?.saveWidgetData(key, value), + ); + await _homeWidgetAccessor?.updateWidget(saved.widgetProviderName); + } + }, + { + 'entity': entity, + }, + ); + + Future replace( + ENTITY entity, + ) => + v( + () async { + entity.toWidgetData.forEach((key, value) async { + await _homeWidgetAccessor?.saveWidgetData(key, value); + }); + + await _homeWidgetAccessor?.updateWidget(entity.widgetProviderName); + }, + { + 'entity': entity, + }, + ); +} diff --git a/lib/framework/repository/key_with_value.dart b/lib/framework/repository/key_with_value.dart index 1f005c79c..c46d1010a 100644 --- a/lib/framework/repository/key_with_value.dart +++ b/lib/framework/repository/key_with_value.dart @@ -1,14 +1,10 @@ import 'package:mem/framework/repository/entity.dart'; -abstract class KeyWithValue extends Entity { - final Key key; - final Value value; - - KeyWithValue(this.key, this.value); +// FIXME IdWithValueの方が命名として適切なのでは? +mixin KeyWithValue on Entity { + late final KEY key; + late final VALUE value; @override - String toString() => { - "key": key, - "value": value, - }.toString(); + Map get toMap => {'key': key, 'value': value}; } diff --git a/lib/framework/repository/key_with_value_repository.dart b/lib/framework/repository/key_with_value_repository.dart index 638d11364..ee6be118d 100644 --- a/lib/framework/repository/key_with_value_repository.dart +++ b/lib/framework/repository/key_with_value_repository.dart @@ -1,11 +1,14 @@ import 'package:mem/framework/repository/key_with_value.dart'; import 'package:mem/framework/repository/repository.dart'; -abstract class KeyWithValueRepository, Key> - extends Repository - with Receiver, DiscarderByKey {} +abstract class KeyWithValueRepository, + KEY> extends Repository { + Future receive(ENTITY entity); -mixin DiscarderByKey, Key, Result> - on Repository { - Future discard(Key key); + Future discard(KEY key); +} + +mixin DiscardAll, KEY> + on KeyWithValueRepository { + Future discardAll(); } diff --git a/lib/framework/repository/order_by.dart b/lib/framework/repository/order_by.dart index 21c7d48ca..6cf1a1d7f 100644 --- a/lib/framework/repository/order_by.dart +++ b/lib/framework/repository/order_by.dart @@ -8,12 +8,12 @@ abstract class OrderBy { String toQuery(); } -class Ascending extends OrderBy { - Ascending(super.columnDefinition); - - @override - String toQuery() => "${columnDefinition.name} ASC"; -} +// class Ascending extends OrderBy { +// Ascending(super.columnDefinition); +// +// @override +// String toQuery() => "${columnDefinition.name} ASC"; +// } class Descending extends OrderBy { Descending(super.columnDefinition); diff --git a/lib/framework/repository/repository.dart b/lib/framework/repository/repository.dart index 8f0807747..181d1b43c 100644 --- a/lib/framework/repository/repository.dart +++ b/lib/framework/repository/repository.dart @@ -19,18 +19,4 @@ import 'package:mem/framework/repository/entity.dart'; // 抽象的には得ると捉える事もできるだろうが、では`update`(更新する)ことはあるだろうか? // 更新することはないように感じる // よって、ここでは`receive`(受け取る)、`replace`(置き換える)などの荷物や事物を扱う際の単語を採用する -abstract class Repository {} - -mixin Receiver on Repository { - Future receive(E entity); -} - -// FIXME 型指定は不要なはずなので、おかしい?(間違っている気がする -// というか、mixinの利用自体がなんか変なので辞めたほうが良いかも -mixin Discarder on Repository { - Future discardAll(); -} - -abstract class RepositoryV1 { - Future receive(E entity); -} +abstract class Repository {} diff --git a/lib/logger/log.dart b/lib/logger/log.dart index f0a9f9f90..201c103af 100644 --- a/lib/logger/log.dart +++ b/lib/logger/log.dart @@ -36,7 +36,7 @@ import 'package:mem/framework/repository/entity.dart'; // 言葉の意味としては「丸太を投げ込め!」という号令で、「航海開始」を表す // // 航海日誌においては、最初の記録ということになる -class Log extends Entity { +class Log with Entity { final Level level; final List prefixes; final dynamic target; @@ -102,6 +102,12 @@ class Log extends Entity { return elements.join('\n'); } + +// coverage:ignore-start + @override + // TODO: implement toMap + Map get toMap => throw UnimplementedError(); +// coverage:ignore-end } enum Level { diff --git a/lib/logger/log_repository.dart b/lib/logger/log_repository.dart index be98fce0d..c203f787b 100644 --- a/lib/logger/log_repository.dart +++ b/lib/logger/log_repository.dart @@ -3,10 +3,9 @@ import 'package:mem/framework/repository/repository.dart'; import 'log.dart'; import 'logger_wrapper.dart'; -class LogRepository extends Repository with Receiver { +class LogRepository extends Repository { LoggerWrapper _loggerWrapper; - @override Future receive(Log entity) async => _loggerWrapper.log( entity.level, entity.buildMessage(), diff --git a/lib/main.dart b/lib/main.dart index 77e3f36f5..375e8099d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,14 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:mem/act_counter/act_counter_repository.dart'; import 'package:mem/act_counter/act_counter_client.dart'; import 'package:mem/core/date_and_time/date_and_time.dart'; -import 'package:mem/databases/definition.dart'; import 'package:mem/logger/log_service.dart'; import 'package:mem/notifications/flutter_local_notifications_wrapper.dart'; import 'package:mem/notifications/notification_actions.dart'; import 'package:mem/notifications/notification_repository.dart'; -import 'package:mem/framework/repository/database_repository.dart'; -import 'package:mem/framework/repository/database_tuple_repository.dart'; import 'package:mem/router.dart'; import 'application.dart'; @@ -17,7 +13,7 @@ Future main({String? languageCode}) => i( () async { WidgetsFlutterBinding.ensureInitialized(); - await NotificationRepository().checkNotification(); + await NotificationRepository().ship(); return _runApplication(languageCode: languageCode); }, @@ -50,7 +46,6 @@ Future launchActCounterConfigure() => i( Future onNotificationResponseReceived(dynamic details) => i( () async { WidgetsFlutterBinding.ensureInitialized(); - await openDatabase(); await onDidReceiveNotificationResponse( details, @@ -90,9 +85,6 @@ Future _runApplication({ }) => i( () async { - await openDatabase(); - ActCounterRepository(); - runApp( MemApplication( initialPath: initialPath, @@ -105,10 +97,6 @@ Future _runApplication({ }, ); -// FIXME 先に初期化が必要なのではなく、Repositoryを利用する際に勝手に初期化されるようにする -Future openDatabase() async => DatabaseTupleRepository.databaseAccessor = - await DatabaseRepository().receive(databaseDefinition); - // FIXME HomeWidget関連の処理、場所が適切ではない const uriSchema = 'mem'; const appId = 'zin.playground.mem'; @@ -119,8 +107,6 @@ const memIdParamName = 'mem_id'; Future backgroundCallback(Uri? uri) => i( () async { if (uri != null && uri.scheme == uriSchema && uri.host == appId) { - await openDatabase(); - if (uri.pathSegments.contains(actCounter)) { final memId = uri.queryParameters[memIdParamName]; diff --git a/lib/mems/actions.dart b/lib/mems/actions.dart index 78baaa4b5..72f7ae16c 100644 --- a/lib/mems/actions.dart +++ b/lib/mems/actions.dart @@ -1,9 +1,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mem/logger/log_service.dart'; -import 'package:mem/mems/mem_item.dart'; import 'package:mem/mems/mem_service.dart'; import 'package:mem/mems/states.dart'; -import 'package:mem/repositories/mem.dart'; +import 'package:mem/mems/mem_entity.dart'; +import 'package:mem/mems/mem_item_entity.dart'; final undoRemoveMem = FutureProvider.autoDispose.family( (ref, memId) => v( @@ -15,14 +15,16 @@ final undoRemoveMem = FutureProvider.autoDispose.family( await MemService().save(removedMemDetail, undo: true); final removeUndoneMem = removeUndone.mem; - if (removeUndoneMem is SavedMem) { + if (removeUndoneMem is SavedMemEntity) { ref.read(memsProvider.notifier).add(removeUndoneMem); for (var element in removeUndone.memItems) { ref.read(memItemsProvider.notifier).upsertAll( - [element], + [ + element, + ], (current, updating) => - current is SavedMemItem && - updating is SavedMemItem && + current is SavedMemItemEntity && + updating is SavedMemItemEntity && current.id == updating.id, ); } diff --git a/lib/mems/detail/actions.dart b/lib/mems/detail/actions.dart index 544ae8bc3..f75860211 100644 --- a/lib/mems/detail/actions.dart +++ b/lib/mems/detail/actions.dart @@ -1,11 +1,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:mem/core/mem_detail.dart'; +import 'package:mem/mems/mem_detail.dart'; import 'package:mem/logger/log_service.dart'; import 'package:mem/mems/detail/states.dart'; import 'package:mem/mems/mem_client.dart'; -import 'package:mem/repositories/mem.dart'; -import 'package:mem/repositories/mem_notification.dart'; import 'package:mem/mems/states.dart'; +import 'package:mem/mems/mem_entity.dart'; +import 'package:mem/mems/mem_notification_entity.dart'; final _memClient = MemClient(); @@ -19,8 +19,10 @@ final saveMem = ); ref.read(memsProvider.notifier).upsertAll( - [saved.mem], - (tmp, item) => tmp is SavedMem && item is SavedMem + [ + saved.mem, + ], + (tmp, item) => tmp is SavedMemEntity && item is SavedMemEntity ? tmp.id == item.id : false, ); @@ -37,8 +39,8 @@ final saveMem = ref.read(memNotificationsProvider.notifier).upsertAll( saved.notifications ?? [], (tmp, item) => - tmp is SavedMemNotification && - item is SavedMemNotification && + tmp is SavedMemNotificationEntity && + item is SavedMemNotificationEntity && tmp.id == item.id, removeWhere: (current) => current.isRepeatByDayOfWeek(), ); @@ -53,14 +55,17 @@ final archiveMem = Provider.autoDispose.family, int?>( () async { final mem = ref.read(memByMemIdProvider(memId)); - final archived = await _memClient.archive(mem as SavedMem); + final archived = await _memClient.archive(mem!); ref .read(editingMemByMemIdProvider(memId).notifier) .updatedBy(archived.mem); ref.read(memsProvider.notifier).upsertAll( - [archived.mem], - (tmp, item) => - tmp is SavedMem && item is SavedMem ? tmp.id == item.id : false); + [ + archived.mem, + ], + (tmp, item) => tmp is SavedMemEntity && item is SavedMemEntity + ? tmp.id == item.id + : false); return archived; }, @@ -71,17 +76,22 @@ final archiveMem = Provider.autoDispose.family, int?>( final unarchiveMem = Provider.autoDispose.family, int?>( (ref, memId) => v( () async { - final mem = ref.read(memByMemIdProvider(memId)); - - final unarchived = await _memClient.unarchive(mem as SavedMem); + final unarchived = await _memClient.unarchive( + ref.read( + memByMemIdProvider(memId), + )!, + ); ref .read(editingMemByMemIdProvider(memId).notifier) .updatedBy(unarchived.mem); ref.read(memsProvider.notifier).upsertAll( - [unarchived.mem], - (tmp, item) => - tmp is SavedMem && item is SavedMem ? tmp.id == item.id : false); + [ + unarchived.mem, + ], + (tmp, item) => tmp is SavedMemEntity && item is SavedMemEntity + ? tmp.id == item.id + : false); return unarchived; }, @@ -95,16 +105,17 @@ final removeMem = Provider.autoDispose.family, int?>( if (memId != null) { final removeSuccess = await _memClient.remove(memId); - ref - .read(removedMemProvider(memId).notifier) - .updatedBy(ref.read(memByMemIdProvider(memId))); + final mem = ref.read(memByMemIdProvider(memId)); + ref.read(removedMemProvider(memId).notifier).updatedBy( + mem, + ); ref.read(removedMemItemsProvider(memId).notifier).updatedBy( ref.read(memItemsByMemIdProvider(memId)), ); // TODO mem notificationsにも同様の処理が必要では? ref.read(memsProvider.notifier).removeWhere( - (element) => element is SavedMem && element.id == memId); + (element) => element is SavedMemEntity && element.id == memId); return removeSuccess; } diff --git a/lib/mems/detail/body.dart b/lib/mems/detail/body.dart index f4cdf4946..51e58b866 100644 --- a/lib/mems/detail/body.dart +++ b/lib/mems/detail/body.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mem/components/created_and_updated_at_texts.dart'; -import 'package:mem/core/mem.dart'; +import 'package:mem/mems/mem.dart'; import 'package:mem/logger/log_service.dart'; import 'package:mem/mems/detail/notifications/mem_notifications_view.dart'; import 'package:mem/components/mem/mem_done_checkbox.dart'; @@ -72,7 +72,9 @@ class _MemDetailBodyComponent extends StatelessWidget { ), Padding( padding: pageBottomPadding, - child: CreatedAndUpdatedAtTexts(_mem), + child: CreatedAndUpdatedAtTexts( + _mem, + ), ), ], ), diff --git a/lib/mems/detail/mem_items_view.dart b/lib/mems/detail/mem_items_view.dart index 88d035089..f37638dc3 100644 --- a/lib/mems/detail/mem_items_view.dart +++ b/lib/mems/detail/mem_items_view.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mem/components/l10n.dart'; -import 'package:mem/core/mem_item.dart'; import 'package:mem/logger/log_service.dart'; import 'package:mem/mems/detail/states.dart'; -import 'package:mem/mems/mem_item.dart'; +import 'package:mem/mems/mem_item_entity.dart'; const keyMemMemo = Key("mem-memo"); @@ -21,7 +20,8 @@ class MemItemsFormFields extends ConsumerWidget { () => ref.read(memItemsByMemIdProvider(_memId).notifier).upsertAll( [previous.copiedWith(value: () => entered)], (current, updating) => current.type == updating.type && - (current is SavedMemItem && updating is SavedMemItem) + (current is SavedMemItemEntity && + updating is SavedMemItemEntity) ? current.id == updating.id : true, ), @@ -35,8 +35,8 @@ class MemItemsFormFields extends ConsumerWidget { } class _MemItemsFormFields extends StatelessWidget { - final List _memItems; - final void Function(dynamic entered, MemItem previous) _onChanged; + final List _memItems; + final void Function(dynamic entered, MemItemEntity previous) _onChanged; const _MemItemsFormFields(this._memItems, this._onChanged); diff --git a/lib/mems/detail/notifications/after_act_started_notification_view.dart b/lib/mems/detail/notifications/after_act_started_notification_view.dart index 2e08a5d4a..ddba00a8b 100644 --- a/lib/mems/detail/notifications/after_act_started_notification_view.dart +++ b/lib/mems/detail/notifications/after_act_started_notification_view.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mem/components/time_text_form_field.dart'; import 'package:mem/logger/log_service.dart'; import 'package:mem/mems/detail/states.dart'; +import 'package:mem/mems/mem_notification_entity.dart'; const keyMemAfterActStartedNotification = Key("mem-after-act-started-notification"); @@ -31,13 +32,19 @@ class AfterActStartedNotificationView extends ConsumerWidget { onTimeChanged: (picked) => ref .read(memNotificationsByMemIdProvider(_memId).notifier) .upsertAll( - [notification.copiedWith(time: () => picked)], + [ + (notification as MemNotificationEntity) + .copiedWith(time: () => picked) + ], (current, updating) => current.type == updating.type, ), onMessageChanged: (value) => ref .read(memNotificationsByMemIdProvider(_memId).notifier) .upsertAll( - [notification.copiedWith(message: () => value)], + [ + (notification as MemNotificationEntity) + .copiedWith(message: () => value) + ], (current, updating) => current.type == updating.type, ), ); diff --git a/lib/mems/detail/notifications/mem_notifications_text.dart b/lib/mems/detail/notifications/mem_notifications_text.dart index 60a05d745..5ad873df0 100644 --- a/lib/mems/detail/notifications/mem_notifications_text.dart +++ b/lib/mems/detail/notifications/mem_notifications_text.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mem/components/l10n.dart'; -import 'package:mem/core/mem_notification.dart'; +import 'package:mem/mems/mem_notification.dart'; import 'package:mem/logger/log_service.dart'; import 'package:mem/mems/detail/states.dart'; import 'package:mem/values/colors.dart'; diff --git a/lib/mems/detail/notifications/mem_notifications_view.dart b/lib/mems/detail/notifications/mem_notifications_view.dart index 9502bf5b0..539e01e5d 100644 --- a/lib/mems/detail/notifications/mem_notifications_view.dart +++ b/lib/mems/detail/notifications/mem_notifications_view.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mem/components/l10n.dart'; -import 'package:mem/core/mem_notification.dart'; +import 'package:mem/mems/mem_notification.dart'; import 'package:mem/logger/log_service.dart'; import 'package:mem/mems/detail/notifications/mem_notifications_text.dart'; import 'package:mem/mems/detail/states.dart'; @@ -11,7 +11,7 @@ import 'package:mem/values/durations.dart'; import 'mem_notifications_page.dart'; -const keyMemNotificationsView = Key("mem-notifications"); +const keyMemNotificationsView = Key('mem-notifications'); class MemNotificationsView extends ConsumerWidget { final int? _memId; @@ -25,14 +25,14 @@ class MemNotificationsView extends ConsumerWidget { ref.watch(memNotificationsByMemIdProvider(_memId)), ), { - "_memId": _memId, + '_memId': _memId, }, ); } class _MemNotificationsView extends StatelessWidget { final int? _memId; - final List _memNotifications; + final Iterable _memNotifications; const _MemNotificationsView( this._memId, @@ -83,8 +83,8 @@ class _MemNotificationsView extends StatelessWidget { ); }, { - "_memId": _memId, - "_memNotifications": _memNotifications, + '_memId': _memId, + '_memNotifications': _memNotifications, }, ); } diff --git a/lib/mems/detail/notifications/mem_repeat_by_day_of_week_notification_view.dart b/lib/mems/detail/notifications/mem_repeat_by_day_of_week_notification_view.dart index 3ca5d37e5..c471bf798 100644 --- a/lib/mems/detail/notifications/mem_repeat_by_day_of_week_notification_view.dart +++ b/lib/mems/detail/notifications/mem_repeat_by_day_of_week_notification_view.dart @@ -3,12 +3,13 @@ import 'package:day_picker/day_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; -import 'package:mem/core/mem_notification.dart'; +import 'package:mem/mems/mem_notification.dart'; import 'package:mem/logger/log_service.dart'; import 'package:mem/mems/detail/states.dart'; +import 'package:mem/mems/mem_notification_entity.dart'; const keyMemRepeatByDaysOfWeekNotification = - Key("mem-repeat-by-days-of-week-notification"); + Key('mem-repeat-by-days-of-week-notification'); class MemRepeatByDaysOfWeekNotificationView extends ConsumerWidget { final int? _memId; @@ -33,7 +34,11 @@ class MemRepeatByDaysOfWeekNotificationView extends ConsumerWidget { (e) => daysOfWeek.singleWhereOrNull( (element) => element.time == e) ?? - MemNotification.repeatByDayOfWeek(_memId, e), + MemNotificationEntity.initialByType( + _memId, + MemNotificationType.repeatByDayOfWeek, + time: () => e, + ), ), (current, updating) => current.type == updating.type && @@ -43,12 +48,17 @@ class MemRepeatByDaysOfWeekNotificationView extends ConsumerWidget { current.memId == _memId && !selected.contains(current.time), ), - {'selected': selected, 'daysOfWeek': daysOfWeek}, +// coverage:ignore-start + { +// coverage:ignore-end + 'selected': selected, + 'daysOfWeek': daysOfWeek, + }, ), ); }, { - "_memId": _memId, + '_memId': _memId, }, ); } diff --git a/lib/mems/detail/notifications/mem_repeat_by_n_day_notification_view.dart b/lib/mems/detail/notifications/mem_repeat_by_n_day_notification_view.dart index 44833aa52..f8fe13f76 100644 --- a/lib/mems/detail/notifications/mem_repeat_by_n_day_notification_view.dart +++ b/lib/mems/detail/notifications/mem_repeat_by_n_day_notification_view.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mem/components/l10n.dart'; import 'package:mem/logger/log_service.dart'; import 'package:mem/mems/detail/states.dart'; +import 'package:mem/mems/mem_notification_entity.dart'; import 'package:mem/values/dimens.dart'; const keyMemRepeatByNDayNotification = Key("mem-repeat-by-n-day-notification"); @@ -27,9 +28,9 @@ class MemRepeatByNDayNotificationView extends ConsumerWidget { ) .upsertAll( [ - notification.copiedWith( + (notification as MemNotificationEntity).copiedWith( time: () => value, - ) + ), ], (current, updating) => current.type == updating.type, ); diff --git a/lib/mems/detail/notifications/mem_repeated_notification_view.dart b/lib/mems/detail/notifications/mem_repeated_notification_view.dart index 621d1a89d..c11e4435f 100644 --- a/lib/mems/detail/notifications/mem_repeated_notification_view.dart +++ b/lib/mems/detail/notifications/mem_repeated_notification_view.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mem/components/date_and_time/time_of_day_view.dart'; import 'package:mem/logger/log_service.dart'; import 'package:mem/mems/detail/states.dart'; +import 'package:mem/mems/mem_notification_entity.dart'; import 'package:mem/settings/states.dart'; import 'package:mem/values/constants.dart'; @@ -16,12 +17,13 @@ class MemRepeatedNotificationView extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) => v( () { - final memRepeatNotification = - ref.watch(memNotificationsByMemIdProvider(_memId).select( - (v) => v.singleWhere( - (element) => element.isRepeated(), + final memRepeatNotification = ref.watch( + memNotificationsByMemIdProvider(_memId).select( + (v) => v.singleWhere( + (element) => element.isRepeated(), + ), ), - )); + ); return _MemRepeatedNotificationView( memRepeatNotification.time, @@ -30,7 +32,7 @@ class MemRepeatedNotificationView extends ConsumerWidget { .read(memNotificationsByMemIdProvider(_memId).notifier) .upsertAll( [ - memRepeatNotification.copiedWith( + (memRepeatNotification as MemNotificationEntity).copiedWith( time: () => picked, ) ], diff --git a/lib/mems/detail/page.dart b/lib/mems/detail/page.dart index 6744c8054..98e044f7e 100644 --- a/lib/mems/detail/page.dart +++ b/lib/mems/detail/page.dart @@ -9,7 +9,7 @@ import 'package:mem/mems/detail/app_bar/remove_mem_action.dart'; import 'package:mem/mems/detail/states.dart'; import 'package:mem/mems/detail/app_bar/transit_act_list_action.dart'; import 'package:mem/mems/detail/app_bar/transit_chart_action.dart'; -import 'package:mem/repositories/mem.dart'; +import 'package:mem/mems/mem_entity.dart'; import 'package:mem/values/colors.dart'; class MemDetailPage extends ConsumerWidget { @@ -21,13 +21,13 @@ class MemDetailPage extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) => v( () => _MemDetailPage( ref.watch(editingMemByMemIdProvider(_memId).select( - (value) => value is SavedMem ? value.id : null, + (value) => value is SavedMemEntity ? value.id : null, )), ref.watch(editingMemByMemIdProvider(_memId).select( - (value) => value is SavedMem, + (value) => value is SavedMemEntity, )), ref.watch(editingMemByMemIdProvider(_memId).select( - (value) => value is SavedMem ? value.isArchived : false, + (value) => value is SavedMemEntity ? value.isArchived : false, )), ), { diff --git a/lib/mems/detail/states.dart b/lib/mems/detail/states.dart index 54ae021ae..3528d8fd6 100644 --- a/lib/mems/detail/states.dart +++ b/lib/mems/detail/states.dart @@ -1,29 +1,32 @@ import 'package:collection/collection.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:mem/core/mem.dart'; -import 'package:mem/core/mem_item.dart'; -import 'package:mem/core/mem_notification.dart'; +import 'package:mem/mems/mem_item.dart'; +import 'package:mem/mems/mem_notification.dart'; import 'package:mem/components/list_value_state_notifier.dart'; import 'package:mem/logger/log_service.dart'; import 'package:mem/components/value_state_notifier.dart'; -import 'package:mem/mems/mem_item.dart'; import 'package:mem/mems/mem_item_repository.dart'; import 'package:mem/mems/states.dart'; -import 'package:mem/repositories/mem_notification.dart'; -import 'package:mem/repositories/mem_notification_repository.dart'; +import 'package:mem/mems/mem_entity.dart'; +import 'package:mem/mems/mem_item_entity.dart'; +import 'package:mem/mems/mem_notification_entity.dart'; +import 'package:mem/mems/mem_notification_repository.dart'; final editingMemByMemIdProvider = StateNotifierProvider.autoDispose - .family, Mem, int?>( + .family, MemEntity, int?>( (ref, memId) => v( - () => ValueStateNotifier( - ref.watch(memByMemIdProvider(memId)) ?? Mem.defaultNew(), - ), + () { + final mem = ref.watch(memByMemIdProvider(memId)); + return ValueStateNotifier( + mem ?? MemEntity("", null, null), + ); + }, {"memId": memId}, ), ); final memItemsByMemIdProvider = StateNotifierProvider.family< - ListValueStateNotifier, List, int?>( + ListValueStateNotifier, List, int?>( (ref, memId) => v( () => ListValueStateNotifier( [ @@ -34,21 +37,21 @@ final memItemsByMemIdProvider = StateNotifierProvider.family< ), ), ) ?? - MemItem.memo(memId), + MemItemEntity(memId, MemItemType.memo, "") ], initializer: (current, notifier) async { if (memId != null) { ref.read(memItemsProvider.notifier).upsertAll( - await MemItemRepository().shipByMemId(memId), + await MemItemRepository().ship(memId: memId), (current, updating) => - current is SavedMemItem && - updating is SavedMemItem && + current is SavedMemItemEntity && + updating is SavedMemItemEntity && current.id == updating.id, ); } }, ), - {"memId": memId}, + {'memId': memId}, ), ); @@ -103,20 +106,23 @@ final memNotificationsByMemIdProvider = StateNotifierProvider.autoDispose [ ...memNotificationsByMemId, if (memNotificationsByMemId.every((element) => !element.isRepeated())) - MemNotification.repeated(memId), + MemNotificationEntity.initialByType( + memId, MemNotificationType.repeat), if (memNotificationsByMemId .every((element) => !element.isRepeatByNDay())) - MemNotification.repeatByNDay(memId), + MemNotificationEntity.initialByType( + memId, MemNotificationType.repeatByNDay), if (memNotificationsByMemId .every((element) => !element.isAfterActStarted())) - MemNotification.afterActStarted(memId), + MemNotificationEntity.initialByType( + memId, MemNotificationType.afterActStarted), ], initializer: (current, notifier) => v( () async { if (memId != null && - current.whereType().isEmpty) { + current.whereType().isEmpty) { ref.read(memNotificationsProvider.notifier).upsertAll( - await MemNotificationRepository().shipByMemId(memId), + await MemNotificationRepository().ship(memId: memId), (current, updating) => updating.isRepeatByDayOfWeek() ? current.memId == updating.memId && current.type == updating.type && diff --git a/lib/mems/list/item/actions.dart b/lib/mems/list/item/actions.dart index ebebf3b82..fc8591d53 100644 --- a/lib/mems/list/item/actions.dart +++ b/lib/mems/list/item/actions.dart @@ -2,46 +2,47 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mem/logger/log_service.dart'; import 'package:mem/mems/mem_service.dart'; import 'package:mem/mems/states.dart'; -import 'package:mem/repositories/mem.dart'; +import 'package:mem/mems/mem_entity.dart'; -final doneMem = Provider.autoDispose.family( +final doneMem = Provider.autoDispose.family( (ref, memId) => v( () { - final mem = ref - .read(memByMemIdProvider(memId))! - .copiedWith(doneAt: () => DateTime.now()); - MemService().doneByMemId(memId).then( (doneMemDetail) => ref.read(memsProvider.notifier).upsertAll( - [doneMemDetail.mem], - (tmp, item) => tmp is SavedMem && item is SavedMem + [ + doneMemDetail.mem, + ], + (tmp, item) => tmp is SavedMemEntity && item is SavedMemEntity ? tmp.id == item.id : false, ), ); - return mem; + return ref + .read(memByMemIdProvider(memId))! + .copiedWith(doneAt: () => DateTime.now()); }, memId, ), ); -final undoneMem = Provider.autoDispose.family( +final undoneMem = Provider.autoDispose.family( (ref, memId) => v( () { - final mem = - ref.read(memByMemIdProvider(memId))!.copiedWith(doneAt: () => null); - MemService().undoneByMemId(memId).then( (undoneMemDetail) => ref.read(memsProvider.notifier).upsertAll( - [undoneMemDetail.mem], - (tmp, item) => tmp is SavedMem && item is SavedMem + [ + undoneMemDetail.mem, + ], + (tmp, item) => tmp is SavedMemEntity && item is SavedMemEntity ? tmp.id == item.id : false, ), ); - return mem; + return ref + .read(memByMemIdProvider(memId))! + .copiedWith(doneAt: () => null); }, memId, ), diff --git a/lib/mems/list/item/subtitle.dart b/lib/mems/list/item/subtitle.dart index bf280ff7c..c27364dc7 100644 --- a/lib/mems/list/item/subtitle.dart +++ b/lib/mems/list/item/subtitle.dart @@ -3,12 +3,12 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mem/components/l10n.dart'; import 'package:mem/components/mem/mem_period.dart'; import 'package:mem/core/date_and_time/date_and_time_period.dart'; -import 'package:mem/core/mem_notification.dart'; +import 'package:mem/mems/mem_notification.dart'; import 'package:mem/logger/log_service.dart'; import 'package:mem/mems/detail/notifications/mem_notifications_text.dart'; import 'package:mem/mems/detail/states.dart'; import 'package:mem/mems/states.dart'; -import 'package:mem/repositories/mem_notification.dart'; +import 'package:mem/mems/mem_notification_entity.dart'; class MemListItemSubtitle extends ConsumerWidget { final int _memId; @@ -22,7 +22,7 @@ class MemListItemSubtitle extends ConsumerWidget { ref.watch(memByMemIdProvider(_memId))?.period, ref.watch( memNotificationsByMemIdProvider(_memId).select( - (v) => v.whereType(), + (v) => v.whereType(), ), ), ), @@ -35,7 +35,7 @@ class MemListItemSubtitle extends ConsumerWidget { class _MemListItemSubtitle extends StatelessWidget { final int _memId; final DateAndTimePeriod? _memPeriod; - final Iterable _savedMemNotifications; + final Iterable _savedMemNotifications; const _MemListItemSubtitle( this._memId, diff --git a/lib/mems/list/item/view.dart b/lib/mems/list/item/view.dart index a82cd9a82..6b81b8f68 100644 --- a/lib/mems/list/item/view.dart +++ b/lib/mems/list/item/view.dart @@ -6,14 +6,14 @@ import 'package:mem/components/mem/list/states.dart'; import 'package:mem/components/mem/mem_done_checkbox.dart'; import 'package:mem/components/mem/mem_name.dart'; import 'package:mem/components/timer.dart'; -import 'package:mem/core/act.dart'; -import 'package:mem/core/mem_notification.dart'; +import 'package:mem/mems/mem_notification.dart'; import 'package:mem/logger/log_service.dart'; import 'package:mem/mems/detail/states.dart'; import 'package:mem/mems/list/item/subtitle.dart'; import 'package:mem/mems/states.dart'; -import 'package:mem/repositories/mem.dart'; -import 'package:mem/repositories/mem_notification.dart'; +import 'package:mem/acts/act_entity.dart'; +import 'package:mem/mems/mem_entity.dart'; +import 'package:mem/mems/mem_notification_entity.dart'; import 'package:mem/values/colors.dart'; import 'actions.dart'; @@ -40,7 +40,7 @@ class MemListItemView extends ConsumerWidget { ? ref.read(doneMem(_memId)) : ref.read(undoneMem(_memId)) ], - (tmp, item) => tmp is SavedMem && item is SavedMem + (tmp, item) => tmp is SavedMemEntity && item is SavedMemEntity ? tmp.id == item.id : false, ); @@ -65,15 +65,16 @@ class MemListItemView extends ConsumerWidget { class _MemListItemView extends ListTile { _MemListItemView( - SavedMem mem, - SavedAct? activeAct, + SavedMemEntity mem, + SavedActEntity? activeAct, Iterable memNotifications, void Function(int memId) onTap, void Function(bool? value, int memId) onMemDoneCheckboxTapped, - void Function(SavedAct? act) onActButtonTapped, + void Function(SavedActEntity? act) onActButtonTapped, ) : super( leading: memNotifications - .where((e) => e is SavedMemNotification && e.isEnabled()) + .where((e) => + e is SavedMemNotificationEntity && e.isEnabled()) .isEmpty && activeAct == null ? MemDoneCheckbox( @@ -100,13 +101,15 @@ class _MemListItemView extends ListTile { ), subtitle: mem.period == null && memNotifications - .where((e) => e is SavedMemNotification && e.isEnabled()) + .where((e) => + e is SavedMemNotificationEntity && e.isEnabled()) .isEmpty ? null : MemListItemSubtitle(mem.id), isThreeLine: mem.period != null && memNotifications - .where((e) => e is SavedMemNotification && e.isEnabled()) + .where( + (e) => e is SavedMemNotificationEntity && e.isEnabled()) .isNotEmpty, tileColor: mem.isArchived ? secondaryGreyColor : null, onTap: () => onTap(mem.id), diff --git a/lib/core/mem.dart b/lib/mems/mem.dart similarity index 73% rename from lib/core/mem.dart rename to lib/mems/mem.dart index 660256578..c1064f1ce 100644 --- a/lib/core/mem.dart +++ b/lib/mems/mem.dart @@ -1,13 +1,12 @@ // FIXME coreからflutterへの依存は排除したい import 'package:flutter/material.dart'; import 'package:mem/core/date_and_time/date_and_time_period.dart'; -import 'package:mem/framework/repository/entity.dart'; import 'package:mem/logger/log_service.dart'; import 'package:mem/notifications/notification/type.dart'; import 'package:mem/notifications/schedule.dart'; -import 'package:mem/repositories/mem.dart'; +import 'package:mem/mems/mem_entity.dart'; -class Mem extends EntityV1 { +class Mem { final String name; final DateTime? doneAt; final DateAndTimePeriod? period; @@ -16,14 +15,13 @@ class Mem extends EntityV1 { bool get isDone => doneAt != null; - factory Mem.defaultNew() => Mem("", null, null); - Iterable periodSchedules( TimeOfDay startOfDay, ) => v( () { - final id = this is SavedMem ? (this as SavedMem).id : null; + final id = + this is SavedMemEntity ? (this as SavedMemEntity).id : null; return id == null ? throw Exception() // coverage:ignore-line @@ -63,22 +61,4 @@ class Mem extends EntityV1 { 'startOfDay': startOfDay, }, ); - - Mem copiedWith({ - String Function()? name, - DateTime? Function()? doneAt, - DateAndTimePeriod? Function()? period, - }) => - Mem( - name == null ? this.name : name(), - doneAt == null ? this.doneAt : doneAt(), - period == null ? this.period : period(), - ); - - @override - String toString() => "${super.toString()}: ${{ - "name": name, - "doneAt": doneAt, - "period": period, - }}"; } diff --git a/lib/mems/mem_client.dart b/lib/mems/mem_client.dart index 046fbccf9..cb2b6d7e0 100644 --- a/lib/mems/mem_client.dart +++ b/lib/mems/mem_client.dart @@ -1,11 +1,11 @@ -import 'package:mem/core/mem.dart'; -import 'package:mem/core/mem_detail.dart'; -import 'package:mem/core/mem_item.dart'; -import 'package:mem/core/mem_notification.dart'; +import 'package:mem/mems/mem.dart'; +import 'package:mem/mems/mem_detail.dart'; +import 'package:mem/mems/mem_notification.dart'; import 'package:mem/logger/log_service.dart'; import 'package:mem/notifications/notification_client.dart'; -import 'package:mem/repositories/mem.dart'; -import 'package:mem/repositories/mem_notification.dart'; +import 'package:mem/mems/mem_entity.dart'; +import 'package:mem/mems/mem_item_entity.dart'; +import 'package:mem/mems/mem_notification_entity.dart'; import 'mem_service.dart'; @@ -14,8 +14,8 @@ class MemClient { final NotificationClient _notificationClient; Future save( - Mem mem, - List memItemList, + MemEntity mem, + List memItemList, List memNotificationList, ) => v( @@ -29,10 +29,10 @@ class MemClient { ); _notificationClient.registerMemNotifications( - (saved.mem as SavedMem).id, - savedMem: saved.mem as SavedMem, + (saved.mem as SavedMemEntity).id, + savedMem: saved.mem as SavedMemEntity, savedMemNotifications: - saved.notifications?.whereType(), + saved.notifications?.whereType(), ); return saved; @@ -44,15 +44,15 @@ class MemClient { }, ); - Future archive(Mem mem) => v( + Future archive(MemEntity mem) => v( () async { // FIXME MemServiceの責務 - if (mem is SavedMem) { + if (mem is SavedMemEntity) { final archived = await _memService.archive(mem); final archivedMem = archived.mem; // FIXME archive後のMemDetailなので、必ずSavedMemのはず - if (archivedMem is SavedMem) { + if (archivedMem is SavedMemEntity) { _notificationClient.cancelMemNotifications(archivedMem.id); } @@ -71,15 +71,16 @@ class MemClient { Future unarchive(Mem mem) => v( () async { - // FIXME MemServiceの責務 - if (mem is SavedMem) { + // FIXME 保存済みかどうかを判定するのはMemServiceの責務? + // Client sideで判定できるものではない気がする + if (mem is SavedMemEntity) { final unarchived = await _memService.unarchive(mem); _notificationClient.registerMemNotifications( - (unarchived.mem as SavedMem).id, - savedMem: unarchived.mem as SavedMem, - savedMemNotifications: - unarchived.notifications?.whereType(), + (unarchived.mem as SavedMemEntity).id, + savedMem: unarchived.mem as SavedMemEntity, + savedMemNotifications: unarchived.notifications + ?.whereType(), ); return unarchived; diff --git a/lib/mems/mem_detail.dart b/lib/mems/mem_detail.dart new file mode 100644 index 000000000..9724586bc --- /dev/null +++ b/lib/mems/mem_detail.dart @@ -0,0 +1,23 @@ +import 'package:mem/mems/mem_notification.dart'; +import 'package:mem/mems/mem_entity.dart'; +import 'package:mem/mems/mem_item_entity.dart'; + +// FIXME 定義するべきではない気がする +// - Mem, MemItems, MemNotificationsの関係はどのレイヤーのもの? +// - Entity~~かDomain~~ +// - DBのFK制約が絡むしEntityかも +// - Repositoryも絡んでいくはず +class MemDetail { + final MemEntity mem; + final List memItems; + final List? notifications; + + MemDetail(this.mem, this.memItems, [this.notifications]); + + @override + String toString() => { + 'mem': mem, + 'memItems': memItems, + 'notifications': notifications, + }.toString(); +} diff --git a/lib/mems/mem_entity.dart b/lib/mems/mem_entity.dart new file mode 100644 index 000000000..ef06727cc --- /dev/null +++ b/lib/mems/mem_entity.dart @@ -0,0 +1,82 @@ +import 'package:mem/core/date_and_time/date_and_time.dart'; +import 'package:mem/core/date_and_time/date_and_time_period.dart'; +import 'package:mem/mems/mem.dart'; +import 'package:mem/databases/table_definitions/mems.dart'; +import 'package:mem/framework/repository/database_tuple_entity.dart'; +import 'package:mem/framework/repository/entity.dart'; + +class MemEntity extends Mem with Entity, Copyable { + MemEntity(super.name, super.doneAt, super.period); + + MemEntity.fromMap(Map map) + : super( + map[defColMemsName.name], + map[defColMemsDoneAt.name], + map[defColMemsStartOn.name] == null && + map[defColMemsEndOn.name] == null + ? null + : DateAndTimePeriod( + start: map[defColMemsStartOn.name] == null + ? null + : DateAndTime.from(map[defColMemsStartOn.name], + timeOfDay: map[defColMemsStartAt.name]), + end: map[defColMemsEndOn.name] == null + ? null + : DateAndTime.from(map[defColMemsEndOn.name], + timeOfDay: map[defColMemsEndAt.name]), + ), + ); + + @override + MemEntity copiedWith({ + String Function()? name, + DateTime? Function()? doneAt, + DateAndTimePeriod? Function()? period, + }) => + MemEntity( + name == null ? this.name : name(), + doneAt == null ? this.doneAt : doneAt(), + period == null ? this.period : period(), + ); + + @override + Map get toMap => { + defColMemsName.name: name, + defColMemsDoneAt.name: doneAt, + defColMemsStartOn.name: period?.start, + defColMemsStartAt.name: + period?.start?.isAllDay == true ? null : period?.start, + defColMemsEndOn.name: period?.end, + defColMemsEndAt.name: + period?.end?.isAllDay == true ? null : period?.end, + }; +} + +class SavedMemEntity extends MemEntity with DatabaseTupleEntity { + SavedMemEntity(super.name, super.doneAt, super.period); + + SavedMemEntity.fromMap( + Map map, + ) : super.fromMap(map) { + withMap(map); + } + + @override + SavedMemEntity copiedWith({ + String Function()? name, + DateTime? Function()? doneAt, + DateAndTimePeriod? Function()? period, + }) => + SavedMemEntity.fromMap( + toMap + ..addAll( + super + .copiedWith( + name: name, + doneAt: doneAt, + period: period, + ) + .toMap, + ), + ); +} diff --git a/lib/mems/mem_item.dart b/lib/mems/mem_item.dart index 9422f7c04..fd47a58c0 100644 --- a/lib/mems/mem_item.dart +++ b/lib/mems/mem_item.dart @@ -1,20 +1,11 @@ -import 'package:mem/core/mem_item.dart'; -import 'package:mem/framework/repository/database_tuple_entity.dart'; - -class SavedMemItem extends MemItem with SavedDatabaseTupleMixin { - @override - int get memId => super.memId as int; +enum MemItemType { + memo, +} - SavedMemItem(super.memId, super.type, super.value); +class MemItem { + final int? memId; + final MemItemType type; + final dynamic value; - @override - SavedMemItem copiedWith({ - int Function()? memId, - dynamic Function()? value, - }) => - SavedMemItem( - memId == null ? this.memId : memId(), - type, - value == null ? this.value : value(), - )..copiedFrom(this); + MemItem(this.memId, this.type, this.value); } diff --git a/lib/mems/mem_item_entity.dart b/lib/mems/mem_item_entity.dart new file mode 100644 index 000000000..a71539d02 --- /dev/null +++ b/lib/mems/mem_item_entity.dart @@ -0,0 +1,58 @@ +import 'package:mem/mems/mem_item.dart'; +import 'package:mem/databases/table_definitions/mem_items.dart'; +import 'package:mem/framework/repository/database_tuple_entity.dart'; +import 'package:mem/framework/repository/entity.dart'; + +class MemItemEntity extends MemItem with Entity, Copyable { + MemItemEntity(super.memId, super.type, super.value); + + MemItemEntity.fromMap(Map map) + : super( + map[defFkMemItemsMemId.name], + MemItemType.values.firstWhere( + (v) => v.name == map[defColMemItemsType.name], + ), + map[defColMemItemsValue.name], + ); + + @override + Map get toMap => { + defFkMemItemsMemId.name: memId, + defColMemItemsType.name: type.name, + defColMemItemsValue.name: value, + }; + + @override + MemItemEntity copiedWith({ + int Function()? memId, + dynamic Function()? value, + }) => + MemItemEntity( + memId == null ? this.memId : memId(), + type, + value == null ? this.value : value(), + ); +} + +class SavedMemItemEntity extends MemItemEntity with DatabaseTupleEntity { + SavedMemItemEntity.fromMap(Map map) : super.fromMap(map) { + withMap(map); + } + + @override + MemItemEntity copiedWith({ + int Function()? memId, + dynamic Function()? value, + }) => + SavedMemItemEntity.fromMap( + toMap + ..addAll( + super + .copiedWith( + memId: memId, + value: value, + ) + .toMap, + ), + ); +} diff --git a/lib/mems/mem_item_repository.dart b/lib/mems/mem_item_repository.dart index 699f7ff11..cc75ef413 100644 --- a/lib/mems/mem_item_repository.dart +++ b/lib/mems/mem_item_repository.dart @@ -1,63 +1,118 @@ -import 'package:mem/core/mem_item.dart'; +import 'package:mem/databases/definition.dart'; +import 'package:mem/databases/table_definitions/base.dart'; import 'package:mem/databases/table_definitions/mem_items.dart'; -import 'package:mem/logger/log_service.dart'; import 'package:mem/framework/repository/database_tuple_repository.dart'; +import 'package:mem/framework/repository/group_by.dart'; +import 'package:mem/framework/repository/order_by.dart'; +import 'package:mem/logger/log_service.dart'; import 'package:mem/framework/repository/condition/conditions.dart'; -import 'package:mem/mems/mem_item.dart'; +import 'package:mem/mems/mem_item_entity.dart'; class MemItemRepository - extends DatabaseTupleRepository { - Future> shipByMemId(int memId) => v( - () => super.ship(condition: Equals(defFkMemItemsMemId.name, memId)), - {'memId': memId}, - ); + extends DatabaseTupleRepository { + MemItemRepository() : super(databaseDefinition, defTableMemItems); - Future> archiveByMemId(int memId) => v( - () async => Future.wait( - (await shipByMemId(memId)).map((e) => super.archive(e))), - {'memId': memId}, + @override + SavedMemItemEntity pack(Map map) => + SavedMemItemEntity.fromMap(map); + + @override + Future> ship({ + int? memId, + Condition? condition, + GroupBy? groupBy, + List? orderBy, + int? offset, + int? limit, + }) => + v( + () => super.ship( + condition: And( + [ + if (memId != null) Equals(defFkMemItemsMemId, memId), + if (condition != null) condition, + ], + ), + groupBy: groupBy, + orderBy: orderBy, + offset: offset, + limit: limit, + ), + { + 'memId': memId, + 'condition': condition, + 'groupBy': groupBy, + 'orderBy': orderBy, + 'offset': offset, + 'limit': limit, + }, ); - Future> unarchiveByMemId(int memId) => v( + Future> archiveBy({ + int? memId, + DateTime? archivedAt, + }) => + v( () async => Future.wait( - (await shipByMemId(memId)).map((e) => super.unarchive(e))), - {'memId': memId}, + await ship( + condition: And( + [ + if (memId != null) Equals(defFkMemItemsMemId, memId), + ], + ), + ).then( + (v) => v.map( + (e) => archive(e, archivedAt: archivedAt), + ), + ), + ), + { + 'memId': memId, + 'archivedAt': archivedAt, + }, ); - Future> wasteByMemId(int memId) => v( - () async => await super.waste(Equals(defFkMemItemsMemId.name, memId)), - {'memId': memId}, + Future> unarchiveBy({ + int? memId, + DateTime? updatedAt, + }) => + v( + () async => Future.wait( + await ship( + condition: And( + [ + if (memId != null) Equals(defFkMemItemsMemId, memId), + ], + ), + ).then( + (v) => v.map( + (e) => unarchive(e, updatedAt: updatedAt), + ), + ), + ), + { + 'memId': memId, + 'updatedAt': updatedAt, + }, ); @override - SavedMemItem pack(Map tuple) => SavedMemItem( - tuple[defFkMemItemsMemId.name], - MemItemType.values.firstWhere( - (v) => v.name == tuple[defColMemItemsType.name], + Future> waste({ + int? memId, + Condition? condition, + }) => + v( + () => super.waste( + condition: And( + [ + if (memId != null) Equals(defPkId, memId), + if (condition != null) condition, // coverage:ignore-line + ], + ), ), - tuple[defColMemItemsValue.name], - )..pack(tuple); - - @override - Map unpack(MemItem entity) { - final map = { - defFkMemItemsMemId.name: entity.memId, - defColMemItemsType.name: entity.type.name, - defColMemItemsValue.name: entity.value, - }; - - if (entity is SavedMemItem) { - map.addAll(entity.unpack()); - } - - return map; - } - - MemItemRepository._() : super(defTableMemItems); - - static MemItemRepository? _instance; - - factory MemItemRepository() => v( - () => _instance ??= MemItemRepository._(), + { + 'memId': memId, + 'condition': condition, + }, ); } diff --git a/lib/core/mem_notification.dart b/lib/mems/mem_notification.dart similarity index 72% rename from lib/core/mem_notification.dart rename to lib/mems/mem_notification.dart index 1be20de26..e037a02d9 100644 --- a/lib/core/mem_notification.dart +++ b/lib/mems/mem_notification.dart @@ -1,14 +1,26 @@ import 'package:collection/collection.dart'; import 'package:intl/intl.dart'; import 'package:mem/core/date_and_time/date_and_time.dart'; -import 'package:mem/framework/repository/entity.dart'; import 'package:mem/logger/log_service.dart'; const _repeatedMessage = "Repeat"; const _repeatByDayOfWeekMessage = "Repeat by day of week"; const _afterActStartedMessage = "Finish?"; -class MemNotification extends EntityV1 { +enum MemNotificationType { + repeat, + repeatByNDay, + repeatByDayOfWeek, + afterActStarted; + + factory MemNotificationType.fromName(String name) => + MemNotificationType.values.singleWhere( + (element) => element.name == name, + orElse: () => throw Exception('Unexpected name: "$name".'), + ); +} + +class MemNotification { // 未保存のMemに紐づくMemNotificationはmemIdをintで持つことができないため暫定的にnullableにしている final int? memId; final MemNotificationType type; @@ -17,6 +29,29 @@ class MemNotification extends EntityV1 { MemNotification(this.memId, this.type, this.time, this.message); + static MemNotification initialByType( + int? memId, + MemNotificationType type, { + int? Function()? time, + }) { + switch (type) { +// coverage:ignore-start + case MemNotificationType.repeat: + return MemNotification( + memId, type, time == null ? null : time(), _repeatedMessage); + case MemNotificationType.repeatByNDay: + return MemNotification( + memId, type, time == null ? 1 : time(), _repeatedMessage); + case MemNotificationType.repeatByDayOfWeek: + return MemNotification(memId, type, time == null ? null : time(), + _repeatByDayOfWeekMessage); + case MemNotificationType.afterActStarted: + return MemNotification( + memId, type, time == null ? null : time(), _afterActStartedMessage); +// coverage:ignore-end + } + } + bool isEnabled() => time != null; bool isRepeated() => type == MemNotificationType.repeat; @@ -27,39 +62,6 @@ class MemNotification extends EntityV1 { bool isAfterActStarted() => type == MemNotificationType.afterActStarted; - factory MemNotification.repeated(int? memId) => MemNotification( - memId, MemNotificationType.repeat, null, _repeatedMessage); - - factory MemNotification.repeatByNDay(int? memId) => MemNotification( - memId, MemNotificationType.repeatByNDay, 1, _repeatedMessage); - - factory MemNotification.repeatByDayOfWeek(int? memId, int time) => - MemNotification(memId, MemNotificationType.repeatByDayOfWeek, time, - _repeatByDayOfWeekMessage); - - factory MemNotification.afterActStarted(int? memId) => MemNotification(memId, - MemNotificationType.afterActStarted, null, _afterActStartedMessage); - - MemNotification copiedWith({ - int Function()? memId, - int? Function()? time, - String Function()? message, - }) => - MemNotification( - memId == null ? this.memId : memId(), - type, - time == null ? this.time : time(), - message == null ? this.message : message(), - ); - - @override - String toString() => "${super.toString()}: ${{ - "memId": memId, - "type": type, - "time": time, - "message": message, - }}"; - static String? toOneLine( Iterable memNotifications, String Function(String at) buildRepeatedNotificationText, @@ -118,20 +120,27 @@ class MemNotification extends EntityV1 { String Function(String nDay, String at) buildRepeatEveryNDayNotificationText, String Function(DateAndTime dateAndTime) formatToTimeOfDay, - ) { - if (repeatByNDay != null && (repeatByNDay.time ?? 0) > 1) { - return buildRepeatEveryNDayNotificationText( - repeatByNDay.time.toString(), - formatToTimeOfDay( - DateAndTime(0, 0, 0, 0, 0, repeat.time), - ), + ) => + v( + () { + if (repeatByNDay != null && (repeatByNDay.time ?? 0) > 1) { + return buildRepeatEveryNDayNotificationText( + repeatByNDay.time.toString(), + formatToTimeOfDay( + DateAndTime(0, 0, 0, 0, 0, repeat.time), + ), + ); + } else { + return buildRepeatedNotificationText(formatToTimeOfDay( + DateAndTime(0, 0, 0, 0, 0, repeat.time), + )); + } + }, + { + 'repeat': repeat, + 'repeatByNDay': repeatByNDay, + }, ); - } else { - return buildRepeatedNotificationText(formatToTimeOfDay( - DateAndTime(0, 0, 0, 0, 0, repeat.time), - )); - } - } static String _oneLineRepeatByDaysOfWeek( Iterable repeatByDayOfWeeks, @@ -147,7 +156,9 @@ class MemNotification extends EntityV1 { .map((e) => dateFormat.format(e)) .join(", "); }, - {'repeatByDayOfWeeks': repeatByDayOfWeeks}, + { + 'repeatByDayOfWeeks': repeatByDayOfWeeks, + }, ); static String _oneLineAfterAct( @@ -157,16 +168,3 @@ class MemNotification extends EntityV1 { buildAfterActStartedNotificationText(DateFormat(DateFormat.HOUR24_MINUTE) .format(DateAndTime(0, 0, 0, 0, 0, afterActStarted.time))); } - -enum MemNotificationType { - repeat, - repeatByNDay, - repeatByDayOfWeek, - afterActStarted; - - factory MemNotificationType.fromName(String name) => - MemNotificationType.values.singleWhere( - (element) => element.name == name, - orElse: () => throw Exception('Unexpected name: "$name".'), - ); -} diff --git a/lib/mems/mem_notification_entity.dart b/lib/mems/mem_notification_entity.dart new file mode 100644 index 000000000..6dfd72628 --- /dev/null +++ b/lib/mems/mem_notification_entity.dart @@ -0,0 +1,76 @@ +import 'package:mem/mems/mem_notification.dart'; +import 'package:mem/databases/table_definitions/mem_notifications.dart'; +import 'package:mem/framework/repository/database_tuple_entity.dart'; +import 'package:mem/framework/repository/entity.dart'; + +class MemNotificationEntity extends MemNotification + with Entity, Copyable { + MemNotificationEntity(super.memId, super.type, super.time, super.message) + : super(); + + MemNotificationEntity.fromMap(Map map) + : super( + map[defFkMemNotificationsMemId.name], + MemNotificationType.fromName(map[defColMemNotificationsType.name]), + map[defColMemNotificationsTime.name], + map[defColMemNotificationsMessage.name], + ); + + static MemNotificationEntity initialByType( + int? memId, + MemNotificationType type, { + int? Function()? time, + }) { + final core = MemNotification.initialByType(memId, type, time: time); + return MemNotificationEntity(memId, type, core.time, core.message); + } + + @override + Map get toMap => { + defFkMemNotificationsMemId.name: memId, + defColMemNotificationsType.name: type.name, + defColMemNotificationsTime.name: time, + defColMemNotificationsMessage.name: message, + }; + + @override + MemNotificationEntity copiedWith({ + int? Function()? memId, + int? Function()? time, + String Function()? message, + }) => + MemNotificationEntity( + memId == null ? this.memId : memId(), + type, + time == null ? this.time : time(), + message == null ? this.message : message(), + ); +} + +class SavedMemNotificationEntity extends MemNotificationEntity + with DatabaseTupleEntity { + SavedMemNotificationEntity.fromMap( + Map map, + ) : super.fromMap(map) { + withMap(map); + } + + @override + SavedMemNotificationEntity copiedWith({ + int? Function()? memId, + int? Function()? time, + String Function()? message, + }) => + SavedMemNotificationEntity.fromMap( + toMap + ..addAll( + super + .copiedWith( + memId: memId, + time: time, + message: message, + ) + .toMap, + ), + ); +} diff --git a/lib/mems/mem_notification_repository.dart b/lib/mems/mem_notification_repository.dart new file mode 100644 index 000000000..6bdf99e4e --- /dev/null +++ b/lib/mems/mem_notification_repository.dart @@ -0,0 +1,109 @@ +import 'package:mem/mems/mem_notification.dart'; +import 'package:mem/databases/definition.dart'; +import 'package:mem/databases/table_definitions/mem_notifications.dart'; +import 'package:mem/framework/repository/condition/in.dart'; +import 'package:mem/framework/repository/database_tuple_repository.dart'; +import 'package:mem/framework/repository/group_by.dart'; +import 'package:mem/framework/repository/order_by.dart'; +import 'package:mem/logger/log_service.dart'; +import 'package:mem/framework/repository/condition/conditions.dart'; +import 'package:mem/mems/mem_notification_entity.dart'; + +class MemNotificationRepository extends DatabaseTupleRepository< + MemNotificationEntity, SavedMemNotificationEntity> { + MemNotificationRepository() + : super(databaseDefinition, defTableMemNotifications); + + @override + SavedMemNotificationEntity pack(Map map) => + SavedMemNotificationEntity.fromMap(map); + + @override + Future> ship({ + int? memId, + Iterable? memIdsIn, + Condition? condition, + GroupBy? groupBy, + List? orderBy, + int? offset, + int? limit, + }) => + v( + () => super.ship( + condition: And( + [ + if (memId != null) Equals(defFkMemNotificationsMemId, memId), + if (memIdsIn != null) + In(defFkMemNotificationsMemId.name, memIdsIn), + if (condition != null) condition, // coverage:ignore-line + ], + ), + groupBy: groupBy, + orderBy: orderBy, + offset: offset, + limit: limit, + ), + { + 'memId': memId, + 'memIdsIn': memIdsIn, + 'condition': condition, + 'groupBy': groupBy, + 'orderBy': orderBy, + 'offset': offset, + 'limit': limit, + }, + ); + + Future> archiveBy({ + int? memId, + Condition? condition, + DateTime? archivedAt, + }) => + v( + () async => await ship(memId: memId, condition: condition).then((v) => + Future.wait(v.map((e) => archive(e, archivedAt: archivedAt)))), + { + 'memId': memId, + 'condition': condition, + 'archivedAt': archivedAt, + }, + ); + + Future> unarchiveBy({ + int? memId, + Condition? condition, + DateTime? updatedAt, + }) => + v( + () async => await ship(memId: memId, condition: condition).then((v) => + Future.wait(v.map((e) => unarchive(e, updatedAt: updatedAt)))), + { + 'memId': memId, + 'condition': condition, + 'updatedAt': updatedAt, + }, + ); + + @override + Future> waste({ + int? memId, + MemNotificationType? type, + Condition? condition, + }) => + v( + () => super.waste( + condition: And( + [ + if (memId != null) Equals(defFkMemNotificationsMemId, memId), + if (type != null) Equals(defColMemNotificationsType, type.name), + if (condition != null) condition, // coverage:ignore-line + ], + ), + ), + { + 'memId': memId, + 'type': type, + 'condition': condition, + }, + ); +} diff --git a/lib/mems/mem_repository.dart b/lib/mems/mem_repository.dart new file mode 100644 index 000000000..bdaccbe19 --- /dev/null +++ b/lib/mems/mem_repository.dart @@ -0,0 +1,82 @@ +import 'package:mem/databases/definition.dart'; +import 'package:mem/databases/table_definitions/base.dart'; +import 'package:mem/databases/table_definitions/mems.dart'; +import 'package:mem/framework/repository/database_tuple_repository.dart'; +import 'package:mem/framework/repository/group_by.dart'; +import 'package:mem/framework/repository/order_by.dart'; +import 'package:mem/logger/log_service.dart'; +import 'package:mem/framework/repository/condition/conditions.dart'; +import 'package:mem/mems/mem_entity.dart'; + +class MemRepository extends DatabaseTupleRepository { + MemRepository() : super(databaseDefinition, defTableMems); + + @override + SavedMemEntity pack(Map map) => SavedMemEntity.fromMap(map); + + @override + Future> ship({ + int? id, + bool? archived, + bool? done, + Condition? condition, + GroupBy? groupBy, + List? orderBy, + int? offset, + int? limit, + }) => + v( + () => super.ship( + condition: And( + [ + if (id != null) Equals(defPkId, id), + if (archived != null) + archived + ? IsNotNull(defColArchivedAt.name) + : IsNull(defColArchivedAt.name), + if (done != null) + done + ? IsNotNull(defColMemsDoneAt.name) + : IsNull(defColMemsDoneAt.name), + if (condition != null) condition, + ], + ), + groupBy: groupBy, + orderBy: orderBy, + offset: offset, + limit: limit, + ), + { + 'id': id, + 'archived': archived, + 'done': done, + 'condition': condition, + 'groupBy': groupBy, + 'orderBy': orderBy, + 'offset': offset, + 'limit': limit, + }, + ); + + @override + Future> waste({ + int? id, + Condition? condition, + }) => + v( + () => super.waste( + condition: And( + [ + if (id != null) Equals(defPkId, id), +// coverage:ignore-start + if (condition != null) condition, +// coverage:ignore-end + ], + ), + ), + { + 'id': id, + 'condition': condition, + }, + ); +} diff --git a/lib/mems/mem_service.dart b/lib/mems/mem_service.dart index 7f49691ea..00e14ff50 100644 --- a/lib/mems/mem_service.dart +++ b/lib/mems/mem_service.dart @@ -1,92 +1,108 @@ import 'package:collection/collection.dart'; -import 'package:mem/core/mem_detail.dart'; -import 'package:mem/core/mem_notification.dart'; +import 'package:mem/mems/mem_detail.dart'; +import 'package:mem/mems/mem_notification.dart'; import 'package:mem/logger/log_service.dart'; -import 'package:mem/mems/mem_item.dart'; import 'package:mem/mems/mem_item_repository.dart'; -import 'package:mem/repositories/mem.dart'; -import 'package:mem/repositories/mem_notification.dart'; -import 'package:mem/repositories/mem_notification_repository.dart'; -import 'package:mem/repositories/mem_repository.dart'; +import 'package:mem/mems/mem_entity.dart'; +import 'package:mem/mems/mem_item_entity.dart'; +import 'package:mem/mems/mem_notification_entity.dart'; +import 'package:mem/mems/mem_notification_repository.dart'; +import 'package:mem/mems/mem_repository.dart'; class MemService { final MemRepository _memRepository; final MemItemRepository _memItemRepository; final MemNotificationRepository _memNotificationRepository; - Future save(MemDetail memDetail, {bool undo = false}) => i( + Future save( + MemDetail memDetail, { + bool undo = false, + }) => + i( () async { final mem = memDetail.mem; - final savedMem = (mem is SavedMem && !undo + final savedMem = (mem is SavedMemEntity && !undo ? await _memRepository.replace(mem) : await _memRepository.receive(mem)); - final savedMemItems = (await Future.wait( - memDetail.memItems.map((e) => (e is SavedMemItem && !undo - ? _memItemRepository.replace( - e.copiedWith(memId: () => savedMem.id), - ) - : _memItemRepository.receive( - e.copiedWith(memId: () => savedMem.id), - ))))); + final savedMemItems = await Future.wait( + memDetail.memItems.map((e) => (e is SavedMemItemEntity && !undo + ? _memItemRepository.replace( + e.copiedWith(memId: () => savedMem.id) + as SavedMemItemEntity, + ) + : _memItemRepository.receive( + e.copiedWith(memId: () => savedMem.id), + ))), + ); final memNotifications = memDetail.notifications; final returnMemNotifications = - List.empty(growable: true); + List.empty(growable: true); if (memNotifications == null) { - await _memNotificationRepository.waste(null, savedMem.id); + await _memNotificationRepository.waste(memId: savedMem.id); } else { returnMemNotifications.addAll(await Future.wait(memNotifications .where((e) => !e.isRepeatByDayOfWeek()) .map((e) { if (e.isEnabled()) { - return (e is SavedMemNotification && !undo - ? _memNotificationRepository.replace(e.copiedWith( + return (e is SavedMemNotificationEntity && !undo + ? _memNotificationRepository.replace((e).copiedWith( memId: () => savedMem.id, )) - : _memNotificationRepository.receive(e.copiedWith( - memId: () => savedMem.id, - ))); + : _memNotificationRepository.receive( + (e as MemNotificationEntity).copiedWith( + memId: () => savedMem.id, + ), + )); } else { - _memNotificationRepository.waste( - null, - savedMem.id, - e.type, - ); - return Future.value(null); + return _memNotificationRepository + .waste( + memId: savedMem.id, + type: e.type, + ) + .then((v) => null); } }))); await _memNotificationRepository.waste( - null, - savedMem.id, - MemNotificationType.repeatByDayOfWeek, + memId: savedMem.id, + type: MemNotificationType.repeatByDayOfWeek, ); for (var entry in memNotifications .where((e) => e.isRepeatByDayOfWeek()) .groupListsBy((e) => e.time) .entries) { - returnMemNotifications.add(await _memNotificationRepository - .receive(entry.value.first.copiedWith( - memId: () => savedMem.id, - ))); + returnMemNotifications.add( + await _memNotificationRepository.receive( + (entry.value.first as MemNotificationEntity).copiedWith( + memId: () => savedMem.id, + ), + ), + ); } } return MemDetail( savedMem, savedMemItems, - returnMemNotifications.whereType().toList(), + returnMemNotifications + .whereType() + .toList(), ); }, - {'memDetail': memDetail}, + { + 'memDetail': memDetail, + 'undo': undo, + }, ); Future doneByMemId(int memId) => i( () async { - final done = (await _memRepository.shipById(memId)) - .copiedWith(doneAt: () => DateTime.now()); + final done = + (await _memRepository.ship(id: memId).then((v) => v.single)) + .copiedWith(doneAt: () => DateTime.now()); return save(MemDetail(done, [])); }, {'memId': memId}, @@ -94,20 +110,21 @@ class MemService { Future undoneByMemId(int memId) => i( () async { - final undone = (await _memRepository.shipById(memId)) - .copiedWith(doneAt: () => null); + final undone = + (await _memRepository.ship(id: memId).then((v) => v.single)) + .copiedWith(doneAt: () => null); return save(MemDetail(undone, [])); }, {'memId': memId}, ); - Future archive(SavedMem mem) => i( + Future archive(SavedMemEntity mem) => i( () async { final archivedMem = await _memRepository.archive(mem); final archivedMemItems = - await _memItemRepository.archiveByMemId(archivedMem.id); + await _memItemRepository.archiveBy(memId: archivedMem.id); final archivedMemNotifications = - await _memNotificationRepository.archiveByMemId(archivedMem.id); + await _memNotificationRepository.archiveBy(memId: archivedMem.id); return MemDetail( archivedMem, @@ -120,13 +137,13 @@ class MemService { }, ); - Future unarchive(SavedMem mem) => i( + Future unarchive(SavedMemEntity mem) => i( () async { final unarchivedMem = await _memRepository.unarchive(mem); final unarchivedMemItems = - await _memItemRepository.unarchiveByMemId(unarchivedMem.id); + await _memItemRepository.unarchiveBy(memId: unarchivedMem.id); final unarchivedMemNotifications = await _memNotificationRepository - .unarchiveByMemId(unarchivedMem.id); + .unarchiveBy(memId: unarchivedMem.id); return MemDetail( unarchivedMem, @@ -140,9 +157,9 @@ class MemService { Future remove(int memId) => v( () async { // TODO https://github.com/zin-/mem/issues/284 - await _memNotificationRepository.waste(null, memId); - await _memItemRepository.wasteByMemId(memId); - await _memRepository.wasteById(memId); + await _memNotificationRepository.waste(memId: memId); + await _memItemRepository.waste(memId: memId); + await _memRepository.waste(id: memId); return true; }, diff --git a/lib/mems/states.dart b/lib/mems/states.dart index 5f9e19c46..e3abd4d68 100644 --- a/lib/mems/states.dart +++ b/lib/mems/states.dart @@ -1,21 +1,20 @@ import 'package:collection/collection.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:mem/components/list_value_state_notifier.dart'; -import 'package:mem/core/mem.dart'; -import 'package:mem/core/mem_detail.dart'; -import 'package:mem/core/mem_item.dart'; +import 'package:mem/mems/mem_detail.dart'; import 'package:mem/components/value_state_notifier.dart'; -import 'package:mem/core/mem_notification.dart'; +import 'package:mem/mems/mem_notification.dart'; import 'package:mem/logger/log_service.dart'; -import 'package:mem/repositories/mem.dart'; -import 'package:mem/repositories/mem_repository.dart'; +import 'package:mem/mems/mem_entity.dart'; +import 'package:mem/mems/mem_item_entity.dart'; +import 'package:mem/mems/mem_repository.dart'; final memsProvider = - StateNotifierProvider, List>( - (ref) => v(() => ListValueStateNotifier([])), + StateNotifierProvider, List>( + (ref) => v(() => ListValueStateNotifier([])), ); -final memItemsProvider = - StateNotifierProvider, List>( +final memItemsProvider = StateNotifierProvider< + ListValueStateNotifier, List>( (ref) => v( () => ListValueStateNotifier([]), ), @@ -26,20 +25,23 @@ final memNotificationsProvider = StateNotifierProvider< ); final memByMemIdProvider = StateNotifierProvider.autoDispose - .family, SavedMem?, int?>( + .family, SavedMemEntity?, int?>( (ref, memId) => v( () => ValueStateNotifier( ref.watch(memsProvider).singleWhereOrNull( - (element) => element is SavedMem ? element.id == memId : false, - ) as SavedMem?, + (element) => + element is SavedMemEntity ? element.id == memId : false, + ) as SavedMemEntity?, initializer: (current, notifier) => v( () async { if (memId != null) { - final savedMem = await MemRepository().findOneBy(id: memId); + final savedMem = await MemRepository() + .ship(id: memId) + .then((value) => value.singleOrNull); ref.read(memsProvider.notifier).upsertAll( [if (savedMem != null) savedMem], (current, updating) => - (current is SavedMem && updating is SavedMem) + (current is SavedMemEntity && updating is SavedMemEntity) ? current.id == updating.id : true, ); @@ -74,15 +76,15 @@ final removedMemDetailProvider = StateNotifierProvider.autoDispose memId, ), ); -final removedMemProvider = - StateNotifierProvider.family, Mem?, int>( +final removedMemProvider = StateNotifierProvider.family< + ValueStateNotifier, MemEntity?, int>( (ref, memId) => v( - () => ValueStateNotifier(null), + () => ValueStateNotifier(null), memId, ), ); final removedMemItemsProvider = StateNotifierProvider.family< - ValueStateNotifier?>, List?, int>( + ValueStateNotifier?>, List?, int>( (ref, memId) => v( () => ValueStateNotifier(null), memId, diff --git a/lib/notifications/mem_notifications.dart b/lib/notifications/mem_notifications.dart index 1a1742fde..d98c3cd95 100644 --- a/lib/notifications/mem_notifications.dart +++ b/lib/notifications/mem_notifications.dart @@ -1,10 +1,10 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; -import 'package:mem/core/act.dart'; -import 'package:mem/core/mem_notification.dart'; +import 'package:mem/acts/act.dart'; +import 'package:mem/mems/mem_notification.dart'; import 'package:mem/logger/log_service.dart'; -import 'package:mem/repositories/mem.dart'; -import 'package:mem/repositories/mem_notification.dart'; +import 'package:mem/mems/mem_entity.dart'; +import 'package:mem/mems/mem_notification_entity.dart'; import 'notification_client.dart'; import 'notification/type.dart'; @@ -15,9 +15,9 @@ const memIdKey = 'memId'; class MemNotifications { static Schedule periodicScheduleOf( - SavedMem savedMem, + SavedMemEntity savedMem, TimeOfDay startOfDay, - Iterable memNotifications, + Iterable memNotifications, Act? latestAct, DateTime now, ) => @@ -69,7 +69,7 @@ class MemNotifications { if (latestAct?.isActive == true) { return null; } else if (memNotifications - .whereType() + .whereType() .where((e) => !e.isAfterActStarted()) .isNotEmpty) { final repeatAt = memNotifications diff --git a/lib/notifications/notification/notification.dart b/lib/notifications/notification/notification.dart index df8acfe7e..ec2597828 100644 --- a/lib/notifications/notification/notification.dart +++ b/lib/notifications/notification/notification.dart @@ -1,7 +1,8 @@ +import 'package:mem/framework/repository/entity.dart'; import 'package:mem/framework/repository/key_with_value.dart'; import 'package:mem/notifications/notification/channel.dart'; -class Notification extends KeyWithValue> { +class Notification with Entity, KeyWithValue> { final String title; final String body; final NotificationChannel channel; @@ -13,19 +14,13 @@ class Notification extends KeyWithValue> { this.body, this.channel, this.payload, - ) : super( - id, - { - "title": title, - "body": body, - "channel": channel, - "payload": payload, - }, - ); - - @override - String toString() => "${super.toString()}: ${{ - "key": key, - "value": value, - }}"; + ) { + key = id; + value = { + 'title': title, + 'body': body, + 'channel': channel, + 'payload': payload, + }; + } } diff --git a/lib/notifications/notification_channels.dart b/lib/notifications/notification_channels.dart index e29a34922..fd59f4bce 100644 --- a/lib/notifications/notification_channels.dart +++ b/lib/notifications/notification_channels.dart @@ -2,8 +2,8 @@ import 'package:collection/collection.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:mem/logger/log_service.dart'; import 'package:mem/notifications/mem_notifications.dart'; -import 'package:mem/repositories/mem_notification_repository.dart'; -import 'package:mem/repositories/mem_repository.dart'; +import 'package:mem/mems/mem_notification_repository.dart'; +import 'package:mem/mems/mem_repository.dart'; import 'notification/action.dart'; import 'notification/notification.dart'; @@ -22,7 +22,9 @@ class NotificationChannels { ) => v( () async { - final title = (await MemRepository().shipById(memId)).name; + final title = await MemRepository() + .ship(id: memId) + .then((value) => value.single.name); String body; switch (notificationType) { case NotificationType.startMem: @@ -32,13 +34,13 @@ class NotificationChannels { body = "end"; break; case NotificationType.repeat: - body = ((await MemNotificationRepository().shipByMemId(memId))) + body = ((await MemNotificationRepository().ship(memId: memId))) .singleWhereOrNull((element) => element.isRepeated()) ?.message ?? "Repeat"; break; case NotificationType.afterActStarted: - body = ((await MemNotificationRepository().shipByMemId(memId))) + body = ((await MemNotificationRepository().ship(memId: memId))) .singleWhere((element) => element.isAfterActStarted()) .message; break; diff --git a/lib/notifications/notification_client.dart b/lib/notifications/notification_client.dart index 0a68b124a..3064b2fb8 100644 --- a/lib/notifications/notification_client.dart +++ b/lib/notifications/notification_client.dart @@ -2,13 +2,13 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; -import 'package:mem/acts/act_repository.dart'; import 'package:mem/components/l10n.dart'; import 'package:mem/logger/log_service.dart'; -import 'package:mem/repositories/mem.dart'; -import 'package:mem/repositories/mem_notification.dart'; -import 'package:mem/repositories/mem_notification_repository.dart'; -import 'package:mem/repositories/mem_repository.dart'; +import 'package:mem/acts/act_repository.dart'; +import 'package:mem/mems/mem_entity.dart'; +import 'package:mem/mems/mem_notification_entity.dart'; +import 'package:mem/mems/mem_notification_repository.dart'; +import 'package:mem/mems/mem_repository.dart'; import 'package:mem/settings/client.dart'; import 'package:mem/settings/keys.dart'; import 'package:mem/values/constants.dart'; @@ -26,7 +26,7 @@ class NotificationClient { final ScheduleClient _scheduleClient; final NotificationRepository _notificationRepository; - final PreferenceClient _preferenceClient; + final PreferenceClientRepository _preferenceClientRepository; final MemRepository _memRepository; final MemNotificationRepository _memNotificationRepository; @@ -34,7 +34,7 @@ class NotificationClient { this.notificationChannels, this._scheduleClient, this._notificationRepository, - this._preferenceClient, + this._preferenceClientRepository, this._memRepository, this._memNotificationRepository, ); @@ -46,7 +46,7 @@ class NotificationClient { NotificationChannels(buildL10n(context)), ScheduleClient(), NotificationRepository(), - PreferenceClient(), + PreferenceClientRepository(), MemRepository(), MemNotificationRepository(), ), @@ -59,7 +59,7 @@ class NotificationClient { static void resetSingleton() => v( () { ScheduleClient.resetSingleton(); - NotificationRepository.resetSingleton(); + NotificationRepository.reset(); _instance = null; }, { @@ -73,7 +73,8 @@ class NotificationClient { ) => v( () async { - final savedMem = await _memRepository.findOneBy(id: memId); + final savedMem = + await _memRepository.ship(id: memId).then((v) => v.singleOrNull); if (savedMem == null || savedMem.isDone || savedMem.isArchived) { await cancelMemNotifications(memId); @@ -125,21 +126,31 @@ class NotificationClient { Future registerMemNotifications( int memId, { - SavedMem? savedMem, - Iterable? savedMemNotifications, + SavedMemEntity? savedMem, + Iterable? savedMemNotifications, }) => v( () async { - final mem = savedMem ?? await _memRepository.shipById(memId); - if (mem.isDone || mem.isArchived) { + final mem = savedMem ?? + await _memRepository + .ship( + id: memId, + ) + .then( + (v) => v.single, + ); + if (mem!.isDone || mem.isArchived) { cancelMemNotifications(memId); } else { - final latestAct = await ActRepository().findOneBy( - memId: memId, - latest: true, - ); + final latestAct = await ActRepository() + .ship( + memId: memId, + latestByMemIds: true, + ) + .then((value) => value.singleOrNull); final startOfDay = - (await _preferenceClient.shipByKey(startOfDayKey)).value ?? + (await _preferenceClientRepository.shipByKey(startOfDayKey)) + .value ?? defaultStartOfDay; for (var schedule in [ ...mem.periodSchedules(startOfDay), @@ -147,7 +158,7 @@ class NotificationClient { mem, startOfDay, savedMemNotifications ?? - await _memNotificationRepository.shipByMemId(memId), + await _memNotificationRepository.ship(memId: memId), latestAct, DateTime.now(), ) @@ -189,7 +200,7 @@ class NotificationClient { final now = DateTime.now(); final memNotifications = - await _memNotificationRepository.shipByMemId(memId); + await _memNotificationRepository.ship(memId: memId); for (var notification in memNotifications.where((element) => element.isEnabled() && element.isAfterActStarted())) { await _scheduleClient.receive( @@ -248,7 +259,7 @@ class NotificationClient { Future _shouldNotify(int memId) => v( () async { final savedMemNotifications = - await MemNotificationRepository().shipByMemId(memId); + await _memNotificationRepository.ship(memId: memId); final repeatByDayOfWeekMemNotifications = savedMemNotifications.where( (element) => element.isEnabled() && element.isRepeatByDayOfWeek(), ); @@ -267,11 +278,11 @@ class NotificationClient { (element) => element.isEnabled() && element.isRepeatByNDay(), ); final lastActTime = await ActRepository() - .findOneBy(memId: memId, latest: true) + .ship(memId: memId, latestByMemIds: true) .then((value) => - value?.period.end ?? + value.singleOrNull?.period.end ?? // FIXME 永続化されている時点でstartは必ずあるので型で表現する - value?.period.start!); + value.singleOrNull?.period.start!); if (lastActTime != null) { if (Duration( diff --git a/lib/notifications/notification_repository.dart b/lib/notifications/notification_repository.dart index 5ee90afc2..2ad0e7837 100644 --- a/lib/notifications/notification_repository.dart +++ b/lib/notifications/notification_repository.dart @@ -1,6 +1,5 @@ import 'package:flutter/foundation.dart'; import 'package:mem/framework/repository/key_with_value_repository.dart'; -import 'package:mem/framework/repository/repository.dart'; import 'package:mem/logger/log_service.dart'; import 'package:mem/notifications/notification/type.dart'; import 'package:mem/permissions/permission.dart'; @@ -11,40 +10,14 @@ import 'notification/notification.dart'; import 'flutter_local_notifications_wrapper.dart'; class NotificationRepository extends KeyWithValueRepository - with Discarder { - FlutterLocalNotificationsWrapper? _flutterLocalNotificationsWrapper = + with DiscardAll { + final FlutterLocalNotificationsWrapper? _flutterLocalNotificationsWrapper = defaultTargetPlatform == TargetPlatform.android ? FlutterLocalNotificationsWrapper(androidDefaultIconPath) : null; - NotificationRepository._(); - - static NotificationRepository? _instance; - - factory NotificationRepository() => v( - () => _instance ??= NotificationRepository._(), - { - "_instance": _instance, - }, - ); - - static void resetSingleton() => v( - () { - FlutterLocalNotificationsWrapper.resetSingleton(); - _instance?._flutterLocalNotificationsWrapper = null; - _instance = null; - }, - { - '_instance': _instance, - }, - ); - - Future checkNotification() => v( - () async => _flutterLocalNotificationsWrapper?.handleAppLaunchDetails(), - ); - @override - Future receive(Notification entity) => v( + Future receive(Notification entity) => v( () async { if (await PermissionHandlerWrapper().grant(Permission.notification)) { await _flutterLocalNotificationsWrapper?.show( @@ -54,25 +27,23 @@ class NotificationRepository extends KeyWithValueRepository entity.channel, entity.payload, ); - return true; - } else { - return false; } }, { - "entity": entity, + 'entity': entity, }, ); - @override - Future discard(int key) => v( - () async { - _flutterLocalNotificationsWrapper?.cancel(key); + Future ship() => v( + () async => + await _flutterLocalNotificationsWrapper?.handleAppLaunchDetails(), + ); - return true; - }, + @override + Future discard(int key) => v( + () async => await _flutterLocalNotificationsWrapper?.cancel(key), { - "key": key, + 'key': key, }, ); @@ -86,4 +57,10 @@ class NotificationRepository extends KeyWithValueRepository )); }, ); + + static void reset() => v( + () { + FlutterLocalNotificationsWrapper.resetSingleton(); + }, + ); } diff --git a/lib/notifications/schedule.dart b/lib/notifications/schedule.dart index 47eec7635..93bf94d30 100644 --- a/lib/notifications/schedule.dart +++ b/lib/notifications/schedule.dart @@ -4,7 +4,7 @@ import 'package:mem/notifications/notification_client.dart'; import 'package:mem/notifications/mem_notifications.dart'; import 'package:mem/notifications/notification/type.dart'; -abstract class Schedule extends Entity { +abstract class Schedule with Entity { final int id; Schedule(this.id); @@ -35,9 +35,9 @@ abstract class Schedule extends Entity { ); @override - String toString() => "${super.toString()}: ${{ - "id": id, - }}"; + Map get toMap => { + 'id': id, + }; } class CancelSchedule extends Schedule { @@ -55,10 +55,11 @@ class TimedSchedule extends Schedule { ); @override - String toString() => "${super.toString()}${{ - "startAt": startAt, - "params": params, - }}"; + Map get toMap => super.toMap + ..addAll({ + 'startAt': startAt, + 'params': params, + }); } class PeriodicSchedule extends TimedSchedule { @@ -72,5 +73,8 @@ class PeriodicSchedule extends TimedSchedule { ); @override - String toString() => "${super.toString()}${{"duration": duration}}"; + Map get toMap => super.toMap + ..addAll({ + 'duration': duration, + }); } diff --git a/lib/notifications/schedule_client.dart b/lib/notifications/schedule_client.dart index 31554c05c..315aba172 100644 --- a/lib/notifications/schedule_client.dart +++ b/lib/notifications/schedule_client.dart @@ -1,6 +1,5 @@ import 'package:mem/framework/repository/repository.dart'; import 'package:mem/logger/log_service.dart'; -import 'package:mem/main.dart'; import 'package:mem/notifications/android_alarm_manager_wrapper.dart'; import 'package:mem/notifications/notification_client.dart'; import 'package:mem/notifications/mem_notifications.dart'; @@ -9,8 +8,7 @@ import 'package:mem/notifications/schedule.dart'; import 'package:mem/permissions/permission.dart'; import 'package:mem/permissions/permission_handler_wrapper.dart'; -class ScheduleClient extends Repository - with Receiver { +class ScheduleClient extends Repository { static ScheduleClient? _instance; final AndroidAlarmManagerWrapper _androidAlarmManagerWrapper; final Future Function(int id, Map params) @@ -41,7 +39,6 @@ class ScheduleClient extends Repository }, ); - @override Future receive(Schedule entity) => v( () async { if (await PermissionHandlerWrapper().grant(Permission.notification)) { @@ -79,8 +76,6 @@ class ScheduleClient extends Repository @pragma('vm:entry-point') Future scheduleCallback(int id, Map params) => i( () async { - await openDatabase(); - await NotificationClient().show( NotificationType.values.singleWhere( (element) => element.name == params[notificationTypeKey], diff --git a/lib/repositories/mem.dart b/lib/repositories/mem.dart deleted file mode 100644 index 663065e2b..000000000 --- a/lib/repositories/mem.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:mem/core/date_and_time/date_and_time_period.dart'; -import 'package:mem/core/mem.dart'; -import 'package:mem/framework/repository/database_tuple_entity.dart'; - -class SavedMem extends Mem with SavedDatabaseTupleMixin { - SavedMem(super.name, super.doneAt, super.period); - - @override - SavedMem copiedWith({ - String Function()? name, - DateTime? Function()? doneAt, - DateAndTimePeriod? Function()? period, - }) => - SavedMem( - name == null ? this.name : name(), - doneAt == null ? this.doneAt : doneAt(), - period == null ? this.period : period(), - )..copiedFrom(this); -} diff --git a/lib/repositories/mem_notification.dart b/lib/repositories/mem_notification.dart deleted file mode 100644 index 2e14ca11a..000000000 --- a/lib/repositories/mem_notification.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:mem/core/mem_notification.dart'; -import 'package:mem/framework/repository/database_tuple_entity.dart'; - -class SavedMemNotification extends MemNotification - with SavedDatabaseTupleMixin { - @override - int get memId => super.memId as int; - - SavedMemNotification(super.memId, super.type, super.time, super.message); - - @override - SavedMemNotification copiedWith({ - int Function()? memId, - int? Function()? time, - String Function()? message, - }) => - SavedMemNotification( - memId == null ? this.memId : memId(), - type, - time == null ? this.time : time(), - message == null ? this.message : message(), - )..copiedFrom(this); -} diff --git a/lib/repositories/mem_notification_repository.dart b/lib/repositories/mem_notification_repository.dart deleted file mode 100644 index 41d919d29..000000000 --- a/lib/repositories/mem_notification_repository.dart +++ /dev/null @@ -1,131 +0,0 @@ -import 'package:mem/core/mem_notification.dart'; -import 'package:mem/databases/table_definitions/mem_notifications.dart'; -import 'package:mem/framework/repository/condition/in.dart'; -import 'package:mem/framework/repository/group_by.dart'; -import 'package:mem/framework/repository/order_by.dart'; -import 'package:mem/logger/log_service.dart'; -import 'package:mem/framework/repository/database_tuple_repository.dart'; -import 'package:mem/framework/repository/condition/conditions.dart'; -import 'package:mem/repositories/mem_notification.dart'; - -class MemNotificationRepository extends DatabaseTupleRepository { - @override - Future> ship({ - Iterable? memIdsIn, - Condition? condition, - GroupBy? groupBy, - List? orderBy, - int? offset, - int? limit, - }) => - v( - () => super.ship( - condition: And( - [ - if (memIdsIn != null) - In(defFkMemNotificationsMemId.name, memIdsIn), - if (condition != null) condition, // coverage:ignore-line - ], - ), - groupBy: groupBy, - orderBy: orderBy, - offset: offset, - limit: limit, - ), - { - 'memIds': memIdsIn, - 'condition': condition, - 'groupBy': groupBy, - 'orderBy': orderBy, - 'offset': offset, - 'limit': limit, - }, - ); - - Future> shipByMemId( - int memId, - ) => - v( - () => super - .ship(condition: Equals(defFkMemNotificationsMemId.name, memId)), - { - "memId": memId, - }, - ); - - Future> archiveByMemId(int memId) => v( - () async => Future.wait( - (await shipByMemId(memId)).map((e) => super.archive(e))), - { - "memId": memId, - }, - ); - - Future> unarchiveByMemId(int memId) => v( - () async => Future.wait( - (await shipByMemId(memId)).map((e) => super.unarchive(e))), - { - "memId": memId, - }, - ); - - @override - Future> waste([ - Condition? condition, - int? memIdIs, - MemNotificationType? type, - ]) => - v( - () => super.waste( - And( - [ -// coverage:ignore-start - if (condition != null) condition, -// coverage:ignore-end - if (memIdIs != null) - Equals(defFkMemNotificationsMemId.name, memIdIs), - if (type != null) - Equals(defColMemNotificationsType.name, type.name), - ], - ), - ), - { - 'condition': condition, - 'memId': memIdIs, - 'type': type, - }, - ); - - @override - SavedMemNotification pack(Map tuple) => SavedMemNotification( - tuple[defFkMemNotificationsMemId.name], - MemNotificationType.fromName(tuple[defColMemNotificationsType.name]), - tuple[defColMemNotificationsTime.name], - tuple[defColMemNotificationsMessage.name], - )..pack(tuple); - - @override - Map unpack(MemNotification entity) { - final map = { - defFkMemNotificationsMemId.name: entity.memId, - defColMemNotificationsType.name: entity.type.name, - defColMemNotificationsTime.name: entity.time, - defColMemNotificationsMessage.name: entity.message, - }; - - if (entity is SavedMemNotification) { - map.addAll(entity.unpack()); - } - - return map; - } - - MemNotificationRepository._() : super(defTableMemNotifications); - - static MemNotificationRepository? _instance; - - factory MemNotificationRepository() => v( - () => _instance ??= MemNotificationRepository._(), - ); -} diff --git a/lib/repositories/mem_repository.dart b/lib/repositories/mem_repository.dart deleted file mode 100644 index 361922b25..000000000 --- a/lib/repositories/mem_repository.dart +++ /dev/null @@ -1,123 +0,0 @@ -import 'package:mem/core/date_and_time/date_and_time.dart'; -import 'package:mem/core/date_and_time/date_and_time_period.dart'; -import 'package:mem/core/mem.dart'; -import 'package:mem/databases/table_definitions/base.dart'; -import 'package:mem/databases/table_definitions/mems.dart'; -import 'package:mem/framework/repository/group_by.dart'; -import 'package:mem/framework/repository/order_by.dart'; -import 'package:mem/logger/log_service.dart'; -import 'package:mem/framework/repository/database_tuple_repository.dart'; -import 'package:mem/framework/repository/condition/conditions.dart'; -import 'package:mem/repositories/mem.dart'; - -class MemRepository extends DatabaseTupleRepository { - @override - Future> ship({ - bool? archived, - bool? done, - Condition? condition, - GroupBy? groupBy, - List? orderBy, - int? offset, - int? limit, - }) => - v( - () => super.ship( - condition: And([ - if (archived != null) - archived - ? IsNotNull(defColArchivedAt.name) - : IsNull(defColArchivedAt.name), - if (done != null) - done - ? IsNotNull(defColMemsDoneAt.name) - : IsNull(defColMemsDoneAt.name), - if (condition != null) condition, // coverage:ignore-line - ]), - orderBy: orderBy, - offset: offset, - limit: limit, - ), - { - 'archived': archived, - 'done': done, - 'condition': condition, - 'groupBy': groupBy, - 'orderBy': orderBy, - 'offset': offset, - 'limit': limit, - }, - ); - - @override - Future findOneBy({ - int? id, - Condition? condition, - List? orderBy, - }) => - v( - () => super.findOneBy( - condition: And([ - if (id != null) Equals(defPkId.name, id), - if (condition != null) condition, // coverage:ignore-line - ]), - orderBy: orderBy, - ), - { - "id": id, - "condition": condition, - "orderBy": orderBy, - }, - ); - - @override - SavedMem pack(Map tuple) { - final startOn = tuple[defColMemsStartOn.name]; - final endOn = tuple[defColMemsEndOn.name]; - - return SavedMem( - tuple[defColMemsName.name], - tuple[defColMemsDoneAt.name], - startOn == null && endOn == null - ? null - : DateAndTimePeriod( - start: startOn == null - ? null - : DateAndTime.from(startOn, - timeOfDay: tuple[defColMemsStartAt.name]), - end: endOn == null - ? null - : DateAndTime.from(endOn, - timeOfDay: tuple[defColMemsEndAt.name]), - ), - )..pack(tuple); - } - - @override - Map unpack(Mem entity) { - final map = { - defColMemsName.name: entity.name, - defColMemsDoneAt.name: entity.doneAt, - defColMemsStartOn.name: entity.period?.start, - defColMemsStartAt.name: - entity.period?.start?.isAllDay == true ? null : entity.period?.start, - defColMemsEndOn.name: entity.period?.end, - defColMemsEndAt.name: - entity.period?.end?.isAllDay == true ? null : entity.period?.end, - }; - - if (entity is SavedMem) { - map.addAll(entity.unpack()); - } - - return map; - } - - MemRepository._() : super(defTableMems); - - static MemRepository? _instance; - - factory MemRepository() => v( - () => _instance ??= MemRepository._(), - ); -} diff --git a/lib/settings/actions.dart b/lib/settings/actions.dart index d35ebe7ef..7705fcb0b 100644 --- a/lib/settings/actions.dart +++ b/lib/settings/actions.dart @@ -3,14 +3,19 @@ import 'package:mem/settings/preference.dart'; import 'package:mem/settings/client.dart'; import 'package:mem/settings/preference_key.dart'; -final _client = PreferenceClient(); +final _client = PreferenceClientRepository(); -Future loadByKey(PreferenceKey key) => v( +Future loadByKey( + PreferenceKey key, +) => + v( () async => (await _client.shipByKey(key)).value, - {"key": key}, + { + 'key': key, + }, ); -Future update, Value>( +Future update, Value>( Key key, Value? value, ) => @@ -19,6 +24,9 @@ Future update, Value>( // coverage:ignore-start ? _client.discard(key) // coverage:ignore-end - : _client.receive(Preference(key, value))), - {"key": key, "value": value}, + : _client.receive(PreferenceEntity(key, value))), + { + 'key': key, + 'value': value, + }, ); diff --git a/lib/settings/client.dart b/lib/settings/client.dart index 80b8b9fcf..4ac0c4d5e 100644 --- a/lib/settings/client.dart +++ b/lib/settings/client.dart @@ -4,22 +4,22 @@ import 'package:mem/settings/preference.dart'; import 'package:mem/settings/preference_key.dart'; import 'package:shared_preferences/shared_preferences.dart'; -class PreferenceClient - extends KeyWithValueRepository { - Future> shipByKey(PreferenceKey key) => v( +class PreferenceClientRepository + extends KeyWithValueRepository { + Future> shipByKey(PreferenceKey key) => v( () async { final saved = (await SharedPreferences.getInstance()).get(key.value); - return Preference( + return PreferenceEntity( key, key.deserialize(saved), ); }, - key, + {'key': key}, ); @override - Future receive(Preference entity) => v( + Future receive(PreferenceEntity entity) => v( () async { final serialized = entity.key.serialize(entity.value); @@ -34,18 +34,12 @@ class PreferenceClient throw UnimplementedError(); // coverage:ignore-line } }, - entity, + {'entity': entity}, ); @override - Future discard(PreferenceKey key) => v( + Future discard(PreferenceKey key) => v( () async => (await SharedPreferences.getInstance()).remove(key.value), - key, + {'key': key}, ); - - PreferenceClient._(); - - static PreferenceClient? _instance; - - factory PreferenceClient() => _instance ??= PreferenceClient._(); } diff --git a/lib/settings/preference.dart b/lib/settings/preference.dart index ff697c271..15caf25ed 100644 --- a/lib/settings/preference.dart +++ b/lib/settings/preference.dart @@ -1,6 +1,11 @@ +import 'package:mem/framework/repository/entity.dart'; import 'package:mem/framework/repository/key_with_value.dart'; import 'package:mem/settings/preference_key.dart'; -class Preference extends KeyWithValue, T?> { - Preference(super.key, super.value); +class PreferenceEntity + with Entity, KeyWithValue, VALUE?> { + PreferenceEntity(PreferenceKey key, VALUE? value) { + this.key = key; + this.value = value; + } } diff --git a/pubspec.yaml b/pubspec.yaml index bdb7b3dad..2b9c1d820 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. # FIXME https://github.com/zin-/mem/issues/321 -version: 0.5.1 +version: 0.5.2 environment: sdk: ^3.5.0 diff --git a/test/act_counter/act_counter_test.dart b/test/act_counter/act_counter_test.dart index dfbdbcd7d..6f0950e80 100644 --- a/test/act_counter/act_counter_test.dart +++ b/test/act_counter/act_counter_test.dart @@ -1,31 +1,54 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mem/act_counter/act_counter.dart'; -import 'package:mem/core/act.dart'; import 'package:mem/core/date_and_time/date_and_time.dart'; import 'package:mem/core/date_and_time/date_and_time_period.dart'; -import 'package:mem/repositories/mem.dart'; +import 'package:mem/databases/table_definitions/base.dart'; +import 'package:mem/acts/act_entity.dart'; +import 'package:mem/mems/mem_entity.dart'; void main() { - test( - ": constructor.", - () { + group('ActCounter.from', () { + test(": updatedAt is last act start.", () { const memId = 1; final zeroDate = DateTime(0); final oneDate = DateTime(1); - final savedMem = SavedMem("constructor", null, null)..id = memId; + final savedMem = SavedMemEntity("constructor", null, null)..id = memId; final acts = [ - SavedAct(memId, DateAndTimePeriod(end: DateAndTime.now())) - ..createdAt = zeroDate - ..updatedAt = oneDate, - SavedAct(memId, DateAndTimePeriod.startNow())..createdAt = zeroDate, + SavedActEntity(memId, DateAndTimePeriod.startNow(), { + defPkId.name: 1, + defColCreatedAt.name: zeroDate, + defColUpdatedAt.name: oneDate + }), + SavedActEntity(memId, DateAndTimePeriod.startNow(), + {defPkId.name: 2, defColCreatedAt.name: zeroDate}) ]; - final actCounter = ActCounter(savedMem, acts); + final actCounter = ActCounter.from(savedMem, acts); - expect(actCounter.lastAct, equals(acts[0])); - }, - ); + expect(actCounter.updatedAt, equals(acts[0].period.start)); + }); + test(": updatedAt is last act end.", () { + const memId = 1; + final zeroDate = DateTime(0); + final oneDate = DateTime(1); + + final savedMem = SavedMemEntity("constructor", null, null)..id = memId; + final acts = [ + SavedActEntity(memId, DateAndTimePeriod(end: DateAndTime.now()), { + defPkId.name: 3, + defColCreatedAt.name: zeroDate, + defColUpdatedAt.name: oneDate + }), + SavedActEntity(memId, DateAndTimePeriod.startNow(), + {defPkId.name: 4, defColCreatedAt.name: zeroDate}) + ]; + + final actCounter = ActCounter.from(savedMem, acts); + + expect(actCounter.updatedAt, equals(acts[0].period.end)); + }); + }); group('period', () { test(': startDate time is 5:00', () { diff --git a/test/components/date_and_time/date_and_time_period_text_form_fields_test.dart b/test/components/date_and_time/date_and_time_period_text_form_fields_test.dart index 3ed28ef22..f4b78aeea 100644 --- a/test/components/date_and_time/date_and_time_period_text_form_fields_test.dart +++ b/test/components/date_and_time/date_and_time_period_text_form_fields_test.dart @@ -12,14 +12,14 @@ void main() { for (final testCase in [ TestCase( - 'pick start', + name: 'pick start', DateAndTimePeriod(start: DateAndTime.from(now)), - (input) => null, + null, ), TestCase( - 'pick end', + name: 'pick end', DateAndTimePeriod(end: DateAndTime.from(now)), - (input) => null, + null, ), ]) { testWidgets( diff --git a/test/components/date_and_time/date_and_time_text_form_field_test.dart b/test/components/date_and_time/date_and_time_text_form_field_test.dart index 5530b1d7c..a587cdc07 100644 --- a/test/components/date_and_time/date_and_time_text_form_field_test.dart +++ b/test/components/date_and_time/date_and_time_text_form_field_test.dart @@ -17,7 +17,7 @@ void main() { }) async { await widgetTester.pumpWidget(MaterialApp( home: Scaffold( - body: DateAndTimeTextFormFieldV2( + body: DateAndTimeTextFormField( dateAndTime, onChanged, selectableRange: selectableRange, diff --git a/test/core/act_test.dart b/test/core/act_test.dart index fa33bf608..dc32f7290 100644 --- a/test/core/act_test.dart +++ b/test/core/act_test.dart @@ -1,5 +1,5 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:mem/core/act.dart'; +import 'package:mem/acts/act.dart'; import 'package:mem/core/date_and_time/date_and_time.dart'; import 'package:mem/core/date_and_time/date_and_time_period.dart'; diff --git a/test/core/date_and_time_period_test.dart b/test/core/date_and_time_period_test.dart index 4a56369f5..011f96107 100644 --- a/test/core/date_and_time_period_test.dart +++ b/test/core/date_and_time_period_test.dart @@ -266,7 +266,7 @@ void main() { group(": compare", () { for (var testCase in [ - TestCaseV2( + TestCase( name: "both are not null", [ DateAndTimePeriod.startNow(), @@ -275,17 +275,17 @@ void main() { DateAndTimePeriod.startNow() .compareTo(DateAndTimePeriod(end: DateAndTime.now())), ), - TestCaseV2( + TestCase( name: "both are null", [null, null], 0, ), - TestCaseV2( + TestCase( name: "a is null, b is not null", [null, DateAndTimePeriod.startNow()], 1, ), - TestCaseV2( + TestCase( name: "a is not null, b is null", [DateAndTimePeriod.startNow(), null], -1, diff --git a/test/core/mem_notification_test.dart b/test/core/mem_notification_test.dart index 39caf79cc..c146eae84 100644 --- a/test/core/mem_notification_test.dart +++ b/test/core/mem_notification_test.dart @@ -1,7 +1,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:intl/intl.dart'; import 'package:mem/core/date_and_time/date_and_time.dart'; -import 'package:mem/core/mem_notification.dart'; +import 'package:mem/mems/mem_notification.dart'; void main() { group('MemNotification', () { @@ -11,8 +11,8 @@ void main() { const memId = 1; const time = 2; - final repeatByDayOfWeek = - MemNotification.repeatByDayOfWeek(memId, time); + final repeatByDayOfWeek = MemNotification(memId, + MemNotificationType.repeatByDayOfWeek, time, "repeatByDayOfWeek"); expect(repeatByDayOfWeek.isRepeatByDayOfWeek(), isTrue); expect(repeatByDayOfWeek.memId, equals(memId)); @@ -20,190 +20,118 @@ void main() { }, ); - group( - 'toOneLine', - () { - String buildRepeatedNotificationText(String at) => "repeat at $at"; - String buildRepeatEveryNDayNotificationText(String nDay, String at) => - "repeat at $at by $nDay"; - String buildAfterActStartedNotificationText(String at) => - "after act at $at"; - String formatToTimeOfDay(DateAndTime dateAndTime) => - "${dateAndTime.hour}:${dateAndTime.minute}"; - - test( - 'no enables.', - () { - const memId = 1; - - final oneLine = MemNotification.toOneLine( - [ - MemNotification( - memId, MemNotificationType.repeat, null, "message") - ], - (at) => fail("no call"), - (nDay, at) => fail("no call"), - (a) => fail("no call"), - (dateAndTime) => fail("no call"), - ); - - expect(oneLine, isNull); - }, - ); - - group( - 'repeat', - () { - test( - 'repeat at 0:0.', - () { - const memId = 1; - const repeatAt = 0; - - final oneLine = MemNotification.toOneLine( - [ - MemNotification.repeated(memId).copiedWith( - time: () => repeatAt, - ) - ], - buildRepeatedNotificationText, - (nDay, at) => fail("no call"), - (a) => fail("no call"), - formatToTimeOfDay, - ); - - expect( - oneLine, - equals(buildRepeatedNotificationText(formatToTimeOfDay( - DateAndTime(0, 0, 0, 0, 0, repeatAt))))); - }, - ); - - test( - 'repeat at 05:00 by 2 day.', - () { - const memId = 1; - const repeatAt = (5 * 60) * 60; - const repeatByNDay = 2; - - final oneLine = MemNotification.toOneLine( - [ - MemNotification.repeated(memId).copiedWith( - time: () => repeatAt, - ), - MemNotification.repeatByNDay(memId).copiedWith( - time: () => repeatByNDay, - ) - ], - (at) => fail("no call"), - buildRepeatEveryNDayNotificationText, - (at) => fail("no call"), - formatToTimeOfDay, - ); - - expect( - oneLine, - equals(buildRepeatEveryNDayNotificationText( - repeatByNDay.toString(), - formatToTimeOfDay( - DateAndTime(0, 0, 0, 0, 0, repeatAt))))); - }, - ); - - test( - 'repeat at 12:00 by 3 day on Mon.', - () { - const memId = 1; - const repeatAt = (5 * 60) * 60; - const repeatByNDay = 2; - - final oneLine = MemNotification.toOneLine( - [ - MemNotification.repeated(memId).copiedWith( - time: () => repeatAt, - ), - MemNotification.repeatByNDay(memId).copiedWith( - time: () => repeatByNDay, - ), - MemNotification.repeatByDayOfWeek(memId, 1), - ], - (at) => fail("no call"), - buildRepeatEveryNDayNotificationText, - (at) => fail("no call"), - formatToTimeOfDay, - ); - - expect( - oneLine, - equals( - "${buildRepeatEveryNDayNotificationText( - repeatByNDay.toString(), - formatToTimeOfDay( - DateAndTime(0, 0, 0, 0, 0, repeatAt), - ), - )}, Mon", - )); - }, - ); - }, - ); - - test( - 'repeat by Tue.', - () { - const memId = 1; - - final oneLine = MemNotification.toOneLine( - [MemNotification.repeatByDayOfWeek(memId, 2)], - buildRepeatedNotificationText, - (a, b) => "$a, $b", - (a) => a, - formatToTimeOfDay, - ); - - expect(oneLine, equals("Tue")); - }, - ); - - test( - 'after act', - () { - const memId = 1; - const time = 2; - final oneLine = MemNotification.toOneLine( - [ - MemNotification.afterActStarted(memId).copiedWith( - time: () => time, - ) - ], - (a) => fail("no call"), - (a, b) => fail("no call"), - buildAfterActStartedNotificationText, - formatToTimeOfDay, - ); - - expect( - oneLine, - buildAfterActStartedNotificationText( - DateFormat(DateFormat.HOUR24_MINUTE) - .format(DateAndTime(0, 0, 0, 0, 0, time)))); - }, - ); - }, - ); + group('toOneLine', () { + String buildRepeatedNotificationText(String at) => "repeat at $at"; + String buildRepeatEveryNDayNotificationText(String nDay, String at) => + "repeat at $at by $nDay"; + String buildAfterActStartedNotificationText(String at) => + "after act at $at"; + String formatToTimeOfDay(DateAndTime dateAndTime) => + "${dateAndTime.hour}:${dateAndTime.minute}"; + + test('no enables.', () { + const memId = 1; + + final oneLine = MemNotification.toOneLine([ + MemNotification(memId, MemNotificationType.repeat, null, "repeat") + ], (at) => fail("no call"), (nDay, at) => fail("no call"), + (a) => fail("no call"), (dateAndTime) => fail("no call")); + + expect(oneLine, isNull); + }); + + group('repeat', () { + test('repeat at 0:0.', () { + const memId = 1; + const repeatAt = 0; + + final oneLine = MemNotification.toOneLine([ + MemNotification(memId, MemNotificationType.repeat, repeatAt, "") + ], buildRepeatedNotificationText, (nDay, at) => fail("no call"), + (a) => fail("no call"), formatToTimeOfDay); + + expect( + oneLine, + equals(buildRepeatedNotificationText( + formatToTimeOfDay(DateAndTime(0, 0, 0, 0, 0, repeatAt))))); + }); + + test('repeat at 05:00 by 2 day.', () { + const memId = 1; + const repeatAt = (5 * 60) * 60; + const repeatByNDay = 2; + + final oneLine = MemNotification.toOneLine([ + MemNotification(memId, MemNotificationType.repeat, repeatAt, ""), + MemNotification( + memId, MemNotificationType.repeatByNDay, repeatByNDay, "") + ], (at) => fail("no call"), buildRepeatEveryNDayNotificationText, + (at) => fail("no call"), formatToTimeOfDay); + + expect( + oneLine, + equals(buildRepeatEveryNDayNotificationText( + repeatByNDay.toString(), + formatToTimeOfDay(DateAndTime(0, 0, 0, 0, 0, repeatAt))))); + }); + + test('repeat at 12:00 by 3 day on Mon.', () { + const memId = 1; + const repeatAt = (5 * 60) * 60; + const repeatByNDay = 2; + + final oneLine = MemNotification.toOneLine([ + MemNotification(memId, MemNotificationType.repeat, repeatAt, ""), + MemNotification( + memId, MemNotificationType.repeatByNDay, repeatByNDay, ""), + MemNotification(memId, MemNotificationType.repeatByDayOfWeek, 1, "") + ], (at) => fail("no call"), buildRepeatEveryNDayNotificationText, + (at) => fail("no call"), formatToTimeOfDay); + + expect( + oneLine, + equals("${buildRepeatEveryNDayNotificationText( + repeatByNDay.toString(), + formatToTimeOfDay(DateAndTime(0, 0, 0, 0, 0, repeatAt)), + )}, Mon")); + }); + }); + + test('repeat by Tue.', () { + const memId = 1; + + final oneLine = MemNotification.toOneLine([ + MemNotification(memId, MemNotificationType.repeatByDayOfWeek, 2, + "repeatByDayOfWeek") + ], buildRepeatedNotificationText, (a, b) => "$a, $b", (a) => a, + formatToTimeOfDay); + + expect(oneLine, equals("Tue")); + }); + + test('after act', () { + const memId = 1; + const time = 2; + final oneLine = MemNotification.toOneLine([ + MemNotification(memId, MemNotificationType.afterActStarted, time, "") + ], (a) => fail("no call"), (a, b) => fail("no call"), + buildAfterActStartedNotificationText, formatToTimeOfDay); + + expect( + oneLine, + buildAfterActStartedNotificationText( + DateFormat(DateFormat.HOUR24_MINUTE) + .format(DateAndTime(0, 0, 0, 0, 0, time)))); + }); + }); }); test('MemNotificationType from unexpected name throw.', () { const name = 'unexpected name'; - expect( - () => MemNotificationType.fromName(name), - throwsA( - (e) { - expect(e.message, 'Unexpected name: "$name".'); - return true; - }, - ), - ); + expect(() => MemNotificationType.fromName(name), throwsA((e) { + expect(e.message, 'Unexpected name: "$name".'); + return true; + })); }); } diff --git a/test/framework/repository/database_tuple_entity_test.dart b/test/framework/repository/database_tuple_entity_test.dart new file mode 100644 index 000000000..536d42ea9 --- /dev/null +++ b/test/framework/repository/database_tuple_entity_test.dart @@ -0,0 +1,67 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:mem/databases/table_definitions/base.dart'; +import 'package:mem/framework/repository/database_tuple_entity.dart'; + +import 'entity_test.dart'; + +const _name = 'DatabaseTupleEntity test'; + +class TestObjectDatabaseTupleEntity extends TestObjectEntity + with DatabaseTupleEntity { + TestObjectDatabaseTupleEntity(super.a); + + TestObjectDatabaseTupleEntity.fromMap(Map map) + : super.fromMap(map) { + withMap(map); + } +} + +void main() => group( + _name, + () { + test( + '#new', + () { + const a = false; + + final testObject = TestObjectDatabaseTupleEntity(a); + + expect(testObject.a, equals(a)); + }, + ); + + test( + '#fromMap', + () { + final map = { + TestObjectEntity.fieldNames[0]: false, + defPkId.name: 1, + defColCreatedAt.name: DateTime.now(), + defColUpdatedAt.name: DateTime.now(), + defColArchivedAt.name: null + }; + + final testObject = TestObjectDatabaseTupleEntity.fromMap(map); + + expect(testObject.a, map[TestObjectEntity.fieldNames[0]]); + }, + ); + + test( + '#toMap', + () { + final map = { + TestObjectEntity.fieldNames[0]: false, + defPkId.name: 1, + defColCreatedAt.name: DateTime.now(), + defColUpdatedAt.name: DateTime.now(), + defColArchivedAt.name: null + }; + + final testObject = TestObjectDatabaseTupleEntity.fromMap(map); + + expect(testObject.toMap, map); + }, + ); + }, + ); diff --git a/test/framework/repository/entity_test.dart b/test/framework/repository/entity_test.dart new file mode 100644 index 000000000..47bc819b6 --- /dev/null +++ b/test/framework/repository/entity_test.dart @@ -0,0 +1,74 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:mem/framework/repository/entity.dart'; + +const _name = 'Entity test'; + +class _TestObject { + final bool a; + + _TestObject(this.a); +} + +class TestObjectEntity extends _TestObject with Entity { + static List fieldNames = ['a']; + + TestObjectEntity(super.a); + + TestObjectEntity.fromMap(Map map) + : super(map[TestObjectEntity.fieldNames[0]]); + + @override + Map get toMap => {fieldNames[0]: a}; +} + +void main() => group( + _name, + () { + test( + '#new', + () { + const a = false; + + final testObject = TestObjectEntity(a); + + expect(testObject.a, equals(a)); + }, + ); + + test( + '#fromMap', + () { + final map = {TestObjectEntity.fieldNames[0]: false}; + + final testObject = TestObjectEntity.fromMap(map); + + expect(testObject.a, equals(map[TestObjectEntity.fieldNames[0]])); + }, + ); + + test( + '#toMap', + () { + const a = false; + + final testObject = TestObjectEntity(a); + + expect( + testObject.toMap, equals({TestObjectEntity.fieldNames[0]: a})); + }, + ); + + test( + '#==', + () { + const a = false; + const b = false; + + final testObjectA = TestObjectEntity(a); + final testObjectB = TestObjectEntity(b); + + expect(testObjectA, equals(testObjectB)); + }, + ); + }, + ); diff --git a/test/helpers.dart b/test/helpers.dart index 7412bab98..bafb2a757 100644 --- a/test/helpers.dart +++ b/test/helpers.dart @@ -4,12 +4,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mem/act_counter/act_counter_repository.dart'; import 'package:mem/act_counter/act_counter_client.dart'; import 'package:mem/act_counter/home_widget_accessor.dart'; import 'package:mem/components/l10n.dart'; import 'package:mem/logger/logger_wrapper.dart'; -import 'package:mem/notifications/notification_repository.dart'; import 'package:mem/notifications/flutter_local_notifications_wrapper.dart'; import 'package:mockito/annotations.dart'; @@ -25,8 +23,6 @@ int randomInt([int max = 42949671]) => Random().nextInt(max); FlutterLocalNotificationsWrapper, // FIXME RepositoryではなくTableをmockする // Repositoryはシステム固有の処理であるのに対して、Tableは永続仮想をラップする役割を持つため - NotificationRepository, - ActCounterRepository, ActCounterClient, ]) void main() {} @@ -48,20 +44,12 @@ Widget buildTestAppWithProvider( child: buildTestApp(widget), ); -class TestCaseV2 { - final I input; - final dynamic expected; +class TestCase { + final INPUT input; + final EXPECTED expected; final String? name; - TestCaseV2(this.input, this.expected, {this.name}); -} - -class TestCase { - final String name; - final T input; - final Function(T input) verify; - - TestCase(this.name, this.input, this.verify); + TestCase(this.input, this.expected, {this.name}); } // Finders diff --git a/test/logger/logger_service_test.dart b/test/logger/logger_service_test.dart index e53a4ad8a..b142842c6 100644 --- a/test/logger/logger_service_test.dart +++ b/test/logger/logger_service_test.dart @@ -23,12 +23,18 @@ void main() { group(": on service level is info", () { for (final testCase in [ - TestCase("verbose", Level.verbose, (Level input) { - verifyNever(mockedLoggerWrapper.log(any, any, any, any)); - }), - TestCase("info", Level.info, (Level input) { - verify(mockedLoggerWrapper.log(input, any, null, null)).called(1); - }), + TestCase( + name: "verbose", + Level.verbose, + (Level input) => + verifyNever(mockedLoggerWrapper.log(any, any, any, any)), + ), + TestCase( + name: "info", + Level.info, + (Level input) => + verify(mockedLoggerWrapper.log(input, any, null, null)).called(1), + ), ]) { test(": log level is ${testCase.name}.", () { final level = testCase.input; @@ -38,7 +44,7 @@ void main() { expect(result, target); - testCase.verify(level); + testCase.expected(level); }); } }); @@ -331,12 +337,17 @@ void main() { }); for (final testCase in [ - TestCase("verbose", Level.verbose, (input) { - verifyNever(mockedLoggerWrapper.log(any, any, any, any)); - }), - TestCase("info", Level.info, (input) { - verifyNever(mockedLoggerWrapper.log(any, any, any, any)); - }), + TestCase( + name: "verbose", + Level.verbose, + (input) => + verifyNever(mockedLoggerWrapper.log(any, any, any, any))), + TestCase( + name: "info", + Level.info, + (input) => + verifyNever(mockedLoggerWrapper.log(any, any, any, any)), + ), ]) { test( ": on error: ${testCase.name}.", @@ -345,6 +356,7 @@ void main() { const errorMessage = "test message future"; final e = Exception(errorMessage); + verifyNever(mockedLoggerWrapper.log(any, any, any, any)); expect( () => LogService().valueLog(level, Future.error(e)), throwsA((thrown) { @@ -371,25 +383,32 @@ void main() { group(": alias", () { for (final testCase in [ - TestCase("verbose", verbose, (input) { - verifyNever(mockedLoggerWrapper.log(any, any, any, any)); - }), - TestCase("info", info, (input) { - verify(mockedLoggerWrapper.log(Level.info, "info", null, null)) - .called(1); - }), - TestCase("warn", warn, (input) { - verify(mockedLoggerWrapper.log(Level.warning, "warn", null, null)) - .called(1); - }), TestCase( - "debug", + name: "verbose", + verbose, + (input) => verifyNever(mockedLoggerWrapper.log(any, any, any, any)), + ), + TestCase( + name: "info", + info, + (input) => + verify(mockedLoggerWrapper.log(Level.info, "info", null, null)) + .called(1), + ), + TestCase( + name: "warn", + warn, + (input) => + verify(mockedLoggerWrapper.log(Level.warning, "warn", null, null)) + .called(1), + ), + TestCase( + name: "debug", // ignore: deprecated_member_use_from_same_package debug, - (input) { - verify(mockedLoggerWrapper.log(Level.debug, "debug", null, null)) - .called(1); - }, + (input) => + verify(mockedLoggerWrapper.log(Level.debug, "debug", null, null)) + .called(1), ), ]) { test(": ${testCase.name}", () { @@ -399,7 +418,7 @@ void main() { expect(result, testMessage); - testCase.verify(testCase.input); + testCase.expected(testCase.input); }); } }); @@ -830,26 +849,37 @@ void main() { group(": alias", () { for (final testCase in [ - TestCase("v", v, (input) { - verifyNever(mockedLoggerWrapper.log(any, any, any, any)); - }), - TestCase("i", i, (input) { - verify(mockedLoggerWrapper.log( - Level.info, "[start] :: null", null, null)) - .called(1); - verify(mockedLoggerWrapper.log(Level.info, "[end] => i", null, null)) - .called(1); - }), - TestCase("w", w, (input) { - verify(mockedLoggerWrapper.log( - Level.warning, "[start] :: null", null, null)) - .called(1); - verify(mockedLoggerWrapper.log( - Level.warning, "[end] => w", null, null)) - .called(1); - }), TestCase( - "d", + name: "v", + v, + (input) => verifyNever(mockedLoggerWrapper.log(any, any, any, any)), + ), + TestCase( + name: "i", + i, + (input) { + verify(mockedLoggerWrapper.log( + Level.info, "[start] :: null", null, null)) + .called(1); + verify(mockedLoggerWrapper.log( + Level.info, "[end] => i", null, null)) + .called(1); + }, + ), + TestCase( + name: "w", + w, + (input) { + verify(mockedLoggerWrapper.log( + Level.warning, "[start] :: null", null, null)) + .called(1); + verify(mockedLoggerWrapper.log( + Level.warning, "[end] => w", null, null)) + .called(1); + }, + ), + TestCase( + name: "d", // ignore: deprecated_member_use_from_same_package d, (input) { @@ -871,7 +901,7 @@ void main() { expect(result, testMessage); - testCase.verify(testCase.input); + testCase.expected(testCase.input); }); } }); diff --git a/test/mems/mem_list_body_test.dart b/test/mems/mem_list_body_test.dart index d0a596c84..257776fa3 100644 --- a/test/mems/mem_list_body_test.dart +++ b/test/mems/mem_list_body_test.dart @@ -9,7 +9,7 @@ import 'package:mem/mems/detail/states.dart'; import 'package:mem/mems/list/body.dart'; import 'package:mem/mems/list/show_new_mem_fab.dart'; import 'package:mem/mems/states.dart'; -import 'package:mem/repositories/mem.dart'; +import 'package:mem/mems/mem_entity.dart'; import '../../integration_test/scenarios/helpers.dart'; @@ -18,13 +18,15 @@ void main() { final scrollController = ScrollController(); final samples = List.generate( 20, - (index) => SavedMem( + (index) => SavedMemEntity( 'Hide & show ShowNewMemFab: mem name - $index', null, null, ) ..id = index - ..createdAt = zeroDate, + ..createdAt = zeroDate + ..updatedAt = null + ..archivedAt = null, ); await widgetTester.pumpWidget(ProviderScope(