This commit is contained in:
elliwood 2025-08-14 19:49:54 +08:00
commit 423ff363f6
27 changed files with 1688 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

0
.gitignore vendored Normal file
View File

View File

@ -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 = "<group>"; };
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 = "<group>";
};
/* 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 = "<group>";
};
ABB4E2092E4B75D900660198 /* Products */ = {
isa = PBXGroup;
children = (
ABB4E2082E4B75D900660198 /* wake.app */,
);
name = Products;
sourceTree = "<group>";
};
/* 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 */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -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
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array/>
</plist>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Bucket
uuid = "A774AEAB-F2DE-4CA6-8FAA-A05AB418F685"
type = "1"
version = "2.0">
</Bucket>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>wake.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
</dict>
</plist>

BIN
wake/.DS_Store vendored Normal file

Binary file not shown.

228
wake/ContentView.swift Normal file
View File

@ -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()
}

BIN
wake/Utils/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,94 @@
import Foundation
//
//struct LoginResponse: Codable {
// let code: Int?
// let message: String?
// let [String: AnyCodable]? // token, userId
//}
// Dictionary<String, Any> 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()
}

34
wake/Utils/NetWork.swift Normal file
View File

@ -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()
}
}

View File

@ -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()
}

31
wake/Utils/User.swift Normal file
View File

@ -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
}
}

BIN
wake/View/.DS_Store vendored Normal file

Binary file not shown.

BIN
wake/View/Components/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -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 //
}
}

View File

@ -0,0 +1,55 @@
import SwiftUI
struct SlideInModal<Content: View>: 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)
}
}

View File

@ -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
}
}
}

BIN
wake/View/Login/.DS_Store vendored Normal file

Binary file not shown.

270
wake/View/Login/Login.swift Normal file
View File

@ -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()
}

BIN
wake/View/Owner/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -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())
}
}

View File

@ -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")
}
}

29
wake/WakeApp.swift Normal file
View File

@ -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")
// }
// }
}
}
}