Files
MobileMusicAssistant/MobileMALiveActivity/MobileMALiveActivityLiveActivity.swift
T
2026-04-19 16:57:57 +02:00

174 lines
6.0 KiB
Swift

//
// MobileMALiveActivityLiveActivity.swift
// MobileMALiveActivity
//
import ActivityKit
import MobileMAShared
import SwiftUI
import UIKit
import WidgetKit
private let activityTeal = Color(red: 0.0, green: 0.82, blue: 0.75)
// MARK: - Artwork View
private struct ArtworkView: View {
let artworkData: Data?
let size: CGFloat
let cornerRadius: CGFloat
let isPlaying: Bool
var body: some View {
Group {
if let artworkData, let uiImage = UIImage(data: artworkData) {
Image(uiImage: uiImage)
.resizable()
.aspectRatio(contentMode: .fill)
} else {
fallbackIcon
}
}
.frame(width: size, height: size)
.clipShape(RoundedRectangle(cornerRadius: cornerRadius))
}
private var fallbackIcon: some View {
ZStack {
activityTeal.opacity(0.2)
Image(systemName: "speaker.wave.3.fill")
.symbolEffect(
.variableColor.iterative.dimInactiveLayers.reversing,
isActive: isPlaying
)
.font(.system(size: size * 0.4, weight: .semibold))
.foregroundStyle(activityTeal)
}
}
}
// MARK: - Widget
struct MobileMALiveActivityLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: MusicActivityAttributes.self) { context in
LockScreenView(state: context.state)
.activityBackgroundTint(activityTeal.opacity(0.2))
} dynamicIsland: { context in
DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
ArtworkView(
artworkData: context.state.artworkData,
size: 50,
cornerRadius: 10,
isPlaying: context.state.isPlaying
)
.padding(.leading, 4)
}
DynamicIslandExpandedRegion(.trailing) {
Image(systemName: context.state.isPlaying ? "pause.circle.fill" : "play.circle.fill")
.font(.title)
.foregroundStyle(activityTeal)
.padding(.trailing, 4)
}
DynamicIslandExpandedRegion(.center) {
VStack(alignment: .leading, spacing: 2) {
Text(context.state.trackTitle)
.font(.headline)
.lineLimit(1)
Text(context.state.artistName)
.font(.subheadline)
.lineLimit(1)
.foregroundStyle(.secondary)
}
}
DynamicIslandExpandedRegion(.bottom) {
HStack(spacing: 4) {
Image(systemName: "hifispeaker.fill")
.font(.caption2)
.foregroundStyle(activityTeal)
Text(context.state.playerName)
.font(.caption)
.foregroundStyle(.secondary)
Spacer()
}
.padding(.horizontal, 8)
}
} compactLeading: {
ArtworkView(
artworkData: context.state.artworkData,
size: 28,
cornerRadius: 6,
isPlaying: context.state.isPlaying
)
.padding(.leading, 2)
} compactTrailing: {
Image(systemName: context.state.isPlaying ? "pause.circle.fill" : "play.circle.fill")
.font(.body)
.foregroundStyle(activityTeal)
.padding(.trailing, 2)
} minimal: {
ArtworkView(
artworkData: context.state.artworkData,
size: 24,
cornerRadius: 12,
isPlaying: context.state.isPlaying
)
}
.keylineTint(activityTeal)
}
}
}
// MARK: - Lock Screen View
private struct LockScreenView: View {
let state: MusicActivityAttributes.ContentState
var body: some View {
HStack(spacing: 12) {
ArtworkView(artworkData: state.artworkData, size: 54, cornerRadius: 10, isPlaying: state.isPlaying)
VStack(alignment: .leading, spacing: 2) {
Text(state.trackTitle.isEmpty ? "Now Playing" : state.trackTitle)
.font(.headline)
.lineLimit(1)
if !state.artistName.isEmpty {
Text(state.artistName)
.font(.subheadline)
.lineLimit(1)
.foregroundStyle(.secondary)
}
HStack(spacing: 4) {
Image(systemName: "hifispeaker.fill")
.font(.caption2)
.foregroundStyle(activityTeal)
Text(state.playerName)
.font(.caption)
.foregroundStyle(.secondary)
}
}
Spacer()
Image(systemName: state.isPlaying ? "pause.circle.fill" : "play.circle.fill")
.font(.system(size: 32))
.foregroundStyle(activityTeal)
}
.padding(16)
}
}
// MARK: - Preview
#Preview("Notification", as: .content, using: MusicActivityAttributes()) {
MobileMALiveActivityLiveActivity()
} contentStates: {
MusicActivityAttributes.ContentState(
trackTitle: "Bohemian Rhapsody", artistName: "Queen",
artworkData: nil, isPlaying: true, playerName: "Living Room")
MusicActivityAttributes.ContentState(
trackTitle: "Bohemian Rhapsody", artistName: "Queen",
artworkData: nil, isPlaying: false, playerName: "Living Room")
}