diff --git a/.github/workflows/CompareScreenshot.yml b/.github/workflows/CompareScreenshot.yml index f8fc64a1e..8b8a4c363 100644 --- a/.github/workflows/CompareScreenshot.yml +++ b/.github/workflows/CompareScreenshot.yml @@ -44,11 +44,12 @@ jobs: retention-days: 30 - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 - if: ${{ always() }} + if: always() with: name: screenshot-diff-reports path: | - **/build/reports + **/reports + **/build/outputs/roborazzi retention-days: 30 - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 @@ -67,4 +68,4 @@ jobs: - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 with: name: pr - path: pr/ \ No newline at end of file + path: pr/ diff --git a/.github/workflows/UnitTest.yml b/.github/workflows/UnitTest.yml index 135b3eb57..6ec2c2b10 100644 --- a/.github/workflows/UnitTest.yml +++ b/.github/workflows/UnitTest.yml @@ -52,14 +52,13 @@ jobs: **/build/outputs/roborazzi retention-days: 30 - - id: test-reports + - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 if: always() - uses: ./.github/actions/upload-distributed-files with: - copy-to: .test-reports - name: reports - name-type: directory - artifact-name: 'test-reports' + name: test-reports + path: | + **/reports + **/build/outputs/roborazzi - id: test-results if: always() diff --git a/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/list_view.imageset/Contents.json b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/list_view.imageset/Contents.json new file mode 100644 index 000000000..2cd81cba5 --- /dev/null +++ b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/list_view.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "trailing-icon 3.pdf", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "trailing-icon 4.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/list_view.imageset/trailing-icon 3.pdf b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/list_view.imageset/trailing-icon 3.pdf new file mode 100644 index 000000000..d8270501d Binary files /dev/null and b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/list_view.imageset/trailing-icon 3.pdf differ diff --git a/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/list_view.imageset/trailing-icon 4.pdf b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/list_view.imageset/trailing-icon 4.pdf new file mode 100644 index 000000000..adb4eaa38 Binary files /dev/null and b/app-ios/Modules/Sources/Assets/Resources/Icons.xcassets/list_view.imageset/trailing-icon 4.pdf differ diff --git a/app-ios/Modules/Sources/Model/Extension/Kotlinx_datetimeInstant.swift b/app-ios/Modules/Sources/Model/Extension/Kotlinx_datetimeInstant.swift index 9895a2bca..cdbcf57e4 100644 --- a/app-ios/Modules/Sources/Model/Extension/Kotlinx_datetimeInstant.swift +++ b/app-ios/Modules/Sources/Model/Extension/Kotlinx_datetimeInstant.swift @@ -1,7 +1,7 @@ import shared extension Kotlinx_datetimeInstant { - public func toDate(calendar: Calendar = .current) -> Date { - return Date(timeIntervalSince1970: TimeInterval(toEpochMilliseconds())) + public func toDate() -> Date { + return Date(timeIntervalSince1970: TimeInterval(epochSeconds)) } } diff --git a/app-ios/Modules/Sources/Model/TimetableRoomGroupItems.swift b/app-ios/Modules/Sources/Model/TimetableRoomGroupItems.swift new file mode 100644 index 000000000..5dae06238 --- /dev/null +++ b/app-ios/Modules/Sources/Model/TimetableRoomGroupItems.swift @@ -0,0 +1,15 @@ +import shared + +public struct TimetableRoomGroupItems: Identifiable, Equatable, Hashable { + public var id: String { + UUID().uuidString + } + + public var room: TimetableRoom + public var items: [TimetableItemWithFavorite] + + public init(room: TimetableRoom, items: [TimetableItemWithFavorite]) { + self.room = room + self.items = items + } +} diff --git a/app-ios/Modules/Sources/Navigation/RootView.swift b/app-ios/Modules/Sources/Navigation/RootView.swift index d0cacf99d..4f111b51d 100644 --- a/app-ios/Modules/Sources/Navigation/RootView.swift +++ b/app-ios/Modules/Sources/Navigation/RootView.swift @@ -10,7 +10,11 @@ import SwiftUI import Theme import Timetable -enum Tab { +enum Tab: Int, CaseIterable, Identifiable { + var id: Int { + rawValue + } + case timeline case floorMap case achievements @@ -18,8 +22,11 @@ enum Tab { } public struct RootView: View { + @Environment(\.horizontalSizeClass) var horizontalSizeClass + @Environment(\.verticalSizeClass) var verticalSizeClass @StateObject var viewModel: RootViewModel = .init() @State var selection = Tab.timeline + @State var selectionForSidebar: Tab.ID? = Tab.timeline.id public init() {} @@ -33,89 +40,155 @@ public struct RootView: View { case .failed: EmptyView() case .loaded(let isAchivementEnabled): - TabView(selection: $selection) { - TimetableView( - sessionViewBuilder: { timetableItem in - SessionView(timetableItem: timetableItem) - } - ) - .tag(Tab.timeline) - .tabItem { - Label { - Text("Timetable") - } icon: { - if selection == .timeline { - Assets.Icons.timetable.swiftUIImage - .renderingMode(.template) - } else { - Assets.Icons.timetableFillOff.swiftUIImage - .renderingMode(.template) + let timetableView = TimetableView( + sessionViewBuilder: { timetableItem in + SessionView(timetableItem: timetableItem) + } + ) + let aboutView = AboutView( + contributorViewProvider: { _ in + ContributorView() + }, + staffViewProvider: { _ in + StaffView() + }, + sponsorViewProvider: { _ in + SponsorView() + } + ) + Group { + if horizontalSizeClass == .regular && verticalSizeClass == .regular { + NavigationSplitView(columnVisibility: .constant(.doubleColumn)) { + List(Tab.allCases, selection: $selectionForSidebar) { + let selected = $0.id == selectionForSidebar + switch $0 { + case .timeline: + TimetableViewLabel(selected: selected) + case .floorMap: + FloorMapViewLabel(selected: selected) + case .achievements: + AchievementsViewLabel(selected: selected) + case .about: + AboutViewLabel(selected: selected) } } - } - FloorMapView() - .tag(Tab.floorMap) - .tabItem { - Label { - Text("Floor Map") - } icon: { - if selection == .floorMap { - Assets.Icons.floorMap.swiftUIImage - .renderingMode(.template) - } else { - Assets.Icons.floorMapFillOff.swiftUIImage - .renderingMode(.template) + } detail: { + if let selectionForSidebar, let tab = Tab(rawValue: selectionForSidebar) { + switch tab { + case .timeline: + timetableView + case .floorMap: + FloorMapView() + case .achievements: + AchievementsView() + case .about: + aboutView } + } else { + fatalError() } } -// if isAchivementEnabled { - AchievementsView() - .tag(Tab.achievements) - .tabItem { - Label { - Text("Achievements") - } icon: { - if selection == .achievements { - Assets.Icons.achievements.swiftUIImage - .renderingMode(.template) - } else { - Assets.Icons.achievementsFillOff.swiftUIImage - .renderingMode(.template) - } + .navigationSplitViewStyle(.balanced) + } else { + TabView(selection: $selection) { + timetableView + .tag(Tab.timeline) + .tabItem { + TimetableViewLabel(selected: selection == .timeline) } - } -// } - AboutView( - contributorViewProvider: { _ in - ContributorView() - }, - staffViewProvider: { _ in - StaffView() - }, - sponsorViewProvider: { _ in - SponsorView() - } - ) - .tag(Tab.about) - .tabItem { - Label { - Text("About") - } icon: { - if selection == .about { - Assets.Icons.info.swiftUIImage - .renderingMode(.template) - } else { - Assets.Icons.infoFillOff.swiftUIImage - .renderingMode(.template) + FloorMapView() + .tag(Tab.floorMap) + .tabItem { + FloorMapViewLabel(selected: selection == .floorMap) + } + // if isAchivementEnabled { + AchievementsView() + .tag(Tab.achievements) + .tabItem { + AchievementsViewLabel(selected: selection == .achievements) + } + // } + aboutView + .tag(Tab.about) + .tabItem { + AboutViewLabel(selected: selection == .about) } - } } + } } .tint(AssetColors.Secondary.onSecondaryContainer.swiftUIColor) } } } +private struct TimetableViewLabel: View { + let selected: Bool + var body: some View { + Label { + Text("Timetable") + } icon: { + if selected { + Assets.Icons.timetable.swiftUIImage + .renderingMode(.template) + } else { + Assets.Icons.timetableFillOff.swiftUIImage + .renderingMode(.template) + } + } + } +} + +private struct FloorMapViewLabel: View { + let selected: Bool + var body: some View { + Label { + Text("Floor Map") + } icon: { + if selected { + Assets.Icons.floorMap.swiftUIImage + .renderingMode(.template) + } else { + Assets.Icons.floorMapFillOff.swiftUIImage + .renderingMode(.template) + } + } + } +} + +private struct AchievementsViewLabel: View { + let selected: Bool + var body: some View { + Label { + Text("Achievements") + } icon: { + if selected { + Assets.Icons.achievements.swiftUIImage + .renderingMode(.template) + } else { + Assets.Icons.achievementsFillOff.swiftUIImage + .renderingMode(.template) + } + } + } +} + +private struct AboutViewLabel: View { + let selected: Bool + var body: some View { + Label { + Text("About") + } icon: { + if selected { + Assets.Icons.info.swiftUIImage + .renderingMode(.template) + } else { + Assets.Icons.infoFillOff.swiftUIImage + .renderingMode(.template) + } + } + } +} + // #Preview { // RootView() // } diff --git a/app-ios/Modules/Sources/Theme/Resources/Colors.xcassets/Error/Error Container.colorset/Contents.json b/app-ios/Modules/Sources/Theme/Resources/Colors.xcassets/Error/Error Container.colorset/Contents.json new file mode 100644 index 000000000..d606dce29 --- /dev/null +++ b/app-ios/Modules/Sources/Theme/Resources/Colors.xcassets/Error/Error Container.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xD6", + "green" : "0xDA", + "red" : "0xFF" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x0A", + "green" : "0x00", + "red" : "0x93" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/app-ios/Modules/Sources/Timetable/RoomType+Extension.swift b/app-ios/Modules/Sources/Timetable/RoomType+Extension.swift new file mode 100644 index 000000000..e8c4903e7 --- /dev/null +++ b/app-ios/Modules/Sources/Timetable/RoomType+Extension.swift @@ -0,0 +1,18 @@ +import shared +import SwiftUI +import Theme + +extension RoomType { + func toColor() -> Color { + let colorAsset = switch self { + case .rooma: AssetColors.Custom.hallA + case .roomb: AssetColors.Custom.hallB + case .roomc: AssetColors.Custom.hallC + case .roomd: AssetColors.Custom.hallD + case .roome: AssetColors.Custom.hallE + case .roomde: AssetColors.Custom.hallD + default: AssetColors.Custom.white + } + return colorAsset.swiftUIColor + } +} diff --git a/app-ios/Modules/Sources/Timetable/TimetableGridItemView.swift b/app-ios/Modules/Sources/Timetable/TimetableGridItemView.swift new file mode 100644 index 000000000..368c74f1a --- /dev/null +++ b/app-ios/Modules/Sources/Timetable/TimetableGridItemView.swift @@ -0,0 +1,81 @@ +import Assets +import Component +import shared +import SwiftUI +import Theme + +struct TimetableGridItemView: View { + let timetableItemWithFavorite: TimetableItemWithFavorite + + var timetableItem: TimetableItem { + self.timetableItemWithFavorite.timetableItem + } + + var body: some View { + VStack(alignment: .leading, spacing: 4) { + Text(timetableItem.title.currentLangTitle) + .textStyle(TypographyTokens.labelLarge) + .multilineTextAlignment(.leading) + + HStack(spacing: 3) { + Assets.Icons.schedule.swiftUIImage + .renderingMode(.template) + Text("\(timetableItem.startsTimeString) - \(timetableItem.endsTimeString)") + .textStyle(TypographyTokens.bodySmall) + } + + Spacer() + + if let session = timetableItem as? TimetableItem.Session { + HStack(alignment: .center, spacing: 4) { + if session.speakers.count > 1 { + ForEach(session.speakers, id: \.self) { speaker in + speakerIcon(speaker) + } + } else if let speaker = session.speakers.first { + speakerIcon(speaker) + Text(speaker.name) + .textStyle(TypographyTokens.labelMedium) + .lineLimit(2) + } + + Spacer() + + if session.message != nil { + Assets.Icons.info.swiftUIImage + .resizable() + .renderingMode(.template) + .frame(width: 16, height: 16) + .foregroundStyle(AssetColors.Error.errorContainer.swiftUIColor) + } + } + } + } + .padding(RadiusTokens.s) + .frame(maxWidth: .infinity, alignment: .leading) + .foregroundStyle(AssetColors.Custom.hallText.swiftUIColor) + .background(timetableItem.room.type.toColor()) + .clipShape(RoundedRectangle(cornerRadius: 4)) + } + + private func speakerIcon(_ speaker: TimetableSpeaker) -> some View { + CacheAsyncImage(url: URL(string: speaker.iconUrl)) { image in + image.resizable() + } placeholder: { + Color.gray + } + .frame(width: 32, height: 32) + .scaledToFill() + .clipShape(RoundedRectangle(cornerRadius: RadiusTokens.xs)) + .overlay( + RoundedRectangle(cornerRadius: RadiusTokens.xs) + .stroke(AssetColors.Outline.outline.swiftUIColor, lineWidth: 1) + ) + } +} + +#Preview { + TimetableGridItemView( + timetableItemWithFavorite: TimetableItemWithFavorite.companion.fake() + ) +} diff --git a/app-ios/Modules/Sources/Timetable/TimetableGridView.swift b/app-ios/Modules/Sources/Timetable/TimetableGridView.swift new file mode 100644 index 000000000..12934adaf --- /dev/null +++ b/app-ios/Modules/Sources/Timetable/TimetableGridView.swift @@ -0,0 +1,147 @@ +import Model +import shared +import SwiftUI +import Theme + +struct TimetableGridView: View { + let roomHeaderSize: CGSize = .init(width: 194, height: 40) + let gridSize: CGSize = .init(width: 194, height: 310) + + let timetableRoomGroupItems: [TimetableRoomGroupItems] + let hours: [Date] + + init( + day: DroidKaigi2023Day, + timetableRoomGroupItems: [TimetableRoomGroupItems] + ) { + self.timetableRoomGroupItems = timetableRoomGroupItems + + var dateComponents = Calendar(identifier: .gregorian) + .dateComponents(in: TimeZone(identifier: "Asia/Tokyo")!, from: day.start.toDate()) + hours = (10 ... 19).compactMap { hour in + dateComponents.hour = hour + dateComponents.minute = 0 + return dateComponents.date + } + } + + var body: some View { + ScrollView(.vertical) { + HStack(alignment: .top, spacing: 0) { + VStack(spacing: 0) { + Text(hourFormatter.string(from: hours[0])) + .textStyle(TypographyTokens.labelMedium) + .frame(height: roomHeaderSize.height + 8, alignment: .bottom) + + ForEach(hours[1...], id: \.self) { hour in + Text(hourFormatter.string(from: hour)) + .textStyle(TypographyTokens.labelMedium) + .frame(height: gridSize.height, alignment: .bottom) + } + } + .padding(.leading, SpacingTokens.m) + .frame(width: 75 - 8, alignment: .leading) + + VStack(spacing: 0) { + Divider().frame(height: roomHeaderSize.height, alignment: .bottom) + + ForEach(hours[1...], id: \.self) { _ in + Divider().frame(height: gridSize.height, alignment: .bottom) + } + } + .frame(width: 8) + + ZStack(alignment: .topLeading) { + VStack(spacing: 0) { + Divider().frame(height: roomHeaderSize.height, alignment: .bottom) + + ForEach(hours[1...], id: \.self) { _ in + Divider().frame(height: gridSize.height, alignment: .bottom) + } + } + + ScrollView(.horizontal) { + HStack(alignment: .top, spacing: 0) { + Divider() + + ForEach(timetableRoomGroupItems) { timetableRoomGroupItem in + VStack(spacing: 0) { + Text(timetableRoomGroupItem.room.name.currentLangTitle) + .textStyle(TypographyTokens.titleSmall) + .frame(height: roomHeaderSize.height) + + ZStack { + ForEach(timetableRoomGroupItem.items, id: \.timetableItem.id.value) { timetableItemWithFavorite in + let frame = itemFrame( + timetableItemWithFavorite: timetableItemWithFavorite, + startTime: hours[0], + gridSize: gridSize + ) + + NavigationLink(value: TimetableRouting.session(timetableItemWithFavorite.timetableItem)) { + TimetableGridItemView( + timetableItemWithFavorite: timetableItemWithFavorite + ) + } + .frame(width: frame.width, height: frame.height) + .position(x: frame.origin.x, y: frame.origin.y) + } + } + } + .frame(width: gridSize.width) + + Divider() + } + } + } + } + } + } + } + + private func itemFrame( + timetableItemWithFavorite: TimetableItemWithFavorite, + startTime: Date, + gridSize: CGSize + ) -> CGRect { + let item = timetableItemWithFavorite.timetableItem + + let heightPerSecond = gridSize.height / (60 * 60) + let itemSpacing = NSDirectionalEdgeInsets(top: 1, leading: 1, bottom: 1, trailing: 1) + + let itemSize = CGSize( + width: gridSize.width - itemSpacing.leading - itemSpacing.trailing, + height: CGFloat(item.endsAt.epochSeconds - item.startsAt.epochSeconds) * heightPerSecond + - itemSpacing.top - itemSpacing.bottom + ) + let itemPosition = CGPoint( + x: itemSize.width / 2 + itemSpacing.leading, + y: (CGFloat(item.startsAt.epochSeconds) - startTime.timeIntervalSince1970) * heightPerSecond + + itemSize.height / 2 + itemSpacing.top + ) + return CGRect(origin: itemPosition, size: itemSize) + } +} + +private let hourFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.calendar = Calendar(identifier: .gregorian) + formatter.locale = Locale(identifier: "Asia/Tokyo") + formatter.dateStyle = .none + formatter.timeStyle = .short + return formatter +}() + +#if DEBUG +#Preview { + TimetableGridView( + day: Timetable.companion.fake().contents.first!.timetableItem.day!, + timetableRoomGroupItems: [ + TimetableRoomGroupItems( + room: Timetable.companion.fake().contents.first!.timetableItem.room, + items: [Timetable.companion.fake().contents.first!] + ), + ] + ) +} +#endif diff --git a/app-ios/Modules/Sources/Timetable/TimetableListItemView.swift b/app-ios/Modules/Sources/Timetable/TimetableListItemView.swift index 6d8a3e8b9..4e6334459 100644 --- a/app-ios/Modules/Sources/Timetable/TimetableListItemView.swift +++ b/app-ios/Modules/Sources/Timetable/TimetableListItemView.swift @@ -86,21 +86,6 @@ struct TimetableListItemView: View { } } -private extension RoomType { - func toColor() -> Color { - let colorAsset = switch self { - case .rooma: AssetColors.Custom.hallA - case .roomb: AssetColors.Custom.hallB - case .roomc: AssetColors.Custom.hallC - case .roomd: AssetColors.Custom.hallD - case .roome: AssetColors.Custom.hallE - case .roomde: AssetColors.Custom.hallD - default: AssetColors.Custom.white - } - return colorAsset.swiftUIColor - } -} - #Preview { TimetableListItemView( timetableItemWithFavorite: TimetableItemWithFavorite.companion.fake(), diff --git a/app-ios/Modules/Sources/Timetable/TimetableView.swift b/app-ios/Modules/Sources/Timetable/TimetableView.swift index 1b1e3074a..15d28aa36 100644 --- a/app-ios/Modules/Sources/Timetable/TimetableView.swift +++ b/app-ios/Modules/Sources/Timetable/TimetableView.swift @@ -14,6 +14,7 @@ enum TimetableRouting: Hashable { public struct TimetableView: View { @Environment(\.colorScheme) var colorScheme @StateObject var viewModel: TimetableViewModel = .init() + @State private var isListPresented: Bool = true private let sessionViewBuilder: ViewProvider let gradient = Gradient(stops: [ .init(color: AssetColors.Surface.surfaceGradientTOP.swiftUIColor, location: 0.0), @@ -78,13 +79,20 @@ public struct TimetableView: View { .frame(height: shouldCollapse ? 53 : 82) .animation(.easeInOut(duration: 0.08), value: shouldCollapse) ) { - TimetableListView( - timetableTimeGroupItems: state.timeGroupTimetableItems, - searchWord: "", - onToggleBookmark: { [weak viewModel] in - viewModel?.toggleBookmark($0) - } - ) + if isListPresented { + TimetableListView( + timetableTimeGroupItems: state.timeGroupTimetableItems, + searchWord: "", + onToggleBookmark: { [weak viewModel] in + viewModel?.toggleBookmark($0) + } + ) + } else { + TimetableGridView( + day: viewModel.state.selectedDay, + timetableRoomGroupItems: state.roomGroupTimetableItems + ) + } } } .background(AssetColors.Surface.surface.swiftUIColor) @@ -132,7 +140,16 @@ public struct TimetableView: View { .buttonStyle(.plain) } ToolbarItem { - Assets.Icons.gridView.swiftUIImage + Button { + isListPresented.toggle() + } label: { + if isListPresented { + Assets.Icons.gridView.swiftUIImage + } else { + Assets.Icons.listView.swiftUIImage + } + } + .buttonStyle(.plain) } } } diff --git a/app-ios/Modules/Sources/Timetable/TimetableViewModel.swift b/app-ios/Modules/Sources/Timetable/TimetableViewModel.swift index 4f206ae45..482a998fe 100644 --- a/app-ios/Modules/Sources/Timetable/TimetableViewModel.swift +++ b/app-ios/Modules/Sources/Timetable/TimetableViewModel.swift @@ -7,6 +7,7 @@ import shared struct TimetableState: ViewModelState { struct LoadedState: Equatable { var timeGroupTimetableItems: [TimetableTimeGroupItems] + var roomGroupTimetableItems: [TimetableRoomGroupItems] var bookmarks: Set } @@ -75,9 +76,22 @@ final class TimetableViewModel: ObservableObject { } var seen: [TimetableTimeGroupItems: Bool] = [:] let distinctedTimetableTimeGroupItems = timetableTimeGroupItems.filter { seen.updateValue(true, forKey: $0) == nil } + let roomGroupTimetableItems = cachedTimetable.dayTimetable(droidKaigi2023Day: state.selectedDay) + .rooms + .map { room in + let items = cachedTimetable.contents + .filter { $0.timetableItem.room == room } + .filter { $0.timetableItem.day == state.selectedDay } + .sorted { $0.timetableItem.startsAt.epochSeconds < $1.timetableItem.startsAt.epochSeconds } + return TimetableRoomGroupItems( + room: room, + items: items + ) + } state.loadedState = .loaded( .init( timeGroupTimetableItems: distinctedTimetableTimeGroupItems, + roomGroupTimetableItems: roomGroupTimetableItems, bookmarks: cachedTimetable.bookmarks ) ) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9b766920d..bd711d40c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,7 +26,7 @@ koin = "3.4.3" ktlint = "0.49.1" kotlinxSerialization = "1.6.0" ktor = "2.3.4" -roborazzi = "1.6.0-alpha-3" +roborazzi = "1.7.0-alpha-2" showkase = "1.0.0-beta18" ksp = "1.9.0-1.0.13" firebaseBom = "32.2.3"