Add TravelAlbum, ProfileSpace, and wired camera transfer modules.
Introduce travel album entry with Sony PTP tethering pipeline, profile space settings page, home routing updates, Launch Screen storyboard, and related tests/docs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -0,0 +1,158 @@
|
||||
//
|
||||
// CreateTravelAlbumSheet.swift
|
||||
// suixinkan
|
||||
//
|
||||
// Created by Codex on 2026/6/29.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
/// 新建相册任务弹窗,支持先拍再买与买了再拍两种模式。
|
||||
struct CreateTravelAlbumSheet: View {
|
||||
let orders: [TravelAlbumAvailableOrder]
|
||||
let isCreating: Bool
|
||||
let onConfirm: (Int, String, String, String, TravelAlbumAvailableOrder?) -> Void
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@State private var mode = TravelAlbumEntryViewModel.modePreShoot
|
||||
@State private var freeCount = ""
|
||||
@State private var singlePrice = ""
|
||||
@State private var packagePrice = ""
|
||||
@State private var selectedOrder: TravelAlbumAvailableOrder?
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
ScrollView {
|
||||
VStack(spacing: AppMetrics.Spacing.medium) {
|
||||
modeCard(
|
||||
title: "先拍再买",
|
||||
description: "拍完后分享给用户,用户在小程序上选择性购买",
|
||||
selected: mode == TravelAlbumEntryViewModel.modePreShoot
|
||||
) {
|
||||
mode = TravelAlbumEntryViewModel.modePreShoot
|
||||
}
|
||||
|
||||
modeCard(
|
||||
title: "买了再拍",
|
||||
description: "用户已在小程序下单,绑定订单后用户可直接选片",
|
||||
selected: mode == TravelAlbumEntryViewModel.modePreOrder
|
||||
) {
|
||||
mode = TravelAlbumEntryViewModel.modePreOrder
|
||||
}
|
||||
|
||||
if mode == TravelAlbumEntryViewModel.modePreShoot {
|
||||
preShootFields
|
||||
} else {
|
||||
preOrderFields
|
||||
}
|
||||
}
|
||||
.padding(AppMetrics.Spacing.pageHorizontal)
|
||||
.padding(.vertical, AppMetrics.Spacing.medium)
|
||||
}
|
||||
.background(Color(hex: 0xF5F7FA))
|
||||
.navigationTitle("新建相册任务")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button("取消") { dismiss() }
|
||||
}
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button(isCreating ? "创建中" : "确认创建") {
|
||||
onConfirm(mode, freeCount, singlePrice, packagePrice, selectedOrder)
|
||||
}
|
||||
.disabled(isCreating)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var preShootFields: some View {
|
||||
VStack(spacing: AppMetrics.Spacing.small) {
|
||||
fieldRow(title: "免费张数", placeholder: "请输入免费张数", text: $freeCount, keyboard: .numberPad)
|
||||
fieldRow(title: "单张价格(元)", placeholder: "请输入单张照片价格", text: $singlePrice, keyboard: .decimalPad)
|
||||
fieldRow(title: "打包价(元)", placeholder: "可选", text: $packagePrice, keyboard: .decimalPad)
|
||||
}
|
||||
}
|
||||
|
||||
private var preOrderFields: some View {
|
||||
VStack(alignment: .leading, spacing: AppMetrics.Spacing.small) {
|
||||
Text("选择绑定订单")
|
||||
.font(.system(size: AppMetrics.FontSize.subheadline, weight: .medium))
|
||||
.foregroundStyle(AppDesign.textSecondary)
|
||||
|
||||
if orders.isEmpty {
|
||||
Text("暂无可绑定订单")
|
||||
.foregroundStyle(AppDesign.placeholder)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding()
|
||||
.background(Color.white)
|
||||
.clipShape(RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.card))
|
||||
} else {
|
||||
ForEach(orders) { order in
|
||||
Button {
|
||||
selectedOrder = order
|
||||
} label: {
|
||||
HStack {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(order.projectName)
|
||||
.font(.system(size: AppMetrics.FontSize.body, weight: .medium))
|
||||
.foregroundStyle(AppDesign.textPrimary)
|
||||
Text("\(order.orderNumber) · \(order.userPhone)")
|
||||
.font(.system(size: AppMetrics.FontSize.caption))
|
||||
.foregroundStyle(AppDesign.textSecondary)
|
||||
}
|
||||
Spacer()
|
||||
Image(systemName: selectedOrder?.orderNumber == order.orderNumber ? "checkmark.circle.fill" : "circle")
|
||||
.foregroundStyle(selectedOrder?.orderNumber == order.orderNumber ? AppDesign.primary : AppDesign.placeholder)
|
||||
}
|
||||
.padding(AppMetrics.Spacing.medium)
|
||||
.background(Color.white)
|
||||
.clipShape(RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.card))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func modeCard(title: String, description: String, selected: Bool, action: @escaping () -> Void) -> some View {
|
||||
Button(action: action) {
|
||||
HStack(alignment: .top, spacing: AppMetrics.Spacing.medium) {
|
||||
Image(systemName: selected ? "largecircle.fill.circle" : "circle")
|
||||
.foregroundStyle(selected ? AppDesign.primary : AppDesign.placeholder)
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(title)
|
||||
.font(.system(size: AppMetrics.FontSize.body, weight: .semibold))
|
||||
.foregroundStyle(AppDesign.textPrimary)
|
||||
Text(description)
|
||||
.font(.system(size: AppMetrics.FontSize.caption))
|
||||
.foregroundStyle(AppDesign.textSecondary)
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.padding(AppMetrics.Spacing.large)
|
||||
.background(Color.white)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.card)
|
||||
.stroke(selected ? AppDesign.primary : Color.clear, lineWidth: 1.5)
|
||||
)
|
||||
.clipShape(RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.card))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
|
||||
private func fieldRow(title: String, placeholder: String, text: Binding<String>, keyboard: UIKeyboardType) -> some View {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text(title)
|
||||
.font(.system(size: AppMetrics.FontSize.caption))
|
||||
.foregroundStyle(AppDesign.textSecondary)
|
||||
TextField(placeholder, text: text)
|
||||
.keyboardType(keyboard)
|
||||
.padding(AppMetrics.Spacing.medium)
|
||||
.background(Color.white)
|
||||
.clipShape(RoundedRectangle(cornerRadius: AppMetrics.CornerRadius.card))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user