// // 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") }