commit 423ff363f61e0626ad1fee30aba5fef51fcb97ee Author: elliwood Date: Thu Aug 14 19:49:54 2025 +0800 动效 diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..127ee76 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/wake.xcodeproj/project.pbxproj b/wake.xcodeproj/project.pbxproj new file mode 100644 index 0000000..7cf4d17 --- /dev/null +++ b/wake.xcodeproj/project.pbxproj @@ -0,0 +1,375 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + AB8773632E4E04400071CB53 /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = AB8773622E4E040E0071CB53 /* .gitignore */; }; + ABB4E2182E4B761400660198 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = ABB4E2172E4B761400660198 /* Alamofire */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + ABB4E21C2E4B763200660198 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + AB8773622E4E040E0071CB53 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = ""; }; + ABB4E2082E4B75D900660198 /* wake.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = wake.app; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + ABB4E20A2E4B75D900660198 /* wake */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = wake; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + ABB4E2052E4B75D900660198 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ABB4E2182E4B761400660198 /* Alamofire in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + ABB4E1FF2E4B75D900660198 = { + isa = PBXGroup; + children = ( + AB8773622E4E040E0071CB53 /* .gitignore */, + ABB4E20A2E4B75D900660198 /* wake */, + ABB4E2092E4B75D900660198 /* Products */, + ); + sourceTree = ""; + }; + ABB4E2092E4B75D900660198 /* Products */ = { + isa = PBXGroup; + children = ( + ABB4E2082E4B75D900660198 /* wake.app */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + ABB4E2072E4B75D900660198 /* wake */ = { + isa = PBXNativeTarget; + buildConfigurationList = ABB4E2132E4B75DF00660198 /* Build configuration list for PBXNativeTarget "wake" */; + buildPhases = ( + ABB4E2042E4B75D900660198 /* Sources */, + ABB4E2052E4B75D900660198 /* Frameworks */, + ABB4E2062E4B75D900660198 /* Resources */, + ABB4E21C2E4B763200660198 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + ABB4E20A2E4B75D900660198 /* wake */, + ); + name = wake; + packageProductDependencies = ( + ABB4E2172E4B761400660198 /* Alamofire */, + ); + productName = wake; + productReference = ABB4E2082E4B75D900660198 /* wake.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + ABB4E2002E4B75D900660198 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1640; + LastUpgradeCheck = 1640; + TargetAttributes = { + ABB4E2072E4B75D900660198 = { + CreatedOnToolsVersion = 16.4; + }; + }; + }; + buildConfigurationList = ABB4E2032E4B75D900660198 /* Build configuration list for PBXProject "wake" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = ABB4E1FF2E4B75D900660198; + minimizedProjectReferenceProxies = 1; + packageReferences = ( + ABB4E2162E4B761400660198 /* XCRemoteSwiftPackageReference "Alamofire" */, + ); + preferredProjectObjectVersion = 77; + productRefGroup = ABB4E2092E4B75D900660198 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + ABB4E2072E4B75D900660198 /* wake */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + ABB4E2062E4B75D900660198 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + AB8773632E4E04400071CB53 /* .gitignore in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + ABB4E2042E4B75D900660198 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + ABB4E2112E4B75DF00660198 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = GB3VPJ54BD; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + ABB4E2122E4B75DF00660198 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = GB3VPJ54BD; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + ABB4E2142E4B75DF00660198 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = GB3VPJ54BD; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.memowake.wake; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + ABB4E2152E4B75DF00660198 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = GB3VPJ54BD; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.memowake.wake; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + ABB4E2032E4B75D900660198 /* Build configuration list for PBXProject "wake" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + ABB4E2112E4B75DF00660198 /* Debug */, + ABB4E2122E4B75DF00660198 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + ABB4E2132E4B75DF00660198 /* Build configuration list for PBXNativeTarget "wake" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + ABB4E2142E4B75DF00660198 /* Debug */, + ABB4E2152E4B75DF00660198 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + ABB4E2162E4B761400660198 /* XCRemoteSwiftPackageReference "Alamofire" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Alamofire/Alamofire.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 5.10.2; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + ABB4E2172E4B761400660198 /* Alamofire */ = { + isa = XCSwiftPackageProductDependency; + package = ABB4E2162E4B761400660198 /* XCRemoteSwiftPackageReference "Alamofire" */; + productName = Alamofire; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = ABB4E2002E4B75D900660198 /* Project object */; +} diff --git a/wake.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/wake.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/wake.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/wake.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/wake.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..8c6ea58 --- /dev/null +++ b/wake.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "e8f130fe30ac6cdc940ef06ee1e8535e9f46ffee6aeead1722b9525562f6ce08", + "pins" : [ + { + "identity" : "alamofire", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Alamofire/Alamofire.git", + "state" : { + "revision" : "513364f870f6bfc468f9d2ff0a95caccc10044c5", + "version" : "5.10.2" + } + } + ], + "version" : 3 +} diff --git a/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/IDEFindNavigatorScopes.plist b/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/IDEFindNavigatorScopes.plist new file mode 100644 index 0000000..5dd5da8 --- /dev/null +++ b/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/IDEFindNavigatorScopes.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/UserInterfaceState.xcuserstate b/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000..ff3dd1d Binary files /dev/null and b/wake.xcodeproj/project.xcworkspace/xcuserdata/elliwood.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/wake.xcodeproj/xcuserdata/elliwood.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/wake.xcodeproj/xcuserdata/elliwood.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..706153b --- /dev/null +++ b/wake.xcodeproj/xcuserdata/elliwood.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/wake.xcodeproj/xcuserdata/elliwood.xcuserdatad/xcschemes/xcschememanagement.plist b/wake.xcodeproj/xcuserdata/elliwood.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..a4c98a3 --- /dev/null +++ b/wake.xcodeproj/xcuserdata/elliwood.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + wake.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/wake/.DS_Store b/wake/.DS_Store new file mode 100644 index 0000000..f26cef2 Binary files /dev/null and b/wake/.DS_Store differ diff --git a/wake/ContentView.swift b/wake/ContentView.swift new file mode 100644 index 0000000..268de74 --- /dev/null +++ b/wake/ContentView.swift @@ -0,0 +1,228 @@ +import SwiftUI + +// 自定义从左向右的过渡动画 +extension AnyTransition { + static var slideFromLeading: AnyTransition { + .asymmetric( + insertion: .move(edge: .trailing).combined(with: .opacity), + removal: .move(edge: .leading).combined(with: .opacity) + ) + } +} + +// 1. 定义路由 +enum Route: Hashable { + case settings +} + +struct ContentView: View { + @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 { + NavigationStack(path: $navigationPath) { + // 添加动画修饰符到 NavigationStack + let _ = Self._printChanges() + let _ = print("Navigation path changed: \(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 + } + }) { + Image(systemName: "gearshape") + .font(.title2) + .padding() + } + } + 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) + + 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(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) + } + + // 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) + } + } + } + .animation(.spring(response: 0.5, dampingFraction: 0.8), value: showSettings) + } + } + } +} +#Preview { + ContentView() +} diff --git a/wake/Utils/.DS_Store b/wake/Utils/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/wake/Utils/.DS_Store differ diff --git a/wake/Utils/LoginURLSession.swift b/wake/Utils/LoginURLSession.swift new file mode 100644 index 0000000..1ddc886 --- /dev/null +++ b/wake/Utils/LoginURLSession.swift @@ -0,0 +1,94 @@ +import Foundation + +// 可选:定义响应模型(根据你的实际返回结构调整) +//struct LoginResponse: Codable { +// let code: Int? +// let message: String? +// let [String: AnyCodable]? // 或具体结构,如 token, userId 等 +//} + +// 因为 Dictionary 不能直接 Codable,这里用 AnyCodable 简化 +//enum AnyCodable: Codable {} + +func passwordLogin(username: String, password: String) { + guard let url = URL(string: "http://192.168.31.156:31646/api/v1/iam/login/password-login") else { + print("❌ 无效的URL") + return + } + + // 1. 创建 URLRequest + var request = URLRequest(url: url) + request.httpMethod = "POST" + + // 2. 设置请求头 + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + // 如果接口需要,也可以加 User-Agent 或其他 header + request.setValue("iOS App", forHTTPHeaderField: "User-Agent") + + // 3. 准备参数 + let parameters = [ + "account": username, + "password": password + ] + + // 4. 将参数编码为 JSON + do { + request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: []) + } catch { + print("❌ 参数序列化失败: \(error)") + return + } + + // 5. 发送请求 + let task = URLSession.shared.dataTask(with: request) { data, response, error in + // 回调在后台线程,更新 UI 需切主线程 + DispatchQueue.main.async { + if let error = error { + print("网络错误: \(error)") + return + } + + // 检查状态码 + if let httpResponse = response as? HTTPURLResponse { + print("状态码: \(httpResponse.statusCode)") + + if httpResponse.statusCode == 404 { + print("⚠️ 接口未找到,请确认:") + print("1. 后端服务是否正在运行?") + print("2. IP 和端口是否正确?") + print("3. 网络是否在同一局域网?") + return + } else if httpResponse.statusCode != 200 { + print("服务器错误: \(httpResponse.statusCode)") + } + } + + // 处理返回数据 + guard let data = data else { + print("❌ 无返回数据") + return + } + + // 打印原始响应(调试用) + if let jsonString = String(data: data, encoding: .utf8) { + print("响应内容: \(jsonString)") + } + + // 尝试解析为 JSON 字典 + do { + if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] { + print("解析成功: \(json)") + + // 如果你有明确的模型,可以用 JSONDecoder 解码 LoginResponse + // let decoder = JSONDecoder() + // let loginResult = try decoder.decode(LoginResponse.self, from: data) + } + } catch { + print("❌ JSON 解析失败: \(error)") + } + } + } + + // 启动请求 + task.resume() +} diff --git a/wake/Utils/NetWork.swift b/wake/Utils/NetWork.swift new file mode 100644 index 0000000..0cdffc9 --- /dev/null +++ b/wake/Utils/NetWork.swift @@ -0,0 +1,34 @@ +import SwiftUI + +class Network: ObservableObject { + @Published var users: [User] = [] + + func getUsers() { + guard let url = URL(string: "http://192.168.31.156:31646/api/iam/login/password-login") else { fatalError("Missing URL") } + + let urlRequest = URLRequest(url: url) + + let dataTask = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in + if let error = error { + print("Request error: ", error) + return + } + + guard let response = response as? HTTPURLResponse else { return } + + if response.statusCode == 200 { + guard let data = data else { return } + DispatchQueue.main.async { + do { + let decodedUsers = try JSONDecoder().decode([User].self, from: data) + self.users = decodedUsers + } catch let error { + print("Error decoding: ", error) + } + } + } + } + + dataTask.resume() + } +} diff --git a/wake/Utils/PasswordLogin.swift b/wake/Utils/PasswordLogin.swift new file mode 100644 index 0000000..72e1426 --- /dev/null +++ b/wake/Utils/PasswordLogin.swift @@ -0,0 +1,49 @@ +import Foundation // 必须导入 + +struct LoginResponse: Codable { + let token: String? + let error: String? +} + +func callLoginAPI() { + // 1. 创建 URL + let urlString = "http://192.168.31.156:31646/api/v1/iam/login/password-login" + guard let url = URL(string: urlString) else { + print("Invalid URL") + return + } + + // 2. 准备请求体 + let body = ["username": "testUser", "password": "test123"] + guard let jsonData = try? JSONSerialization.data(withJSONObject: body) else { + print("JSON encoding failed") + return + } + print(jsonData) + // 3. 配置请求 + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.httpBody = jsonData + print(request) + // 4. 发送请求 + let task = URLSession.shared.dataTask(with: request) { data, response, error in + if let error = error { + print("Error: \(error.localizedDescription)") + return + } + + guard let data = data else { + print("No data received") + return + } + + do { + let result = try JSONDecoder().decode(LoginResponse.self, from: data) + print("Token: \(result.token ?? "nil")") + } catch { + print("Decoding error: \(error)") + } + } + task.resume() +} diff --git a/wake/Utils/User.swift b/wake/Utils/User.swift new file mode 100644 index 0000000..2587619 --- /dev/null +++ b/wake/Utils/User.swift @@ -0,0 +1,31 @@ +import Foundation + +struct User: Identifiable, Decodable { + var id: Int + var name: String + var username: String + var email: String + var address: Address + var phone: String + var website: String + var company: Company + + struct Address: Decodable { + var street: String + var suite: String + var city: String + var zipcode: String + var geo: Geo + + struct Geo: Decodable { + var lat: String + var lng: String + } + } + + struct Company: Decodable { + var name: String + var catchPhrase: String + var bs: String + } +} diff --git a/wake/View/.DS_Store b/wake/View/.DS_Store new file mode 100644 index 0000000..e3d77d1 Binary files /dev/null and b/wake/View/.DS_Store differ diff --git a/wake/View/Components/.DS_Store b/wake/View/Components/.DS_Store new file mode 100644 index 0000000..98fe9e1 Binary files /dev/null and b/wake/View/Components/.DS_Store differ diff --git a/wake/View/Components/Button.swift b/wake/View/Components/Button.swift new file mode 100644 index 0000000..4f7f097 --- /dev/null +++ b/wake/View/Components/Button.swift @@ -0,0 +1,114 @@ +import SwiftUI + +// 按钮类型 +enum ButtonType { + case primary // 主按钮(蓝色) + case secondary // 次要按钮(灰色边框) + case danger // 危险按钮(红色) +} + +// 按钮大小(可选) +enum ButtonSize { + case small + case medium + case large +} + +struct CustomButton: View { + // MARK: - 属性 + let text: String + let type: ButtonType + let size: ButtonSize + let fullWidth: Bool // 是否占满父容器宽度 + let isLoading: Bool // 是否加载中 + let action: () -> Void // 点击回调(无参数) + + // MARK: - 主体视图 + var body: some View { + Button(action: { + if !isLoading { // 防止加载时触发 + action() + } + }) { + if isLoading { + // 显示加载动画 + HStack { + ProgressView() // iOS 加载指示器 + .progressViewStyle(CircularProgressViewStyle(tint: .white)) + Text("加载中...") + } + } else { + Text(text) + .fontWeight(.medium) + } + } + .font(font(for: size)) + .padding(padding(for: size)) + .frame(maxWidth: fullWidth ? .infinity : (width(for: size))) + .background(backgroundColor) + .foregroundColor(.white) + .cornerRadius(8) + .overlay( + // 次要按钮:只显示边框 + RoundedRectangle(cornerRadius: 8) + .stroke(borderColor, lineWidth: 2) + .opacity(type == .secondary ? 1 : 0) + ) + .disabled(isLoading) // 加载时禁用点击 + .opacity(isLoading ? 0.8 : 1.0) + } + + // MARK: - 计算属性:背景色 + private var backgroundColor: Color { + switch type { + case .primary: + return .blue + case .secondary: + return .clear + case .danger: + return .red + } + } + + // MARK: - 计算属性:边框色 + private var borderColor: Color { + switch type { + case .secondary: + return .gray + default: + return .clear + } + } + + // MARK: - 辅助函数:字体 + private func font(for size: ButtonSize) -> Font { + switch size { + case .small: + return .caption + case .medium: + return .subheadline + case .large: + return .headline + } + } + + // MARK: - 辅助函数:内边距 + private func padding(for size: ButtonSize) -> EdgeInsets { + let horizontal = CGFloat(16) + let vertical: CGFloat + switch size { + case .small: + vertical = 6 + case .medium: + vertical = 10 + case .large: + vertical = 14 + } + return EdgeInsets(top: vertical, leading: horizontal, bottom: vertical, trailing: horizontal) + } + + // MARK: - 辅助函数:固定宽度(非全宽时) + private func width(for size: ButtonSize) -> CGFloat? { + return nil // 自适应宽度,除非你想要固定值 + } +} diff --git a/wake/View/Components/SheetModal.swift b/wake/View/Components/SheetModal.swift new file mode 100644 index 0000000..a2bcdde --- /dev/null +++ b/wake/View/Components/SheetModal.swift @@ -0,0 +1,55 @@ +import SwiftUI + +struct SlideInModal: View { + @Binding var isPresented: Bool + let onDismiss: () -> Void + let content: () -> Content + + // 动画配置 - 更慢的动画 + private let animation = Animation.spring( + response: 0.8, // 增加响应时间使动画更慢 + dampingFraction: 0.6, // 减少阻尼系数使弹跳更明显 + blendDuration: 0.8 // 增加混合时间使过渡更平滑 + ) + + var body: some View { + ZStack(alignment: .leading) { + // 遮罩背景 + if isPresented { + Color.black + .opacity(0.5) + .edgesIgnoringSafeArea(.all) + .transition(.opacity) + .zIndex(1) + .onTapGesture { + withAnimation(animation) { + isPresented = false + onDismiss() + } + } + + // 弹窗内容 + VStack(spacing: 0) { + // 顶部安全区域占位 + Color.clear + .frame(height: UIApplication.shared.windows.first?.safeAreaInsets.top ?? 0) + + // 内容区域 + content() + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding(.bottom, UIApplication.shared.windows.first?.safeAreaInsets.bottom ?? 0) + } + .frame(width: UIScreen.main.bounds.width * 0.8) + .frame(maxHeight: .infinity) + .background(Color(.systemBackground)) + .edgesIgnoringSafeArea(.vertical) + .offset(x: isPresented ? 0 : -UIScreen.main.bounds.width) + .zIndex(2) + .transition(.move(edge: .leading)) + .animation(animation, value: isPresented) + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .edgesIgnoringSafeArea(.all) + } +} \ No newline at end of file diff --git a/wake/View/Components/TextInput.swift b/wake/View/Components/TextInput.swift new file mode 100644 index 0000000..7bbe8eb --- /dev/null +++ b/wake/View/Components/TextInput.swift @@ -0,0 +1,50 @@ +import SwiftUI + +// 输入框类型枚举(更优雅的方式) +enum TextFieldType { + case username + case password + case number + case email + case text +} + +struct CustomTextField: View { + // MARK: - 属性 + let placeholder: String + let type: TextFieldType + + // 双向绑定的值(关键!) + @Binding var value: String + + // MARK: - 主体视图 + var body: some View { + Group { // 使用 Group 统一修饰符 + if type == .password { + // 密码框 + SecureField(placeholder, text: $value) + } else { + // 普通文本框 + TextField(placeholder, text: $value) + .textInputAutocapitalization(.never) // 关闭自动大写 + .disableAutocorrection(true) // 关闭自动纠错 + } + } + .padding() + .background(Color(.systemGray6)) + .cornerRadius(8) + .keyboardType(keyboardType(for: type)) // 设置键盘类型 + } + + // MARK: - 根据类型返回键盘类型 + private func keyboardType(for type: TextFieldType) -> UIKeyboardType { + switch type { + case .number: + return .numberPad + case .email: + return .emailAddress + default: + return .default + } + } +} diff --git a/wake/View/Login/.DS_Store b/wake/View/Login/.DS_Store new file mode 100644 index 0000000..dfa523b Binary files /dev/null and b/wake/View/Login/.DS_Store differ diff --git a/wake/View/Login/Login.swift b/wake/View/Login/Login.swift new file mode 100644 index 0000000..8729e26 --- /dev/null +++ b/wake/View/Login/Login.swift @@ -0,0 +1,270 @@ +import SwiftUI +import Alamofire + +struct Post: Codable { + let id: Int + let title: String + let body: String + let userId: Int +} +struct Login: Encodable { + let account: String + let password: String +} + +struct LoginView: View { + @State private var showModal = false + @State private var showSettings = false + @State private var contentOffset: CGFloat = 0 + // 用户名称/邮箱 + @State private var username="" + // 密码 + @State private var password="" + // 登录loading + @State private var isLoading=false + + // 登录点击事件 + func handleLogin() { + withAnimation { + isLoading = true + } + // 示例调用 + passwordLogin(username: "jyq@memo.cn", password: "111111") + // 注意:这里应该在网络请求完成后设置 isLoading = false + // 例如在 passwordLogin 的回调中 + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + withAnimation { + self.isLoading = false + } + } + } + + // get 请求 + func get(){ + AF.request("http://192.168.31.156:31646/api/v1/iam/access-token-refresh").response { response in + debugPrint(response) + } + } + // post 请求 + func post(){ + let login = Login(account: username, password: password) + print(login) + AF.request("http://192.168.31.156:31646/api/v1/iam/login/password-login", method: .post,parameters: login).response{ + response in debugPrint(response) + } + } + +// func createPost() { +// Task { +// do { +// print("12132412354365342") +// let newPost = try await NetworkManager.shared.request( +// endpoint: "/iam/login/password-login", +// method: .post, +// parameters: [ +// "account": username, +// "password": password +// ] +// ) as Post +// // 在字符串中添加变量 \(变量) +// print("登录成功:$newPost.id)\(username)") +// } catch { +// print("登录失败:$error)") +// +// } +// } +// } + var body: some View { + ZStack { + // Main content with slide effect + 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 + } + }) { + Image(systemName: "gearshape") + .font(.title2) + .padding() + } + } + + Text("邮箱登录").font(.title) + + // 登录表单 + VStack { + // 账号 + CustomTextField( + placeholder: "请输入用户名", + type: .username, + value: $username + ) + // 密码 + CustomTextField( + placeholder: "请输入密码", + type: .password, + value: $password + ) + // 登录 + CustomButton( + text: "登录", + type: .primary, + size: .large, + fullWidth: true, + isLoading: isLoading + ) { + handleLogin() + } + } + .padding() + 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 + SlideInModal(isPresented: $showModal, onDismiss: { + withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) { + showModal = false + } + }) { + 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) + + 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(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: { + print("Setting") + }) { + HStack(spacing: 16) { + Image(systemName: "person.circle.fill") + .foregroundColor(.purple) + .frame(width: 24, height: 24) + Text("Setting") + .font(.headline) + .foregroundColor(.primary) + Spacer() + } + .padding() + .cornerRadius(10) + .contentShape(Rectangle()) // 使整个区域可点击 + } + .buttonStyle(PlainButtonStyle()) // 移除按钮默认样式 + } + .padding(.horizontal, 16) + // 这里可以添加其他设置项 + Spacer() + } + .padding(.vertical, 8) + } + } + } + } + +#Preview { + LoginView() +} diff --git a/wake/View/Owner/.DS_Store b/wake/View/Owner/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/wake/View/Owner/.DS_Store differ diff --git a/wake/View/Owner/ModalContentView.swift b/wake/View/Owner/ModalContentView.swift new file mode 100644 index 0000000..5a884c7 --- /dev/null +++ b/wake/View/Owner/ModalContentView.swift @@ -0,0 +1,111 @@ +import SwiftUI + +struct ModalContentView: View { + let goBack: () -> Void + @Environment(\.dismiss) private var dismissModal + + var body: some View { + 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()) + + VStack(alignment: .leading, spacing: 4) { + Text("用户名") + .font(.headline) + Text("ID: 12345678") + .font(.subheadline) + .foregroundColor(.secondary) + } + + Spacer() + } + .padding(.horizontal, 16) + .padding(.top, 16) + + // 会员区域 + VStack(alignment: .leading, spacing: 8) { + Text("会员等级").font(.headline) + 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) { + ModalButton(icon: "crown.fill", color: .orange, text: "My Memories") + ModalButton(icon: "clock.fill", color: .blue, text: "My Bind Box") + + // 跳转设置页 + Button(action: { + goBack() + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + // 模拟从左边滑入的效果 + } + }) { + 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) + } + .buttonStyle(PlainButtonStyle()) + } + .padding(.horizontal, 16) + + Spacer() + } + .padding(.vertical, 8) + } +} + +// MARK: - 按钮组件 +struct ModalButton: View { + let icon: String + let color: Color + let text: String + let action: () -> Void + + init(icon: String, color: Color, text: String, action: @escaping () -> Void = {}) { + self.icon = icon + self.color = color + self.text = text + self.action = action + } + + var body: some View { + Button(action: action) { + HStack(spacing: 16) { + Image(systemName: icon) + .foregroundColor(color) + .frame(width: 24, height: 24) + Text(text) + .font(.headline) + .foregroundColor(.primary) + Spacer() + } + .padding() + .background(Color(.systemBackground)) + .cornerRadius(10) + } + .buttonStyle(PlainButtonStyle()) + } +} diff --git a/wake/View/Owner/SettingsView.swift b/wake/View/Owner/SettingsView.swift new file mode 100644 index 0000000..d49e3ef --- /dev/null +++ b/wake/View/Owner/SettingsView.swift @@ -0,0 +1,201 @@ +import SwiftUI + +struct SettingsView: View { + @Environment(\.dismiss) private var dismiss + @State private var isAppeared = false + @Binding var isPresented: Bool + + // Animation configuration + private let animation = Animation.spring( + response: 0.8, + dampingFraction: 0.6, + blendDuration: 0.8 + ) + + 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) { + isPresented = false + } + }) { + HStack(spacing: 4) { + Image(systemName: "chevron.left") + .font(.system(size: 16, weight: .medium)) + .foregroundColor(.blue) + Text("Back") + .font(.system(size: 17, weight: .regular)) + .foregroundColor(.blue) + } + } + } + } + .animation(animation, value: isAppeared) + } + +} +} + +// MARK: - Preview +#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") + } +} diff --git a/wake/WakeApp.swift b/wake/WakeApp.swift new file mode 100644 index 0000000..84b8be9 --- /dev/null +++ b/wake/WakeApp.swift @@ -0,0 +1,29 @@ +// +// WakeApp.swift +// Wake +// +// Created by elliwood on 2025/8/11. +// + +import SwiftUI + +@main +struct WakeApp: App { + var body: some Scene { + WindowGroup { + ContentView() +// SettingsView() + // 导航栏按钮 + // TabView{ + // ContentView() + // .tabItem{ + // Label("wake", systemImage: "book") + // } + // SettingView() + // .tabItem{ + // Label("setting", systemImage: "gear") + // } + // } + } + } +}