-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
-
106
-
107
-
108
-
109
-
110
-
111
-
112
-
113
-
114
-
115
-
116
-
117
-
118
-
119
-
120
-
121
-
122
-
123
-
124
-
125
-
126
-
127
-
128
-
129
-
130
-
131
-
132
-
133
-
134
-
135
-
136
-
137
-
138
-
139
-
140
-
141
-
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
-
153
-
154
-
155
-
156
-
157
-
158
-
159
-
160
-
161
-
162
-
163
-
164
-
165
-
166
-
167
-
168
-
169
-
170
-
171
// 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 onPlaybackActionHandler: ((PlaybackAction) async -> Void)?
@State private var isPerformingAction = false
var body: some View {
HStack {
HStack {
Button {
Task {
await performAction(.Prev)
}
} label: {
Label("Previous", systemImage: "backward.end.fill")
}
.font(.title3)
.disabled(isPerformingAction)
.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(.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)
Text(line2)
.font(.subheadline)
.lineLimit(1)
}
.frame(maxWidth: .infinity)
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)
}
private func performAction(_ action: PlaybackAction) async {
guard let handler = onPlaybackActionHandler else {
return
}
isPerformingAction = true
await handler(action)
isPerformingAction = false
}
}
extension PlaybackBar {
func onPlaybackAction(_ handler: @escaping (PlaybackAction) async -> Void)
-> PlaybackBar
{
var new = self
new.onPlaybackActionHandler = handler
return new
}
}
#Preview("PlaybackBar") {
@Previewable @State var zone_id: String? = "foo"
let 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)
),
allowedActions: [.Play, .Pause, .Prev]
),
"bar": MockZone(
id: "bar",
name: "Bar",
playback: PlacKit.PLAC_TRANSPORT_PLAYBACK_STOPPED,
allowedActions: [.Play, .Pause, .Prev, .Next]
),
]
PlaybackBar(zone_id: $zone_id, zones: zones)
.onPlaybackAction { action in
do {
try await Task.sleep(for: .seconds(1))
print(action)
} catch {}
}
}