From a358f59fb762147a98fbc79b6221335d77357112 Mon Sep 17 00:00:00 2001 From: ryoya ito Date: Sun, 25 Sep 2022 19:34:06 +0900 Subject: [PATCH] add event --- .../DroidKaigi2022.xcodeproj/project.pbxproj | 2 + app-ios/DroidKaigi2022/iOS/Info.plist | 10 ++-- app-ios/Package.swift | 6 ++ app-ios/Sources/AppFeature/AppView.swift | 20 +++++-- app-ios/Sources/Event/Event.swift | 48 ++++++++++++++++ app-ios/Sources/Event/Mock.swift | 11 ++++ .../Sources/SearchFeature/SearchView.swift | 15 +++-- .../Sources/SessionFeature/SessionView.swift | 55 +++++++++++++++++-- .../commonMain/resources/MR/base/strings.xml | 3 + .../commonMain/resources/MR/en/strings.xml | 3 + .../commonMain/resources/MR/zh/strings.xml | 3 + 11 files changed, 158 insertions(+), 18 deletions(-) create mode 100644 app-ios/Sources/Event/Event.swift create mode 100644 app-ios/Sources/Event/Mock.swift diff --git a/app-ios/DroidKaigi2022/DroidKaigi2022.xcodeproj/project.pbxproj b/app-ios/DroidKaigi2022/DroidKaigi2022.xcodeproj/project.pbxproj index ce4199940..78673cf0f 100644 --- a/app-ios/DroidKaigi2022/DroidKaigi2022.xcodeproj/project.pbxproj +++ b/app-ios/DroidKaigi2022/DroidKaigi2022.xcodeproj/project.pbxproj @@ -324,6 +324,7 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = iOS/Info.plist; + INFOPLIST_KEY_NSCalendarsUsageDescription = "Use for adding event to calendar"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -356,6 +357,7 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = iOS/Info.plist; + INFOPLIST_KEY_NSCalendarsUsageDescription = "Use for adding event to calendar"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; diff --git a/app-ios/DroidKaigi2022/iOS/Info.plist b/app-ios/DroidKaigi2022/iOS/Info.plist index f267fdc14..c81e290ae 100644 --- a/app-ios/DroidKaigi2022/iOS/Info.plist +++ b/app-ios/DroidKaigi2022/iOS/Info.plist @@ -2,12 +2,12 @@ + CFBundleLocalizations + + en + ja + ITSAppUsesNonExemptEncryption - CFBundleLocalizations - - en - ja - diff --git a/app-ios/Package.swift b/app-ios/Package.swift index f410bada7..61cfcef93 100644 --- a/app-ios/Package.swift +++ b/app-ios/Package.swift @@ -69,6 +69,7 @@ var package = Package( .target(name: "Auth"), .target(name: "Container"), .target(name: "ContributorFeature"), + .target(name: "Event"), .target(name: "MapFeature"), .target(name: "SponsorFeature"), .target(name: "Theme"), @@ -133,6 +134,9 @@ var package = Package( .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), ] ), + .target( + name: "Event" + ), .target( name: "MapFeature", dependencies: [ @@ -156,6 +160,7 @@ var package = Package( name: "SearchFeature", dependencies: [ .target(name: "CommonComponents"), + .target(name: "Event"), .target(name: "SessionFeature"), .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), ] @@ -166,6 +171,7 @@ var package = Package( .target(name: "appioscombined"), .target(name: "Assets"), .target(name: "CommonComponents"), + .target(name: "Event"), .target(name: "Model"), .target(name: "Theme"), .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), diff --git a/app-ios/Sources/AppFeature/AppView.swift b/app-ios/Sources/AppFeature/AppView.swift index 4a48153c0..00789cdcf 100644 --- a/app-ios/Sources/AppFeature/AppView.swift +++ b/app-ios/Sources/AppFeature/AppView.swift @@ -5,6 +5,7 @@ import Assets import Auth import ComposableArchitecture import Container +import Event import MapFeature import SearchFeature import SessionFeature @@ -65,19 +66,22 @@ public struct AppEnvironment { public let sessionsRepository: SessionsRepository public let announcementsRepository: AnnouncementsRepository public let staffRepository: StaffRepository + public let eventKitClient: EventKitClientProtocol public init( contributorsRepository: ContributorsRepository, sponsorsRepository: SponsorsRepository, sessionsRepository: SessionsRepository, announcementsRepository: AnnouncementsRepository, - staffRepository: StaffRepository + staffRepository: StaffRepository, + eventKitClient: EventKitClientProtocol ) { self.contributorsRepository = contributorsRepository self.sponsorsRepository = sponsorsRepository self.sessionsRepository = sessionsRepository self.announcementsRepository = announcementsRepository self.staffRepository = staffRepository + self.eventKitClient = eventKitClient } } @@ -90,7 +94,8 @@ public extension AppEnvironment { sponsorsRepository: container.get(type: SponsorsRepository.self), sessionsRepository: container.get(type: SessionsRepository.self), announcementsRepository: container.get(type: AnnouncementsRepository.self), - staffRepository: container.get(type: StaffRepository.self) + staffRepository: container.get(type: StaffRepository.self), + eventKitClient: EventKitClient() ) } @@ -100,7 +105,8 @@ public extension AppEnvironment { sponsorsRepository: FakeSponsorsRepository(), sessionsRepository: FakeSessionsRepository(), announcementsRepository: FakeAnnouncementsRepository(), - staffRepository: FakeStaffRepository() + staffRepository: FakeStaffRepository(), + eventKitClient: EventKitClientMock() ) } } @@ -147,7 +153,8 @@ public let appReducer = Reducer.combine( action: /AppAction.search, environment: { .init( - sessionsRepository: $0.sessionsRepository + sessionsRepository: $0.sessionsRepository, + eventKitClient: $0.eventKitClient ) } ), @@ -155,7 +162,10 @@ public let appReducer = Reducer.combine( state: \.sessionState, action: /AppAction.session, environment: { - .init(sessionsRepository: $0.sessionsRepository) + .init( + sessionsRepository: $0.sessionsRepository, + eventKitClient: $0.eventKitClient + ) } ), .init { state, action, _ in diff --git a/app-ios/Sources/Event/Event.swift b/app-ios/Sources/Event/Event.swift new file mode 100644 index 000000000..197ec75ac --- /dev/null +++ b/app-ios/Sources/Event/Event.swift @@ -0,0 +1,48 @@ +import EventKit +import UIKit + +public protocol EventKitClientProtocol { + func requestAccessIfNeeded() async throws -> Bool + func addEvent( + title: String, + startDate: Date, + endDate: Date + ) throws +} + +public struct EventKitClient: EventKitClientProtocol { + private let eventStore = EKEventStore() + + public init() {} + + public func requestAccessIfNeeded() async throws -> Bool { + switch EKEventStore.authorizationStatus(for: .event) { + case .denied, .restricted: + let _ = await UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!) + case .authorized: + return true + case .notDetermined: + break + @unknown default: + break + } + return try await eventStore.requestAccess(to: .event) + } + + public func addEvent( + title: String, + startDate: Date, + endDate: Date + ) throws { + guard let defaultCalendar = eventStore.defaultCalendarForNewEvents else { + return + } + let event = EKEvent(eventStore: eventStore) + event.title = title + event.startDate = startDate + event.endDate = endDate + event.calendar = defaultCalendar + + try eventStore.save(event, span: .thisEvent) + } +} diff --git a/app-ios/Sources/Event/Mock.swift b/app-ios/Sources/Event/Mock.swift new file mode 100644 index 000000000..5643830b6 --- /dev/null +++ b/app-ios/Sources/Event/Mock.swift @@ -0,0 +1,11 @@ +import Foundation + +public struct EventKitClientMock: EventKitClientProtocol { + public init() {} + + public func requestAccessIfNeeded() async throws -> Bool { + return true + } + + public func addEvent(title: String, startDate: Date, endDate: Date) throws {} +} diff --git a/app-ios/Sources/SearchFeature/SearchView.swift b/app-ios/Sources/SearchFeature/SearchView.swift index ce945dd10..351580162 100644 --- a/app-ios/Sources/SearchFeature/SearchView.swift +++ b/app-ios/Sources/SearchFeature/SearchView.swift @@ -1,5 +1,6 @@ import CommonComponents import ComposableArchitecture +import Event import Model import SessionFeature import SwiftUI @@ -34,11 +35,14 @@ public enum SearchAction { public struct SearchEnvironment { public let sessionsRepository: SessionsRepository + public let eventKitClient: EventKitClientProtocol public init( - sessionsRepository: SessionsRepository + sessionsRepository: SessionsRepository, + eventKitClient: EventKitClientProtocol ) { self.sessionsRepository = sessionsRepository + self.eventKitClient = eventKitClient } } @@ -47,7 +51,10 @@ public let searchReducer = Reducer state: \.sessionState, action: /SearchAction.session, environment: { - .init(sessionsRepository: $0.sessionsRepository) + .init( + sessionsRepository: $0.sessionsRepository, + eventKitClient: $0.eventKitClient + ) } ), .init { state, action, environment in @@ -101,7 +108,6 @@ public let searchReducer = Reducer } ) - public struct SearchView: View { private let store: Store @@ -188,7 +194,8 @@ struct SearchView_Previews: PreviewProvider { ), reducer: .empty, environment: SearchEnvironment( - sessionsRepository: FakeSessionsRepository() + sessionsRepository: FakeSessionsRepository(), + eventKitClient: EventKitClientMock() ) ) ) diff --git a/app-ios/Sources/SessionFeature/SessionView.swift b/app-ios/Sources/SessionFeature/SessionView.swift index 901bb61ad..854eecd35 100644 --- a/app-ios/Sources/SessionFeature/SessionView.swift +++ b/app-ios/Sources/SessionFeature/SessionView.swift @@ -1,6 +1,7 @@ import appioscombined import Assets import ComposableArchitecture +import Event import Model import SwiftUI import Theme @@ -8,6 +9,7 @@ import Theme public struct SessionState: Equatable { public var timetableItemWithFavorite: TimetableItemWithFavorite public var isShareSheetShown: Bool = false + public var eventAddConfirmAlert: AlertState? public init(timetableItemWithFavorite: TimetableItemWithFavorite) { self.timetableItemWithFavorite = timetableItemWithFavorite @@ -20,13 +22,21 @@ public enum SessionAction { case tapFavorite case tapShare case hideShareSheet + case showEventAddConfirmAlert + case hideEventAddConfirmAlert + case addEvent } public struct SessionEnvironment { public let sessionsRepository: SessionsRepository + public let eventKitClient: EventKitClientProtocol - public init(sessionsRepository: SessionsRepository) { + public init( + sessionsRepository: SessionsRepository, + eventKitClient: EventKitClientProtocol + ) { self.sessionsRepository = sessionsRepository + self.eventKitClient = eventKitClient } } @@ -34,8 +44,11 @@ public let sessionReducer = Reducerスライド (同時通訳) このセッションは事情により中止となりました + イベントを追加します。 + OK + キャンセル 設定 diff --git a/core/model/src/commonMain/resources/MR/en/strings.xml b/core/model/src/commonMain/resources/MR/en/strings.xml index 529611adb..8ac9640fb 100644 --- a/core/model/src/commonMain/resources/MR/en/strings.xml +++ b/core/model/src/commonMain/resources/MR/en/strings.xml @@ -48,6 +48,9 @@ SLIDE (interpretation) このセッションは事情により中止となりました + Add an event + OK + Cancel Setting diff --git a/core/model/src/commonMain/resources/MR/zh/strings.xml b/core/model/src/commonMain/resources/MR/zh/strings.xml index 3a06c6529..e3094e9cf 100644 --- a/core/model/src/commonMain/resources/MR/zh/strings.xml +++ b/core/model/src/commonMain/resources/MR/zh/strings.xml @@ -49,6 +49,9 @@ (interpretation) このセッションは事情により中止となりました + Add an event + OK + Cancel 设置