LiveActivity.swift 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import ActivityKit
  2. import SwiftUI
  3. import WidgetKit
  4. struct LiveActivity: Widget {
  5. let dateFormatter: DateFormatter = {
  6. var f = DateFormatter()
  7. f.dateStyle = .none
  8. f.timeStyle = .short
  9. return f
  10. }()
  11. func changeLabel(context: ActivityViewContext<LiveActivityAttributes>) -> Text {
  12. if let change = context.state.change {
  13. Text("\(change > 0 ? "+" : "")\(change)")
  14. } else {
  15. Text("--")
  16. }
  17. }
  18. func updatedLabel(context: ActivityViewContext<LiveActivityAttributes>) -> Text {
  19. Text("Updated: \(dateFormatter.string(from: context.state.date))")
  20. }
  21. func bgLabel(context: ActivityViewContext<LiveActivityAttributes>) -> Text {
  22. if context.isStale {
  23. Text("--")
  24. } else {
  25. Text("\(context.state.bg)")
  26. }
  27. }
  28. @ViewBuilder func bgAndTrend(context: ActivityViewContext<LiveActivityAttributes>) -> some View {
  29. if context.isStale {
  30. Text("--")
  31. } else {
  32. Text("\(context.state.bg)")
  33. if let trendSystemImage = context.state.trendSystemImage {
  34. Image(systemName: trendSystemImage)
  35. }
  36. }
  37. }
  38. var body: some WidgetConfiguration {
  39. ActivityConfiguration(for: LiveActivityAttributes.self) { context in
  40. // Lock screen/banner UI goes here
  41. VStack(alignment: .trailing, spacing: 0) {
  42. HStack(alignment: .top) {
  43. HStack(alignment: .center, spacing: 3) {
  44. bgAndTrend(context: context).font(.title)
  45. }
  46. Spacer()
  47. changeLabel(context: context).font(.title3)
  48. }
  49. .imageScale(.small)
  50. updatedLabel(context: context).font(.caption).offset(y: -3)
  51. }
  52. .padding(.horizontal, 20)
  53. .padding(.vertical, 10)
  54. .activityBackgroundTint(Color.cyan.opacity(0.2))
  55. .activitySystemActionForegroundColor(Color.white)
  56. } dynamicIsland: { context in
  57. DynamicIsland {
  58. // Expanded UI goes here. Compose the expanded UI through
  59. // various regions, like leading/trailing/center/bottom
  60. DynamicIslandExpandedRegion(.leading) {
  61. HStack(spacing: 3) {
  62. bgAndTrend(context: context)
  63. }.imageScale(.small).font(.title).padding(.leading, 5)
  64. }
  65. DynamicIslandExpandedRegion(.trailing) {
  66. changeLabel(context: context).font(.title).padding(.trailing, 5)
  67. }
  68. DynamicIslandExpandedRegion(.bottom) {
  69. updatedLabel(context: context).font(.caption)
  70. }
  71. } compactLeading: {
  72. HStack(spacing: 1) {
  73. bgAndTrend(context: context)
  74. }.bold().imageScale(.small)
  75. } compactTrailing: {
  76. changeLabel(context: context)
  77. } minimal: {
  78. bgLabel(context: context).bold()
  79. }
  80. .widgetURL(URL(string: "freeaps-x://"))
  81. .keylineTint(Color.cyan.opacity(0.5))
  82. }
  83. }
  84. }
  85. private extension LiveActivityAttributes {
  86. static var preview: LiveActivityAttributes {
  87. LiveActivityAttributes(startDate: Date())
  88. }
  89. }
  90. private extension LiveActivityAttributes.ContentState {
  91. static var test: LiveActivityAttributes.ContentState {
  92. LiveActivityAttributes.ContentState(bg: "100", trendSystemImage: "arrow.right", change: 2, date: Date())
  93. }
  94. }
  95. #Preview("Notification", as: .content, using: LiveActivityAttributes.preview) {
  96. LiveActivity()
  97. } contentStates: {
  98. LiveActivityAttributes.ContentState.test
  99. }