You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
333 lines
12 KiB
Swift
333 lines
12 KiB
Swift
//
|
|
// liveappLiveActivity.swift
|
|
// liveapp
|
|
//
|
|
// Created by Cloud Solutions on 14/12/2025.
|
|
//
|
|
|
|
import ActivityKit
|
|
import WidgetKit
|
|
import SwiftUI
|
|
|
|
struct LiveActivitiesAppAttributes: ActivityAttributes, Identifiable {
|
|
public typealias LiveDeliveryData = ContentState
|
|
|
|
public struct ContentState: Codable, Hashable { }
|
|
|
|
var id = UUID()
|
|
}
|
|
|
|
// Create shared default with custom group
|
|
let sharedDefault = UserDefaults(suiteName: "group.alhabib.patientApp")!
|
|
|
|
@available(iOSApplicationExtension 16.1, *)
|
|
struct FootballMatchApp: Widget {
|
|
var body: some WidgetConfiguration {
|
|
ActivityConfiguration(for: LiveActivitiesAppAttributes.self) { context in
|
|
let matchName = sharedDefault.string(forKey: context.attributes.prefixedKey("matchName"))!
|
|
let ruleFile = sharedDefault.string(forKey: context.attributes.prefixedKey("ruleFile"))!
|
|
|
|
let teamAName = sharedDefault.string(forKey: context.attributes.prefixedKey("teamAName"))!
|
|
let teamAState = sharedDefault.string(forKey: context.attributes.prefixedKey("teamAState"))!
|
|
let teamAScore = sharedDefault.integer(forKey: context.attributes.prefixedKey("teamAScore"))
|
|
let teamALogo = sharedDefault.string(forKey: context.attributes.prefixedKey("teamALogo"))!
|
|
|
|
let teamBName = sharedDefault.string(forKey: context.attributes.prefixedKey("teamBName"))!
|
|
let teamBState = sharedDefault.string(forKey: context.attributes.prefixedKey("teamBState"))!
|
|
let teamBScore = sharedDefault.integer(forKey: context.attributes.prefixedKey("teamBScore"))
|
|
let teamBLogo = sharedDefault.string(forKey: context.attributes.prefixedKey("teamBLogo"))!
|
|
|
|
let rule = (try? String(contentsOfFile: ruleFile, encoding: .utf8)) ?? ""
|
|
let matchStartDate = Date(timeIntervalSince1970: sharedDefault.double(forKey: context.attributes.prefixedKey("matchStartDate")) / 1000)
|
|
let matchEndDate = Date(timeIntervalSince1970: sharedDefault.double(forKey: context.attributes.prefixedKey("matchEndDate")) / 1000)
|
|
let matchRemainingTime = matchStartDate...matchEndDate
|
|
|
|
ZStack {
|
|
LinearGradient(colors: [Color.black.opacity(0.5),Color.black.opacity(0.3)], startPoint: .topLeading, endPoint: .bottom)
|
|
|
|
HStack {
|
|
ZStack {
|
|
VStack(alignment: .center, spacing: 2.0) {
|
|
|
|
Spacer()
|
|
|
|
Text(teamAName)
|
|
.lineLimit(1)
|
|
.font(.subheadline)
|
|
.fontWeight(.bold)
|
|
.multilineTextAlignment(.center)
|
|
|
|
Text(teamAState)
|
|
.lineLimit(1)
|
|
.font(.footnote)
|
|
.fontWeight(.bold)
|
|
.multilineTextAlignment(.center)
|
|
}
|
|
.frame(width: 70, height: 120)
|
|
.padding(.bottom, 8)
|
|
.padding(.top, 8)
|
|
.background(.white.opacity(0.4), in: RoundedRectangle(cornerRadius: 16, style: .continuous))
|
|
|
|
ZStack {
|
|
if let uiImageTeamA = UIImage(contentsOfFile: teamALogo)
|
|
{
|
|
Image(uiImage: uiImageTeamA)
|
|
.resizable()
|
|
.frame(width: 80, height: 80)
|
|
.offset(y:-20)
|
|
}
|
|
}
|
|
}
|
|
|
|
VStack(alignment: .center, spacing: 6.0) {
|
|
HStack {
|
|
Text("\(teamAScore)")
|
|
.font(.title)
|
|
.fontWeight(.bold)
|
|
|
|
Text(":")
|
|
.font(.title)
|
|
.fontWeight(.bold)
|
|
.foregroundStyle(.primary)
|
|
|
|
Text("\(teamBScore)")
|
|
.font(.title)
|
|
.fontWeight(.bold)
|
|
}
|
|
.padding(.horizontal, 5.0)
|
|
.background(.white.opacity(0.4), in: RoundedRectangle(cornerRadius: 8, style: .continuous))
|
|
|
|
HStack(alignment: .center, spacing: 2.0) {
|
|
Text(timerInterval: matchRemainingTime, countsDown: true)
|
|
.multilineTextAlignment(.center)
|
|
.frame(width: 50)
|
|
.monospacedDigit()
|
|
.font(.footnote)
|
|
.foregroundStyle(.white)
|
|
}
|
|
|
|
VStack(alignment: .center, spacing: 1.0) {
|
|
Link(destination: URL(string: "la://my.app/stats")!) {
|
|
Text("See stats 📊")
|
|
}.padding(.vertical, 5).padding(.horizontal, 5)
|
|
Text(matchName)
|
|
.font(.footnote)
|
|
.foregroundStyle(.white)
|
|
.padding(.bottom, 5)
|
|
Text(rule)
|
|
.font(.footnote)
|
|
.foregroundStyle(.white.opacity(0.5))
|
|
}
|
|
}
|
|
.padding(.vertical, 6.0)
|
|
|
|
ZStack {
|
|
VStack(alignment: .center, spacing: 2.0) {
|
|
|
|
Spacer()
|
|
|
|
Text(teamBName)
|
|
.lineLimit(1)
|
|
.font(.subheadline)
|
|
.fontWeight(.bold)
|
|
.multilineTextAlignment(.center)
|
|
|
|
Text(teamBState)
|
|
.lineLimit(1)
|
|
.font(.footnote)
|
|
.fontWeight(.bold)
|
|
.multilineTextAlignment(.center)
|
|
}
|
|
.frame(width: 70, height: 120)
|
|
.padding(.bottom, 8)
|
|
.padding(.top, 8)
|
|
.background(.white.opacity(0.4), in: RoundedRectangle(cornerRadius: 16, style: .continuous))
|
|
|
|
ZStack {
|
|
if let uiImageTeamB = UIImage(contentsOfFile: teamBLogo)
|
|
{
|
|
Image(uiImage: uiImageTeamB)
|
|
.resizable()
|
|
.frame(width: 80, height: 80)
|
|
.offset(y:-20)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.padding(.horizontal, 2.0)
|
|
}.frame(height: 160)
|
|
} dynamicIsland: { context in
|
|
let matchName = sharedDefault.string(forKey: context.attributes.prefixedKey("matchName"))!
|
|
|
|
let teamAName = sharedDefault.string(forKey: context.attributes.prefixedKey("teamAName"))!
|
|
let teamAState = sharedDefault.string(forKey: context.attributes.prefixedKey("teamAState"))!
|
|
let teamAScore = sharedDefault.integer(forKey: context.attributes.prefixedKey("teamAScore"))
|
|
let teamALogo = sharedDefault.string(forKey: context.attributes.prefixedKey("teamALogo"))!
|
|
|
|
let teamBName = sharedDefault.string(forKey: context.attributes.prefixedKey("teamBName"))!
|
|
let teamBState = sharedDefault.string(forKey: context.attributes.prefixedKey("teamBState"))!
|
|
let teamBScore = sharedDefault.integer(forKey: context.attributes.prefixedKey("teamBScore"))
|
|
let teamBLogo = sharedDefault.string(forKey: context.attributes.prefixedKey("teamBLogo"))!
|
|
|
|
let matchStartDate = Date(timeIntervalSince1970: sharedDefault.double(forKey: context.attributes.prefixedKey("matchStartDate")) / 1000)
|
|
let matchEndDate = Date(timeIntervalSince1970: sharedDefault.double(forKey: context.attributes.prefixedKey("matchEndDate")) / 1000)
|
|
let matchRemainingTime = matchStartDate...matchEndDate
|
|
|
|
return DynamicIsland {
|
|
DynamicIslandExpandedRegion(.leading) {
|
|
VStack(alignment: .center, spacing: 2.0) {
|
|
|
|
if let uiImageTeamA = UIImage(contentsOfFile: teamALogo)
|
|
{
|
|
Image(uiImage: uiImageTeamA)
|
|
.resizable()
|
|
.frame(width: 80, height: 80)
|
|
.offset(y:0)
|
|
}
|
|
|
|
Spacer()
|
|
|
|
Text(teamAName)
|
|
.lineLimit(1)
|
|
.font(.subheadline)
|
|
.fontWeight(.bold)
|
|
.multilineTextAlignment(.center)
|
|
|
|
Text(teamAState)
|
|
.lineLimit(1)
|
|
.font(.footnote)
|
|
.fontWeight(.bold)
|
|
.multilineTextAlignment(.center)
|
|
}
|
|
.frame(width: 70, height: 120)
|
|
.padding(.bottom, 8)
|
|
.padding(.top, 8)
|
|
|
|
|
|
}
|
|
DynamicIslandExpandedRegion(.trailing) {
|
|
VStack(alignment: .center, spacing: 2.0) {
|
|
|
|
if let uiImageTeamB = UIImage(contentsOfFile: teamBLogo)
|
|
{
|
|
Image(uiImage: uiImageTeamB)
|
|
.resizable()
|
|
.frame(width: 80, height: 80)
|
|
.offset(y:0)
|
|
}
|
|
|
|
Spacer()
|
|
|
|
Text(teamBName)
|
|
.lineLimit(1)
|
|
.font(.subheadline)
|
|
.fontWeight(.bold)
|
|
.multilineTextAlignment(.center)
|
|
|
|
Text(teamBState)
|
|
.lineLimit(1)
|
|
.font(.footnote)
|
|
.fontWeight(.bold)
|
|
.multilineTextAlignment(.center)
|
|
}
|
|
.frame(width: 70, height: 120)
|
|
.padding(.bottom, 8)
|
|
.padding(.top, 8)
|
|
|
|
|
|
}
|
|
DynamicIslandExpandedRegion(.center) {
|
|
VStack(alignment: .center, spacing: 6.0) {
|
|
HStack {
|
|
Text("\(teamAScore)")
|
|
.font(.title)
|
|
.fontWeight(.bold)
|
|
|
|
Text(":")
|
|
.font(.title)
|
|
.fontWeight(.bold)
|
|
.foregroundStyle(.primary)
|
|
|
|
Text("\(teamBScore)")
|
|
.font(.title)
|
|
.fontWeight(.bold)
|
|
}
|
|
.padding(.horizontal, 5.0)
|
|
.background(.thinMaterial, in: RoundedRectangle(cornerRadius: 8, style: .continuous))
|
|
|
|
HStack(alignment: .center, spacing: 2.0) {
|
|
Text(timerInterval: matchRemainingTime, countsDown: true)
|
|
.multilineTextAlignment(.center)
|
|
.frame(width: 50)
|
|
.monospacedDigit()
|
|
.font(.footnote)
|
|
.foregroundStyle(.white)
|
|
}
|
|
|
|
VStack(alignment: .center, spacing: 1.0) {
|
|
Link(destination: URL(string: "la://my.app/stats")!) {
|
|
Text("See stats 📊")
|
|
}.padding(.vertical, 5).padding(.horizontal, 5)
|
|
Text(matchName)
|
|
.font(.footnote)
|
|
.foregroundStyle(.white)
|
|
}
|
|
|
|
}
|
|
.padding(.vertical, 6.0)
|
|
}
|
|
} compactLeading: {
|
|
HStack {
|
|
if let uiImageTeamA = UIImage(contentsOfFile: teamALogo)
|
|
{
|
|
Image(uiImage: uiImageTeamA)
|
|
.resizable()
|
|
.frame(width: 26, height: 26)
|
|
}
|
|
|
|
Text("\(teamAScore)")
|
|
.font(.title)
|
|
.fontWeight(.bold)
|
|
}
|
|
} compactTrailing: {
|
|
HStack {
|
|
Text("\(teamBScore)")
|
|
.font(.title)
|
|
.fontWeight(.bold)
|
|
if let uiImageTeamB = UIImage(contentsOfFile: teamBLogo)
|
|
{
|
|
Image(uiImage: uiImageTeamB)
|
|
.resizable()
|
|
.frame(width: 26, height: 26)
|
|
}
|
|
}
|
|
} minimal: {
|
|
ZStack {
|
|
if let uiImageTeamA = UIImage(contentsOfFile: teamALogo)
|
|
{
|
|
Image(uiImage: uiImageTeamA)
|
|
.resizable()
|
|
.frame(width: 26, height: 26)
|
|
.offset(x:-6)
|
|
}
|
|
|
|
if let uiImageTeamB = UIImage(contentsOfFile: teamBLogo)
|
|
{
|
|
Image(uiImage: uiImageTeamB)
|
|
.resizable()
|
|
.frame(width: 26, height: 26)
|
|
.offset(x:6)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extension LiveActivitiesAppAttributes {
|
|
func prefixedKey(_ key: String) -> String {
|
|
return "\(id)_\(key)"
|
|
}
|
|
}
|
|
|