193 lines
7.3 KiB
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))
|
|
}
|
|
}
|
|
}
|