Changes
3 changed files (+205/-71)
-
-
@@ -17,14 +17,95 @@import PlacKit import os protocol NowPlaying { var oneline: String { get } var twoline: (String, String?) { get } var threeline: (String, String?, String?) { get } /// Seek information. The first element represents current seek position and the second represents current song's length. /// Both numbers are in seconds, starts from 0. var seek: (UInt64, UInt64)? { get } var imageKey: String? { get } } class CoreNowPlaying: NowPlaying { var ptr: UnsafeMutablePointer<PlacKit.plac_transport_now_playing> var oneline: String { return String(cString: ptr.pointee.one_line_line1) } var twoline: (String, String?) { return ( String(cString: ptr.pointee.two_line_line1), ptr.pointee.two_line_line2.map { String(cString: $0) } ) } var threeline: (String, String?, String?) { return ( String(cString: ptr.pointee.three_line_line1), ptr.pointee.three_line_line2.map { String(cString: $0) }, ptr.pointee.three_line_line3.map { String(cString: $0) }, ) } var seek: (UInt64, UInt64)? { if !ptr.pointee.has_length || !ptr.pointee.has_seek_position { return nil } return (ptr.pointee.seek_position, ptr.pointee.length) } var imageKey: String? { return ptr.pointee.image_key.map { String(cString: $0) } } init(ptr: UnsafeMutablePointer<PlacKit.plac_transport_now_playing>) { self.ptr = ptr } deinit { PlacKit.plac_transport_now_playing_release(ptr) } } class MockNowPlaying: NowPlaying { var oneline: String var twoline: (String, String?) var threeline: (String, String?, String?) var seek: (UInt64, UInt64)? var imageKey: String? init( line1: String, line2: String? = nil, line3: String? = nil, seek: (UInt64, UInt64)? = nil, imageKey: String? = nil ) { self.oneline = line1 self.twoline = (line1, line2) self.threeline = (line1, line2, line3) self.seek = seek self.imageKey = imageKey } } protocol Zone { var id: String { get } var name: String { get } var playback: PlacKit.plac_transport_playback_state { get } var nowPlaying: NowPlaying? { get } } class CoreZone: Identifiable, Zone { var ptr: UnsafeMutablePointer<PlacKit.plac_transport_zone> var nowPlaying: (any NowPlaying)? var id: String { return String(cString: ptr.pointee.id)
-
@@ -40,6 +121,7 @@ }init(ptr: UnsafeMutablePointer<PlacKit.plac_transport_zone>) { self.ptr = ptr self.nowPlaying = ptr.pointee.now_playing.map { CoreNowPlaying(ptr: $0) } } deinit {
-
@@ -51,11 +133,18 @@ class MockZone: Identifiable, Zone {var id: String var name: String var playback: plac_transport_playback_state var nowPlaying: (any NowPlaying)? init(id: String, name: String, playback: plac_transport_playback_state) { init( id: String, name: String, playback: plac_transport_playback_state, nowPlaying: (any NowPlaying)? = nil ) { self.id = id self.name = name self.playback = playback self.nowPlaying = nowPlaying } }
-
-
-
@@ -123,7 +123,7 @@ PureConnectedView(zone_id: $zone_id, zones: zones).onAppear { queue.async { let logger = Logger() logger.debug("Starting getEvent loop...") while true {
-
@@ -224,72 +224,3 @@ ]PureConnectedView<MockZone>(zone_id: $zone_id, zones: zones) } struct PlaybackBar<TZone: Zone & Identifiable>: View { @Binding var zone_id: String? var zones: [String: TZone] = [:] private var zone: TZone? { zones.first(where: { _, zone in zone.id == zone_id })?.value } var body: some View { HStack { HStack { if zone?.playback == PlacKit.PLAC_TRANSPORT_PLAYBACK_PLAYING { Button { } label: { Label("Pause", systemImage: "pause.fill") } } else { Button { } label: { Label("Play", systemImage: "play.fill") } } } .labelStyle(.iconOnly) .buttonStyle(.borderless) .font(.system(size: 20)) Spacer() if zones.count > 0 && zone_id != nil { Picker("Zone", selection: $zone_id) { ForEach( zones.values.sorted(by: { $0.id < $1.id }) ) { zone in Text(zone.name).tag(zone.id as String?) } } .scaledToFit() } } .padding([.horizontal], 10) .padding([.vertical], 8) } } #Preview("PlaybackBar") { @Previewable @State var zone_id: String? = "foo" var zones: [String: MockZone] = [ "foo": MockZone( id: "foo", name: "Foo", playback: PlacKit.PLAC_TRANSPORT_PLAYBACK_PLAYING ), "bar": MockZone( id: "bar", name: "Bar", playback: PlacKit.PLAC_TRANSPORT_PLAYBACK_STOPPED ), ] PlaybackBar(zone_id: $zone_id, zones: zones) }
-
-
-
@@ -0,0 +1,114 @@// Copyright 2025 Shota FUJI // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 import PlacKit import SwiftUI struct PlaybackBar<TZone: Zone & Identifiable>: View { @Binding var zone_id: String? var zones: [String: TZone] = [:] private var zone: TZone? { zones.first(where: { _, zone in zone.id == zone_id })?.value } var body: some View { VStack(spacing: 12) { VStack { if let line1 = zone?.nowPlaying?.twoline.0 { Text(line1) .font(.headline) .lineLimit(1) .frame(maxWidth: .infinity, alignment: .leading) } else { Text("Not Playing") .font(.subheadline) .foregroundStyle(.secondary) .lineLimit(1) .frame(maxWidth: .infinity, alignment: .leading) } Text(zone?.nowPlaying?.twoline.1 ?? " ") .font(.subheadline) .lineLimit(1) .frame(maxWidth: .infinity, alignment: .leading) } HStack { HStack { if zone?.playback == PlacKit.PLAC_TRANSPORT_PLAYBACK_PLAYING { Button { } label: { Label("Pause", systemImage: "pause.fill") } } else { Button { } label: { Label("Play", systemImage: "play.fill") } } } .labelStyle(.iconOnly) .buttonStyle(.borderless) .font(.system(size: 20)) Spacer() if zones.count > 0 && zone_id != nil { Picker("Zone", selection: $zone_id) { ForEach( zones.values.sorted(by: { $0.id < $1.id }) ) { zone in Text(zone.name).tag(zone.id as String?) } } .scaledToFit() } } } .padding([.horizontal], 10) .padding([.vertical], 6) } } #Preview("PlaybackBar") { @Previewable @State var zone_id: String? = "foo" var zones: [String: MockZone] = [ "foo": MockZone( id: "foo", name: "Foo", playback: PlacKit.PLAC_TRANSPORT_PLAYBACK_PLAYING, nowPlaying: MockNowPlaying( line1: "My great song", line2: "Some untrusted artist", seek: (5, 3 * 60 + 30) ) ), "bar": MockZone( id: "bar", name: "Bar", playback: PlacKit.PLAC_TRANSPORT_PLAYBACK_STOPPED ), ] PlaybackBar(zone_id: $zone_id, zones: zones) }
-