29e5c0a739
- 命名去缩写:xcodegen 项目 + 四 target CDrop*→CommilitiaDrop*(CommilitiaDrop / …Share / …Widgets / …Tests);3 个 widget/control kind net.commilitia.cdrop.*→net.commilitia.Commilitia-Drop.*(旧装控件将成孤儿,需在设备重加);Tests @testable import CommilitiaDrop。刻意不动线协议 / 身份标识——bundle ID 家族、URL scheme cdrop://、Bonjour _cdrop._tcp、keychain service、DataChannel cdrop-file、entitlements 文件名——以免破跨端互通或丢会话 - 新增 macOS app target CommilitiaDropMac(platform macOS / deploymentTarget 26.0 / ARCHS arm64 / 与 iOS 同 bundle net.commilitia.Commilitia-Drop,同 App Store 记录 + 同 App Group):sources 仅 MacApp/,Info.plist 经 xcodegen 生成,本地 ad-hoc 免账号编译 - Mac 壳 MacApp/(Allman,#if os(macOS) 守卫,独立 @main 不与 iOS CDropApp 冲突):CommilitiaDropMacApp(@main + 单窗口 Window + windowResizability contentMinSize + 去 ⌘N + MenuBarExtra window 风格)、MacRootView(NavigationSplitView 侧栏五区骨架 传输/文件/消息/设备/设置 + 占位详情 MacPlaceholder + 品牌头)、MacMenuContent(菜单栏占位) - Support/macOS/AppMac.entitlements:App Group group.net.commilitia.Commilitia-Drop 预置待用;macOS 下 App Group 属受限能力须 provisioning profile,故脚手架阶段 project.yml 暂不引用,待 Phase 6 Developer ID 签名再挂 - 根 Justfile:ios-sim-build / ios-device 跟随重命名(项目 / scheme / .app 路径);新增 mac-build(xcodegen + xcodebuild -sdk macosx,本地 ad-hoc) - 验证:CommilitiaDropMac.app 与 CommilitiaDrop.app(iOS 模拟器)均 BUILD SUCCEEDED;WebRTC 走 -clonedSourcePackagesDirPath 复用已缓存 xcframework
119 lines
4.2 KiB
Swift
119 lines
4.2 KiB
Swift
import AppIntents
|
||
import SwiftUI
|
||
import WidgetKit
|
||
|
||
// 主屏 / 锁屏小组件「云剪贴板」(#6):在控制中心两控件之外,提供一个可放主屏的交互式小组件,含上传 /
|
||
// 拉取两个按钮(iOS 17+ 交互式小组件,按钮直接跑 AppIntent,无需打开 app)。触发结果经 WidgetFeedback
|
||
// 弹本地通知反馈。按钮复用与控制中心相同的 Upload/PullClipboardIntent。
|
||
struct ClipboardWidgetEntry: TimelineEntry
|
||
{
|
||
let date: Date
|
||
}
|
||
|
||
// 纯按钮小组件,无动态内容:单条永不刷新的时间线即可(交互由按钮的 AppIntent 驱动)。
|
||
struct ClipboardWidgetProvider: TimelineProvider
|
||
{
|
||
func placeholder(in context: Context) -> ClipboardWidgetEntry { ClipboardWidgetEntry(date: Date()) }
|
||
|
||
func getSnapshot(in context: Context, completion: @escaping (ClipboardWidgetEntry) -> Void)
|
||
{
|
||
completion(ClipboardWidgetEntry(date: Date()))
|
||
}
|
||
|
||
func getTimeline(in context: Context, completion: @escaping (Timeline<ClipboardWidgetEntry>) -> Void)
|
||
{
|
||
completion(Timeline(entries: [ ClipboardWidgetEntry(date: Date()) ], policy: .never))
|
||
}
|
||
}
|
||
|
||
struct ClipboardWidgetView: View
|
||
{
|
||
@Environment(\.widgetFamily) private var family
|
||
var entry: ClipboardWidgetEntry
|
||
|
||
var body: some View
|
||
{
|
||
Group
|
||
{
|
||
if family == .systemMedium { mediumLayout } else { smallLayout }
|
||
}
|
||
.containerBackground(for: .widget) { brandBackground }
|
||
}
|
||
|
||
// 小尺寸:slim 品牌 header(吉祥物 + 名)+ 两个全宽按钮填满余下高度(最大化利用空间、大点按区)。
|
||
private var smallLayout: some View
|
||
{
|
||
VStack(spacing: 10)
|
||
{
|
||
HStack(spacing: 8)
|
||
{
|
||
BrandMark(size: 30)
|
||
Text(t("ios.widget.clipboardName"))
|
||
.font(.subheadline).bold()
|
||
.lineLimit(1)
|
||
Spacer(minLength: 0)
|
||
}
|
||
actionButton(t("ios.control.upload"), "square.and.arrow.up", UploadClipboardIntent())
|
||
actionButton(t("ios.control.pull"), "square.and.arrow.down", PullClipboardIntent())
|
||
}
|
||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||
.padding(14)
|
||
}
|
||
|
||
// 中尺寸:左侧大吉祥物作视觉锚,右侧名 + 两按钮竖排,吃满横向空间。
|
||
private var mediumLayout: some View
|
||
{
|
||
HStack(spacing: 18)
|
||
{
|
||
BrandMark(size: 72)
|
||
VStack(alignment: .leading, spacing: 9)
|
||
{
|
||
Text(t("ios.widget.clipboardName"))
|
||
.font(.headline)
|
||
.lineLimit(1)
|
||
actionButton(t("ios.control.upload"), "square.and.arrow.up", UploadClipboardIntent())
|
||
actionButton(t("ios.control.pull"), "square.and.arrow.down", PullClipboardIntent())
|
||
}
|
||
.frame(maxWidth: .infinity, alignment: .leading)
|
||
}
|
||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||
.padding(16)
|
||
}
|
||
|
||
// 品牌底:淡品牌色斜向渐变(取代原 .fill.tertiary 灰底),与吉祥物 / 按钮同色系。
|
||
private var brandBackground: some View
|
||
{
|
||
LinearGradient(
|
||
colors: [ Color.cdropAccent.opacity(0.18), Color.cdropAccent.opacity(0.05) ],
|
||
startPoint: .topLeading, endPoint: .bottomTrailing)
|
||
}
|
||
|
||
private func actionButton(_ title: String, _ symbol: String, _ intent: some AppIntent) -> some View
|
||
{
|
||
Button(intent: intent)
|
||
{
|
||
Label(title, systemImage: symbol)
|
||
.font(.caption).fontWeight(.semibold)
|
||
.lineLimit(1)
|
||
.frame(maxWidth: .infinity)
|
||
.padding(.vertical, 3)
|
||
}
|
||
.buttonStyle(.borderedProminent)
|
||
.tint(.cdropAccent)
|
||
}
|
||
}
|
||
|
||
struct CloudClipboardWidget: Widget
|
||
{
|
||
var body: some WidgetConfiguration
|
||
{
|
||
StaticConfiguration(kind: "net.commilitia.Commilitia-Drop.widget.clipboard", provider: ClipboardWidgetProvider())
|
||
{ entry in
|
||
ClipboardWidgetView(entry: entry)
|
||
}
|
||
.configurationDisplayName(t("ios.widget.clipboardName"))
|
||
.description(t("ios.widget.clipboardDesc"))
|
||
.supportedFamilies([ .systemSmall, .systemMedium ])
|
||
}
|
||
}
|