feat: 样式优化
This commit is contained in:
parent
b118b91001
commit
df32ea71bb
@ -1,5 +1,5 @@
|
||||
{
|
||||
"originHash" : "e8f130fe30ac6cdc940ef06ee1e8535e9f46ffee6aeead1722b9525562f6ce08",
|
||||
"originHash" : "7ea295cc5e3eb8ef644b89ce2b47a7600994b67c8582ee354b643cd63250740d",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "alamofire",
|
||||
@ -9,6 +9,51 @@
|
||||
"revision" : "513364f870f6bfc468f9d2ff0a95caccc10044c5",
|
||||
"version" : "5.10.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "cocoalumberjack",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/CocoaLumberjack/CocoaLumberjack.git",
|
||||
"state" : {
|
||||
"revision" : "a9ed4b6f9bdedce7d77046f43adfb8ce1fd54114",
|
||||
"version" : "3.9.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "lottie-spm",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/airbnb/lottie-spm.git",
|
||||
"state" : {
|
||||
"revision" : "04f2fd18cc9404a0a0917265a449002674f24ec9",
|
||||
"version" : "4.5.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "svgkit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/SVGKit/SVGKit.git",
|
||||
"state" : {
|
||||
"revision" : "58152b9f7c85eab239160b36ffdfd364aa43d666",
|
||||
"version" : "3.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-log",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-log",
|
||||
"state" : {
|
||||
"revision" : "ce592ae52f982c847a4efc0dd881cc9eb32d29f2",
|
||||
"version" : "1.6.4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "waterfallgrid",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/paololeonardi/WaterfallGrid.git",
|
||||
"state" : {
|
||||
"revision" : "c7c08652c3540adf8e48409c351879b4caea7e89",
|
||||
"version" : "1.1.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
|
||||
@ -16,6 +16,7 @@ enum AppRoute: Hashable {
|
||||
case about
|
||||
case permissionManagement
|
||||
case feedback
|
||||
case blindList
|
||||
|
||||
@ViewBuilder
|
||||
var view: some View {
|
||||
@ -48,6 +49,8 @@ enum AppRoute: Hashable {
|
||||
PermissionManagementView()
|
||||
case .feedback:
|
||||
FeedbackView()
|
||||
case .blindList:
|
||||
BlindListView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -196,27 +196,27 @@ struct UserProfileModal: View {
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
|
||||
// Box
|
||||
// Button(action: {
|
||||
// Router.shared.navigate(to: .mediaUpload)
|
||||
// }) {
|
||||
// HStack(spacing: 16) {
|
||||
// SVGImage(svgName: "Box")
|
||||
// .foregroundColor(.orange)
|
||||
// .frame(width: 20, height: 20)
|
||||
//Box
|
||||
Button(action: {
|
||||
Router.shared.navigate(to: .blindList)
|
||||
}) {
|
||||
HStack(spacing: 16) {
|
||||
SVGImage(svgName: "Box")
|
||||
.foregroundColor(.orange)
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
// Text("My Blind Box")
|
||||
// .font(Typography.font(for: .body))
|
||||
// .fontWeight(.bold)
|
||||
// .foregroundColor(.themeTextMessageMain)
|
||||
Text("My Blind Box")
|
||||
.font(Typography.font(for: .body))
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(.themeTextMessageMain)
|
||||
|
||||
// Spacer()
|
||||
// }
|
||||
// .padding()
|
||||
// .cornerRadius(10)
|
||||
// .contentShape(Rectangle())
|
||||
// }
|
||||
// .buttonStyle(PlainButtonStyle())
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
.cornerRadius(10)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
|
||||
// setting
|
||||
Button(action: {
|
||||
|
||||
@ -25,7 +25,7 @@ struct AboutUsView: View {
|
||||
VStack(spacing: 0) {
|
||||
// IP Address Section
|
||||
VStack(spacing: 12) {
|
||||
SVGImage(svgName: "AboutIP")
|
||||
SVGImageHtml(svgName: "AboutIP")
|
||||
.frame(width: 102, height: 102)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
@ -33,11 +33,11 @@ struct AboutUsView: View {
|
||||
|
||||
// Version & ICP Info
|
||||
VStack(spacing: 12) {
|
||||
Text("Version : 1.1.1")
|
||||
Text("Version : 2.0.0")
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(.themeTextMessageMain)
|
||||
|
||||
Text("ICP 备案号: 京ICP备XXXXXXXX号")
|
||||
Text("ICP 备案号: 沪ICP备2025133004号-2A")
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(.themeTextMessageMain)
|
||||
}
|
||||
|
||||
@ -121,7 +121,7 @@ struct AccountView: View {
|
||||
|
||||
}) {
|
||||
Text("Confirm")
|
||||
.foregroundColor(.themeTextWhite)
|
||||
.foregroundColor(.themeTextMessage)
|
||||
.font(.system(size: 12))
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
|
||||
483
wake/View/Owner/BlindListView.swift
Normal file
483
wake/View/Owner/BlindListView.swift
Normal file
@ -0,0 +1,483 @@
|
||||
import SwiftUI
|
||||
import AVKit
|
||||
import WaterfallGrid
|
||||
|
||||
// MARK: - API Response Models for BlindList
|
||||
struct BlindListMaterialResponse: Decodable {
|
||||
let code: Int
|
||||
let data: [BlindBoxItem]
|
||||
}
|
||||
|
||||
struct BlindBoxItem: Identifiable, Decodable {
|
||||
let id: Int64
|
||||
let boxCode: String
|
||||
let userId: Int64
|
||||
let name: String
|
||||
let boxType: String
|
||||
let features: String?
|
||||
let resultFile: FileInfo?
|
||||
let status: String
|
||||
let workflowInstanceId: Int64?
|
||||
let videoGenerateTime: String?
|
||||
let createTime: String
|
||||
let coverFile: FileInfo?
|
||||
let description: String
|
||||
|
||||
struct FileInfo: Decodable {
|
||||
let id: String
|
||||
let fileName: String
|
||||
let url: String
|
||||
let metadata: [String: String]?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case fileName = "file_name"
|
||||
case url
|
||||
case metadata
|
||||
}
|
||||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case boxCode = "box_code"
|
||||
case userId = "user_id"
|
||||
case name
|
||||
case boxType = "box_type"
|
||||
case features
|
||||
case resultFile = "result_file"
|
||||
case status
|
||||
case workflowInstanceId = "workflow_instance_id"
|
||||
case videoGenerateTime = "video_generate_time"
|
||||
case createTime = "create_time"
|
||||
case coverFile = "cover_file"
|
||||
case description
|
||||
}
|
||||
}
|
||||
|
||||
enum BlindListMemoryMediaType: Equatable {
|
||||
case image(String)
|
||||
case video(url: String, previewUrl: String)
|
||||
|
||||
var isVideo: Bool {
|
||||
if case .video = self { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
var url: String {
|
||||
switch self {
|
||||
case .image(let url):
|
||||
return url
|
||||
case .video(let url, _):
|
||||
return url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - View Models
|
||||
struct BlindListFileInfo {
|
||||
let id: String
|
||||
let fileName: String
|
||||
let url: String
|
||||
}
|
||||
|
||||
struct BlindListMemoryItem: Identifiable {
|
||||
let id: String
|
||||
let name: String
|
||||
let description: String
|
||||
let fileInfo: BlindListFileInfo
|
||||
let previewFileInfo: BlindListFileInfo
|
||||
|
||||
var title: String { name }
|
||||
var subtitle: String { description }
|
||||
|
||||
var mediaType: BlindListMemoryMediaType {
|
||||
// Determine media type based on file extension or other criteria
|
||||
// For now, default to image
|
||||
return .image(fileInfo.url)
|
||||
}
|
||||
|
||||
var aspectRatio: CGFloat { 1.0 }
|
||||
}
|
||||
|
||||
struct BlindListView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@State private var memories: [BlindListMemoryItem] = []
|
||||
@State private var isLoading = false
|
||||
@State private var errorMessage: String?
|
||||
@State private var selectedMemory: BlindListMemoryItem? = nil
|
||||
|
||||
let columns = [
|
||||
GridItem(.flexible(), spacing: 1),
|
||||
GridItem(.flexible(), spacing: 1)
|
||||
]
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ZStack {
|
||||
VStack(spacing: 0) {
|
||||
// 顶部导航栏
|
||||
HStack {
|
||||
Button(action: {
|
||||
self.dismiss()
|
||||
}) {
|
||||
Image(systemName: "chevron.left")
|
||||
.foregroundColor(.themeTextMessageMain)
|
||||
.font(.system(size: 20))
|
||||
}
|
||||
Spacer()
|
||||
Text("我的盲盒")
|
||||
.foregroundColor(.themeTextMessageMain)
|
||||
.font(Typography.font(for: .body, family: .quicksandBold))
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
.background(Color.themeTextWhiteSecondary)
|
||||
|
||||
// 内容区域
|
||||
ZStack {
|
||||
Color.themeTextWhiteSecondary.ignoresSafeArea()
|
||||
|
||||
ScrollView {
|
||||
WaterfallGrid(memories) { memory in
|
||||
BlindListMemoryCard(memory: memory)
|
||||
.onTapGesture {
|
||||
withAnimation(.spring()) {
|
||||
selectedMemory = memory
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 4)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 全屏模态
|
||||
if let memory = selectedMemory {
|
||||
BlindListFullScreenMediaView(memory: memory, isPresented: $selectedMemory)
|
||||
.transition(.opacity)
|
||||
.zIndex(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.onAppear {
|
||||
fetchList()
|
||||
}
|
||||
}
|
||||
|
||||
private func fetchList() {
|
||||
isLoading = true
|
||||
errorMessage = nil
|
||||
|
||||
NetworkService.shared.get(path: "/blind_boxs/query", parameters: nil) { [self] (result: Result<BlindListMaterialResponse, NetworkError>) in
|
||||
DispatchQueue.main.async { [self] in
|
||||
self.isLoading = false
|
||||
|
||||
switch result {
|
||||
case .success(let response):
|
||||
print("✅ Successfully fetched \(response.data.count) blind box items")
|
||||
// Convert BlindBoxItem to BlindListMemoryItem
|
||||
self.memories = response.data
|
||||
.filter { $0.status == "Opened" }
|
||||
.map { item in
|
||||
BlindListMemoryItem(
|
||||
id: String(item.id),
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
fileInfo: BlindListFileInfo(
|
||||
id: item.resultFile?.id ?? "",
|
||||
fileName: item.resultFile?.fileName ?? "",
|
||||
url: item.resultFile?.url ?? ""
|
||||
),
|
||||
previewFileInfo: BlindListFileInfo(
|
||||
id: item.coverFile?.id ?? "",
|
||||
fileName: item.coverFile?.fileName ?? "",
|
||||
url: item.coverFile?.url ?? ""
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
self.errorMessage = error.localizedDescription
|
||||
print("❌ Failed to fetch blind box items: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct BlindListFullScreenMediaView: View {
|
||||
let memory: BlindListMemoryItem
|
||||
@Binding var isPresented: BlindListMemoryItem?
|
||||
@State private var isVideoPlaying = false
|
||||
@State private var showControls = true
|
||||
@State private var controlsTimer: Timer? = nil
|
||||
@State private var imageAspectRatio: CGFloat = 1.0
|
||||
@State private var isLoading = true
|
||||
|
||||
private func loadAspectRatio(from url: URL) {
|
||||
guard let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil),
|
||||
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [CFString: Any],
|
||||
let width = imageProperties[kCGImagePropertyPixelWidth] as? CGFloat,
|
||||
let height = imageProperties[kCGImagePropertyPixelHeight] as? CGFloat,
|
||||
height > 0 else {
|
||||
imageAspectRatio = 16/9
|
||||
isLoading = false
|
||||
return
|
||||
}
|
||||
|
||||
imageAspectRatio = width / height
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color.black.ignoresSafeArea()
|
||||
|
||||
ZStack {
|
||||
GeometryReader { geometry in
|
||||
switch memory.mediaType {
|
||||
case .image(let url):
|
||||
if let imageURL = URL(string: url) {
|
||||
AsyncImage(url: imageURL) { phase in
|
||||
switch phase {
|
||||
case .success(let image):
|
||||
GeometryReader { geometry in
|
||||
ZStack {
|
||||
Color.black
|
||||
image
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(
|
||||
width: min(geometry.size.width, geometry.size.height * imageAspectRatio),
|
||||
height: min(geometry.size.height, geometry.size.width / imageAspectRatio)
|
||||
)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
.onAppear {
|
||||
if let uiImage = image.asUIImage() {
|
||||
let size = uiImage.size
|
||||
imageAspectRatio = size.width / size.height
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
case .failure(_):
|
||||
Image(systemName: "exclamationmark.triangle")
|
||||
.foregroundColor(.red)
|
||||
case .empty:
|
||||
ProgressView()
|
||||
@unknown default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case .video(_, let previewUrl):
|
||||
GeometryReader { geometry in
|
||||
ZStack {
|
||||
Color.clear
|
||||
BlindListVideoPlayer(url: memory.mediaType.url, isPlaying: $isVideoPlaying)
|
||||
.aspectRatio(imageAspectRatio, contentMode: .fit)
|
||||
.frame(
|
||||
width: min(geometry.size.width, geometry.size.height * imageAspectRatio),
|
||||
height: min(geometry.size.height, geometry.size.width / imageAspectRatio)
|
||||
)
|
||||
.onAppear {
|
||||
if let previewUrl = URL(string: previewUrl) {
|
||||
loadAspectRatio(from: previewUrl)
|
||||
}
|
||||
isVideoPlaying = true
|
||||
}
|
||||
.onDisappear {
|
||||
isVideoPlaying = false
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VStack {
|
||||
HStack {
|
||||
Button(action: {
|
||||
withAnimation(.spring()) {
|
||||
isPresented = nil
|
||||
}
|
||||
}) {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 20, weight: .bold))
|
||||
.foregroundColor(.white)
|
||||
.padding(12)
|
||||
.background(Circle().fill(Color.black.opacity(0.4)))
|
||||
}
|
||||
.padding(.leading, 16)
|
||||
.padding(.top, UIApplication.shared.windows.first?.safeAreaInsets.top ?? 0)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.zIndex(2)
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
.statusBar(hidden: true)
|
||||
}
|
||||
.onTapGesture {
|
||||
if case .video = memory.mediaType {
|
||||
withAnimation(.easeInOut) {
|
||||
showControls.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
.statusBar(hidden: true)
|
||||
.onAppear {
|
||||
UIApplication.shared.isIdleTimerDisabled = true
|
||||
}
|
||||
.onDisappear {
|
||||
UIApplication.shared.isIdleTimerDisabled = false
|
||||
controlsTimer?.invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct BlindListVideoPlayer: UIViewControllerRepresentable {
|
||||
let url: String
|
||||
@Binding var isPlaying: Bool
|
||||
|
||||
func makeUIViewController(context: Context) -> AVPlayerViewController {
|
||||
let controller = AVPlayerViewController()
|
||||
let player = AVPlayer(url: URL(string: url)!)
|
||||
controller.player = player
|
||||
controller.showsPlaybackControls = true
|
||||
controller.videoGravity = .resizeAspect
|
||||
controller.view.backgroundColor = .clear
|
||||
controller.view.isOpaque = false
|
||||
|
||||
return controller
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
|
||||
if isPlaying {
|
||||
uiViewController.player?.play()
|
||||
} else {
|
||||
uiViewController.player?.pause()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct BlindListMemoryCard: View {
|
||||
let memory: BlindListMemoryItem
|
||||
@State private var aspectRatio: CGFloat = 1.0
|
||||
@State private var isLoading = true
|
||||
|
||||
private func loadAspectRatio(from url: URL) {
|
||||
guard let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil),
|
||||
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [CFString: Any],
|
||||
let width = imageProperties[kCGImagePropertyPixelWidth] as? CGFloat,
|
||||
let height = imageProperties[kCGImagePropertyPixelHeight] as? CGFloat,
|
||||
height > 0 else {
|
||||
aspectRatio = 16/9
|
||||
isLoading = false
|
||||
return
|
||||
}
|
||||
|
||||
aspectRatio = width / height
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
ZStack {
|
||||
Group {
|
||||
switch memory.mediaType {
|
||||
case .image(let url):
|
||||
if let url = URL(string: url) {
|
||||
AsyncImage(url: url) { phase in
|
||||
Group {
|
||||
if let image = phase.image {
|
||||
GeometryReader { geometry in
|
||||
ZStack {
|
||||
Color.black
|
||||
image
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(
|
||||
width: min(geometry.size.width, geometry.size.height * aspectRatio),
|
||||
height: min(geometry.size.height, geometry.size.width / aspectRatio)
|
||||
)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
.aspectRatio(aspectRatio, contentMode: aspectRatio > 1 ? .fit : .fill)
|
||||
.onAppear {
|
||||
if let uiImage = image.asUIImage() {
|
||||
let size = uiImage.size
|
||||
aspectRatio = size.width / size.height
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
} else if phase.error != nil {
|
||||
Color.gray.opacity(0.3)
|
||||
} else {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case .video(_, let previewUrl):
|
||||
if let previewUrl = URL(string: previewUrl) {
|
||||
AsyncImage(url: previewUrl) { phase in
|
||||
Group {
|
||||
if let image = phase.image {
|
||||
image
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.onAppear {
|
||||
loadAspectRatio(from: previewUrl)
|
||||
}
|
||||
} else if phase.error != nil {
|
||||
Color.gray.opacity(0.3)
|
||||
} else {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Color.gray.opacity(0.3)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(
|
||||
width: (UIScreen.main.bounds.width / 2) - 24,
|
||||
height: (UIScreen.main.bounds.width / 2 - 24) / (isLoading ? 1 : aspectRatio)
|
||||
)
|
||||
.clipped()
|
||||
.cornerRadius(12)
|
||||
|
||||
if case .video = memory.mediaType {
|
||||
Image(systemName: "play.circle.fill")
|
||||
.font(.system(size: 40))
|
||||
.foregroundColor(.white.opacity(0.9))
|
||||
.shadow(radius: 3)
|
||||
}
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(memory.title)
|
||||
.font(Typography.font(for: .body, family: .quicksandBold))
|
||||
.foregroundColor(.themeTextMessageMain)
|
||||
.lineLimit(1)
|
||||
|
||||
Text(memory.subtitle)
|
||||
.font(.system(size: 14))
|
||||
.foregroundColor(.themeTextMessageMain)
|
||||
.lineLimit(2)
|
||||
}
|
||||
.padding(.horizontal, 2)
|
||||
.padding(.bottom, 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -28,16 +28,18 @@ struct PermissionManagementView: View {
|
||||
title: "Gallery Permissions",
|
||||
isEnabled: photoLibraryStatus == .authorized
|
||||
) {
|
||||
requestPhotoLibraryPermission() // 请求相册权限
|
||||
requestPhotoLibraryPermission()
|
||||
}
|
||||
.background(Color.white)
|
||||
|
||||
// 2. 通知权限
|
||||
PermissionRow(
|
||||
title: "Notification Permissions",
|
||||
isEnabled: notificationStatus == .authorized
|
||||
) {
|
||||
requestNotificationPermission() // 请求通知权限
|
||||
requestNotificationPermission()
|
||||
}
|
||||
.background(Color.white)
|
||||
}
|
||||
.background(Color.white)
|
||||
.cornerRadius(16)
|
||||
@ -159,13 +161,6 @@ struct PermissionRow: View {
|
||||
.padding()
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
// 添加底部边框
|
||||
.overlay(
|
||||
Rectangle()
|
||||
.frame(height: 1)
|
||||
.foregroundColor(Color(.systemGray6)),
|
||||
alignment: .bottom
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -60,7 +60,11 @@ struct SettingsView: View {
|
||||
settingRow(
|
||||
icon: "Suport",
|
||||
title: "Support & Service",
|
||||
action: {}
|
||||
action: {
|
||||
if let url = URL(string: "https://work.weixin.qq.com/kfid/kfca0ac87f4e05e8bfd") {
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// 关于我们
|
||||
|
||||
@ -315,6 +315,7 @@ struct MediaUploadView: View {
|
||||
/// 处理继续按钮点击
|
||||
private func handleContinue() {
|
||||
// 获取所有已上传文件的结果
|
||||
Router.shared.navigate(to: .blindBox(mediaType: .video))
|
||||
let uploadResults = uploadManager.uploadResults
|
||||
guard !uploadResults.isEmpty else {
|
||||
print("⚠️ 没有可用的文件ID")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user