Skip to content

Commit

Permalink
add event
Browse files Browse the repository at this point in the history
  • Loading branch information
ry-itto committed Sep 25, 2022
1 parent d78abfc commit a358f59
Show file tree
Hide file tree
Showing 11 changed files with 158 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
10 changes: 5 additions & 5 deletions app-ios/DroidKaigi2022/iOS/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>ja</string>
</array>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>ja</string>
</array>
</dict>
</plist>
6 changes: 6 additions & 0 deletions app-ios/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down Expand Up @@ -133,6 +134,9 @@ var package = Package(
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
]
),
.target(
name: "Event"
),
.target(
name: "MapFeature",
dependencies: [
Expand All @@ -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"),
]
Expand All @@ -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"),
Expand Down
20 changes: 15 additions & 5 deletions app-ios/Sources/AppFeature/AppView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Assets
import Auth
import ComposableArchitecture
import Container
import Event
import MapFeature
import SearchFeature
import SessionFeature
Expand Down Expand Up @@ -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
}
}

Expand All @@ -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()
)
}

Expand All @@ -100,7 +105,8 @@ public extension AppEnvironment {
sponsorsRepository: FakeSponsorsRepository(),
sessionsRepository: FakeSessionsRepository(),
announcementsRepository: FakeAnnouncementsRepository(),
staffRepository: FakeStaffRepository()
staffRepository: FakeStaffRepository(),
eventKitClient: EventKitClientMock()
)
}
}
Expand Down Expand Up @@ -147,15 +153,19 @@ public let appReducer = Reducer<AppState, AppAction, AppEnvironment>.combine(
action: /AppAction.search,
environment: {
.init(
sessionsRepository: $0.sessionsRepository
sessionsRepository: $0.sessionsRepository,
eventKitClient: $0.eventKitClient
)
}
),
sessionReducer.optional().pullback(
state: \.sessionState,
action: /AppAction.session,
environment: {
.init(sessionsRepository: $0.sessionsRepository)
.init(
sessionsRepository: $0.sessionsRepository,
eventKitClient: $0.eventKitClient
)
}
),
.init { state, action, _ in
Expand Down
48 changes: 48 additions & 0 deletions app-ios/Sources/Event/Event.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
11 changes: 11 additions & 0 deletions app-ios/Sources/Event/Mock.swift
Original file line number Diff line number Diff line change
@@ -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 {}
}
15 changes: 11 additions & 4 deletions app-ios/Sources/SearchFeature/SearchView.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import CommonComponents
import ComposableArchitecture
import Event
import Model
import SessionFeature
import SwiftUI
Expand Down Expand Up @@ -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
}
}

Expand All @@ -47,7 +51,10 @@ public let searchReducer = Reducer<SearchState, SearchAction, SearchEnvironment>
state: \.sessionState,
action: /SearchAction.session,
environment: {
.init(sessionsRepository: $0.sessionsRepository)
.init(
sessionsRepository: $0.sessionsRepository,
eventKitClient: $0.eventKitClient
)
}
),
.init { state, action, environment in
Expand Down Expand Up @@ -101,7 +108,6 @@ public let searchReducer = Reducer<SearchState, SearchAction, SearchEnvironment>
}
)


public struct SearchView: View {
private let store: Store<SearchState, SearchAction>

Expand Down Expand Up @@ -188,7 +194,8 @@ struct SearchView_Previews: PreviewProvider {
),
reducer: .empty,
environment: SearchEnvironment(
sessionsRepository: FakeSessionsRepository()
sessionsRepository: FakeSessionsRepository(),
eventKitClient: EventKitClientMock()
)
)
)
Expand Down
55 changes: 51 additions & 4 deletions app-ios/Sources/SessionFeature/SessionView.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import appioscombined
import Assets
import ComposableArchitecture
import Event
import Model
import SwiftUI
import Theme

public struct SessionState: Equatable {
public var timetableItemWithFavorite: TimetableItemWithFavorite
public var isShareSheetShown: Bool = false
public var eventAddConfirmAlert: AlertState<SessionAction>?

public init(timetableItemWithFavorite: TimetableItemWithFavorite) {
self.timetableItemWithFavorite = timetableItemWithFavorite
Expand All @@ -20,22 +22,33 @@ 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
}
}

public let sessionReducer = Reducer<SessionState, SessionAction, SessionEnvironment> { state, action, environment in

switch action {
case .tapCalendar:
return .run { @MainActor _ in
// TODO: Add event using EventKit
let timetableItem = state.timetableItemWithFavorite.timetableItem
return .run { @MainActor subscriber in
if try await environment.eventKitClient.requestAccessIfNeeded() {
subscriber.send(.showEventAddConfirmAlert)
}
}
.receive(on: DispatchQueue.main.eraseToAnyScheduler())
.eraseToEffect()
Expand All @@ -60,6 +73,36 @@ public let sessionReducer = Reducer<SessionState, SessionAction, SessionEnvironm
case .hideShareSheet:
state.isShareSheetShown = false
return .none
case .showEventAddConfirmAlert:
state.eventAddConfirmAlert = .init(
title: .init(StringsKt.shared.session_event_add_confirm.localized()),
buttons: [
.cancel(
.init(StringsKt.shared.session_event_add_confirm_cancel.localized()),
action: .send(.hideEventAddConfirmAlert)
),
.default(
.init(StringsKt.shared.session_event_add_confirm_ok.localized()),
action: .send(.addEvent)
),
]
)
return .none
case .hideEventAddConfirmAlert:
state.eventAddConfirmAlert = nil
return .none
case .addEvent:
let timetableItem = state.timetableItemWithFavorite.timetableItem
do {
try environment.eventKitClient.addEvent(
title: timetableItem.title.jaTitle,
startDate: timetableItem.startsAt.toDate(),
endDate: timetableItem.endsAt.toDate()
)
} catch let e {
print(e)
}
return .none
}
}

Expand Down Expand Up @@ -162,6 +205,7 @@ public struct SessionView: View {
.sheet(isPresented: viewStore.binding(get: { $0.isShareSheetShown }, send: .hideShareSheet)) {
ShareTextView(text: timetableItem.shareText)
}
.alert(store.scope(state: \.eventAddConfirmAlert), dismiss: .hideEventAddConfirmAlert)
}
}
}
Expand All @@ -173,7 +217,10 @@ struct SessionView_Previews: PreviewProvider {
store: .init(
initialState: .init(timetableItemWithFavorite: TimetableItemWithFavorite.companion.fake()),
reducer: .empty,
environment: SessionEnvironment(sessionsRepository: FakeSessionsRepository())
environment: SessionEnvironment(
sessionsRepository: FakeSessionsRepository(),
eventKitClient: EventKitClientMock()
)
)
)
}
Expand Down
3 changes: 3 additions & 0 deletions core/model/src/commonMain/resources/MR/base/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
<string name="session_slide">スライド</string>
<string name="session_language_interpretation">(同時通訳)</string>
<string name="session_cancel">このセッションは事情により中止となりました</string>
<string name="session_event_add_confirm">イベントを追加します。</string>
<string name="session_event_add_confirm_ok">OK</string>
<string name="session_event_add_confirm_cancel">キャンセル</string>

<!-- Setting -->
<string name="setting_title">設定</string>
Expand Down
3 changes: 3 additions & 0 deletions core/model/src/commonMain/resources/MR/en/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
<string name="session_slide">SLIDE</string>
<string name="session_language_interpretation">(interpretation)</string>
<string name="session_cancel">このセッションは事情により中止となりました</string>
<string name="session_event_add_confirm">Add an event</string>
<string name="session_event_add_confirm_ok">OK</string>
<string name="session_event_add_confirm_cancel">Cancel</string>

<!-- Setting -->
<string name="setting_title">Setting</string>
Expand Down
3 changes: 3 additions & 0 deletions core/model/src/commonMain/resources/MR/zh/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
<!-- TODO:Translation into Chinese -->
<string name="session_language_interpretation">(interpretation)</string>
<string name="session_cancel">このセッションは事情により中止となりました</string>
<string name="session_event_add_confirm">Add an event</string>
<string name="session_event_add_confirm_ok">OK</string>
<string name="session_event_add_confirm_cancel">Cancel</string>

<!-- Setting -->
<string name="setting_title">设置</string>
Expand Down

0 comments on commit a358f59

Please sign in to comment.