Stemify iOS 1.1 and WebUI updates, repo restructure into desktop/ios/server/web
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
894dd5a695
commit
df5d847169
157 files changed
+3935
-742
No files matched your search
@@ -0,0 +1,100 @@
|
||||
import SwiftUI
|
||||
import WidgetKit
|
||||
|
||||
@main
|
||||
struct StemifyWidgets: WidgetBundle {
|
||||
var body: some Widget {
|
||||
MixerWidget()
|
||||
MixWidget()
|
||||
if #available(iOS 18.0, *) { MixControl() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Audio intents run in the app's process, so the extension's copy never does anything.
|
||||
@MainActor
|
||||
enum MixRemote {
|
||||
static func run(_ a: MixAction) {}
|
||||
}
|
||||
|
||||
// ---- timeline -----------------------------------------------------------------------------
|
||||
|
||||
struct MixerEntry: TimelineEntry {
|
||||
var date = Date()
|
||||
var snap: WidgetSnapshot
|
||||
var art: UIImage?
|
||||
|
||||
/// The app reloads the widgets whenever the song or mix changes, so one entry is enough.
|
||||
static func now() -> MixerEntry {
|
||||
let s = WidgetStore.load()
|
||||
let art = s.art == nil ? nil : WidgetStore.artURL.flatMap { UIImage(contentsOfFile: $0.path) }
|
||||
return MixerEntry(snap: s, art: art)
|
||||
}
|
||||
|
||||
static let sample = MixerEntry(snap: WidgetSnapshot(
|
||||
title: "Midnight City", artist: "M83", mix: "Karaoke", next: "A Cappella", playing: true,
|
||||
stems: [
|
||||
WidgetStem(name: "vocals", label: "Vocals", color: 0xFF7AB6, level: 0.8, percent: 100, muted: true),
|
||||
WidgetStem(name: "drums", label: "Drums", color: 0xFFB547, level: 0.8, percent: 100),
|
||||
WidgetStem(name: "bass", label: "Bass", color: 0x7C8CFF, level: 0.9, percent: 127),
|
||||
WidgetStem(name: "other", label: "Other", color: 0x4CD07D, level: 0.7, percent: 77),
|
||||
]))
|
||||
}
|
||||
|
||||
struct MixerProvider: TimelineProvider {
|
||||
func placeholder(in context: Context) -> MixerEntry { .sample }
|
||||
|
||||
func getSnapshot(in context: Context, completion: @escaping (MixerEntry) -> Void) {
|
||||
let e = MixerEntry.now()
|
||||
completion(context.isPreview && !e.snap.hasStems ? .sample : e)
|
||||
}
|
||||
|
||||
func getTimeline(in context: Context, completion: @escaping (Timeline<MixerEntry>) -> Void) {
|
||||
completion(Timeline(entries: [.now()], policy: .never))
|
||||
}
|
||||
}
|
||||
|
||||
// ---- widgets ------------------------------------------------------------------------------
|
||||
|
||||
struct MixerWidget: Widget {
|
||||
var body: some WidgetConfiguration {
|
||||
StaticConfiguration(kind: WidgetStore.mixerKind, provider: MixerProvider()) { e in
|
||||
MixerView(entry: e).containerBackground(for: .widget) { Look.bg }
|
||||
}
|
||||
.configurationDisplayName("Stem Mixer")
|
||||
.description("A knob for each stem. Tap the arrows to turn it, tap the knob to mute it.")
|
||||
.supportedFamilies([.systemMedium, .systemLarge])
|
||||
}
|
||||
}
|
||||
|
||||
struct MixWidget: Widget {
|
||||
var body: some WidgetConfiguration {
|
||||
StaticConfiguration(kind: WidgetStore.mixKind, provider: MixerProvider()) { e in
|
||||
MixView(entry: e)
|
||||
}
|
||||
.configurationDisplayName("Mix")
|
||||
.description("The mix that's playing, and a button for the next one.")
|
||||
.supportedFamilies([.systemSmall, .accessoryRectangular, .accessoryCircular])
|
||||
}
|
||||
}
|
||||
|
||||
/// Swaps the Lock Screen's flashlight or camera for a Mix button. Also works in Control Center and on the Action button.
|
||||
@available(iOS 18.0, *)
|
||||
struct MixControl: ControlWidget {
|
||||
var body: some ControlWidgetConfiguration {
|
||||
StaticControlConfiguration(kind: WidgetStore.controlKind, provider: Provider()) { mix in
|
||||
ControlWidgetButton(action: CycleMixIntent()) {
|
||||
Label(mix, systemImage: "slider.vertical.3")
|
||||
}
|
||||
}
|
||||
.displayName("Change Mix")
|
||||
.description("Moves to the next stem mix while stems play.")
|
||||
}
|
||||
|
||||
struct Provider: ControlValueProvider {
|
||||
var previewValue: String { "Karaoke" }
|
||||
func currentValue() async throws -> String {
|
||||
let s = WidgetStore.load()
|
||||
return s.hasStems ? s.mix : "Mix"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user