diff --git a/homete/Model/Domain/Cohabitant/Housework/HouseworkItem.swift b/homete/Model/Domain/Cohabitant/Housework/HouseworkItem.swift index 76dc04a5..81310b69 100644 --- a/homete/Model/Domain/Cohabitant/Housework/HouseworkItem.swift +++ b/homete/Model/Domain/Cohabitant/Housework/HouseworkItem.swift @@ -76,6 +76,23 @@ struct HouseworkItem: Identifiable, Equatable, Sendable, Hashable, Codable { ) } + func updateRejected(at now: Date, reviewer: String, comment: String) -> Self { + + return .init( + id: id, + indexedDate: indexedDate, + title: title, + point: point, + state: .incomplete, + executorId: executorId, + executedAt: executedAt, + reviewerId: reviewer, + approvedAt: now, + reviewerComment: comment, + expiredAt: expiredAt + ) + } + func updateIncomplete() -> Self { return .init( diff --git a/homete/Model/Domain/Cohabitant/Housework/HouseworkListStore.swift b/homete/Model/Domain/Cohabitant/Housework/HouseworkListStore.swift index 3eb1259b..0675b857 100644 --- a/homete/Model/Domain/Cohabitant/Housework/HouseworkListStore.swift +++ b/homete/Model/Domain/Cohabitant/Housework/HouseworkListStore.swift @@ -69,74 +69,43 @@ final class HouseworkListStore { Task.detached { - // TODO: PushNotificationContentにファクトリーメソッドを定義する - let notificationContent = PushNotificationContent( - title: "新しい家事が登録されました", - message: newItem.title - ) + let notificationContent = PushNotificationContent.addNewHouseworkItem(newItem.title) try await self.cohabitantPushNotificationClient.send(self.cohabitantId, notificationContent) } } func requestReview(target: HouseworkItem, now: Date, executor: String) async throws { - - let targetIndexedDate = target.indexedDate - let targetId = target.id - - guard let targetItem = items.item(targetId, targetIndexedDate) else { - - preconditionFailure("Not found target item(id: \(targetId), indexedDate: \(targetIndexedDate))") - } - - let updatedItem = targetItem.updatePendingApproval(at: now, changer: executor) - try await houseworkClient.insertOrUpdateItem(updatedItem, cohabitantId) - - Task.detached { - - let notificationContent = PushNotificationContent( - title: "確認が必要な家事があります", - message: "問題なければ「\(updatedItem.title)」の完了に感謝を伝えましょう!" - ) - try await self.cohabitantPushNotificationClient.send(self.cohabitantId, notificationContent) + + try await updateAndSave(target: target) { + $0.updatePendingApproval(at: now, changer: executor) + } notification: { + .requestReviewMessage(houseworkTitle: target.title) } } func approved(target: HouseworkItem, now: Date, reviwer: Account, comment: String) async throws { - - let targetIndexedDate = target.indexedDate - let targetId = target.id - - guard let targetItem = items.item(targetId, targetIndexedDate) else { - - preconditionFailure("Not found target item(id: \(targetId), indexedDate: \(targetIndexedDate))") + + try await updateAndSave(target: target) { + $0.updateApproved(at: now, reviewer: reviwer.id, comment: comment) + } notification: { + .approvedMessage(reviwerName: reviwer.userName, houseworkTitle: target.title, comment: comment) } - - let updatedItem = targetItem.updateApproved(at: now, reviewer: reviwer.id, comment: comment) - try await houseworkClient.insertOrUpdateItem(updatedItem, cohabitantId) - - Task.detached { - - let notificationContent = PushNotificationContent.approvedMessage( - reviwerName: reviwer.userName, - houseworkTitle: target.title, - comment: comment - ) - try await self.cohabitantPushNotificationClient.send(self.cohabitantId, notificationContent) + } + + func rejected(target: HouseworkItem, now: Date, reviwer: Account, comment: String) async throws { + + try await updateAndSave(target: target) { + $0.updateRejected(at: now, reviewer: reviwer.id, comment: comment) + } notification: { + .rejectedMessage(reviwerName: reviwer.userName, houseworkTitle: target.title, comment: comment) } } - - func returnToIncomplete(target: HouseworkItem, now: Date) async throws { - - let targetIndexedDate = target.indexedDate - let targetId = target.id - - guard let targetItem = items.item(targetId, targetIndexedDate) else { - - preconditionFailure("Not found target item(id: \(targetId), indexedDate: \(targetIndexedDate))") + + func returnToIncomplete(target: HouseworkItem) async throws { + + try await updateAndSave(target: target) { + $0.updateIncomplete() } - - let updatedItem = targetItem.updateIncomplete() - try await houseworkClient.insertOrUpdateItem(updatedItem, cohabitantId) } func remove(_ target: HouseworkItem) async throws { @@ -146,10 +115,34 @@ final class HouseworkListStore { } private extension HouseworkListStore { - + func clear() async { - + await houseworkClient.removeListener(houseworkObserveKey) items.removeAll() } + + func updateAndSave( + target: HouseworkItem, + transform: (HouseworkItem) -> HouseworkItem, + notification: (() -> PushNotificationContent)? = nil + ) async throws { + + guard let targetItem = items.item(target) else { + preconditionFailure("Not found target item(\(target))") + } + + let updatedItem = transform(targetItem) + try await houseworkClient.insertOrUpdateItem(updatedItem, cohabitantId) + + if let notification { + let content = notification() + Task.detached { + try await self.cohabitantPushNotificationClient.send( + self.cohabitantId, + content + ) + } + } + } } diff --git a/homete/Model/Domain/Cohabitant/Housework/StoredAllHouseworkList.swift b/homete/Model/Domain/Cohabitant/Housework/StoredAllHouseworkList.swift index eeff99e5..0758c3bc 100644 --- a/homete/Model/Domain/Cohabitant/Housework/StoredAllHouseworkList.swift +++ b/homete/Model/Domain/Cohabitant/Housework/StoredAllHouseworkList.swift @@ -25,12 +25,12 @@ struct StoredAllHouseworkList: Equatable, Sendable { return .init(value: items) } - func item(_ id: String, _ indexedDate: HouseworkIndexedDate) -> HouseworkItem? { + func item(_ item: HouseworkItem) -> HouseworkItem? { guard let targetDayList = value.first( - where: { $0.metaData.indexedDate == indexedDate } + where: { $0.metaData.indexedDate == item.indexedDate } ), - let targetItem = targetDayList.items.first(where: { $0.id == id }) else { return nil } + let targetItem = targetDayList.items.first(where: { $0.id == item.id }) else { return nil } return targetItem } diff --git a/homete/Model/Domain/PushNotificationContent.swift b/homete/Model/Domain/PushNotificationContent.swift index f5f23fc4..d8a01041 100644 --- a/homete/Model/Domain/PushNotificationContent.swift +++ b/homete/Model/Domain/PushNotificationContent.swift @@ -11,11 +11,32 @@ struct PushNotificationContent: Equatable { } extension PushNotificationContent { - + + static func addNewHouseworkItem(_ houseworkTitle: String) -> Self { + return .init( + title: "新しい家事が登録されました", + message: houseworkTitle + ) + } + + static func requestReviewMessage(houseworkTitle: String) -> Self { + return .init( + title: "確認が必要な家事があります", + message: "問題なければ「\(houseworkTitle)」の完了に感謝を伝えましょう!" + ) + } + static func approvedMessage(reviwerName: String, houseworkTitle: String, comment: String) -> Self { return .init( title: "\(reviwerName)が「\(houseworkTitle)」を承認しました!", message: comment ) } + + static func rejectedMessage(reviwerName: String, houseworkTitle: String, comment: String) -> Self { + return .init( + title: "「\(houseworkTitle)」を再確認してください", + message: comment + ) + } } diff --git a/homete/Resouces/Localizable.xcstrings b/homete/Resouces/Localizable.xcstrings index a7184aa2..864de677 100644 --- a/homete/Resouces/Localizable.xcstrings +++ b/homete/Resouces/Localizable.xcstrings @@ -159,6 +159,10 @@ }, "共に家事を頑張るパートナーへ、エールを送り合いませんか?" : { + }, + "再確認してもらう" : { + "comment" : "A button label that triggers a request to resend a confirmation message to the executor.", + "isCommentAutoGenerated" : true }, "削除" : { @@ -309,5 +313,5 @@ } }, - "version" : "1.0" + "version" : "1.1" } \ No newline at end of file diff --git a/homete/Views/HouseworkApproval/HouseworkApprovalView.swift b/homete/Views/HouseworkApproval/HouseworkApprovalView.swift index 1c85137a..e2306f32 100644 --- a/homete/Views/HouseworkApproval/HouseworkApprovalView.swift +++ b/homete/Views/HouseworkApproval/HouseworkApprovalView.swift @@ -13,6 +13,7 @@ struct HouseworkApprovalView: View { @Environment(\.loginContext.account) var account @Environment(\.dismiss) var dismiss @CommonError var commonError + @LoadingState var loadingState @State var inputMessage = "" @@ -41,6 +42,7 @@ struct HouseworkApprovalView: View { .toolbar { navigationLeadingItem() } + .fullScreenLoadingIndicator(loadingState) } } } @@ -101,7 +103,7 @@ private extension HouseworkApprovalView { func actionButtonContent() -> some View { VStack(spacing: .space16) { Button { - Task { + loadingState.task { await tappedApproveButton() } } label: { @@ -110,12 +112,14 @@ private extension HouseworkApprovalView { } .subPrimaryButtonStyle() Button { - // TODO: 家事を未完了に戻す + loadingState.task { + await tappedReconfirmationButton() + } } label: { - Text("未完了に戻す") + Text("再確認してもらう") .frame(maxWidth: .infinity) } - .destructiveButtonStyle() + .primaryButtonStyle() } .disabled(inputMessage.isEmpty) } @@ -141,6 +145,23 @@ private extension HouseworkApprovalView { commonError = .init(error: error) } } + + func tappedReconfirmationButton() async { + + do { + + try await houseworkListStore.rejected( + target: item, + now: .now, + reviwer: account, + comment: inputMessage + ) + dismiss() + } catch { + + commonError = .init(error: error) + } + } } #Preview { diff --git a/homete/Views/HouseworkDetailView/HouseworkDetailView.swift b/homete/Views/HouseworkDetailView/HouseworkDetailView.swift index ae628ca3..72eac2e3 100644 --- a/homete/Views/HouseworkDetailView/HouseworkDetailView.swift +++ b/homete/Views/HouseworkDetailView/HouseworkDetailView.swift @@ -84,7 +84,7 @@ private extension HouseworkDetailView { func didChangeItems() { - guard let targetItem = houseworkListStore.items.item(item.id, item.indexedDate) else { return } + guard let targetItem = houseworkListStore.items.item(item) else { return } withAnimation { diff --git a/homete/Views/HouseworkDetailView/SubViews/HouseworkDetailActionContent.swift b/homete/Views/HouseworkDetailView/SubViews/HouseworkDetailActionContent.swift index 698c53ee..2c4ce35f 100644 --- a/homete/Views/HouseworkDetailView/SubViews/HouseworkDetailActionContent.swift +++ b/homete/Views/HouseworkDetailView/SubViews/HouseworkDetailActionContent.swift @@ -103,7 +103,7 @@ private extension HouseworkDetailActionContent { do { - try await houseworkListStore.returnToIncomplete(target: item, now: .now) + try await houseworkListStore.returnToIncomplete(target: item) } catch { commonErrorContent = .init(error: error) diff --git a/hometeSnapshotTests/__Snapshots__/PreviewTests.generated/HouseworkApprovalView_0-iPhone-16-Pro-Max.1.png b/hometeSnapshotTests/__Snapshots__/PreviewTests.generated/HouseworkApprovalView_0-iPhone-16-Pro-Max.1.png index 9dc8af88..57704c9f 100644 Binary files a/hometeSnapshotTests/__Snapshots__/PreviewTests.generated/HouseworkApprovalView_0-iPhone-16-Pro-Max.1.png and b/hometeSnapshotTests/__Snapshots__/PreviewTests.generated/HouseworkApprovalView_0-iPhone-16-Pro-Max.1.png differ diff --git a/hometeSnapshotTests/__Snapshots__/PreviewTests.generated/HouseworkApprovalView_0-iPhone-16.1.png b/hometeSnapshotTests/__Snapshots__/PreviewTests.generated/HouseworkApprovalView_0-iPhone-16.1.png index 258766eb..ed017c5d 100644 Binary files a/hometeSnapshotTests/__Snapshots__/PreviewTests.generated/HouseworkApprovalView_0-iPhone-16.1.png and b/hometeSnapshotTests/__Snapshots__/PreviewTests.generated/HouseworkApprovalView_0-iPhone-16.1.png differ diff --git a/hometeTests/Domain/Housework/HouseworkListStoreTest.swift b/hometeTests/Domain/Housework/HouseworkListStoreTest.swift index 6d3eb694..101fdbf0 100644 --- a/hometeTests/Domain/Housework/HouseworkListStoreTest.swift +++ b/hometeTests/Domain/Housework/HouseworkListStoreTest.swift @@ -15,6 +15,13 @@ struct HouseworkListStoreTest { private let inputId = "houseworkObserveKey" private let inputCohabitantId = "cohabitantId" + + @MainActor + struct UpdateStatusCase { + + private let inputId = "houseworkObserveKey" + private let inputCohabitantId = "cohabitantId" + } @Test("家事リストのロードを行うと最新の家事リストを常に監視し続ける") func loadHouseworkList() async throws { @@ -121,6 +128,9 @@ struct HouseworkListStoreTest { } } } +} + +extension HouseworkListStoreTest.UpdateStatusCase { @Test("家事の完了確認を依頼すると、パートナーにその旨Push通知を送信する") func requestReview() async throws { @@ -193,7 +203,6 @@ struct HouseworkListStoreTest { executorId: "dummyExecutor", executedAt: .distantPast ) - let requestedAt = Date() let updatedHouseworkItem = inputHouseworkItem.updateProperties( state: .incomplete, executorId: nil, @@ -223,10 +232,7 @@ struct HouseworkListStoreTest { // Act - try await store.returnToIncomplete( - target: inputHouseworkItem, - now: requestedAt - ) + try await store.returnToIncomplete(target: inputHouseworkItem) } } @@ -332,4 +338,78 @@ struct HouseworkListStoreTest { } } } + + @Test("家事を却下すると、却下情報を更新しパートナーに通知を送信する") + // swiftlint:disable:next function_body_length + func rejected() async throws { + + // Arrange + + let inputHouseworkItem = HouseworkItem.makeForTest( + id: 1, + state: .pendingApproval, + executorId: "executorId", + executedAt: .distantPast + ) + let rejectedAt = Date() + let inputReviewer = Account( + id: "reviewerId", + userName: "レビュアー", + fcmToken: nil, + cohabitantId: inputCohabitantId + ) + let inputComment = "もう一度確認してください" + let updatedHouseworkItem = inputHouseworkItem.updateRejected( + at: rejectedAt, + reviewer: inputReviewer.id, + comment: inputComment + ) + let expectedNotificationContent = PushNotificationContent.rejectedMessage( + reviwerName: inputReviewer.userName, + houseworkTitle: inputHouseworkItem.title, + comment: inputComment + ) + + await confirmation(expectedCount: 2) { confirmation in + + let _: Void = await withCheckedContinuation { continuation in + + let store = HouseworkListStore( + houseworkClient: .init( + insertOrUpdateItemHandler: { item, cohabitantId in + + // Assert + + #expect(item == updatedHouseworkItem) + #expect(cohabitantId == inputCohabitantId) + confirmation() + } + ), + cohabitantPushNotificationClient: .init { id, content in + + // Assert + + #expect(id == inputCohabitantId) + #expect(content == expectedNotificationContent) + confirmation() + continuation.resume() + }, + items: [.makeForTest(items: [inputHouseworkItem])], + cohabitantId: inputCohabitantId + ) + + // Act + + Task { + + try await store.rejected( + target: inputHouseworkItem, + now: rejectedAt, + reviwer: inputReviewer, + comment: inputComment + ) + } + } + } + } } diff --git a/hometeTests/Domain/Housework/StoredAllHouseworkListTest.swift b/hometeTests/Domain/Housework/StoredAllHouseworkListTest.swift index 3b086791..7c58f494 100644 --- a/hometeTests/Domain/Housework/StoredAllHouseworkListTest.swift +++ b/hometeTests/Domain/Housework/StoredAllHouseworkListTest.swift @@ -59,7 +59,7 @@ struct StoredAllHouseworkListTest { calendar: .current ) - let actual = list.item(expectedItem.id, expectedItem.indexedDate) + let actual = list.item(expectedItem) #expect(actual == expectedItem) }