Files
jrshikoku/targets/widget/TrainFollowLiveActivity.swift

193 lines
7.3 KiB
Swift

import ActivityKit
import SwiftUI
import WidgetKit
// MARK: - Live Activity
// MARK: Lock screen / notification banner
private struct TrainFollowBannerView: View {
let context: ActivityViewContext<TrainFollowAttributes>
private var delayColor: Color {
context.state.delayMinutes > 0 ? .red : Color(red: 0, green: 0.72, blue: 0.42)
}
var body: some View {
VStack(spacing: 0) {
// Header
HStack {
Image(systemName: "tram.fill")
.foregroundColor(.white)
Text("\(context.attributes.lineName) \(context.attributes.trainNumber)")
.font(.subheadline).fontWeight(.bold)
.foregroundColor(.white)
Spacer()
Text(context.attributes.destination + "行き")
.font(.caption)
.foregroundColor(.white.opacity(0.9))
}
.padding(.horizontal, 14)
.padding(.vertical, 8)
.background(Color(red: 0, green: 0.6, blue: 0.8))
// Body
HStack(spacing: 0) {
// Current station
VStack(spacing: 2) {
Text("現在")
.font(.caption2)
.foregroundColor(.secondary)
Text(context.state.currentStation)
.font(.headline)
.fontWeight(.semibold)
.lineLimit(1)
.minimumScaleFactor(0.7)
}
.frame(maxWidth: .infinity)
Image(systemName: "chevron.right.2")
.foregroundColor(Color(red: 0, green: 0.6, blue: 0.8))
.font(.title3)
// Next station
VStack(spacing: 2) {
Text("次の停車駅")
.font(.caption2)
.foregroundColor(.secondary)
Text(context.state.nextStation)
.font(.headline)
.fontWeight(.bold)
.lineLimit(1)
.minimumScaleFactor(0.7)
.foregroundColor(Color(red: 0, green: 0.6, blue: 0.8))
}
.frame(maxWidth: .infinity)
Divider()
.padding(.vertical, 4)
// Delay + arrival
VStack(spacing: 2) {
if context.state.delayMinutes > 0 {
Text("\(context.state.delayMinutes)分遅れ")
.font(.caption)
.fontWeight(.bold)
.foregroundColor(delayColor)
} else {
Text("定刻")
.font(.caption)
.foregroundColor(delayColor)
}
Text("\(context.state.scheduledArrival)")
.font(.caption2)
.foregroundColor(.secondary)
}
.frame(maxWidth: .infinity)
}
.padding(.horizontal, 14)
.padding(.vertical, 10)
}
}
}
// MARK: Dynamic Island expanded
private struct TrainFollowExpandedView: View {
let context: ActivityViewContext<TrainFollowAttributes>
var body: some View {
VStack(spacing: 4) {
// Top row: line + train number
HStack {
Label(context.attributes.lineName, systemImage: "tram.fill")
.font(.caption)
.foregroundColor(Color(red: 0, green: 0.6, blue: 0.8))
Spacer()
Text(context.attributes.trainNumber)
.font(.caption).fontWeight(.semibold)
Text("")
Text(context.attributes.destination)
.font(.caption).fontWeight(.semibold)
}
Divider()
// Station row
HStack {
Text(context.state.currentStation)
.font(.subheadline)
Image(systemName: "arrow.right")
.foregroundColor(Color(red: 0, green: 0.6, blue: 0.8))
Text(context.state.nextStation)
.font(.subheadline).fontWeight(.bold)
.foregroundColor(Color(red: 0, green: 0.6, blue: 0.8))
Spacer()
if context.state.delayMinutes > 0 {
Text("\(context.state.delayMinutes)分遅れ")
.font(.caption).fontWeight(.bold).foregroundColor(.red)
} else {
Text("定刻")
.font(.caption).foregroundColor(.green)
}
Text("\(context.state.scheduledArrival)")
.font(.caption).foregroundColor(.secondary)
}
}
.padding(12)
}
}
// MARK: Widget
struct TrainFollowLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: TrainFollowAttributes.self) { context in
TrainFollowBannerView(context: context)
.activityBackgroundTint(Color(.systemBackground))
} dynamicIsland: { context in
DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
Label(context.attributes.trainNumber, systemImage: "tram.fill")
.font(.caption).fontWeight(.semibold)
.foregroundColor(Color(red: 0, green: 0.6, blue: 0.8))
}
DynamicIslandExpandedRegion(.trailing) {
if context.state.delayMinutes > 0 {
Text("\(context.state.delayMinutes)分遅れ")
.font(.caption).fontWeight(.bold).foregroundColor(.red)
} else {
Text("定刻")
.font(.caption).foregroundColor(.green)
}
}
DynamicIslandExpandedRegion(.center) {
Text("\(context.state.currentStation)\(context.state.nextStation)")
.font(.caption)
}
DynamicIslandExpandedRegion(.bottom) {
HStack {
Text(context.attributes.lineName)
.font(.caption2).foregroundColor(.secondary)
Spacer()
Text(context.attributes.destination + "\(context.state.scheduledArrival)")
.font(.caption2).foregroundColor(.secondary)
}
.padding(.horizontal, 8)
}
} compactLeading: {
Label(context.attributes.trainNumber, systemImage: "tram.fill")
.font(.caption2).fontWeight(.semibold)
.foregroundColor(Color(red: 0, green: 0.6, blue: 0.8))
} compactTrailing: {
Text(context.state.nextStation)
.font(.caption2).lineLimit(1)
} minimal: {
Image(systemName: "tram.fill")
.foregroundColor(Color(red: 0, green: 0.6, blue: 0.8))
}
.keylineTint(Color(red: 0, green: 0.6, blue: 0.8))
}
}
}