Widget v2 glassmorphism + lyrics + backup system

- Widget data layer: waveform sampling, CIAreaAverage color extraction,
  secondary color computation, crossfade countdown from DJ profiles
- Widget views full rewrite: glass panel, 40-bar waveform Canvas with
  SeekToIntent tap zones, color-adaptive theming, transport controls
- Small: inset glass. Medium/Large: flush edge-to-edge (no red border)
- Medium: art + info + waveform + controls + Up Next footer
- Large: art + info + waveform + controls + 3-item queue + crossfade footer
- Live lyrics: karaoke word-by-word gradient fill, LRCLIB search/fetch,
  timing editor with tap-to-sync, embed via Companion API
- Backup system: .nvdbackup export/import via ZIPFoundation, UTI for AirDrop
- Smart DJ bulk prefetch: single gzipped request on app launch
- clearAll() now covers all 17 widget keys including seekToTime
This commit is contained in:
Dallas Groot 2026-04-13 23:36:22 -07:00
parent 7413163d57
commit 2a0b3d8c47

View file

@ -109,24 +109,28 @@ struct NowPlayingWidgetView: View {
struct GlassPanel<Content: View>: View {
let colors: WidgetColors
var flush: Bool = false
@ViewBuilder var content: () -> Content
var body: some View {
let radius: CGFloat = flush ? 0 : 16
let outerPad: CGFloat = flush ? 0 : 8
content()
.padding(12)
.padding(flush ? 14 : 12)
.background(
RoundedRectangle(cornerRadius: 16, style: .continuous)
RoundedRectangle(cornerRadius: radius, style: .continuous)
.fill(.ultraThinMaterial)
.overlay(
RoundedRectangle(cornerRadius: 16, style: .continuous)
RoundedRectangle(cornerRadius: radius, style: .continuous)
.fill(colors.accent.opacity(0.06))
)
.overlay(
RoundedRectangle(cornerRadius: 16, style: .continuous)
.stroke(.white.opacity(0.12), lineWidth: 0.5)
RoundedRectangle(cornerRadius: radius, style: .continuous)
.stroke(.white.opacity(0.12), lineWidth: flush ? 0 : 0.5)
)
)
.padding(8)
.padding(outerPad)
}
}
@ -334,7 +338,7 @@ struct MediumContent: View {
let colors: WidgetColors
var body: some View {
GlassPanel(colors: colors) {
GlassPanel(colors: colors, flush: true) {
VStack(spacing: 6) {
// Top row: art + song info
HStack(spacing: 14) {
@ -444,7 +448,7 @@ struct LargeContent: View {
let colors: WidgetColors
var body: some View {
GlassPanel(colors: colors) {
GlassPanel(colors: colors, flush: true) {
VStack(spacing: 8) {
// Header: art + info + waveform
HStack(spacing: 16) {