قرار المالك 2026-07-27: باك إند سيرو PHP هو المعتمد، وتطبيقاته المجرّبة ميدانياً تحل محل إعادة البناء المؤرشفة. سيرو نفسه لم يُمسّ. الخريطة: backend · payment_server · loction_server · ride_server · passenger_server · docker · dashboard · stress_test → الجذر siro_rider → apps/rider siro_driver → apps/driver siro_admin → dashboards/admin siro_service → dashboards/service android_bot → apps/android_bot socialBot → apps/socialBot نُسخ المتعقَّب في git سيرو فقط عبر `git archive` (3,198 ملفاً / ~169 م.ب) لا `cp -r` — فاستُثنيت مخلفات البناء تلقائياً. بلا أي تعديل محتوى عمداً: كل ما يلي يصير فرقاً مقروءاً مقابل المصدر. لم يُستورد وسببه: siromove.com (الموقع التسويقي يبقى marketing/ في تريبز، سيرو فيه 8 ملفات) · docs و planning (تريبز له docs/ الخاص) · deploy.sh (ليس نشراً على سيرفر بل `git add . && git push origin --all` — فخّ في مستودع آخر) · transit_dashboard (بانتظار قرار مصير backend-transit و dashboards/transit-web). ⚠️ لا يبني بعد — ثلاثة نواقص متوقعة ومقصودة: 1. `.env` و `lib/env/env.g.dart` غير متعقَّبين في سيرو (أسرار لكل مستأجر): كل تطبيق فلاتر يحتاج .env خاصاً ثم توليد env.g.dart بـ build_runner. 2. إعدادات Firebase (9 ملفات google-services.json و GoogleService-Info.plist) يستبعدها .gitignore تريبز — ولكل مستأجر مشروع Firebase خاص أصلاً. 3. apps/driver في سيرو يشير إلى `../../Intaleq/packages/get` خارج المستودع → يجب ضمّ الحزم داخله أسوة بـ apps/rider. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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)
|
|
}
|
|
}
|
|
}
|
|
}
|