feat(apple/widgets): expanded island — large full-width End Session button
The expanded island's height was underused. Bottom region is now the platform-conventional shape: one info row (status leading, live latency/bitrate trailing — the mode string stays on the Lock Screen where there's room), then a LARGE full-width End Session action. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -56,16 +56,19 @@ struct PunktfunkSessionLiveActivity: Widget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DynamicIslandExpandedRegion(.bottom) {
|
DynamicIslandExpandedRegion(.bottom) {
|
||||||
HStack(alignment: .center, spacing: 12) {
|
// The expanded island's height is there to be used: one info row (status
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
// leading, live numbers trailing — the mode string stays on the Lock
|
||||||
|
// Screen), then the platform-conventional LARGE full-width action button.
|
||||||
|
VStack(spacing: 10) {
|
||||||
|
HStack {
|
||||||
StatusLine(state: context.state)
|
StatusLine(state: context.state)
|
||||||
StatsLine(state: context.state)
|
|
||||||
}
|
|
||||||
Spacer(minLength: 8)
|
Spacer(minLength: 8)
|
||||||
EndButton()
|
StatsLine(state: context.state, showMode: false)
|
||||||
|
}
|
||||||
|
EndButton(fullWidth: true)
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 4)
|
.padding(.horizontal, 4)
|
||||||
.padding(.top, 8)
|
.padding(.top, 6)
|
||||||
}
|
}
|
||||||
} compactLeading: {
|
} compactLeading: {
|
||||||
Image(systemName: "play.tv.fill")
|
Image(systemName: "play.tv.fill")
|
||||||
@@ -174,9 +177,11 @@ private struct StatusLine: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The live numbers, one quiet line: latency and bitrate (the sparse ~30 s pushes) ahead of the
|
/// The live numbers, one quiet line: latency and bitrate (the sparse ~30 s pushes) ahead of the
|
||||||
/// mode. Anything unreported simply doesn't appear.
|
/// mode (`showMode` false on the island, where the row shares space with the status line).
|
||||||
|
/// Anything unreported simply doesn't appear.
|
||||||
private struct StatsLine: View {
|
private struct StatsLine: View {
|
||||||
let state: PunktfunkSessionAttributes.ContentState
|
let state: PunktfunkSessionAttributes.ContentState
|
||||||
|
var showMode = true
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
@@ -186,12 +191,14 @@ private struct StatsLine: View {
|
|||||||
if let mbps = state.mbps {
|
if let mbps = state.mbps {
|
||||||
stat("arrow.down", String(format: "%.0f Mb/s", mbps))
|
stat("arrow.down", String(format: "%.0f Mb/s", mbps))
|
||||||
}
|
}
|
||||||
|
if showMode {
|
||||||
Text(state.modeLine)
|
Text(state.modeLine)
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.foregroundStyle(.tertiary)
|
.foregroundStyle(.tertiary)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func stat(_ icon: String, _ text: String) -> some View {
|
private func stat(_ icon: String, _ text: String) -> some View {
|
||||||
HStack(spacing: 3) {
|
HStack(spacing: 3) {
|
||||||
@@ -244,8 +251,22 @@ private struct CompactReadout: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// End-stream button — runs EndStreamIntent in the app process (LiveActivityIntent).
|
/// End-stream button — runs EndStreamIntent in the app process (LiveActivityIntent).
|
||||||
|
/// `fullWidth` is the expanded island's large bottom action (the platform convention there);
|
||||||
|
/// the compact form remains the Lock Screen banner's trailing button while backgrounded.
|
||||||
private struct EndButton: View {
|
private struct EndButton: View {
|
||||||
|
var fullWidth = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
if fullWidth {
|
||||||
|
Button(intent: EndStreamIntent()) {
|
||||||
|
Label("End Session", systemImage: "stop.fill")
|
||||||
|
.font(.subheadline.weight(.semibold))
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding(.vertical, 2)
|
||||||
|
}
|
||||||
|
.tint(.red)
|
||||||
|
.buttonStyle(.bordered)
|
||||||
|
} else {
|
||||||
Button(intent: EndStreamIntent()) {
|
Button(intent: EndStreamIntent()) {
|
||||||
Label("End", systemImage: "stop.fill")
|
Label("End", systemImage: "stop.fill")
|
||||||
.font(.caption).bold()
|
.font(.caption).bold()
|
||||||
@@ -253,6 +274,7 @@ private struct EndButton: View {
|
|||||||
.tint(.red)
|
.tint(.red)
|
||||||
.buttonStyle(.bordered)
|
.buttonStyle(.bordered)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Previews (Xcode canvas)
|
// MARK: - Previews (Xcode canvas)
|
||||||
|
|||||||
Reference in New Issue
Block a user