mpush_ios_demo/mpush_liveActivity_extension/mpushTaxiLiveActivity.swift (196 lines of code) (raw):
//
// mpushTaxiLiveActivity.swift
// mpush_liveActivity_extension
//
// Created by Miracle on 2025/3/31.
// Copyright © 2025 alibaba. All rights reserved.
//
import ActivityKit
import WidgetKit
import SwiftUI
struct mpushTaxiAttributes: ActivityAttributes {
// 内容状态定义动态参数
public struct ContentState: Codable, Hashable {
// 行程状态,"1" - 已接单, "2" - 前往中, "3" - 行程中, "4" - 已完成
var status: String?
// 剩余距离(单位:米)
var distance: String?
// 预计到达时间(单位:分钟)
var eta: String?
// 提示语
var prompt: String?
}
// 静态参数 - 创建后不会变化
// 打车软件名称
var appName: String
// 打车软件logo
var appLogo: String
}
@available(iOS 16.1, *)
struct mpushTaxiLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: mpushTaxiAttributes.self) { context in
// Lock screen/banner UI goes here
TaxiLockStatusView(attributes: context.attributes, state: context.state)
} dynamicIsland: { context in
DynamicIsland {
// Expanded UI goes here. Compose the expanded UI through
// various regions, like leading/trailing/center/bottom
DynamicIslandExpandedRegion(.center) {
TaxiExpandedView(attributes: context.attributes, state: context.state)
}
} compactLeading: {
// 左侧显示打车软件的logo和名称
HStack {
let imageName = context.attributes.appLogo
// 尝试从Assets中加载该名称的图片
if UIImage(named: imageName) != nil {
// 本地Assets中存在该图片
Image(imageName)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width:36, height: 36)
.cornerRadius(18)
} else {
// 本地Assets中不存在该图片,使用默认shop图片
Image("car")
.resizable().aspectRatio(contentMode: .fit)
.frame(width: 36, height:36)
.cornerRadius(18)
}
Text(context.attributes.appName).font(.system(size: 12)).lineLimit(1)
}
} compactTrailing: {
// 右侧显示对应状态
Text(statusTitle(state: context.state)).font(.system(size: 12)).lineLimit(1)
} minimal: {
// 显示对应状态
Text(statusTitle(state: context.state)).font(.system(size: 12)).lineLimit(1)
}
.widgetURL(URL(string: "pocdemo://dynamicisland"))
.keylineTint(Color.red)
}
}
func statusTitle(state: mpushTaxiAttributes.ContentState) -> String {
switch state.status {
case "1":
return "接单中"
case "2":
return "前往中"
case "3":
return "行程中"
case "4":
return "已完成"
default:
return "状态未知"
}
}
}
@available(iOS 16.1, *)
struct TaxiLockStatusView: View {
let attributes: mpushTaxiAttributes
let state: mpushTaxiAttributes.ContentState
var body: some View {
HStack(spacing: 12) {
// 左侧显示打车软件图标与名称(所有状态都显示)
VStack(alignment: .center) {
let imageName = attributes.appLogo
// 尝试从Assets中加载该名称的图片
if UIImage(named: imageName) != nil {
// 本地Assets中存在该图片
Image(imageName)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width:36, height: 36)
.cornerRadius(18)
} else {
// 本地Assets中不存在该图片,使用默认car图片
Image("car")
.resizable().aspectRatio(contentMode: .fit)
.frame(width: 36, height:36)
.cornerRadius(18)
}
Text(attributes.appName).font(.system(size: 12)).lineLimit(1)
}.frame(width:60)
// 中间和右侧内容,根据状态显示不同内容
VStack(alignment: .leading, spacing: 4) {
switch state.status {
case "1":
// 已接单
Text("司机已接单").font(.system(size: 16, weight: .medium))
if let promptText = state.prompt, !promptText.isEmpty {
Text(promptText).font(.system(size: 13)).foregroundColor(.secondary)
}
case "2":
// 前往中
Text("预计\(checkIfNumeric(input: state.eta))分钟到达").font(.system(size: 16, weight: .medium))
Text("距您\(checkIfNumeric(input: state.distance))米").font(.system(size: 13)).foregroundColor(.secondary)
case "3":
// 行程中
Text("行程进行中").font(.system(size: 16, weight: .medium))
if let distanceString = state.distance, !distanceString.isEmpty {
if let distanceValue = Double(distanceString) {
let km = String(format: "%.1f", distanceValue / 1000.0)
Text("剩余\(km)公里").font(.system(size: 13)).foregroundColor(.secondary)
} else {
Text("未知").font(.system(size: 13)).foregroundColor(.secondary)
}
}
case "4":
//已送达
Text("行程已完成").font(.system(size: 16, weight: .medium))
if let promptText = state.prompt, !promptText.isEmpty {
Text(promptText).font(.system(size: 13)).foregroundColor(.secondary)
}
default:
Text("行程状态未知").font(.system(size: 16, weight: .medium))
}
}.frame(maxWidth: .infinity, alignment: .leading)
}
.padding()
.background(Color(UIColor.systemBackground))
}
}
// 扩展模式视图
@available(iOS 16.1, *)
struct TaxiExpandedView: View {
let attributes: mpushTaxiAttributes
let state: mpushTaxiAttributes.ContentState
var body: some View {
VStack(alignment: .center) {
HStack(alignment: .center, spacing: 12) {
// 左侧显示打车软件图标与名称(所有状态都显示)
VStack(alignment: .center) {
let imageName = attributes.appLogo
// 尝试从Assets中加载该名称的图片
if UIImage(named: imageName) != nil {
// 本地Assets中存在该图片
Image(imageName)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width:36, height: 36)
.cornerRadius(18)
} else {
// 本地Assets中不存在该图片,使用默认car图片
Image("car")
.resizable().aspectRatio(contentMode: .fit)
.frame(width: 36, height:36)
.cornerRadius(18)
}
Text(attributes.appName).font(.system(size: 12)).lineLimit(1)
}.frame(width:60)
// 中间和右侧内容,根据状态显示不同内容
VStack(alignment: .leading, spacing: 4) {
switch state.status {
case "1":
// 已接单
Text("司机已接单").font(.system(size: 16, weight: .medium))
if let promptText = state.prompt, !promptText.isEmpty {
Text(promptText).font(.system(size: 13)).foregroundColor(.secondary)
}
case "2":
// 前往中
Text("预计\(checkIfNumeric(input: state.eta))分钟到达").font(.system(size: 16, weight: .medium))
Text("距您\(checkIfNumeric(input: state.distance))米").font(.system(size: 13)).foregroundColor(.secondary)
case "3":
// 行程中
Text("行程进行中").font(.system(size: 16, weight: .medium))
if let distanceString = state.distance, !distanceString.isEmpty {
if let distanceValue = Double(distanceString) {
let km = String(format: "%.1f", distanceValue / 1000.0)
Text("剩余\(km)公里").font(.system(size: 13)).foregroundColor(.secondary)
} else {
Text("未知").font(.system(size: 13)).foregroundColor(.secondary)
}
}
case "4":
//已送达
Text("行程已完成").font(.system(size: 16, weight: .medium))
if let promptText = state.prompt, !promptText.isEmpty {
Text(promptText).font(.system(size: 13)).foregroundColor(.secondary)
}
default:
Text("行程状态未知").font(.system(size: 16, weight: .medium))
}
}.frame(maxWidth: .infinity, alignment: .leading)
}
Spacer().frame(height: 8)
//打开应用按钮
Button(action: {}) {
HStack {
Image("openDemo").resizable().aspectRatio(contentMode: .fit).frame(width: 36, height: 20)
Text("Open the Demo App")
}.frame(maxWidth: .infinity)
.background(Color.blue.opacity(0.1))
.foregroundColor(.blue)
.cornerRadius(12)
}.frame(width: 300, height: 60)
}
}
}
fileprivate func checkIfNumeric(input: String?) -> String {
// 检查是否为nil或空字符串
guard let input = input, !input.isEmpty else {
return "未知"
}
// 创建匹配数字(包括小数和负数)的正则表达式
let numericRegex = #"^-?([0-9]+(\.[0-9]*)?|\.[0-9]+)$"#
// 检查字符串是否匹配正则表达式
if let _ = input.range(of: numericRegex, options: .regularExpression) {
return input
} else {
return "未知"}
}