Skip to content

Commit

Permalink
Merge pull request DroidKaigi#1268 from woxtu/timetable-grid
Browse files Browse the repository at this point in the history
  • Loading branch information
ry-itto committed Sep 22, 2023
2 parents e55dd62 + 86e9f76 commit bc95318
Show file tree
Hide file tree
Showing 12 changed files with 362 additions and 25 deletions.
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

0 comments on commit bc95318

Please sign in to comment.