feat: admit numbered iPhone companion
This commit is contained in:
parent
fb8607f248
commit
fe1aaade0e
20 changed files with 1563 additions and 7 deletions
|
|
@ -0,0 +1,203 @@
|
|||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
struct ContentView: View {
|
||||
@EnvironmentObject private var client: HoloLakeSyncClient
|
||||
@State private var pairingText = ""
|
||||
@State private var captureTitle = ""
|
||||
@State private var captureBody = ""
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
ZStack {
|
||||
LinearGradient(
|
||||
colors: [HoloLakeDesign.deep, HoloLakeDesign.lake, HoloLakeDesign.violet.opacity(0.78)],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
)
|
||||
.ignoresSafeArea()
|
||||
|
||||
ScrollView {
|
||||
VStack(spacing: 16) {
|
||||
header
|
||||
if client.session == nil { pairingCard } else { dashboard }
|
||||
boundaryCard
|
||||
}
|
||||
.padding(.horizontal, 18)
|
||||
.padding(.vertical, 16)
|
||||
}
|
||||
}
|
||||
.toolbar(.hidden, for: .navigationBar)
|
||||
.alert("HoloLake", isPresented: Binding(
|
||||
get: { client.errorText != nil },
|
||||
set: { if !$0 { client.errorText = nil } }
|
||||
)) {
|
||||
Button("知道了", role: .cancel) { client.errorText = nil }
|
||||
} message: {
|
||||
Text(client.errorText ?? "")
|
||||
}
|
||||
}
|
||||
.preferredColorScheme(.dark)
|
||||
}
|
||||
|
||||
private var header: some View {
|
||||
HStack(alignment: .center) {
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
Text("HoloLake").font(.title.weight(.semibold)).tracking(2)
|
||||
Text("同一频道 · iPhone 轻入口")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(HoloLakeDesign.muted)
|
||||
}
|
||||
Spacer()
|
||||
Circle()
|
||||
.fill(client.session == nil ? HoloLakeDesign.gold : HoloLakeDesign.mint)
|
||||
.frame(width: 11, height: 11)
|
||||
.shadow(color: client.session == nil ? HoloLakeDesign.gold : HoloLakeDesign.mint, radius: 8)
|
||||
}
|
||||
.foregroundStyle(HoloLakeDesign.pearl)
|
||||
.padding(.top, 4)
|
||||
}
|
||||
|
||||
private var pairingCard: some View {
|
||||
LakeCard {
|
||||
VStack(alignment: .leading, spacing: 14) {
|
||||
Text("连接电脑端").font(.title3.weight(.semibold))
|
||||
Text("先在电脑端打开“多端同步”,再扫描配对码或粘贴配对链接。配对密钥只保存在这台 iPhone 的钥匙串。")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(HoloLakeDesign.muted)
|
||||
|
||||
TextField("hololake://pair?…", text: $pairingText, axis: .vertical)
|
||||
.textInputAutocapitalization(.never)
|
||||
.autocorrectionDisabled()
|
||||
.padding(14)
|
||||
.background(.black.opacity(0.22), in: RoundedRectangle(cornerRadius: 16))
|
||||
|
||||
HStack {
|
||||
Button("从剪贴板读取") {
|
||||
pairingText = UIPasteboard.general.string ?? ""
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
|
||||
Button("校验并配对") {
|
||||
Task { await client.pair(from: pairingText) }
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.tint(HoloLakeDesign.gold)
|
||||
.foregroundStyle(HoloLakeDesign.deep)
|
||||
.disabled(pairingText.isEmpty || client.isWorking)
|
||||
}
|
||||
}
|
||||
}
|
||||
.foregroundStyle(HoloLakeDesign.pearl)
|
||||
}
|
||||
|
||||
private var dashboard: some View {
|
||||
VStack(spacing: 16) {
|
||||
LakeCard {
|
||||
VStack(alignment: .leading, spacing: 14) {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(client.session?.desktopName ?? "电脑端")
|
||||
.font(.title3.weight(.semibold))
|
||||
Text(client.stateText)
|
||||
.font(.caption)
|
||||
.foregroundStyle(HoloLakeDesign.muted)
|
||||
}
|
||||
Spacer()
|
||||
Button {
|
||||
Task { await client.sync() }
|
||||
} label: {
|
||||
if client.isWorking {
|
||||
ProgressView()
|
||||
} else {
|
||||
Label("同步", systemImage: "arrow.triangle.2.circlepath")
|
||||
}
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.tint(HoloLakeDesign.mint)
|
||||
.foregroundStyle(HoloLakeDesign.deep)
|
||||
.disabled(client.isWorking)
|
||||
}
|
||||
|
||||
if let snapshot = client.snapshot {
|
||||
HStack(spacing: 10) {
|
||||
MetricTile(label: "作品", value: "\(snapshot.webNovel.workCount)")
|
||||
MetricTile(label: "章节", value: "\(snapshot.webNovel.chapterCount)")
|
||||
MetricTile(label: "教育表", value: "\(snapshot.education.activeTableCount)")
|
||||
}
|
||||
} else {
|
||||
Text("保持静止;只有你点击同步时才会连接电脑。")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(HoloLakeDesign.muted)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LakeCard {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("随手记回频道").font(.headline)
|
||||
TextField("标题(可选)", text: $captureTitle)
|
||||
.padding(12)
|
||||
.background(.black.opacity(0.2), in: RoundedRectangle(cornerRadius: 14))
|
||||
TextField("正文", text: $captureBody, axis: .vertical)
|
||||
.lineLimit(4...10)
|
||||
.padding(12)
|
||||
.background(.black.opacity(0.2), in: RoundedRectangle(cornerRadius: 14))
|
||||
Button("写入电脑端收件箱") {
|
||||
let title = captureTitle
|
||||
let body = captureBody
|
||||
Task {
|
||||
await client.sync(captureTitle: title, captureBody: body)
|
||||
if client.errorText == nil {
|
||||
captureTitle = ""
|
||||
captureBody = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
.buttonStyle(.bordered)
|
||||
.disabled(captureBody.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || client.isWorking)
|
||||
}
|
||||
}
|
||||
|
||||
if let works = client.snapshot?.webNovel.works, !works.isEmpty {
|
||||
LakeCard {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("最近作品").font(.headline)
|
||||
ForEach(works.prefix(5)) { work in
|
||||
HStack {
|
||||
Text(work.title).lineLimit(1)
|
||||
Spacer()
|
||||
Text(work.status)
|
||||
.font(.caption)
|
||||
.foregroundStyle(HoloLakeDesign.muted)
|
||||
}
|
||||
if work.id != works.prefix(5).last?.id {
|
||||
Divider().overlay(.white.opacity(0.12))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button("解除本机配对", role: .destructive) {
|
||||
client.disconnect()
|
||||
}
|
||||
.font(.footnote)
|
||||
}
|
||||
.foregroundStyle(HoloLakeDesign.pearl)
|
||||
}
|
||||
|
||||
private var boundaryCard: some View {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text("HLP-IOS-CLIENT-001")
|
||||
.font(.caption2.monospaced())
|
||||
.foregroundStyle(HoloLakeDesign.gold)
|
||||
Text("手机是同一频道的远程身体入口,不复制电脑桌面,不在电脑离线时执行人格或行业任务。")
|
||||
.font(.caption)
|
||||
.foregroundStyle(HoloLakeDesign.muted)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.horizontal, 4)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in a new issue