From ebdde19985fd459c3fee1de470d1e9280afe73e5 Mon Sep 17 00:00:00 2001 From: ryoya ito Date: Tue, 22 Aug 2023 14:42:59 +0900 Subject: [PATCH] apply & add required package --- .../confsched2023/shared/KmpHelper.kt | 11 +++ .../DroidKaigi2023.xcodeproj/project.pbxproj | 4 + .../xcshareddata/swiftpm/Package.resolved | 99 +++++++++++++++++++ .../DroidKaigi2023/GoogleService-Info.plist | 36 +++++++ app-ios/Modules/Package.swift | 19 ++++ .../Modules/Sources/Auth/Authenticator.swift | 18 ++++ .../Contributor/ContributorViewModel.swift | 6 +- .../Sources/KMPContainer/Container.swift | 25 +++++ .../ContributorsDataProvider.swift | 26 ++++- .../Kotlinx_coroutines_coreFlow.swift | 34 +++++++ .../KMPContainer/SessionsDataProvider.swift | 22 ++++- .../KMPContainer/SponsorsDataProvider.swift | 22 ++++- .../KMPContainer/StampDataProvider.swift | 17 +++- .../Sources/RemoteConfig/RemoteConfig.swift | 11 +++ .../Sources/Sponsor/SponsorViewModel.swift | 15 +-- .../Bookmark/BookmarkViewModel.swift | 5 +- .../Timetable/Search/SearchViewModel.swift | 5 +- .../Timetable/TimetableViewModel.swift | 5 +- 18 files changed, 355 insertions(+), 25 deletions(-) create mode 100644 app-ios-shared/src/iosMain/kotlin/io/github/droidkaigi/confsched2023/shared/KmpHelper.kt create mode 100644 app-ios/App/DroidKaigi2023/DroidKaigi2023/GoogleService-Info.plist create mode 100644 app-ios/Modules/Sources/Auth/Authenticator.swift create mode 100644 app-ios/Modules/Sources/KMPContainer/Container.swift create mode 100644 app-ios/Modules/Sources/KMPContainer/Exetnsions/Kotlinx_coroutines_coreFlow.swift create mode 100644 app-ios/Modules/Sources/RemoteConfig/RemoteConfig.swift diff --git a/app-ios-shared/src/iosMain/kotlin/io/github/droidkaigi/confsched2023/shared/KmpHelper.kt b/app-ios-shared/src/iosMain/kotlin/io/github/droidkaigi/confsched2023/shared/KmpHelper.kt new file mode 100644 index 000000000..ada67cb79 --- /dev/null +++ b/app-ios-shared/src/iosMain/kotlin/io/github/droidkaigi/confsched2023/shared/KmpHelper.kt @@ -0,0 +1,11 @@ +package io.github.droidkaigi.confsched2023.shared + +import kotlinx.cinterop.BetaInteropApi +import kotlinx.cinterop.ObjCProtocol +import kotlinx.cinterop.getOriginalKotlinClass + +@OptIn(BetaInteropApi::class) +fun KmpEntryPoint.get(objCProtocol: ObjCProtocol): Any { + val kClazz = getOriginalKotlinClass(objCProtocol)!! + return get(kClazz) +} \ No newline at end of file diff --git a/app-ios/App/DroidKaigi2023/DroidKaigi2023.xcodeproj/project.pbxproj b/app-ios/App/DroidKaigi2023/DroidKaigi2023.xcodeproj/project.pbxproj index 11691a128..340023bc0 100644 --- a/app-ios/App/DroidKaigi2023/DroidKaigi2023.xcodeproj/project.pbxproj +++ b/app-ios/App/DroidKaigi2023/DroidKaigi2023.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ D77DAF8329FAA14A007195DB /* DroidKaigi2023Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D77DAF8229FAA14A007195DB /* DroidKaigi2023Tests.swift */; }; D77DAF8D29FAA14A007195DB /* DroidKaigi2023UITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D77DAF8C29FAA14A007195DB /* DroidKaigi2023UITests.swift */; }; D77DAF8F29FAA14A007195DB /* DroidKaigi2023UITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D77DAF8E29FAA14A007195DB /* DroidKaigi2023UITestsLaunchTests.swift */; }; + E823229C2A93E4BA005C0DBD /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = E823229B2A93E4BA005C0DBD /* GoogleService-Info.plist */; }; E8BCCCE72A8011E8004010F7 /* Modules in Resources */ = {isa = PBXBuildFile; fileRef = E8BCCCE62A8011E8004010F7 /* Modules */; }; E8BCCCEC2A80123E004010F7 /* Navigation in Frameworks */ = {isa = PBXBuildFile; productRef = E8BCCCEB2A80123E004010F7 /* Navigation */; }; E8BCCCF12A80126D004010F7 /* ci_post_clone.sh in Resources */ = {isa = PBXBuildFile; fileRef = E8BCCCEF2A80126D004010F7 /* ci_post_clone.sh */; }; @@ -46,6 +47,7 @@ D77DAF8829FAA14A007195DB /* DroidKaigi2023UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DroidKaigi2023UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D77DAF8C29FAA14A007195DB /* DroidKaigi2023UITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DroidKaigi2023UITests.swift; sourceTree = ""; }; D77DAF8E29FAA14A007195DB /* DroidKaigi2023UITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DroidKaigi2023UITestsLaunchTests.swift; sourceTree = ""; }; + E823229B2A93E4BA005C0DBD /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; E8BCCCE62A8011E8004010F7 /* Modules */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Modules; path = ../../Modules; sourceTree = ""; }; E8BCCCEF2A80126D004010F7 /* ci_post_clone.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = ci_post_clone.sh; sourceTree = ""; }; /* End PBXFileReference section */ @@ -112,6 +114,7 @@ D77DAF7029FAA148007195DB /* App.swift */, D77DAF7429FAA149007195DB /* Assets.xcassets */, D77DAF7629FAA149007195DB /* DroidKaigi2023.entitlements */, + E823229B2A93E4BA005C0DBD /* GoogleService-Info.plist */, D77DAF7729FAA149007195DB /* Preview Content */, ); path = DroidKaigi2023; @@ -261,6 +264,7 @@ files = ( E8BCCCE72A8011E8004010F7 /* Modules in Resources */, D77DAF7929FAA149007195DB /* Preview Assets.xcassets in Resources */, + E823229C2A93E4BA005C0DBD /* GoogleService-Info.plist in Resources */, D77DAF7529FAA149007195DB /* Assets.xcassets in Resources */, E8BCCCF12A80126D004010F7 /* ci_post_clone.sh in Resources */, ); diff --git a/app-ios/App/DroidKaigi2023/DroidKaigi2023.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/app-ios/App/DroidKaigi2023/DroidKaigi2023.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e41312909..d2f08b69f 100644 --- a/app-ios/App/DroidKaigi2023/DroidKaigi2023.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/app-ios/App/DroidKaigi2023/DroidKaigi2023.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,14 @@ { "pins" : [ + { + "identity" : "abseil-cpp-binary", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/abseil-cpp-binary.git", + "state" : { + "revision" : "bfc0b6f81adc06ce5121eb23f628473638d67c5c", + "version" : "1.2022062300.0" + } + }, { "identity" : "collectionconcurrencykit", "kind" : "remoteSourceControl", @@ -27,6 +36,87 @@ "version" : "1.7.2" } }, + { + "identity" : "firebase-ios-sdk", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/firebase-ios-sdk", + "state" : { + "revision" : "df2171b0c6afb9e9d4f7e07669d558c510b9f6be", + "version" : "10.13.0" + } + }, + { + "identity" : "googleappmeasurement", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleAppMeasurement.git", + "state" : { + "revision" : "03b9beee1a61f62d32c521e172e192a1663a5e8b", + "version" : "10.13.0" + } + }, + { + "identity" : "googledatatransport", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleDataTransport.git", + "state" : { + "revision" : "aae45a320fd0d11811820335b1eabc8753902a40", + "version" : "9.2.5" + } + }, + { + "identity" : "googleutilities", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleUtilities.git", + "state" : { + "revision" : "c38ce365d77b04a9a300c31061c5227589e5597b", + "version" : "7.11.5" + } + }, + { + "identity" : "grpc-binary", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/grpc-binary.git", + "state" : { + "revision" : "f1b366129d1125be7db83247e003fc333104b569", + "version" : "1.50.2" + } + }, + { + "identity" : "gtm-session-fetcher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/gtm-session-fetcher.git", + "state" : { + "revision" : "d415594121c9e8a4f9d79cecee0965cf35e74dbd", + "version" : "3.1.1" + } + }, + { + "identity" : "leveldb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/leveldb.git", + "state" : { + "revision" : "0706abcc6b0bd9cedfbb015ba840e4a780b5159b", + "version" : "1.22.2" + } + }, + { + "identity" : "nanopb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/nanopb.git", + "state" : { + "revision" : "819d0a2173aff699fb8c364b6fb906f7cdb1a692", + "version" : "2.30909.0" + } + }, + { + "identity" : "promises", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/promises.git", + "state" : { + "revision" : "e70e889c0196c76d22759eb50d6a0270ca9f1d9e", + "version" : "2.3.1" + } + }, { "identity" : "sourcekitten", "kind" : "remoteSourceControl", @@ -108,6 +198,15 @@ "version" : "1.0.2" } }, + { + "identity" : "swift-protobuf", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-protobuf.git", + "state" : { + "revision" : "ce20dc083ee485524b802669890291c0d8090170", + "version" : "1.22.1" + } + }, { "identity" : "swift-syntax", "kind" : "remoteSourceControl", diff --git a/app-ios/App/DroidKaigi2023/DroidKaigi2023/GoogleService-Info.plist b/app-ios/App/DroidKaigi2023/DroidKaigi2023/GoogleService-Info.plist new file mode 100644 index 000000000..3d248d712 --- /dev/null +++ b/app-ios/App/DroidKaigi2023/DroidKaigi2023/GoogleService-Info.plist @@ -0,0 +1,36 @@ + + + + + CLIENT_ID + 1046519477136-gni68m3pq6059ivee422f6mfpp7h1c0h.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.1046519477136-gni68m3pq6059ivee422f6mfpp7h1c0h + ANDROID_CLIENT_ID + 1046519477136-8nv86645bl6a09hr0rqrr7qso3u9n16u.apps.googleusercontent.com + API_KEY + AIzaSyDl9v9XzP31jrauM1twqkNPi-m5J3B2nrM + GCM_SENDER_ID + 1046519477136 + PLIST_VERSION + 1 + BUNDLE_ID + io.github.droidkaigi.DroidKaigi2023 + PROJECT_ID + ssot-api-staging + STORAGE_BUCKET + ssot-api-staging.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:1046519477136:ios:a9459b469c5a1984a8f915 + + diff --git a/app-ios/Modules/Package.swift b/app-ios/Modules/Package.swift index a73d69a5f..084f69203 100644 --- a/app-ios/Modules/Package.swift +++ b/app-ios/Modules/Package.swift @@ -23,6 +23,7 @@ var package = Package( .package(url: "https://github.com/realm/SwiftLint", from: "0.52.4"), .package(url: "https://github.com/SwiftGen/SwiftGenPlugin", from: "6.6.2"), .package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.0.0"), + .package(url: "https://github.com/firebase/firebase-ios-sdk", from: "10.13.0"), ], targets: [ .target( @@ -53,6 +54,14 @@ var package = Package( ] ), + .target( + name: "Auth", + dependencies: [ + "shared", + .product(name: "FirebaseAuth", package: "firebase-ios-sdk"), + ] + ), + .target( name: "Component", dependencies: [ @@ -91,7 +100,9 @@ var package = Package( .target( name: "KMPContainer", dependencies: [ + "Auth", "shared", + "RemoteConfig", .product(name: "Dependencies", package: "swift-dependencies"), ] ), @@ -177,6 +188,14 @@ var package = Package( ] ), + .target( + name: "RemoteConfig", + dependencies: [ + "shared", + .product(name: "FirebaseRemoteConfig", package: "firebase-ios-sdk"), + ] + ), + .target( name: "Model", dependencies: [ diff --git a/app-ios/Modules/Sources/Auth/Authenticator.swift b/app-ios/Modules/Sources/Auth/Authenticator.swift new file mode 100644 index 000000000..ed0fb6dda --- /dev/null +++ b/app-ios/Modules/Sources/Auth/Authenticator.swift @@ -0,0 +1,18 @@ +import FirebaseAuth +import shared + +public class AuthenticatorImpl: Authenticator { + public init() {} + public func currentUser() async throws -> shared.User? { + let currentUser = Auth.auth().currentUser + let idToken = try await currentUser?.getIDToken() + + return idToken.map(User.init(idToken:)) + } + + public func signInAnonymously() async throws -> shared.User? { + let result = try await Auth.auth().signInAnonymously() + let idToken = try await result.user.getIDToken() + return User(idToken: idToken) + } +} diff --git a/app-ios/Modules/Sources/Contributor/ContributorViewModel.swift b/app-ios/Modules/Sources/Contributor/ContributorViewModel.swift index 2956f002d..eba7227ab 100644 --- a/app-ios/Modules/Sources/Contributor/ContributorViewModel.swift +++ b/app-ios/Modules/Sources/Contributor/ContributorViewModel.swift @@ -17,8 +17,10 @@ final class ContributorViewModel: ObservableObject { state.contributors = .loading do { - let contributors = try await contributorsData.contributors() - state.contributors = .loaded(contributors) + try await contributorsData.refresh() + for try await contributors in contributorsData.contributors() { + state.contributors = .loaded(contributors) + } } catch let error { state.contributors = .failed(error) } diff --git a/app-ios/Modules/Sources/KMPContainer/Container.swift b/app-ios/Modules/Sources/KMPContainer/Container.swift new file mode 100644 index 000000000..6390cc7b3 --- /dev/null +++ b/app-ios/Modules/Sources/KMPContainer/Container.swift @@ -0,0 +1,25 @@ +import Auth +import Firebase +import RemoteConfig +import shared + +struct Container { + static let shared: Container = .init() + + private let entryPoint: KmpEntryPoint + private init() { + FirebaseApp.configure() + entryPoint = .init() + entryPoint.doInit( + remoteConfigApi: RemoteConfigApiImpl(), + authenticator: AuthenticatorImpl() + ) + } + + func get(type: TypeProtocol) -> ReturnType where TypeProtocol: Protocol { + guard let object = entryPoint.get(objCProtocol: type) as? ReturnType else { + fatalError("Not found instance for \(type)") + } + return object + } +} diff --git a/app-ios/Modules/Sources/KMPContainer/ContributorsDataProvider.swift b/app-ios/Modules/Sources/KMPContainer/ContributorsDataProvider.swift index 9f365f354..1a4ab2b9b 100644 --- a/app-ios/Modules/Sources/KMPContainer/ContributorsDataProvider.swift +++ b/app-ios/Modules/Sources/KMPContainer/ContributorsDataProvider.swift @@ -2,18 +2,36 @@ import Dependencies import shared public struct ContributorsDataProvider { - public let contributors: () async throws -> [Contributor] + private static var contributorsRepository: ContributorsRepository { + Container.shared.get(type: ContributorsRepository.self) + } + + public let refresh: () async throws -> Void + public let contributors: () -> AsyncThrowingStream<[Contributor], Error> } extension ContributorsDataProvider: DependencyKey { + @MainActor public static var liveValue: ContributorsDataProvider = ContributorsDataProvider( - contributors: { @MainActor in - try await FakeContributorsApiClient().contributors() + refresh: { @MainActor in + try await contributorsRepository.refresh() + }, + contributors: { + contributorsRepository.contributors().stream() + } + ) + + public static var testValue: ContributorsDataProvider = ContributorsDataProvider( + refresh: {}, + contributors: { + .init { + Contributor.companion.fakes() + } } ) } -public extension DependencyValues { + public extension DependencyValues { var contributorsData: ContributorsDataProvider { get { self[ContributorsDataProvider.self] } set { self[ContributorsDataProvider.self] = newValue } diff --git a/app-ios/Modules/Sources/KMPContainer/Exetnsions/Kotlinx_coroutines_coreFlow.swift b/app-ios/Modules/Sources/KMPContainer/Exetnsions/Kotlinx_coroutines_coreFlow.swift new file mode 100644 index 000000000..f405fd7f6 --- /dev/null +++ b/app-ios/Modules/Sources/KMPContainer/Exetnsions/Kotlinx_coroutines_coreFlow.swift @@ -0,0 +1,34 @@ +import shared + +class FlowCollector: Kotlinx_coroutines_coreFlowCollector { + let callback: (T) -> Void + + init(callback: @escaping (T) -> Void) { + self.callback = callback + } + + func emit(value: Any?, completionHandler: @escaping (Error?) -> Void) { + if let value = value as? T { + callback(value) + } + completionHandler(nil) + } +} + +public extension Kotlinx_coroutines_coreFlow { + // Note: Calling Kotlin suspend functions from Swift/Objective-C is currently supported only on main thread + @MainActor + func stream() -> AsyncThrowingStream { + return AsyncThrowingStream { [weak self] continuation in + self?.collect(collector: FlowCollector(callback: { value in + continuation.yield(value) + }), completionHandler: { error in + if let error = error { + continuation.finish(throwing: error) + } else { + continuation.finish() + } + }) + } + } +} diff --git a/app-ios/Modules/Sources/KMPContainer/SessionsDataProvider.swift b/app-ios/Modules/Sources/KMPContainer/SessionsDataProvider.swift index d4f4a1368..17a66e1b7 100644 --- a/app-ios/Modules/Sources/KMPContainer/SessionsDataProvider.swift +++ b/app-ios/Modules/Sources/KMPContainer/SessionsDataProvider.swift @@ -2,15 +2,33 @@ import Dependencies import shared public struct SessionsDataProvider { - public let timetable: () async throws -> Timetable + private static var sessionsRepository: SessionsRepository { + Container.shared.get(type: SessionsRepository.self) + } + + public let timetable: () -> AsyncThrowingStream + public let toggleBookmark: (TimetableItemId) async throws -> Void } extension SessionsDataProvider: DependencyKey { + @MainActor static public var liveValue: SessionsDataProvider = SessionsDataProvider( timetable: { - Timetable.companion.fake() + sessionsRepository.getTimetableStream().stream() + }, + toggleBookmark: { @MainActor id in + try await sessionsRepository.toggleBookmark(id: id) } ) + + public static var testValue: SessionsDataProvider = SessionsDataProvider( + timetable: { + .init { + Timetable.companion.fake() + } + }, + toggleBookmark: {_ in} + ) } public extension DependencyValues { diff --git a/app-ios/Modules/Sources/KMPContainer/SponsorsDataProvider.swift b/app-ios/Modules/Sources/KMPContainer/SponsorsDataProvider.swift index 847e9ebec..6595e4179 100644 --- a/app-ios/Modules/Sources/KMPContainer/SponsorsDataProvider.swift +++ b/app-ios/Modules/Sources/KMPContainer/SponsorsDataProvider.swift @@ -2,13 +2,31 @@ import Dependencies import shared public struct SponsorsDataProvider { - public let sponsors: () async throws -> [Sponsor] + private static var sponsorsRepository: SponsorsRepository { + Container.shared.get(type: SponsorsRepository.self) + } + + public let refresh: () async throws -> Void + public let sponsors: () -> AsyncThrowingStream<[Sponsor], Error> } extension SponsorsDataProvider: DependencyKey { + @MainActor public static var liveValue: SponsorsDataProvider = SponsorsDataProvider( + refresh: { @MainActor in + try await sponsorsRepository.refresh() + }, + sponsors: { + sponsorsRepository.sponsors().stream() + } + ) + + public static var testValue: SponsorsDataProvider = SponsorsDataProvider( + refresh: {}, sponsors: { - Sponsor.companion.fakes() + .init { + Sponsor.companion.fakes() + } } ) } diff --git a/app-ios/Modules/Sources/KMPContainer/StampDataProvider.swift b/app-ios/Modules/Sources/KMPContainer/StampDataProvider.swift index 1f1782818..413824331 100644 --- a/app-ios/Modules/Sources/KMPContainer/StampDataProvider.swift +++ b/app-ios/Modules/Sources/KMPContainer/StampDataProvider.swift @@ -2,13 +2,26 @@ import Dependencies import shared public struct StampDataProvider { - public let stampEnabled: () async throws -> Bool + private static var stampRepository: StampRepository { + Container.shared.get(type: StampRepository.self) + } + + public let stampEnabled: () -> AsyncThrowingStream } extension StampDataProvider: DependencyKey { + @MainActor public static var liveValue: StampDataProvider = StampDataProvider( stampEnabled: { - true + stampRepository.getStampEnabledStream().stream() + } + ) + + public static var testValue: StampDataProvider = StampDataProvider( + stampEnabled: { + .init { + true + } } ) } diff --git a/app-ios/Modules/Sources/RemoteConfig/RemoteConfig.swift b/app-ios/Modules/Sources/RemoteConfig/RemoteConfig.swift new file mode 100644 index 000000000..c391352f5 --- /dev/null +++ b/app-ios/Modules/Sources/RemoteConfig/RemoteConfig.swift @@ -0,0 +1,11 @@ +import FirebaseRemoteConfig +import shared + +public class RemoteConfigApiImpl: RemoteConfigApi { + public init() {} + public func getBoolean(key: String) async throws -> KotlinBoolean { + .init( + bool: RemoteConfig.remoteConfig().configValue(forKey: key).boolValue + ) + } +} diff --git a/app-ios/Modules/Sources/Sponsor/SponsorViewModel.swift b/app-ios/Modules/Sources/Sponsor/SponsorViewModel.swift index d6a53c812..9da2a6596 100644 --- a/app-ios/Modules/Sources/Sponsor/SponsorViewModel.swift +++ b/app-ios/Modules/Sources/Sponsor/SponsorViewModel.swift @@ -17,13 +17,14 @@ final class SponsorViewModel: ObservableObject { state.planGroupedSponsors = .loading do { - let sponsors = try await sponsorsData.sponsors() - - state.planGroupedSponsors = .loaded( - [Plan: [Sponsor]](grouping: sponsors) { - $0.plan - } - ) + try await sponsorsData.refresh() + for try await sponsors in sponsorsData.sponsors() { + state.planGroupedSponsors = .loaded( + [Plan: [Sponsor]](grouping: sponsors) { + $0.plan + } + ) + } } catch let error { state.planGroupedSponsors = .failed(error) } diff --git a/app-ios/Modules/Sources/Timetable/Bookmark/BookmarkViewModel.swift b/app-ios/Modules/Sources/Timetable/Bookmark/BookmarkViewModel.swift index 3040496a5..8349792d5 100644 --- a/app-ios/Modules/Sources/Timetable/Bookmark/BookmarkViewModel.swift +++ b/app-ios/Modules/Sources/Timetable/Bookmark/BookmarkViewModel.swift @@ -22,8 +22,9 @@ final class BookmarkViewModel: ObservableObject { func load() async { state.timeGroupTimetableItems = .loading do { - let timetable = try await sessionsData.timetable() - cachedTimetable = timetable + for try await timetable in sessionsData.timetable() { + cachedTimetable = timetable + } } catch let error { state.timeGroupTimetableItems = .failed(error) } diff --git a/app-ios/Modules/Sources/Timetable/Search/SearchViewModel.swift b/app-ios/Modules/Sources/Timetable/Search/SearchViewModel.swift index df06fab80..1da57e787 100644 --- a/app-ios/Modules/Sources/Timetable/Search/SearchViewModel.swift +++ b/app-ios/Modules/Sources/Timetable/Search/SearchViewModel.swift @@ -38,8 +38,9 @@ final class SearchViewModel: ObservableObject { func load() async { state.loadingState = .loading do { - let timetable = try await sessionsData.timetable() - cachedTimetable = timetable + for try await timetable in sessionsData.timetable() { + cachedTimetable = timetable + } } catch let error { state.loadingState = .failed(error) } diff --git a/app-ios/Modules/Sources/Timetable/TimetableViewModel.swift b/app-ios/Modules/Sources/Timetable/TimetableViewModel.swift index 2b92ed8ff..67cd417db 100644 --- a/app-ios/Modules/Sources/Timetable/TimetableViewModel.swift +++ b/app-ios/Modules/Sources/Timetable/TimetableViewModel.swift @@ -22,8 +22,9 @@ final class TimetableViewModel: ObservableObject { func load() async { state.timeGroupTimetableItems = .loading do { - let timetable = try await sessionsData.timetable() - cachedTimetable = timetable + for try await timetable in sessionsData.timetable() { + cachedTimetable = timetable + } } catch let error { state.timeGroupTimetableItems = .failed(error) }