diff --git a/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/UserInterfaceState.xcuserstate b/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/UserInterfaceState.xcuserstate index 69f8b3e..553e11c 100644 Binary files a/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/UserInterfaceState.xcuserstate and b/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/wake/ContentView.swift b/wake/ContentView.swift index 268de74..74d4525 100644 --- a/wake/ContentView.swift +++ b/wake/ContentView.swift @@ -1,228 +1,194 @@ import SwiftUI -// 自定义从左向右的过渡动画 +// MARK: - 自定义过渡动画 extension AnyTransition { + /// 创建从左向右的滑动过渡动画 static var slideFromLeading: AnyTransition { .asymmetric( - insertion: .move(edge: .trailing).combined(with: .opacity), - removal: .move(edge: .leading).combined(with: .opacity) + insertion: .move(edge: .trailing).combined(with: .opacity), // 从右侧滑入 + removal: .move(edge: .leading).combined(with: .opacity) // 向左侧滑出 ) } } -// 1. 定义路由 +// MARK: - 路由定义 enum Route: Hashable { - case settings + case settings // 设置页面路由 } +// MARK: - 主视图 struct ContentView: View { - @State private var showModal = false - @State private var showSettings = false - @State private var navigationPath = NavigationPath() - @State private var contentOffset: CGFloat = 0 + // MARK: - 状态属性 + @State private var showModal = false // 控制用户资料弹窗显示 + @State private var showSettings = false // 控制设置页面显示 + @State private var navigationPath = NavigationPath() // 导航路径 + @State private var contentOffset: CGFloat = 0 // 内容偏移量 - var body: some View { + // MARK: - 主体视图 + var body: some View { NavigationStack(path: $navigationPath) { - // 添加动画修饰符到 NavigationStack + // 调试信息 let _ = Self._printChanges() - let _ = print("Navigation path changed: \(navigationPath)") + let _ = print("导航路径已更新: \(navigationPath)") + // 主内容区域 ZStack { + // 主内容视图 VStack { VStack(spacing: 20) { - // This spacer ensures content stays below the status bar + // 状态栏占位 Spacer().frame(height: UIApplication.shared.windows.first?.safeAreaInsets.top ?? 0) - // 顶部栏 + + // 顶部导航栏 - 左侧设置按钮 HStack { - Spacer() - Button(action: { - withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) { - showModal = true - } - }) { + // 设置按钮 + Button(action: showUserProfile) { Image(systemName: "gearshape") .font(.title2) .padding() } + Spacer() // 将按钮推到左侧 } Spacer() - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color(.systemBackground)) - .offset(x: showModal ? UIScreen.main.bounds.width * 0.35 : 0) - .animation(.spring(response: 0.5, dampingFraction: 0.8), value: showModal) - .edgesIgnoringSafeArea(.all) - } - // 添加半透明遮罩层 - if showModal { - Color.black.opacity(0.4) - .edgesIgnoringSafeArea(.all) - .onTapGesture { - withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) { - showModal = false - } - } - .transition(.opacity) - } - - // Modal with animation - will be pushed off-screen by SettingsView - SlideInModal(isPresented: $showModal, onDismiss: { - withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) { - showModal = false - } - }) { - // Modal content - // Modal content with offset for SettingsView - VStack(spacing: 20) { - // 用户信息区域 - HStack(alignment: .center, spacing: 16) { - // 头像 - Image(systemName: "person.circle.fill") - .resizable() - .aspectRatio(contentMode: .fill) - .frame(width: 60, height: 60) - .foregroundColor(.blue) - .clipShape(Circle()) - // 姓名和ID - VStack(alignment: .leading, spacing: 4) { - Text("用户名") - .font(.headline) - .foregroundColor(.primary) + // 内容列表 + List { + Section(header: Text("我的收藏")) { + ForEach(1...5, id: \.self) { item in + HStack { + Image(systemName: "photo") + .foregroundColor(.blue) + .frame(width: 40, height: 40) + .background(Color.blue.opacity(0.1)) + .cornerRadius(8) + + VStack(alignment: .leading, spacing: 4) { + Text("项目 \(item)") + .font(.headline) + Text("这是第\(item)个项目的描述") + .font(.subheadline) + .foregroundColor(.secondary) + } + + Spacer() + + Image(systemName: "chevron.right") + .foregroundColor(.gray) + } + .padding(.vertical, 4) + } + } - Text("ID: 12345678") - .font(.subheadline) - .foregroundColor(.secondary) + Section(header: Text("最近活动")) { + ForEach(6...10, id: \.self) { item in + HStack { + Image(systemName: "clock") + .foregroundColor(.orange) + .frame(width: 40, height: 40) + .background(Color.orange.opacity(0.1)) + .cornerRadius(8) + + VStack(alignment: .leading, spacing: 4) { + Text("活动 \(item)") + .font(.headline) + Text("\(item)分钟前更新") + .font(.subheadline) + .foregroundColor(.secondary) + } + + Spacer() + + Text("查看") + .font(.caption) + .padding(6) + .background(Color.blue.opacity(0.1)) + .foregroundColor(.blue) + .cornerRadius(4) + } + .padding(.vertical, 4) + } + } } + .listStyle(GroupedListStyle()) + .padding(.top, 0) - Spacer() } - .padding(.horizontal, 16) - .padding(.top, 16) - - VStack(alignment: .leading, spacing: 8) { - Text("会员等级") - .font(.headline) - .foregroundColor(.primary) - Text("会员时间") - .font(.subheadline) - .foregroundColor(.secondary) - Text("会员中心") - .font(.subheadline) - .foregroundColor(.secondary) - } - .frame(maxWidth: .infinity, alignment: .leading) - .padding(16) - .background(Color(red: 0.92, green: 0.92, blue: 0.92)) - .cornerRadius(10) - .padding(.horizontal, 16) - - VStack(spacing: 12) { - // memories - Button(action: { - print("memories") - }) { - HStack(spacing: 16) { - Image(systemName: "crown.fill") - .foregroundColor(.orange) - .frame(width: 24, height: 24) - - Text("My Memories") - .font(.headline) - .foregroundColor(.primary) - - Spacer() - } - .padding() - .cornerRadius(10) - .contentShape(Rectangle()) // 使整个区域可点击 - } - .buttonStyle(PlainButtonStyle()) // 移除按钮默认样式 - - // Box - Button(action: { - print("Box") - }) { - HStack(spacing: 16) { - Image(systemName: "clock.fill") - .foregroundColor(.blue) - .frame(width: 24, height: 24) - Text("My Bind Box") - .font(.headline) - .foregroundColor(.primary) - - Spacer() - } - .padding() - .cornerRadius(10) - .contentShape(Rectangle()) // 使整个区域可点击 - } - .buttonStyle(PlainButtonStyle()) // 移除按钮默认样式 - - // setting - Button(action: { - withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) { - showSettings = true - } - }) { - HStack(spacing: 16) { - Image(systemName: "person.circle.fill") - .foregroundColor(.purple) - .frame(width: 24, height: 24) - Text("Setting") - .font(.headline) - .foregroundColor(.primary) - Spacer() - Image(systemName: "chevron.right") - .foregroundColor(.gray) - } - .padding() - .background(Color(.systemBackground)) - .cornerRadius(10) - .contentShape(Rectangle()) // 使整个区域可点击 - } - .buttonStyle(PlainButtonStyle()) // 移除按钮默认样式 - } - .padding(.horizontal, 16) - // 这里可以添加其他设置项 - Spacer() - } - .padding(.vertical, 8) - } - - // Apply offset to the entire modal when SettingsView is shown - .offset(x: showSettings ? UIScreen.main.bounds.width : 0) - .animation(.spring(response: 0.5, dampingFraction: 0.8), value: showSettings) - - ZStack { - // Semi-transparent overlay for settings - if showSettings { - Color.black.opacity(0.4) - .edgesIgnoringSafeArea(.all) - .onTapGesture { - withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) { - showSettings = false - } - } - .transition(.opacity) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color(.systemBackground)) + // 当显示弹窗时,主内容向右偏移 + .offset(x: showModal ? UIScreen.main.bounds.width * 0.8 : 0) + // 页面内元素的动画 + .animation(.spring(response: 0.6, dampingFraction: 0.8), value: showModal) + .edgesIgnoringSafeArea(.all) } - // Full screen settings view with slide animation - if showSettings { - SettingsView(isPresented: $showSettings) - .transition(.move(edge: .leading)) - .zIndex(1) // Ensure it's above other content - .onAppear { - // Reset the navigation path when settings appear - navigationPath.removeLast(navigationPath.count) - } + // 用户资料弹窗 + SlideInModal( + isPresented: $showModal, + onDismiss: hideUserProfile + ) { + UserProfileModal( + showModal: $showModal, + showSettings: $showSettings + ) } + // 当显示设置页面时,将弹窗向右移出屏幕 + .offset(x: showSettings ? UIScreen.main.bounds.width : 0) + .animation(.spring(response: 0.6, dampingFraction: 0.8), value: showSettings) + + // 设置页面遮罩层 + ZStack { + // 半透明遮罩 + if showSettings { + Color.black.opacity(0) + .edgesIgnoringSafeArea(.all) + .onTapGesture(perform: hideSettings) + .transition(.opacity) + } + + // 设置页面 + if showSettings { + SettingsView(isPresented: $showSettings) + .transition(.move(edge: .leading)) // 从左侧滑入 + .zIndex(1) // 确保设置页面在其他内容之上 + .onAppear(perform: resetNavigationPath) + } + } + .animation(.spring(response: 0.6, dampingFraction: 0.8), value: showSettings) } - .animation(.spring(response: 0.5, dampingFraction: 0.8), value: showSettings) - } - } - } -} + } + } + + // MARK: - 私有方法 + + /// 显示用户资料弹窗 + private func showUserProfile() { + withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) { + showModal.toggle() + } + } + + /// 隐藏用户资料弹窗 + private func hideUserProfile() { + withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) { + showModal = false + } + } + + /// 隐藏设置页面 + private func hideSettings() { + withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) { + showSettings = false + } + } + + /// 重置导航路径 + private func resetNavigationPath() { + navigationPath.removeLast(navigationPath.count) + } +} + +// MARK: - 预览 #Preview { ContentView() } diff --git a/wake/Extensions/ViewExtensions.swift b/wake/Extensions/ViewExtensions.swift new file mode 100644 index 0000000..668b510 --- /dev/null +++ b/wake/Extensions/ViewExtensions.swift @@ -0,0 +1,22 @@ +import SwiftUI + +// MARK: - 圆角扩展 +struct RoundedCorner: Shape { + var radius: CGFloat = .infinity + var corners: UIRectCorner = .allCorners + + func path(in rect: CGRect) -> Path { + let path = UIBezierPath( + roundedRect: rect, + byRoundingCorners: corners, + cornerRadii: CGSize(width: radius, height: radius) + ) + return Path(path.cgPath) + } +} + +extension View { + func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View { + clipShape(RoundedCorner(radius: radius, corners: corners)) + } +} diff --git a/wake/View/Components/SheetModal.swift b/wake/View/Components/SheetModal.swift index a2bcdde..d464001 100644 --- a/wake/View/Components/SheetModal.swift +++ b/wake/View/Components/SheetModal.swift @@ -16,8 +16,8 @@ struct SlideInModal: View { ZStack(alignment: .leading) { // 遮罩背景 if isPresented { - Color.black - .opacity(0.5) + Color.clear + .contentShape(Rectangle()) .edgesIgnoringSafeArea(.all) .transition(.opacity) .zIndex(1) @@ -49,7 +49,7 @@ struct SlideInModal: View { .animation(animation, value: isPresented) } } - .frame(maxWidth: .infinity, maxHeight: .infinity) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) .edgesIgnoringSafeArea(.all) } } \ No newline at end of file diff --git a/wake/View/Components/UserProfileModal.swift b/wake/View/Components/UserProfileModal.swift new file mode 100644 index 0000000..d9f4fc5 --- /dev/null +++ b/wake/View/Components/UserProfileModal.swift @@ -0,0 +1,135 @@ +import SwiftUI + +struct UserProfileModal: View { + @Binding var showModal: Bool + @Binding var showSettings: Bool + + var body: some View { + // Modal content with transparent background + VStack(spacing: 20) { + Spacer() + .frame(height: UIApplication.shared.windows.first?.safeAreaInsets.top ?? 0) + // 用户信息区域 + HStack(alignment: .center, spacing: 16) { + // 头像 + Image(systemName: "person.circle.fill") + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: 60, height: 60) + .foregroundColor(.blue) + .clipShape(Circle()) + + // 姓名和ID + VStack(alignment: .leading, spacing: 4) { + Text("用户名") + .font(.headline) + .foregroundColor(.primary) + + Text("ID: 12345678") + .font(.subheadline) + .foregroundColor(.secondary) + } + + Spacer() + } + .padding(.horizontal, 16) + .padding(.top, 16) + + VStack(alignment: .leading, spacing: 8) { + Text("会员等级") + .font(.headline) + .foregroundColor(.primary) + Text("会员时间") + .font(.subheadline) + .foregroundColor(.secondary) + Text("会员中心") + .font(.subheadline) + .foregroundColor(.secondary) + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(16) + .background(Color.clear) + .padding(.horizontal, 16) + + VStack(spacing: 12) { + // memories + Button(action: { + print("memories") + }) { + HStack(spacing: 16) { + Image(systemName: "crown.fill") + .foregroundColor(.orange) + .frame(width: 24, height: 24) + + Text("My Memories") + .font(.headline) + .foregroundColor(.primary) + + Spacer() + } + .padding() + .cornerRadius(10) + .contentShape(Rectangle()) // 使整个区域可点击 + } + .buttonStyle(PlainButtonStyle()) // 移除按钮默认样式 + + // Box + Button(action: { + print("Box") + }) { + HStack(spacing: 16) { + Image(systemName: "clock.fill") + .foregroundColor(.blue) + .frame(width: 24, height: 24) + Text("My Bind Box") + .font(.headline) + .foregroundColor(.primary) + + Spacer() + } + .padding() + .cornerRadius(10) + .contentShape(Rectangle()) // 使整个区域可点击 + } + .buttonStyle(PlainButtonStyle()) // 移除按钮默认样式 + + // setting + Button(action: { + withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) { + showSettings = true + } + }) { + HStack(spacing: 16) { + Image(systemName: "person.circle.fill") + .foregroundColor(.purple) + .frame(width: 24, height: 24) + Text("Setting") + .font(.headline) + .foregroundColor(.primary) + Spacer() + Image(systemName: "chevron.right") + .foregroundColor(.gray) + } + .padding() + .background(Color.clear) + .overlay( + RoundedRectangle(cornerRadius: 10) + .stroke(Color.gray.opacity(0.2), lineWidth: 1) + ) + .contentShape(Rectangle()) // 使整个区域可点击 + } + .buttonStyle(PlainButtonStyle()) // 移除按钮默认样式 + } + .padding(.horizontal, 16) + Spacer() + } + .frame(width: UIScreen.main.bounds.width * 0.8) + .background(Color(red: 0.87, green: 0.87, blue: 0.87)) + .cornerRadius(20, corners: [.topRight, .bottomRight]) + .edgesIgnoringSafeArea(.all) + } +} + +#Preview { + UserProfileModal(showModal: .constant(true), showSettings: .constant(false)) +} diff --git a/wake/View/Owner/SettingsView.swift b/wake/View/Owner/SettingsView.swift index d49e3ef..5446eb8 100644 --- a/wake/View/Owner/SettingsView.swift +++ b/wake/View/Owner/SettingsView.swift @@ -1,153 +1,33 @@ import SwiftUI +/// 设置页面视图 struct SettingsView: View { + // MARK: - 属性 + + /// 环境变量 - 用于dismiss视图 @Environment(\.dismiss) private var dismiss - @State private var isAppeared = false + + /// 状态 - 控制视图显示/隐藏 @Binding var isPresented: Bool - // Animation configuration + // MARK: - 动画配置 + + /// 动画配置 private let animation = Animation.spring( - response: 0.8, - dampingFraction: 0.6, - blendDuration: 0.8 + response: 0.6, // 响应时间 + dampingFraction: 0.9, // 阻尼系数 + blendDuration: 0.8 // 混合时间 ) + // MARK: - 主体视图 + var body: some View { VStack(spacing: 0) { - // Custom navigation bar + // 自定义导航栏 HStack { + // 返回按钮 Button(action: { - withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) { - isPresented = false - } - }) { - HStack(spacing: 4) { - Image(systemName: "chevron.left") - .font(.system(size: 17, weight: .semibold)) - Text("Back") - } - .foregroundColor(.blue) - .padding() - } - Spacer() - Text("Settings") - .font(.headline) - .padding() - Spacer() - // Invisible view to balance the layout - Color.clear - .frame(width: 44, height: 44) - } - .background(Color(.systemBackground)) - - // Settings content - List(0..<1) { _ in - // This empty section ensures proper spacing - Section { - EmptyView() - } header: { - EmptyView() - } - // Add an invisible section header to remove extra top padding - Section(header: EmptyView()) { - EmptyView() - } - // Account & Security - HStack { - Color.clear - .frame(width: 12, height: 24) - .background(Color(.systemBackground)) - Image(systemName: "person.crop.circle") - .font(.system(size: 24)) - .foregroundColor(.gray) - Text("Account & Security") - Spacer() - Image(systemName: "chevron.right") - .foregroundColor(.gray) - Color.clear - .frame(width: 12, height: 24) - .background(Color(.systemBackground)) - } - .listRowBackground(Color(.systemBackground)) - - // Permission Management - HStack { - Color.clear - .frame(width: 12, height: 24) - .background(Color(.systemBackground)) - Image(systemName: "lock.shield") - .font(.system(size: 24)) - .foregroundColor(.gray) - Text("Permission Management") - Spacer() - Image(systemName: "chevron.right") - .foregroundColor(.gray) - Color.clear - .frame(width: 12, height: 24) - .background(Color(.systemBackground)) - } - .listRowBackground(Color(.systemBackground)) - - // Support & Service - HStack { - Color.clear - .frame(width: 12, height: 24) - .background(Color(.systemBackground)) - Image(systemName: "questionmark.circle") - .font(.system(size: 24)) - .foregroundColor(.gray) - Text("Support & Service") - Spacer() - Image(systemName: "chevron.right") - .foregroundColor(.gray) - Color.clear - .frame(width: 12, height: 24) - .background(Color(.systemBackground)) - } - .listRowBackground(Color(.systemBackground)) - - // About Us - HStack { - Color.clear - .frame(width: 12, height: 24) - .background(Color(.systemBackground)) - Image(systemName: "info.circle") - .font(.system(size: 24)) - .foregroundColor(.gray) - Text("About Us") - Spacer() - Image(systemName: "chevron.right") - .foregroundColor(.gray) - Color.clear - .frame(width: 12, height: 24) - .background(Color(.systemBackground)) - } - .listRowBackground(Color(.systemBackground)) - } - .listStyle(GroupedListStyle()) - .navigationTitle("Setting") - .navigationBarTitleDisplayMode(.inline) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color(.systemGray6)) - .environment(\.horizontalSizeClass, .regular) - .environment(\.defaultMinListRowHeight, 50) - .listRowInsets(EdgeInsets()) - .onAppear { - // Remove extra separators below the list - UITableView.appearance().tableFooterView = UIView() - // Remove separator inset - UITableView.appearance().separatorInset = .zero - // Remove extra space at the top of the table view - UITableView.appearance().contentInset = .zero - } - .toolbar { - ToolbarItem(placement: .navigationBarLeading) { - Button(action: { - withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) { - isAppeared = false - } - // Delay the dismiss to allow the animation to complete - DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + withAnimation(animation) { isPresented = false } }) { @@ -155,47 +35,121 @@ struct SettingsView: View { Image(systemName: "chevron.left") .font(.system(size: 16, weight: .medium)) .foregroundColor(.blue) - Text("Back") + Text("返回") .font(.system(size: 17, weight: .regular)) .foregroundColor(.blue) } } + + Spacer() + + // 标题 + Text("设置") + .font(.headline) + + Spacer() + + // 用于平衡布局的透明视图 + Color.clear + .frame(width: 44, height: 44) } + .padding(.horizontal) + .padding(.vertical, 8) + .background(Color(.systemBackground)) + + // 设置项列表 + List { + // 空Section用于调整间距 + Section { EmptyView() } + + // 账号与安全 + settingRow( + icon: "person.crop.circle", + title: "账号与安全", + action: {} + ) + + // 权限管理 + settingRow( + icon: "lock.shield", + title: "权限管理", + action: {} + ) + + // 支持与服务 + settingRow( + icon: "questionmark.circle", + title: "支持与服务", + action: {} + ) + + // 关于我们 + settingRow( + icon: "info.circle", + title: "关于我们", + action: {} + ) + } + .listStyle(GroupedListStyle()) + .listRowInsets(EdgeInsets()) } - .animation(animation, value: isAppeared) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color(.systemBackground)) + .environment(\.horizontalSizeClass, .regular) + .environment(\.defaultMinListRowHeight, 50) + .onAppear(perform: configureTableView) } -} + // MARK: - 私有方法 + + /// 配置TableView外观 + private func configureTableView() { + // 移除列表底部分隔线 + UITableView.appearance().tableFooterView = UIView() + // 移除分隔线缩进 + UITableView.appearance().separatorInset = .zero + // 移除列表顶部额外间距 + UITableView.appearance().contentInset = .zero + } + + /// 创建设置项行视图 + /// - Parameters: + /// - icon: 图标名称 + /// - title: 标题 + /// - action: 点击动作 + /// - Returns: 返回设置项行视图 + private func settingRow(icon: String, title: String, action: @escaping () -> Void) -> some View { + Button(action: action) { + HStack { + // 左侧图标 + Image(systemName: icon) + .font(.system(size: 24)) + .foregroundColor(.gray) + .frame(width: 40) + + // 标题 + Text(title) + .foregroundColor(.primary) + + Spacer() + + // 右侧箭头 + Image(systemName: "chevron.right") + .font(.system(size: 14)) + .foregroundColor(.gray) + } + .padding(.vertical, 12) + .padding(.horizontal, 16) + .background(Color(.systemBackground)) + } + .buttonStyle(PlainButtonStyle()) + .listRowBackground(Color(.systemBackground)) + } } -// MARK: - Preview +// MARK: - 预览 #Preview { NavigationView { SettingsView(isPresented: .constant(true)) } } - -// MARK: - Subviews -struct AccountSecurityView: View { - var body: some View { - Text("Account & Security") - } -} - -struct PermissionManagementView: View { - var body: some View { - Text("Permission Management") - } -} - -struct SupportServiceView: View { - var body: some View { - Text("Support & Service") - } -} - -struct AboutUsView: View { - var body: some View { - Text("About Us") - } -}