Skip to content

Commit 50db11b

Browse files
committed
iOS: [Schedule] Load and preprocess schedule data
Temporary route all feature to ScheduleView for development.
1 parent 1657646 commit 50db11b

File tree

7 files changed

+159
-21
lines changed

7 files changed

+159
-21
lines changed

iosApp/OPass.xcodeproj/project.pbxproj

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
D30235DB2CDC7AA80033D599 /* BlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D30235DA2CDC7AA80033D599 /* BlurView.swift */; };
1212
D307A8812C76039D00BB00FB /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = D307A8802C76039D00BB00FB /* GoogleService-Info.plist */; };
1313
D307A8932C760F8E00BB00FB /* AppearanceSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D307A8922C760F8E00BB00FB /* AppearanceSettingsView.swift */; };
14+
D334ABD22E5F3ECB00E2B1E8 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = D334ABD12E5F3ECB00E2B1E8 /* OrderedCollections */; };
15+
D334ABD52E5F443B00E2B1E8 /* Algorithms in Frameworks */ = {isa = PBXBuildFile; productRef = D334ABD42E5F443B00E2B1E8 /* Algorithms */; };
1416
D34BEECB2C723BC6008F9A4C /* OPassApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D34BEECA2C723BC6008F9A4C /* OPassApp.swift */; };
1517
D34BEECD2C723BC6008F9A4C /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D34BEECC2C723BC6008F9A4C /* ContentView.swift */; };
1618
D34BEECF2C723BC7008F9A4C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D34BEECE2C723BC7008F9A4C /* Assets.xcassets */; };
@@ -22,6 +24,7 @@
2224
D35A64FF2CDB0C9F0085D41F /* RobotoCondensed-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D35A64F32CDB0C9F0085D41F /* RobotoCondensed-Bold.ttf */; };
2325
D35A65002CDB0C9F0085D41F /* LICENSE.txt in Resources */ = {isa = PBXBuildFile; fileRef = D35A64F22CDB0C9F0085D41F /* LICENSE.txt */; };
2426
D35A65012CDB0C9F0085D41F /* RobotoCondensed-BoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D35A64F42CDB0C9F0085D41F /* RobotoCondensed-BoldItalic.ttf */; };
27+
D3883CD22E61BC540085DA75 /* ScheduleModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3883CD12E61BC500085DA75 /* ScheduleModel.swift */; };
2528
D39AC9DC2C7B395D00084689 /* SelectEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39AC9DB2C7B395D00084689 /* SelectEventView.swift */; };
2629
D39AC9DE2C7B398E00084689 /* SelectEventViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D39AC9DD2C7B398E00084689 /* SelectEventViewModel.swift */; };
2730
D3B1E7802C774623002DBA07 /* SafariView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3B1E77F2C774623002DBA07 /* SafariView.swift */; };
@@ -92,6 +95,7 @@
9295
D35A64F62CDB0C9F0085D41F /* RobotoCondensed-Light.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "RobotoCondensed-Light.ttf"; sourceTree = "<group>"; };
9396
D35A64F72CDB0C9F0085D41F /* RobotoCondensed-LightItalic.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "RobotoCondensed-LightItalic.ttf"; sourceTree = "<group>"; };
9497
D35A64F82CDB0C9F0085D41F /* RobotoCondensed-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "RobotoCondensed-Regular.ttf"; sourceTree = "<group>"; };
98+
D3883CD12E61BC500085DA75 /* ScheduleModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleModel.swift; sourceTree = "<group>"; };
9599
D39AC9DB2C7B395D00084689 /* SelectEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectEventView.swift; sourceTree = "<group>"; };
96100
D39AC9DD2C7B398E00084689 /* SelectEventViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectEventViewModel.swift; sourceTree = "<group>"; };
97101
D3B1E77F2C774623002DBA07 /* SafariView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariView.swift; sourceTree = "<group>"; };
@@ -119,6 +123,8 @@
119123
isa = PBXFrameworksBuildPhase;
120124
buildActionMask = 2147483647;
121125
files = (
126+
D334ABD52E5F443B00E2B1E8 /* Algorithms in Frameworks */,
127+
D334ABD22E5F3ECB00E2B1E8 /* OrderedCollections in Frameworks */,
122128
D3BE9CFA2E5F20AF0003666A /* FirebasePerformance in Frameworks */,
123129
D3C398182C73A327002FFD42 /* OneSignalInAppMessages in Frameworks */,
124130
D3BE9CF02E5F20AF0003666A /* FirebaseAnalytics in Frameworks */,
@@ -243,6 +249,7 @@
243249
isa = PBXGroup;
244250
children = (
245251
D3BE9CE42E5DAA2B0003666A /* View */,
252+
D3883CD12E61BC500085DA75 /* ScheduleModel.swift */,
246253
D3BE9CE22E5CB1B60003666A /* ScheduleViewModel.swift */,
247254
);
248255
path = Schedule;
@@ -320,6 +327,8 @@
320327
D3BE9CF52E5F20AF0003666A /* FirebaseCrashlytics */,
321328
D3BE9CF72E5F20AF0003666A /* FirebaseFirestore */,
322329
D3BE9CF92E5F20AF0003666A /* FirebasePerformance */,
330+
D334ABD12E5F3ECB00E2B1E8 /* OrderedCollections */,
331+
D334ABD42E5F443B00E2B1E8 /* Algorithms */,
323332
);
324333
productName = OPass;
325334
productReference = D34BEEC72C723BC6008F9A4C /* OPass.app */;
@@ -371,12 +380,16 @@
371380
en,
372381
Base,
373382
"zh-Hant",
383+
ja,
384+
nan,
374385
);
375386
mainGroup = D34BEEBE2C723BC6008F9A4C;
376387
packageReferences = (
377388
D3C398122C73A327002FFD42 /* XCRemoteSwiftPackageReference "OneSignal-XCFramework" */,
378389
D3BE9CE92E5DFBA20003666A /* XCRemoteSwiftPackageReference "SwiftDate" */,
379390
D3BE9CEC2E5F20AF0003666A /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
391+
D334ABD02E5F3ECB00E2B1E8 /* XCRemoteSwiftPackageReference "swift-collections" */,
392+
D334ABD32E5F443B00E2B1E8 /* XCRemoteSwiftPackageReference "swift-algorithms" */,
380393
);
381394
productRefGroup = D34BEEC82C723BC6008F9A4C /* Products */;
382395
projectDirPath = "";
@@ -476,6 +489,7 @@
476489
D39AC9DE2C7B398E00084689 /* SelectEventViewModel.swift in Sources */,
477490
D3BE9CE62E5DAAC70003666A /* SessionDetailView.swift in Sources */,
478491
D3BE9CE82E5DFA780003666A /* Session.swift in Sources */,
492+
D3883CD22E61BC540085DA75 /* ScheduleModel.swift in Sources */,
479493
D34BEECB2C723BC6008F9A4C /* OPassApp.swift in Sources */,
480494
D307A8932C760F8E00BB00FB /* AppearanceSettingsView.swift in Sources */,
481495
D39AC9DC2C7B395D00084689 /* SelectEventView.swift in Sources */,
@@ -789,6 +803,22 @@
789803
/* End XCConfigurationList section */
790804

791805
/* Begin XCRemoteSwiftPackageReference section */
806+
D334ABD02E5F3ECB00E2B1E8 /* XCRemoteSwiftPackageReference "swift-collections" */ = {
807+
isa = XCRemoteSwiftPackageReference;
808+
repositoryURL = "https://github.com/apple/swift-collections.git";
809+
requirement = {
810+
kind = upToNextMajorVersion;
811+
minimumVersion = 1.2.1;
812+
};
813+
};
814+
D334ABD32E5F443B00E2B1E8 /* XCRemoteSwiftPackageReference "swift-algorithms" */ = {
815+
isa = XCRemoteSwiftPackageReference;
816+
repositoryURL = "https://github.com/apple/swift-algorithms.git";
817+
requirement = {
818+
kind = upToNextMajorVersion;
819+
minimumVersion = 1.2.1;
820+
};
821+
};
792822
D3BE9CE92E5DFBA20003666A /* XCRemoteSwiftPackageReference "SwiftDate" */ = {
793823
isa = XCRemoteSwiftPackageReference;
794824
repositoryURL = "https://github.com/malcommac/SwiftDate";
@@ -816,6 +846,16 @@
816846
/* End XCRemoteSwiftPackageReference section */
817847

818848
/* Begin XCSwiftPackageProductDependency section */
849+
D334ABD12E5F3ECB00E2B1E8 /* OrderedCollections */ = {
850+
isa = XCSwiftPackageProductDependency;
851+
package = D334ABD02E5F3ECB00E2B1E8 /* XCRemoteSwiftPackageReference "swift-collections" */;
852+
productName = OrderedCollections;
853+
};
854+
D334ABD42E5F443B00E2B1E8 /* Algorithms */ = {
855+
isa = XCSwiftPackageProductDependency;
856+
package = D334ABD32E5F443B00E2B1E8 /* XCRemoteSwiftPackageReference "swift-algorithms" */;
857+
productName = Algorithms;
858+
};
819859
D3BE9CEA2E5DFBA20003666A /* SwiftDate */ = {
820860
isa = XCSwiftPackageProductDependency;
821861
package = D3BE9CE92E5DFBA20003666A /* XCRemoteSwiftPackageReference "SwiftDate" */;

iosApp/OPass.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 28 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

iosApp/OPass/Event/EventView.swift

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,9 @@ struct EventView: View {
8181
@ViewBuilder
8282
private func featureButton(_ feature: Feature) -> some View {
8383
VStack {
84-
Button {
85-
86-
} label: {
84+
NavigationLink(destination: {
85+
ScheduleView()
86+
}, label: {
8787
CachedAsyncImage(url: URL(string: feature.iconUrl ?? "")) { phase in
8888
switch phase {
8989
case .success(let image):
@@ -99,7 +99,12 @@ struct EventView: View {
9999
.padding(3)
100100
}
101101
}
102-
}
102+
})
103+
// Button {
104+
//
105+
// } label: {
106+
//
107+
// }
103108
.buttonStyle(.bordered)
104109
.tint(feature.color)
105110
.frame(width: 50, height: 50)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//
2+
// ScheduleModel.swift
3+
// OPass
4+
//
5+
// Created by Brian Chang on 2025/8/29.
6+
//
7+
// SPDX-FileCopyrightText: 2025 OPass
8+
// SPDX-License-Identifier: GPL-3.0-only
9+
//
10+
11+
import Shared
12+
import SwiftDate
13+
import OrderedCollections
14+
15+
struct ScheduleData {
16+
var sessions: [(DateInRegion, [(DateInRegion, ArraySlice<Session>)])]
17+
var speakers: OrderedDictionary<String, Speaker>
18+
var types: OrderedDictionary<String, LocalizedObject>
19+
var rooms: OrderedDictionary<String, LocalizedObject>
20+
var tags: OrderedDictionary<String, LocalizedObject>
21+
}
22+
23+
enum ScheduleViewState {
24+
case ready(ScheduleData)
25+
case failed(Error)
26+
case loading
27+
}

iosApp/OPass/Schedule/ScheduleViewModel.swift

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,20 @@
1010

1111
import Shared
1212
import SwiftUI
13+
import SwiftDate
14+
import Algorithms
15+
import OrderedCollections
1316

14-
@MainActor @Observable
17+
@MainActor
18+
@Observable
1519
class ScheduleViewModel {
16-
@ObservationIgnored @AppStorage("EventID") private var eventID = ""
20+
@ObservationIgnored
21+
@AppStorage("EventID") private var eventID = ""
1722

18-
private(set) var schedule: Schedule?
19-
var sessions: [Session]?
20-
var error: Error?
23+
private(set) var schedule: ScheduleData?
24+
private(set) var error: Error?
2125

22-
enum ViewState {
23-
case loading
24-
case ready(Schedule)
25-
case failed(Error)
26-
}
27-
28-
var viewState: ViewState {
26+
var viewState: ScheduleViewState {
2927
if let schedule { return .ready(schedule) }
3028
if let error { return .failed(error) }
3129
return .loading
@@ -53,7 +51,9 @@ class ScheduleViewModel {
5351
while let (source, result) = await group.next() {
5452
switch result {
5553
case .success(let schedule):
56-
self.schedule = schedule
54+
if let schedule {
55+
await processSchedule(schedule)
56+
}
5757

5858
if source == .remote {
5959
group.cancelAll()
@@ -66,7 +66,23 @@ class ScheduleViewModel {
6666
}
6767
}
6868

69-
func processSchedule(_ schedule: Schedule) {
70-
self.sessions = schedule.sessions
69+
func processSchedule(_ schedule: Schedule) async {
70+
let sessions = schedule.sessions
71+
.chunked(on: { $0.startDate.dateTruncated(from: .hour)! })
72+
.sorted(by: { $0.0 < $1.0 })
73+
.map {($0.0, $0.1.chunked(on: { $0.startDate.dateTruncated(from: .second)! }))}
74+
75+
let speakers = OrderedDictionary(schedule.speakers.map({ ($0.id, $0) }), uniquingKeysWith: { $1 })
76+
let types = OrderedDictionary(schedule.sessionTypes.map({ ($0.id, $0) }), uniquingKeysWith: { $1 })
77+
let rooms = OrderedDictionary(schedule.rooms.map({ ($0.id, $0) }), uniquingKeysWith: { $1 })
78+
let tags = OrderedDictionary(schedule.tags.map({ ($0.id, $0) }), uniquingKeysWith: { $1 })
79+
80+
return self.schedule = .init(
81+
sessions: sessions,
82+
speakers: speakers,
83+
types: types,
84+
rooms: rooms,
85+
tags: tags
86+
)
7187
}
7288
}

iosApp/OPass/Schedule/View/ScheduleView.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,21 @@ struct ScheduleView: View {
1616

1717
var body: some View {
1818
VStack {
19-
19+
switch viewModel.viewState {
20+
case .ready(let schedule):
21+
VStack {
22+
Text("Loaded")
23+
}
24+
case .failed(let error):
25+
ContentUnavailableView(
26+
"Something Went Wrong",
27+
systemImage: "",
28+
description: .init("\(error.localizedDescription) (\(error)")
29+
)
30+
case .loading:
31+
ProgressView("Loading Schedule")
32+
.task { await viewModel.loadSchedule() }
33+
}
2034
}
2135
.analyticsScreen(name: "ScheduleView")
2236
.environment(viewModel)

iosApp/Supporting Files/Localizable.xcstrings

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@
8888
},
8989
"Hello, World!" : {
9090

91+
},
92+
"Loaded" : {
93+
9194
},
9295
"Loading" : {
9396
"localizations" : {
@@ -98,6 +101,9 @@
98101
}
99102
}
100103
}
104+
},
105+
"Loading Schedule" : {
106+
101107
},
102108
"Made with Love" : {
103109
"localizations" : {
@@ -198,6 +204,9 @@
198204
}
199205
}
200206
}
207+
},
208+
"Something Went Wrong" : {
209+
201210
},
202211
"Source Code" : {
203212
"localizations" : {

0 commit comments

Comments
 (0)