Files
intaleq/ios/RideWidget/RideWidgetLiveActivity.swift
2026-02-28 01:12:28 +03:00

201 lines
8.5 KiB
Swift
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.Intaleq.intaleq")!
@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("IntaleqIcon")
.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("IntaleqIcon")
.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("IntaleqIcon")
.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)
}
}
}
}