Changes
2 changed files (+148/-60)
-
-
@@ -0,0 +1,76 @@// 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 Artwork: View { @Environment(\.placConnection) var conn: Connection? let imageKey: String let width: CGFloat let height: CGFloat private var url: URL? { guard let conn = conn else { return nil } let req = PlacKit.plac_image_get_options_make() defer { PlacKit.plac_image_get_options_release(req) } PlacKit.plac_image_get_options_set_content_type( req, PlacKit.PLAC_IMAGE_CONTENT_TYPE_PNG ) PlacKit.plac_image_get_options_set_size( req, PlacKit.PLAC_IMAGE_SCALING_METHOD_FIT, Int(width), Int(height) ) guard let builtUrl = PlacKit.plac_connection_get_image_url( conn.ptr, imageKey, req ) else { return nil } defer { builtUrl.deallocate() } return URL(string: String(cString: builtUrl)) } var body: some View { AsyncImage(url: url) { phase in if let image = phase.image { image.resizable() } else if phase.error != nil { Color.gray } else { ProgressView() .scaleEffect(0.5) } } .id(url) .clipShape(.rect(cornerRadius: 2)) } }
-
-
-
@@ -33,86 +33,97 @@@State private var isPerformingAction = false var body: some View { HStack { HStack { Button { Task { await performAction(.Prev) } } label: { Label("Previous", systemImage: "backward.end.fill") VStack(alignment: .leading, spacing: 14) { HStack(spacing: 8) { if let imageKey = zone?.nowPlaying?.imageKey { Artwork(imageKey: imageKey, width: 96, height: 96) .frame(width: 48, height: 48) } VStack(alignment: .leading, spacing: 2) { let line1 = zone?.nowPlaying?.twoline.0 ?? " " let line2 = zone?.nowPlaying?.twoline.1 ?? " " Text(line1) .font(.headline) .lineLimit(1) Text(line2) .font(.subheadline) .lineLimit(1) } .font(.title3) .disabled(isPerformingAction) .disabled(zone?.isAllowedTo(.Prev) != true) .frame(maxWidth: .infinity, alignment: .leading) } if zone?.playback == PlacKit.PLAC_TRANSPORT_PLAYBACK_PLAYING { HStack { HStack { Button { Task { await performAction(.Pause) await performAction(.Prev) } } label: { Label("Pause", systemImage: "pause.fill") Label("Previous", systemImage: "backward.end.fill") } .font(.title) .font(.title3) .disabled(isPerformingAction) .disabled(zone?.isAllowedTo(.Pause) != true) } else { .disabled(zone?.isAllowedTo(.Prev) != true) if zone?.playback == PlacKit.PLAC_TRANSPORT_PLAYBACK_PLAYING { Button { Task { await performAction(.Pause) } } label: { Label("Pause", systemImage: "pause.fill") } .font(.title) .disabled(isPerformingAction) .disabled(zone?.isAllowedTo(.Pause) != true) } else { Button { Task { await performAction(.Play) } } label: { Label("Play", systemImage: "play.fill") } .font(.title) .disabled(isPerformingAction) .disabled(zone?.isAllowedTo(.Play) != true) } Button { Task { await performAction(.Play) await performAction(.Next) } } label: { Label("Play", systemImage: "play.fill") Label("Next", systemImage: "forward.end.fill") } .font(.title) .font(.title3) .disabled(isPerformingAction) .disabled(zone?.isAllowedTo(.Play) != true) .disabled(zone?.isAllowedTo(.Next) != true) } Button { Task { await performAction(.Next) } } label: { Label("Next", systemImage: "forward.end.fill") } .font(.title3) .disabled(isPerformingAction) .disabled(zone?.isAllowedTo(.Next) != true) } .labelStyle(.iconOnly) .buttonStyle(.borderless) VStack { let line1 = zone?.nowPlaying?.twoline.0 ?? " " let line2 = zone?.nowPlaying?.twoline.1 ?? " " Text(line1) .font(.headline) .lineLimit(1) .labelStyle(.iconOnly) .buttonStyle(.borderless) Text(line2) .font(.subheadline) .lineLimit(1) } .frame(maxWidth: .infinity) 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?) 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() } .scaledToFit() } } .padding([.horizontal], 10) .padding([.vertical], 6) .padding([.horizontal], 12) .padding([.vertical], 10) } private func performAction(_ action: PlaybackAction) async {
-
@@ -149,7 +160,8 @@ playback: PlacKit.PLAC_TRANSPORT_PLAYBACK_PLAYING,nowPlaying: MockNowPlaying( line1: "My great song", line2: "Some untrusted artist", seek: (5, 3 * 60 + 30) seek: (5, 3 * 60 + 30), imageKey: "foo" ), allowedActions: [.Play, .Pause, .Prev] ),
-