Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[iOS] Add the timetable grid #1268

Merged
merged 7 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
}
}
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -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))
}
}
15 changes: 15 additions & 0 deletions app-ios/Modules/Sources/Model/TimetableRoomGroupItems.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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
}
}
18 changes: 18 additions & 0 deletions app-ios/Modules/Sources/Timetable/RoomType+Extension.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
81 changes: 81 additions & 0 deletions app-ios/Modules/Sources/Timetable/TimetableGridItemView.swift
Original file line number Diff line number Diff line change
@@ -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()
)
}
147 changes: 147 additions & 0 deletions app-ios/Modules/Sources/Timetable/TimetableGridView.swift
Original file line number Diff line number Diff line change
@@ -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
15 changes: 0 additions & 15 deletions app-ios/Modules/Sources/Timetable/TimetableListItemView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
Loading
Loading