201 lines
8.4 KiB
Swift
201 lines
8.4 KiB
Swift
import ActivityKit
|
||
import WidgetKit
|
||
import SwiftUI
|
||
|
||
// 1️⃣ Attributes (كما هو مع Plugin: ContentState فارغ والقراءة من AppGroup)
|
||
struct LiveActivitiesAppAttributes: ActivityAttributes, Identifiable {
|
||
public typealias LiveDeliveryData = ContentState
|
||
public struct ContentState: Codable, Hashable { }
|
||
var id = UUID()
|
||
}
|
||
|
||
// 2️⃣ Prefix helper
|
||
extension LiveActivitiesAppAttributes {
|
||
func prefixedKey(_ key: String) -> String {
|
||
return "\(id)_\(key)"
|
||
}
|
||
}
|
||
|
||
// 3️⃣ Shared App Group
|
||
let sharedDefault = UserDefaults(suiteName: "group.com.Siro.siro")!
|
||
|
||
@available(iOS 16.1, *)
|
||
struct RideWidgetLiveActivity: Widget {
|
||
var body: some WidgetConfiguration {
|
||
ActivityConfiguration(for: LiveActivitiesAppAttributes.self) { context in
|
||
|
||
// ===== Read from shared defaults =====
|
||
let status = sharedDefault.string(forKey: context.attributes.prefixedKey("status")) ?? "waiting"
|
||
let driverName = sharedDefault.string(forKey: context.attributes.prefixedKey("driverName")) ?? "السائق"
|
||
let carDetails = sharedDefault.string(forKey: context.attributes.prefixedKey("carDetails")) ?? ""
|
||
let etaText = sharedDefault.string(forKey: context.attributes.prefixedKey("etaText")) ?? "--"
|
||
let progressRaw = sharedDefault.double(forKey: context.attributes.prefixedKey("progress"))
|
||
|
||
// Clamp progress (0..1)
|
||
let progress = min(max(progressRaw, 0.0), 1.0)
|
||
|
||
// ===== Production Lock Screen UI (White background) =====
|
||
ZStack {
|
||
RoundedRectangle(cornerRadius: 18)
|
||
.fill(Color.white)
|
||
|
||
VStack(alignment: .leading, spacing: 12) {
|
||
// Header
|
||
HStack(alignment: .center, spacing: 10) {
|
||
|
||
// App icon badge (clean)
|
||
ZStack {
|
||
RoundedRectangle(cornerRadius: 10)
|
||
.fill(Color.black.opacity(0.04))
|
||
.frame(width: 38, height: 38)
|
||
|
||
Image("SiroIcon")
|
||
.resizable()
|
||
.aspectRatio(contentMode: .fit)
|
||
.frame(width: 26, height: 26)
|
||
}
|
||
|
||
VStack(alignment: .leading, spacing: 3) {
|
||
Text(status == "waiting" ? "السائق في الطريق إليك" : "الرحلة جارية")
|
||
.font(.headline)
|
||
.foregroundColor(.black)
|
||
|
||
Text("\(driverName) • \(carDetails)")
|
||
.font(.subheadline)
|
||
.foregroundColor(.gray)
|
||
.lineLimit(1)
|
||
}
|
||
|
||
Spacer()
|
||
|
||
Text(etaText)
|
||
.font(.title2)
|
||
.bold()
|
||
.foregroundColor(.green)
|
||
.minimumScaleFactor(0.7)
|
||
}
|
||
|
||
// Progress bar with big car
|
||
GeometryReader { geometry in
|
||
let barHeight: CGFloat = 10
|
||
let carSize: CGFloat = 26
|
||
let usableWidth = max(0, geometry.size.width - carSize)
|
||
let x = min(max(0, usableWidth * CGFloat(progress)), usableWidth)
|
||
|
||
ZStack(alignment: .leading) {
|
||
RoundedRectangle(cornerRadius: 6)
|
||
.fill(Color.black.opacity(0.08))
|
||
.frame(height: barHeight)
|
||
|
||
RoundedRectangle(cornerRadius: 6)
|
||
.fill(Color.green)
|
||
.frame(width: max(0, geometry.size.width * CGFloat(progress)),
|
||
height: barHeight)
|
||
|
||
// Car marker (bigger + slightly above the bar)
|
||
Image(systemName: "car.side.fill")
|
||
.resizable()
|
||
.aspectRatio(contentMode: .fit)
|
||
.frame(width: carSize, height: carSize)
|
||
.foregroundColor(.black)
|
||
.background(
|
||
Circle()
|
||
.fill(Color.white)
|
||
.frame(width: carSize + 8, height: carSize + 8)
|
||
.shadow(color: Color.black.opacity(0.12), radius: 4, x: 0, y: 2)
|
||
)
|
||
.offset(x: x, y: -10)
|
||
}
|
||
}
|
||
.frame(height: 34)
|
||
|
||
// Footer micro status (optional but production-ish)
|
||
HStack(spacing: 6) {
|
||
Circle()
|
||
.fill(status == "waiting" ? Color.orange : Color.green)
|
||
.frame(width: 8, height: 8)
|
||
|
||
Text(status == "waiting" ? "بانتظار وصول السائق" : "أنت الآن في الرحلة")
|
||
.font(.caption)
|
||
.foregroundColor(.gray)
|
||
|
||
Spacer()
|
||
}
|
||
}
|
||
.padding(.horizontal, 14)
|
||
.padding(.vertical, 14)
|
||
}
|
||
.padding(.horizontal, 8)
|
||
|
||
} dynamicIsland: { context in
|
||
// ===== Read again for dynamic island =====
|
||
let status = sharedDefault.string(forKey: context.attributes.prefixedKey("status")) ?? "waiting"
|
||
let driverName = sharedDefault.string(forKey: context.attributes.prefixedKey("driverName")) ?? "السائق"
|
||
let carDetails = sharedDefault.string(forKey: context.attributes.prefixedKey("carDetails")) ?? ""
|
||
let etaText = sharedDefault.string(forKey: context.attributes.prefixedKey("etaText")) ?? "--"
|
||
let progressRaw = sharedDefault.double(forKey: context.attributes.prefixedKey("progress"))
|
||
let progress = min(max(progressRaw, 0.0), 1.0)
|
||
|
||
return DynamicIsland {
|
||
DynamicIslandExpandedRegion(.leading) {
|
||
HStack(spacing: 6) {
|
||
Image("SiroIcon")
|
||
.resizable()
|
||
.aspectRatio(contentMode: .fit)
|
||
.frame(width: 20, height: 20)
|
||
.clipShape(RoundedRectangle(cornerRadius: 5))
|
||
|
||
Image(systemName: "car.side.fill")
|
||
.foregroundColor(.green)
|
||
}
|
||
}
|
||
|
||
DynamicIslandExpandedRegion(.trailing) {
|
||
VStack(alignment: .trailing, spacing: 2) {
|
||
Text(etaText)
|
||
.font(.headline)
|
||
.foregroundColor(.green)
|
||
|
||
Text(status == "waiting" ? "قادم إليك" : "جاري")
|
||
.font(.caption)
|
||
.foregroundColor(.gray)
|
||
}
|
||
}
|
||
|
||
DynamicIslandExpandedRegion(.center) {
|
||
VStack(spacing: 4) {
|
||
Text(status == "waiting" ? "السائق في الطريق" : "الرحلة جارية")
|
||
.font(.subheadline)
|
||
|
||
ProgressView(value: progress)
|
||
.progressViewStyle(.linear)
|
||
}
|
||
}
|
||
|
||
DynamicIslandExpandedRegion(.bottom) {
|
||
Text("\(driverName) • \(carDetails)")
|
||
.font(.caption)
|
||
.foregroundColor(.gray)
|
||
.lineLimit(1)
|
||
}
|
||
|
||
} compactLeading: {
|
||
Image("SiroIcon")
|
||
.resizable()
|
||
.aspectRatio(contentMode: .fit)
|
||
.frame(width: 18, height: 18)
|
||
.clipShape(RoundedRectangle(cornerRadius: 4))
|
||
|
||
} compactTrailing: {
|
||
Text(etaText)
|
||
.font(.caption2)
|
||
.minimumScaleFactor(0.7)
|
||
|
||
} minimal: {
|
||
Image(systemName: "car.side.fill")
|
||
.foregroundColor(.green)
|
||
}
|
||
}
|
||
}
|
||
}
|