feat(ui): add centered talk orb
This commit is contained in:
@@ -290,7 +290,7 @@ final class NodeAppModel {
|
||||
Self.color(fromHex: self.seamColorHex) ?? Self.defaultSeamColor
|
||||
}
|
||||
|
||||
private static let defaultSeamColor = Color(red: 0.62, green: 0.88, blue: 1.0)
|
||||
private static let defaultSeamColor = Color(red: 127 / 255.0, green: 184 / 255.0, blue: 212 / 255.0)
|
||||
|
||||
private static func color(fromHex raw: String?) -> Color? {
|
||||
let trimmed = (raw ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
@@ -166,6 +166,12 @@ private struct CanvasContent: View {
|
||||
.padding(.top, 10)
|
||||
.padding(.trailing, 10)
|
||||
}
|
||||
.overlay(alignment: .center) {
|
||||
if self.appModel.talkMode.isEnabled {
|
||||
TalkOrbOverlay()
|
||||
.transition(.opacity)
|
||||
}
|
||||
}
|
||||
.overlay(alignment: .topLeading) {
|
||||
StatusPill(
|
||||
bridge: self.bridgeStatus,
|
||||
|
||||
74
apps/ios/Sources/Voice/TalkOrbOverlay.swift
Normal file
74
apps/ios/Sources/Voice/TalkOrbOverlay.swift
Normal file
@@ -0,0 +1,74 @@
|
||||
import SwiftUI
|
||||
|
||||
struct TalkOrbOverlay: View {
|
||||
@Environment(NodeAppModel.self) private var appModel
|
||||
@State private var pulse: Bool = false
|
||||
|
||||
var body: some View {
|
||||
let seam = self.appModel.seamColor
|
||||
let status = self.appModel.talkMode.statusText.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
VStack(spacing: 14) {
|
||||
ZStack {
|
||||
Circle()
|
||||
.stroke(seam.opacity(0.26), lineWidth: 2)
|
||||
.frame(width: 220, height: 220)
|
||||
.scaleEffect(self.pulse ? 1.15 : 0.96)
|
||||
.opacity(self.pulse ? 0.0 : 1.0)
|
||||
.animation(.easeOut(duration: 1.3).repeatForever(autoreverses: false), value: self.pulse)
|
||||
|
||||
Circle()
|
||||
.stroke(seam.opacity(0.18), lineWidth: 2)
|
||||
.frame(width: 220, height: 220)
|
||||
.scaleEffect(self.pulse ? 1.45 : 1.02)
|
||||
.opacity(self.pulse ? 0.0 : 0.9)
|
||||
.animation(.easeOut(duration: 1.9).repeatForever(autoreverses: false).delay(0.2), value: self.pulse)
|
||||
|
||||
Circle()
|
||||
.fill(
|
||||
RadialGradient(
|
||||
colors: [
|
||||
seam.opacity(0.95),
|
||||
seam.opacity(0.40),
|
||||
Color.black.opacity(0.55),
|
||||
],
|
||||
center: .center,
|
||||
startRadius: 1,
|
||||
endRadius: 92))
|
||||
.frame(width: 136, height: 136)
|
||||
.overlay(
|
||||
Circle()
|
||||
.stroke(seam.opacity(0.35), lineWidth: 1)
|
||||
)
|
||||
.shadow(color: seam.opacity(0.32), radius: 26, x: 0, y: 0)
|
||||
.shadow(color: Color.black.opacity(0.50), radius: 22, x: 0, y: 10)
|
||||
}
|
||||
.contentShape(Circle())
|
||||
.onTapGesture {
|
||||
self.appModel.talkMode.userTappedOrb()
|
||||
}
|
||||
|
||||
if !status.isEmpty, status != "Off" {
|
||||
Text(status)
|
||||
.font(.system(.footnote, design: .rounded).weight(.semibold))
|
||||
.foregroundStyle(Color.white.opacity(0.92))
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 8)
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(Color.black.opacity(0.40))
|
||||
.overlay(
|
||||
Capsule().stroke(seam.opacity(0.22), lineWidth: 1)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
.padding(28)
|
||||
.onAppear {
|
||||
self.pulse = true
|
||||
}
|
||||
.accessibilityElement(children: .combine)
|
||||
.accessibilityLabel("Talk Mode \(status)")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user