diff --git a/TodoNote/TodoNote.xcodeproj/project.pbxproj b/TodoNote/TodoNote.xcodeproj/project.pbxproj index 415063c..9f81833 100644 --- a/TodoNote/TodoNote.xcodeproj/project.pbxproj +++ b/TodoNote/TodoNote.xcodeproj/project.pbxproj @@ -23,6 +23,9 @@ AA1932872A830F5B002C846F /* SyncReadyTodoUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA1932842A830F5B002C846F /* SyncReadyTodoUseCase.swift */; }; AA1932882A830F5B002C846F /* SyncReadyTodoUseCaseInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA1932852A830F5B002C846F /* SyncReadyTodoUseCaseInput.swift */; }; AA1932892A830F5B002C846F /* SyncReadyTodoUseCaseResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA1932862A830F5B002C846F /* SyncReadyTodoUseCaseResult.swift */; }; + AA1932902A837A11002C846F /* SignOutUseCaseResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA19328D2A837A11002C846F /* SignOutUseCaseResult.swift */; }; + AA1932912A837A11002C846F /* SignOutUseCaseInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA19328E2A837A11002C846F /* SignOutUseCaseInput.swift */; }; + AA1932922A837A11002C846F /* SignOutUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA19328F2A837A11002C846F /* SignOutUseCase.swift */; }; AABB4F832A80D7A5008FC467 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AABB4F822A80D7A5008FC467 /* Preview Assets.xcassets */; }; AABB4F852A80D7A5008FC467 /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = AABB4F842A80D7A5008FC467 /* Persistence.swift */; }; AABB4F882A80D7A5008FC467 /* TodoNote.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = AABB4F862A80D7A5008FC467 /* TodoNote.xcdatamodeld */; }; @@ -47,9 +50,7 @@ AAE79F912A80E376009823FB /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE79F8A2A80E376009823FB /* SettingsView.swift */; }; AAE79F922A80E376009823FB /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE79F8B2A80E376009823FB /* SettingsViewModel.swift */; }; AAE79F932A80E376009823FB /* SettingsViewRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE79F8C2A80E376009823FB /* SettingsViewRouter.swift */; }; - AAE79F942A80E376009823FB /* SettingsListSwitchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE79F8E2A80E376009823FB /* SettingsListSwitchView.swift */; }; AAE79F952A80E376009823FB /* SettingsListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE79F8F2A80E376009823FB /* SettingsListItemView.swift */; }; - AAE79F962A80E376009823FB /* SettingsListAppIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE79F902A80E376009823FB /* SettingsListAppIconView.swift */; }; AAE79F9C2A80E4E3009823FB /* AlertItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE79F9B2A80E4E3009823FB /* AlertItem.swift */; }; AAE79F9E2A80E51D009823FB /* RSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE79F9D2A80E51D009823FB /* RSwiftUI.swift */; }; AAE79FA12A80E543009823FB /* ListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE79FA02A80E543009823FB /* ListItemView.swift */; }; @@ -118,6 +119,9 @@ AA1932842A830F5B002C846F /* SyncReadyTodoUseCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncReadyTodoUseCase.swift; sourceTree = ""; }; AA1932852A830F5B002C846F /* SyncReadyTodoUseCaseInput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncReadyTodoUseCaseInput.swift; sourceTree = ""; }; AA1932862A830F5B002C846F /* SyncReadyTodoUseCaseResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncReadyTodoUseCaseResult.swift; sourceTree = ""; }; + AA19328D2A837A11002C846F /* SignOutUseCaseResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignOutUseCaseResult.swift; sourceTree = ""; }; + AA19328E2A837A11002C846F /* SignOutUseCaseInput.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignOutUseCaseInput.swift; sourceTree = ""; }; + AA19328F2A837A11002C846F /* SignOutUseCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignOutUseCase.swift; sourceTree = ""; }; AABB4F782A80D7A4008FC467 /* TodoNote.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TodoNote.app; sourceTree = BUILT_PRODUCTS_DIR; }; AABB4F822A80D7A5008FC467 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; AABB4F842A80D7A5008FC467 /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; @@ -145,9 +149,7 @@ AAE79F8A2A80E376009823FB /* SettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; AAE79F8B2A80E376009823FB /* SettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; AAE79F8C2A80E376009823FB /* SettingsViewRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewRouter.swift; sourceTree = ""; }; - AAE79F8E2A80E376009823FB /* SettingsListSwitchView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsListSwitchView.swift; sourceTree = ""; }; AAE79F8F2A80E376009823FB /* SettingsListItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsListItemView.swift; sourceTree = ""; }; - AAE79F902A80E376009823FB /* SettingsListAppIconView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsListAppIconView.swift; sourceTree = ""; }; AAE79F9B2A80E4E3009823FB /* AlertItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertItem.swift; sourceTree = ""; }; AAE79F9D2A80E51D009823FB /* RSwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RSwiftUI.swift; sourceTree = ""; }; AAE79FA02A80E543009823FB /* ListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListItemView.swift; sourceTree = ""; }; @@ -270,6 +272,16 @@ path = SyncReadyTodo; sourceTree = ""; }; + AA19328C2A837A11002C846F /* SignOut */ = { + isa = PBXGroup; + children = ( + AA19328F2A837A11002C846F /* SignOutUseCase.swift */, + AA19328E2A837A11002C846F /* SignOutUseCaseInput.swift */, + AA19328D2A837A11002C846F /* SignOutUseCaseResult.swift */, + ); + path = SignOut; + sourceTree = ""; + }; AABB4F6F2A80D7A4008FC467 = { isa = PBXGroup; children = ( @@ -320,8 +332,8 @@ AABB4F902A80D7A5008FC467 /* TodoNoteTests */ = { isa = PBXGroup; children = ( - AA1932792A82F8F8002C846F /* Repository */, AABB4F912A80D7A5008FC467 /* TodoNoteTests.swift */, + AA1932792A82F8F8002C846F /* Repository */, ); path = TodoNoteTests; sourceTree = ""; @@ -448,9 +460,7 @@ AAE79F8D2A80E376009823FB /* View */ = { isa = PBXGroup; children = ( - AAE79F8E2A80E376009823FB /* SettingsListSwitchView.swift */, AAE79F8F2A80E376009823FB /* SettingsListItemView.swift */, - AAE79F902A80E376009823FB /* SettingsListAppIconView.swift */, ); path = View; sourceTree = ""; @@ -499,6 +509,7 @@ children = ( AAE79FC32A80FDA6009823FB /* UseCaseProctol.swift */, AAE79FB32A80FD2F009823FB /* Launch */, + AA19328C2A837A11002C846F /* SignOut */, AA1932832A830F5B002C846F /* SyncReadyTodo */, AA19327C2A830A09002C846F /* UpdateTodo */, ); @@ -917,9 +928,9 @@ AAE79FAA2A80E882009823FB /* HomeView.swift in Sources */, AAE79F732A80DEEA009823FB /* SafariUtil.swift in Sources */, AAE79F9E2A80E51D009823FB /* RSwiftUI.swift in Sources */, + AA1932902A837A11002C846F /* SignOutUseCaseResult.swift in Sources */, AABB4F852A80D7A5008FC467 /* Persistence.swift in Sources */, AAE79F9C2A80E4E3009823FB /* AlertItem.swift in Sources */, - AAE79F942A80E376009823FB /* SettingsListSwitchView.swift in Sources */, AAE79FC02A80FD2F009823FB /* CheckVersionUseCase.swift in Sources */, AAE79FE02A81180B009823FB /* AppConstValues.swift in Sources */, AAE79FBC2A80FD2F009823FB /* LaunchUseCaseInput.swift in Sources */, @@ -942,6 +953,7 @@ AAE79F6C2A80DE50009823FB /* SplashView.swift in Sources */, AA1932882A830F5B002C846F /* SyncReadyTodoUseCaseInput.swift in Sources */, AAE79F752A80DF13009823FB /* R.generated.swift in Sources */, + AA1932922A837A11002C846F /* SignOutUseCase.swift in Sources */, AA1932732A8248AB002C846F /* LoginViewModel.swift in Sources */, AAE79FB22A80FAE1009823FB /* RegistrationStatus.swift in Sources */, AA1932752A824B7C002C846F /* TodoItemView.swift in Sources */, @@ -958,13 +970,13 @@ AA1932822A830A2F002C846F /* UpdateTodoUseCaseResult.swift in Sources */, AAE79FC82A80FFBB009823FB /* AppTitleView.swift in Sources */, AAE79F852A80E1BC009823FB /* ActivityIndicatorView.swift in Sources */, - AAE79F962A80E376009823FB /* SettingsListAppIconView.swift in Sources */, AAE79FD42A810D20009823FB /* PreviewUtil.swift in Sources */, AAE79F6E2A80DE5C009823FB /* SplashViewModel.swift in Sources */, AAE79F772A80DF48009823FB /* FAPage.swift in Sources */, AAE79F932A80E376009823FB /* SettingsViewRouter.swift in Sources */, AAE79F912A80E376009823FB /* SettingsView.swift in Sources */, AAE79F952A80E376009823FB /* SettingsListItemView.swift in Sources */, + AA1932912A837A11002C846F /* SignOutUseCaseInput.swift in Sources */, AAE79FBF2A80FD2F009823FB /* CheckVersionUseCaseInput.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/TodoNote/TodoNote/Model/FAPage.swift b/TodoNote/TodoNote/Model/FAPage.swift index d27a670..36dda89 100644 --- a/TodoNote/TodoNote/Model/FAPage.swift +++ b/TodoNote/TodoNote/Model/FAPage.swift @@ -10,11 +10,13 @@ import Foundation enum FAPage: String { case splash = "スプラッシュ" + // 設定 case settings = "設定" case settingsFeedback = "設定/お問い合わせ" case settingsUserPolicy = "設定/利用規約" case settingsPrivacyPolicy = "設定/プライバシーポリシー" case settingsPublisher = "設定/運営会社" + case settingsLicense = "設定/ライセンス情報" } extension FAPage { diff --git a/TodoNote/TodoNote/Repository/TodoRepository.swift b/TodoNote/TodoNote/Repository/TodoRepository.swift index c8080c4..884510e 100644 --- a/TodoNote/TodoNote/Repository/TodoRepository.swift +++ b/TodoNote/TodoNote/Repository/TodoRepository.swift @@ -118,11 +118,35 @@ class TodoRepository { } } + func fetch(status: RegistrationStatus) async throws -> [Todo] { + let request = TodoEntity.fetchRequest() + request.predicate = NSPredicate(format: "status == %@", status.rawValue) + + return try await MainActor.run { + let result = try context.fetch(request) + return result.compactMap { $0.toModel() } + } + } + func fetchCount() throws -> Int { let request = TodoEntity.fetchRequest() return try context.count(for: request) } + /// 指定したステータスのレコードをすべて削除する + func deleteAll(status: RegistrationStatus) async throws { + let request = TodoEntity.fetchRequest() + request.predicate = NSPredicate(format: "status == %@", status.rawValue) + + try await MainActor.run { + let entities = try context.fetch(request) + for entity in entities { + context.delete(entity) + } + try context.save() + } + } + /// すべてのレコードを削除する func deleteAll() async throws { let request = TodoEntity.fetchRequest() diff --git a/TodoNote/TodoNote/Resource/Info.plist b/TodoNote/TodoNote/Resource/Info.plist index 1671836..4212512 100644 --- a/TodoNote/TodoNote/Resource/Info.plist +++ b/TodoNote/TodoNote/Resource/Info.plist @@ -22,6 +22,8 @@ ITSAppUsesNonExemptEncryption + ITSAppUsesNonExemptEncryption + NSUserTrackingUsageDescription If allowed, it will be easier to display ads that match your preferences. SKAdNetworkItems diff --git a/TodoNote/TodoNote/Screen/Settings/Settings/SettingsView.swift b/TodoNote/TodoNote/Screen/Settings/Settings/SettingsView.swift index 51b3cdf..dbe7749 100644 --- a/TodoNote/TodoNote/Screen/Settings/Settings/SettingsView.swift +++ b/TodoNote/TodoNote/Screen/Settings/Settings/SettingsView.swift @@ -4,7 +4,6 @@ // Created by KENJIWADA on 2023/03/16. // -import SafariServices import SwiftUI struct SettingsView: View { @@ -71,13 +70,13 @@ struct SettingsView: View { } Section { - Button(action: { model.onClickSignOutButton(from: viewController) }) { - HStack { - R.string.localizable.logout.text - .foregroundColor(Color.red) - .font(.system(size: 15)) - } - .frame(maxWidth: .infinity) + Button(action: { + model.onClickSignOutButton(from: viewController) + }) { + R.string.localizable.logout.text + .foregroundColor(Color.red) + .font(.system(size: 15, weight: .bold)) + .frame(maxWidth: .infinity) } } @@ -95,14 +94,11 @@ struct SettingsView: View { .navigationTitle(R.string.localizable.settings_title.text) .navigationBarTitleDisplayMode(.inline) .onAppear { - model.update() - FAPage.settings.send() + model.onAppear(from: viewController) } .toolbar { ToolbarItem(placement: .navigationBarLeading) { - Button(action: { - viewController?.dismiss(animated: true) - }) { + Button(action: onClickCloseButton) { R.string.localizable.close.text } } @@ -110,6 +106,10 @@ struct SettingsView: View { .alert(item: $model.alertItem) { $0.alert } .actionSheet(item: $model.actionSheetItem) { $0.sheet } } + + private func onClickCloseButton() { + viewController?.dismiss(animated: true) + } } struct SettingsView_Previews: PreviewProvider { diff --git a/TodoNote/TodoNote/Screen/Settings/Settings/SettingsViewModel.swift b/TodoNote/TodoNote/Screen/Settings/Settings/SettingsViewModel.swift index 13e03b3..2b84472 100644 --- a/TodoNote/TodoNote/Screen/Settings/Settings/SettingsViewModel.swift +++ b/TodoNote/TodoNote/Screen/Settings/Settings/SettingsViewModel.swift @@ -4,7 +4,6 @@ // Created by KENJIWADA on 2023/03/18. // -import FirebaseAuth import SwiftUI class SettingsViewModel: ObservableObject { @@ -14,6 +13,8 @@ class SettingsViewModel: ObservableObject { @Published var alertItem: AlertItem? + private let signOutUseCase = SignOutUseCase() + init() { // アプリバージョンの取得 if let version = Bundle.main.infoDictionary?["CFBundleVersion"] as? String, @@ -27,7 +28,9 @@ class SettingsViewModel: ObservableObject { // MARK: - - func update() {} + func onAppear(from _: UIViewController?) { + FAPage.settings.send() + } func onClickSignOutButton(from viewController: UIViewController?) { actionSheetItem = ActionSheetItem( @@ -47,14 +50,26 @@ class SettingsViewModel: ObservableObject { // MARK: - private func logout(viewController: UIViewController?) { - do { - try Auth.auth().signOut() - - viewController?.dismiss(animated: false) { - SceneDelegate.shared?.rootViewController.switchToLoginScreen() + Task { + let result = await signOutUseCase.execute(.init()) + switch result { + case .success: + await moveLoginScreen(from: viewController) + case let .failed(error): + alertItem = AlertItem( + alert: Alert( + title: R.string.localizable.error.text, + message: Text(error.localizedDescription) + ) + ) } - } catch { - // TODO: エラー処理 + } + } + + @MainActor + private func moveLoginScreen(from viewController: UIViewController?) { + viewController?.dismiss(animated: false) { + SceneDelegate.shared?.rootViewController.switchToLoginScreen() } } } diff --git a/TodoNote/TodoNote/Screen/Settings/Settings/SettingsViewRouter.swift b/TodoNote/TodoNote/Screen/Settings/Settings/SettingsViewRouter.swift index 54b72ed..8116368 100644 --- a/TodoNote/TodoNote/Screen/Settings/Settings/SettingsViewRouter.swift +++ b/TodoNote/TodoNote/Screen/Settings/Settings/SettingsViewRouter.swift @@ -9,32 +9,12 @@ import SafariServices import SwiftUI struct SettingsViewRouter { - /// ストア - static func moveStore(from _: UIViewController?) { -// let vc = UIViewController.hostingController { -// StoreView( -// fromInformation: false -// ) -// } -// vc.hidesBottomBarWhenPushed = true -// viewController?.navigationController?.pushViewController(vc, animated: true) - } - - /// このアプリについて - static func moveAbout(from _: UIViewController?) { -// let vc = UIViewController.hostingController { -// AboutSettingsView() -// } -// vc.hidesBottomBarWhenPushed = true -// viewController?.navigationController?.pushViewController(vc, animated: true) - } - /// ライセンス static func moveLicences(from viewController: UIViewController?) { let vc = LicensePlistViewController(tableViewStyle: .insetGrouped) vc.hidesBottomBarWhenPushed = true viewController?.navigationController?.pushViewController(vc, animated: true) -// FAPage.settingsLicense.send() + FAPage.settingsLicense.send() } /// お問合せ diff --git a/TodoNote/TodoNote/Screen/Settings/View/SettingsListAppIconView.swift b/TodoNote/TodoNote/Screen/Settings/View/SettingsListAppIconView.swift deleted file mode 100644 index d1e8219..0000000 --- a/TodoNote/TodoNote/Screen/Settings/View/SettingsListAppIconView.swift +++ /dev/null @@ -1,93 +0,0 @@ -// -// SettingsListAppIconView.swift -// -// Created by KENJIWADA on 2023/04/02. -// - -import SwiftUI - -struct SettingsListAppIconView: View { - let image: Image? - - var imageColor: Color = .init(UIColor.secondaryLabel) - - let title: Text - - let description: Text? - - var descriptionColor: Color = .init(UIColor.secondaryLabel) - - var descriptionFont: Font = .system(size: 15, weight: .light) - - let action: (() -> Void)? - - var body: some View { - ListItemView(action: action, hasArrow: true) { - HStack(spacing: 16) { - if let image = image { - VStack { - image - .font(.system(size: 28)) - .foregroundColor(imageColor) - .frame(width: 28, height: 28) - .cornerRadius(4) - .scaledToFill() - .padding(.top, 8) - - Spacer(minLength: 0) - } - .frame(width: 32) - } - - VStack(alignment: .leading, spacing: 4) { - title - .font(.system(size: 17, weight: .regular)) - .foregroundColor(Color(UIColor.label)) - - if let desc = description { - desc - .font(descriptionFont) - .foregroundColor(descriptionColor) - } - } - } - .padding(.vertical, 4) - } - } -} - -struct SettingsListAppIconView_Previews: PreviewProvider { - static var previews: some View { - NavigationView { - List { -// SettingsListItemView( -// image: R.image.crown_1.image.resizable(), -// title: Text("おしらせ"), -// description: Text("アプリの更新をお知らせします。"), -// action: nil -// ) -// -// SettingsListItemView( -// image: R.image.diamond_1.image.resizable(), -// title: Text("おしらせ"), -// description: Text("アプリの更新をお知らせします。"), -// action: nil -// ) -// -// SettingsListItemView( -// image: R.image.diamond_2.image.resizable(), -// title: Text("おしらせ"), -// description: Text("アプリの更新をお知らせします。"), -// action: nil -// ) - - SettingsListItemView( - image: Image(systemName: "info.circle"), - title: Text("おしらせ"), - description: Text("アプリの更新をお知らせします。"), - action: nil - ) - } - } - } -} diff --git a/TodoNote/TodoNote/Screen/Settings/View/SettingsListSwitchView.swift b/TodoNote/TodoNote/Screen/Settings/View/SettingsListSwitchView.swift deleted file mode 100644 index bcd8380..0000000 --- a/TodoNote/TodoNote/Screen/Settings/View/SettingsListSwitchView.swift +++ /dev/null @@ -1,100 +0,0 @@ -// -// SettingsListSwitchView.swift -// -// Created by KENJIWADA on 2022/06/19. -// Copyright © 2022 KENJI WADA. All rights reserved. -// - -import SwiftUI - -struct SettingsListSwitchView: View { - let image: Image? - - var imageColor: Color = .init(UIColor.secondaryLabel) - - let title: Text - - var titleFont: Font = .system(size: 16, weight: .regular) - - let description: Text? - - var descriptionColor: Color = .init(UIColor.secondaryLabel) - - var descriptionFont: Font = .system(size: 15, weight: .light) - - @Binding var isEnable: Bool - - let action: (() -> Void)? - - init( - image: Image? = nil, - imageColor: Color = Color(UIColor.secondaryLabel), - title: Text, - titleFont: Font = .system(size: 16, weight: .regular), - description: Text? = nil, - descriptionColor: Color = Color(UIColor.secondaryLabel), - descriptionFont: Font = .system(size: 15, weight: .light), - isEnable: Binding, - action: (() -> Void)? = nil - ) { - self.image = image - self.imageColor = imageColor - self.title = title - self.titleFont = titleFont - self.description = description - self.descriptionColor = descriptionColor - self.descriptionFont = descriptionFont - _isEnable = isEnable - self.action = action - } - - var body: some View { - ListItemView(action: action, hasArrow: false) { - HStack(spacing: 8) { - if let image = image { - VStack { - image - .font(.system(size: 28)) - .foregroundColor(imageColor) - - Spacer(minLength: 0) - } - .frame(width: 32) - .padding(.trailing, 8) - } - - VStack(alignment: .leading, spacing: 4) { - title - .font(titleFont) - .foregroundColor(Color(UIColor.label)) - - if let desc = description { - desc - .font(descriptionFont) - .foregroundColor(descriptionColor) - } - } - Spacer(minLength: 0) - Toggle("", isOn: $isEnable) - .labelsHidden() - } - .padding(.vertical, 4) - } - } -} - -struct SettingsListSwitchView_Previews: PreviewProvider { - static var previews: some View { - NavigationView { - List { - SettingsListSwitchView( - image: Image(systemName: "info.circle"), - title: Text("おしらせ"), - description: Text("アプリの更新をお知らせします。"), - isEnable: .constant(false), - action: nil - ) - } - } - } -} diff --git a/TodoNote/TodoNote/UseCase/Launch/Launch/LaunchUseCase.swift b/TodoNote/TodoNote/UseCase/Launch/Launch/LaunchUseCase.swift index 23b6b38..b945565 100644 --- a/TodoNote/TodoNote/UseCase/Launch/Launch/LaunchUseCase.swift +++ b/TodoNote/TodoNote/UseCase/Launch/Launch/LaunchUseCase.swift @@ -8,9 +8,24 @@ import FirebaseAuth import Foundation class LaunchUseCase: UseCaseProctol { + let todoRepository: TodoRepository + let checkVersionUseCase = CheckVersionUseCase() + init(todoRepository: TodoRepository = TodoRepository()) { + self.todoRepository = todoRepository + } + func execute(_: LaunchUseCaseInput) async -> LaunchUseCaseResult { + return await deleteEditingItems() + } + + private func deleteEditingItems() async -> LaunchUseCaseResult { + do { + try await todoRepository.deleteAll(status: RegistrationStatus.editing) + } catch { + // エラーは無視する + } return await checkLoggedIn() } diff --git a/TodoNote/TodoNote/UseCase/SignOut/SignOutUseCase.swift b/TodoNote/TodoNote/UseCase/SignOut/SignOutUseCase.swift new file mode 100644 index 0000000..1de2b58 --- /dev/null +++ b/TodoNote/TodoNote/UseCase/SignOut/SignOutUseCase.swift @@ -0,0 +1,60 @@ +// +// SignOutUseCase.swift +// TodoNote +// +// Created by KENJIWADA on 2023/08/09. +// + +import FirebaseAuth + +/// BL-S02 ログアウト +class SignOutUseCase: UseCaseProctol { + private let todoRepository: TodoRepository + + private let syncReadyTodoUseCase: SyncReadyTodoUseCase + + init( + todoRepository: TodoRepository = TodoRepository(), + syncReadyTodoUseCase: SyncReadyTodoUseCase = SyncReadyTodoUseCase() + ) { + self.todoRepository = todoRepository + self.syncReadyTodoUseCase = syncReadyTodoUseCase + } + + func execute(_ input: SignOutUseCaseInput) async -> SignOutUseCaseResult { + return await syncReadyData(input: input) + } + + /// ステータスが `ready` のレコードがあればサーバーに同期する + private func syncReadyData(input: SignOutUseCaseInput) async -> SignOutUseCaseResult { + let result = await syncReadyTodoUseCase.execute(.init()) + switch result { + case .success: + return await deleteAllLocalData(input: input) + case let .failed(error): + return .failed(error) + } + } + + /// ローカルデータを全削除する + private func deleteAllLocalData(input: SignOutUseCaseInput) async -> SignOutUseCaseResult { + do { + try await todoRepository.deleteAll() + + return await signOut(input: input) + } catch { + return .failed(error) + } + } + + /// サインアウトする + private func signOut(input _: SignOutUseCaseInput) async -> SignOutUseCaseResult { + do { + try Auth.auth().signOut() + + return .success + } catch { + return .failed(error) + } + } +} diff --git a/TodoNote/TodoNote/UseCase/SignOut/SignOutUseCaseInput.swift b/TodoNote/TodoNote/UseCase/SignOut/SignOutUseCaseInput.swift new file mode 100644 index 0000000..f76d0ae --- /dev/null +++ b/TodoNote/TodoNote/UseCase/SignOut/SignOutUseCaseInput.swift @@ -0,0 +1,10 @@ +// +// SignOutUseCaseInput.swift +// TodoNote +// +// Created by KENJIWADA on 2023/08/09. +// + +import Foundation + +struct SignOutUseCaseInput {} diff --git a/TodoNote/TodoNote/UseCase/SignOut/SignOutUseCaseResult.swift b/TodoNote/TodoNote/UseCase/SignOut/SignOutUseCaseResult.swift new file mode 100644 index 0000000..f3646f6 --- /dev/null +++ b/TodoNote/TodoNote/UseCase/SignOut/SignOutUseCaseResult.swift @@ -0,0 +1,16 @@ +// +// SignOutUseCaseResult.swift +// TodoNote +// +// Created by KENJIWADA on 2023/08/09. +// + +import Foundation + +enum SignOutUseCaseResult { + /// 成功 + case success + + /// 失敗 + case failed(Error) +} diff --git a/TodoNote/TodoNote/UseCase/SyncReadyTodo/SyncReadyTodoUseCase.swift b/TodoNote/TodoNote/UseCase/SyncReadyTodo/SyncReadyTodoUseCase.swift index 8b3e4e0..d861731 100644 --- a/TodoNote/TodoNote/UseCase/SyncReadyTodo/SyncReadyTodoUseCase.swift +++ b/TodoNote/TodoNote/UseCase/SyncReadyTodo/SyncReadyTodoUseCase.swift @@ -5,14 +5,72 @@ // Created by KENJIWADA on 2023/08/09. // +import FirebaseAuth +import FirebaseFirestore import Foundation +/// BL-Z01 データの同期 class SyncReadyTodoUseCase: UseCaseProctol { + private let todoRepository: TodoRepository + private let firestore = Firestore.firestore() + + init(todoRepository: TodoRepository = TodoRepository()) { + self.todoRepository = todoRepository + } + func execute(_ input: SyncReadyTodoUseCaseInput) async -> SyncReadyTodoUseCaseResult { - return await addTodo(input: input) + return await fetchReadyItems(input: input) + } + + private func fetchReadyItems(input: SyncReadyTodoUseCaseInput) async -> SyncReadyTodoUseCaseResult { + do { + let results = try await todoRepository.fetch(status: RegistrationStatus.ready) + if results.isEmpty { + return .success + } + + return await syncItems(input: input, items: results) + } catch { + return .failed(error) + } } - private func addTodo(input _: SyncReadyTodoUseCaseInput) async -> SyncReadyTodoUseCaseResult { + /// Firestore へデータを同期する + private func syncItems(input _: SyncReadyTodoUseCaseInput, items: [Todo]) async -> SyncReadyTodoUseCaseResult { + do { + guard let userId = Auth.auth().currentUser?.uid else { + fatalError("UserId が取得できない") + } + + let collectionRef = firestore.collection("version/1/users/\(userId)/items") + for item in items { + let documentRef = collectionRef.document(item.todoId.rawValue) + if item.finished { + try await documentRef.delete() + } else { + try await documentRef.setData( + [ + "title": item.title, + "desc": item.description ?? "", + "datetime": item.datetime, + "create_at": item.createdAt, + "update_at": item.updatedAt, + ] + ) + } + } + + return await updateLocalData(items: items) + } catch { + return .failed(error) + } + } + + private func updateLocalData(items _: [Todo]) async -> SyncReadyTodoUseCaseResult { + // TODO: ローカルのTODOアイテムのステータスを complete に変更する + + // TODO: finished になっていて、サーバー上のアイテムを削除済みの場合は、ローカルデータも併せて削除する + return .success } } diff --git a/TodoNote/TodoNote/UseCase/SyncReadyTodo/SyncReadyTodoUseCaseInput.swift b/TodoNote/TodoNote/UseCase/SyncReadyTodo/SyncReadyTodoUseCaseInput.swift index f0b59ec..cb3c5ec 100644 --- a/TodoNote/TodoNote/UseCase/SyncReadyTodo/SyncReadyTodoUseCaseInput.swift +++ b/TodoNote/TodoNote/UseCase/SyncReadyTodo/SyncReadyTodoUseCaseInput.swift @@ -7,9 +7,4 @@ import Foundation -struct SyncReadyTodoUseCaseInput { - let todoId: TodoId - let title: String - let description: String? - let datetime: Date -} +struct SyncReadyTodoUseCaseInput {} diff --git a/document/BL-S02.md b/document/BL-S02.md new file mode 100644 index 0000000..6ba3f26 --- /dev/null +++ b/document/BL-S02.md @@ -0,0 +1,6 @@ +# BL-S02 ログアウト + +1. ステータスが `ready` のレコードがあればサーバーに同期する + * 「BL-Z01 データの同期」を実行する +2. ローカルデータを全削除する +3. サインアウトする \ No newline at end of file diff --git a/document/BL-Z01.md b/document/BL-Z01.md new file mode 100644 index 0000000..e4245bc --- /dev/null +++ b/document/BL-Z01.md @@ -0,0 +1,10 @@ +# BL-Z01 データの同期 + +1. ステータスが `ready` のレコードを取得する + * がなければ処理は終了 +2. Firestore へデータの同期 + * finished が false の場合、FirestoreのDocument を作成/更新する + * finished が true の場合、FirestoreのDocument を削除する +3. ステータスの変更 + * finished が false の場合、レコードを削除する + * finished が true の場合、ステータスを complete に変更する \ No newline at end of file diff --git a/document/README.md b/document/README.md index 4609e45..1f015c7 100644 --- a/document/README.md +++ b/document/README.md @@ -7,6 +7,10 @@ * オフラインでも利用可能な TODO 管理アプリ * ユーザーにネットワーク状態を意識させない +## 現時点での制約 + +* Firebase Auth の匿名ログインを利用しており、Twitterアカウントでのログインなどの追加認証方法を提供していない。このため一度ログアウトしてしまうと、サーバーに保存したデータにはアクセスできなくなる。よって、現在時点では ログイン時にサーバーからのデータ取得という処理は実装していない + ## 画面遷移図 screen-transition @@ -35,7 +39,7 @@ * Save ボタンの押下で、既存のレコードを削除し、ステータスを `editing` から `ready` へ変更。サーバーへ同期後は `complete` へ変更する * Cancel ボタンの押下で、`editing` のレコードを削除する -## 画面・ユースケース +## 画面 | ID | 画面名 | 備考 | |:-----|:-----|:-----| @@ -48,6 +52,7 @@ | S-2 | ライセンス | LicensePlistViewController で表示 | | S-3 | 問い合わせ | Googleフォームを SFSafariViewController で表示 | +## ユースケース | ID | ユースケース名 | 備考 | |:-----|:-----|:-----| @@ -58,5 +63,5 @@ | BL-2 | fetch todo list | sort option | | BL-3 | update todo | | | BL-S01 | settings | | -| BL-S02 | logout | | -| BL-Z01 | sync ready | ready のレコードの同期を実施する | +| [BL-S02](./BL-S02.md) | ログアウト | | +| [BL-Z01](./BL-Z01.md) | データの同期 | ready のレコードの同期を実施する |