From e29994f49d94b51a63296a86c8569cafd9ba4da7 Mon Sep 17 00:00:00 2001 From: AndyChen1024 Date: Mon, 14 Sep 2026 21:10:43 +0800 Subject: [PATCH 01/10] =?UTF-8?q?chore(skills):=20=E5=BC=95=E5=85=A5=20age?= =?UTF-8?q?nt=20skills=20=E4=B8=8E=E9=94=81=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skills-lock.json | 89 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 skills-lock.json diff --git a/skills-lock.json b/skills-lock.json new file mode 100644 index 0000000..95d49ad --- /dev/null +++ b/skills-lock.json @@ -0,0 +1,89 @@ +{ + "version": 1, + "skills": { + "brainstorming": { + "source": "obra/superpowers", + "sourceType": "github", + "skillPath": "skills/brainstorming/SKILL.md", + "computedHash": "b58d55b64742ecd37923985ec7bdfbe678ffbcb9895562abd28c0a32dcacd7fb" + }, + "dispatching-parallel-agents": { + "source": "obra/superpowers", + "sourceType": "github", + "skillPath": "skills/dispatching-parallel-agents/SKILL.md", + "computedHash": "b011107f17188e0b2898bb1856b5bac8b49537cc86e737c82126558a235bd62a" + }, + "executing-plans": { + "source": "obra/superpowers", + "sourceType": "github", + "skillPath": "skills/executing-plans/SKILL.md", + "computedHash": "78a07800ee99a5861ae10be59d79fad085fdca10444e8aaeb2691597e357cff8" + }, + "finishing-a-development-branch": { + "source": "obra/superpowers", + "sourceType": "github", + "skillPath": "skills/finishing-a-development-branch/SKILL.md", + "computedHash": "4f571e7befb8b0946d9d7770232c818812412e9ab1dfb811f847edfba1fc6275" + }, + "receiving-code-review": { + "source": "obra/superpowers", + "sourceType": "github", + "skillPath": "skills/receiving-code-review/SKILL.md", + "computedHash": "59361179fc9225173e0c145ffd9bafbf9fdbf8b43ce3dc308497a0b4f83a6063" + }, + "requesting-code-review": { + "source": "obra/superpowers", + "sourceType": "github", + "skillPath": "skills/requesting-code-review/SKILL.md", + "computedHash": "004f340ae65811095d6a9bb0a759c87d0a8085c5e705dbb019b011626293d2e9" + }, + "subagent-driven-development": { + "source": "obra/superpowers", + "sourceType": "github", + "skillPath": "skills/subagent-driven-development/SKILL.md", + "computedHash": "367cc1d3216d6672b947c8c6583dbd6440ea407beb6da75a97d3a8ee1a23640a" + }, + "systematic-debugging": { + "source": "obra/superpowers", + "sourceType": "github", + "skillPath": "skills/systematic-debugging/SKILL.md", + "computedHash": "a2750dfbaed1690da73b120254892c4f926b4576cb82eb16fc877783941fe39b" + }, + "test-driven-development": { + "source": "obra/superpowers", + "sourceType": "github", + "skillPath": "skills/test-driven-development/SKILL.md", + "computedHash": "a4126a9c5f17be3c228aa9e5755898ade283448b86099f56f40852e8c14532be" + }, + "using-git-worktrees": { + "source": "obra/superpowers", + "sourceType": "github", + "skillPath": "skills/using-git-worktrees/SKILL.md", + "computedHash": "48c8a1040e378e0cb7208107294e3afa9067b9370c3f33b49a443eb6b7528b0d" + }, + "using-superpowers": { + "source": "obra/superpowers", + "sourceType": "github", + "skillPath": "skills/using-superpowers/SKILL.md", + "computedHash": "c7abe8bdc2099bbe4f245454d32c7563ccd2bb41dcc218b02b461c2acc3c6ae6" + }, + "verification-before-completion": { + "source": "obra/superpowers", + "sourceType": "github", + "skillPath": "skills/verification-before-completion/SKILL.md", + "computedHash": "b529822bdac4b197b180fe12225cff86b930b65910435103c10622df7f041651" + }, + "writing-plans": { + "source": "obra/superpowers", + "sourceType": "github", + "skillPath": "skills/writing-plans/SKILL.md", + "computedHash": "27c75044e444a4fc68798c3f799dc23eaff3a6bfabff32e6ad75bb20f881c094" + }, + "writing-skills": { + "source": "obra/superpowers", + "sourceType": "github", + "skillPath": "skills/writing-skills/SKILL.md", + "computedHash": "e5dfc1a942f56dc757421b0d87ae1f38ab693ebc71138e65268fd408bb5c93d3" + } + } +} From 599f1c7f8e44b0cb49433281e21926cf1ec51930 Mon Sep 17 00:00:00 2001 From: AndyChen1024 Date: Mon, 14 Sep 2026 21:10:43 +0800 Subject: [PATCH 02/10] =?UTF-8?q?chore:=20=E5=BF=BD=E7=95=A5=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E4=BA=A7=E7=89=A9=E4=B8=8E=20AI=20=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c015afc --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# 临时目录(日志/截图等) +tmp/ + +# macOS +.DS_Store + +# Xcode +xcuserdata/ +*.xcuserstate +DerivedData/ +build/ + +# Swift Package Manager +.build/ +.swiftpm/ + +# AI 工具本地配置 / 安装的技能(可经 skills-lock.json 重装) +.claude/ +.agents/ From 295efac07c9398f5d75451da39527572ba1ebb9e Mon Sep 17 00:00:00 2001 From: AndyChen1024 Date: Mon, 14 Sep 2026 21:10:43 +0800 Subject: [PATCH 03/10] =?UTF-8?q?build:=20=E5=BC=95=E5=85=A5=20IosClientKi?= =?UTF-8?q?t=20SPM=20=E5=8C=85=E6=B8=85=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IosClientKit/Package.swift | 52 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 IosClientKit/Package.swift diff --git a/IosClientKit/Package.swift b/IosClientKit/Package.swift new file mode 100644 index 0000000..160ecfc --- /dev/null +++ b/IosClientKit/Package.swift @@ -0,0 +1,52 @@ +// swift-tools-version: 6.0 +import PackageDescription + +let package = Package( + name: "IosClientKit", + defaultLocalization: "en", + platforms: [ + .iOS("17.0"), + .macOS(.v14), + ], + products: [ + // 组合根:App 宿主只链接这一个产品,其余模块经依赖传递带入。 + .library(name: "App", targets: ["App"]), + ], + targets: [ + // 叶子模块 + .target(name: "CoreModel"), + .target(name: "CoreCommon"), + .target(name: "DesignSystem"), + + // 领域层(纯 Swift:不 import SwiftUI / 数据框架) + .target(name: "CoreDomain", dependencies: ["CoreModel"]), + + // 基础设施层 + .target(name: "CoreNetwork", dependencies: ["CoreModel", "CoreCommon"]), + .target(name: "CoreDatabase", dependencies: ["CoreModel"]), + .target( + name: "CoreRepository", + dependencies: ["CoreDomain", "CoreNetwork", "CoreDatabase", "CoreModel", "CoreCommon"] + ), + + // UI 层 + .target(name: "CoreUI", dependencies: ["DesignSystem", "CoreCommon"]), + + // Feature 层 + .target( + name: "FeatureHome", + dependencies: ["CoreDomain", "CoreUI", "CoreModel", "CoreCommon"] + ), + .target(name: "FeatureAuth", dependencies: ["CoreDomain", "CoreUI", "CoreModel"]), + + // 组合根(依赖一切,用于 DI 装配) + .target( + name: "App", + dependencies: [ + "FeatureHome", "FeatureAuth", "CoreRepository", + "CoreUI", "DesignSystem", "CoreDomain", "CoreModel", + ] + ), + ], + swiftLanguageModes: [.v5] +) From 4fe0a26432c4eebbe4874e15cc8837215bab4173 Mon Sep 17 00:00:00 2001 From: AndyChen1024 Date: Mon, 14 Sep 2026 21:10:44 +0800 Subject: [PATCH 04/10] =?UTF-8?q?chore:=20core=20=E5=A4=9A=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E9=AA=A8=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IosClientKit/Sources/CoreCommon/Logger.swift | 14 +++++++ .../CoreDatabase/PersistenceController.swift | 28 +++++++++++++ .../Sources/CoreDomain/HomeRepository.swift | 8 ++++ .../Sources/CoreDomain/HomeUseCase.swift | 16 ++++++++ IosClientKit/Sources/CoreModel/Habit.swift | 27 +++++++++++++ IosClientKit/Sources/CoreModel/HomeData.swift | 19 +++++++++ .../Sources/CoreNetwork/APIClient.swift | 37 +++++++++++++++++ .../Sources/CoreNetwork/HomeDTO.swift | 15 +++++++ .../CoreRepository/FakeHomeRepository.swift | 40 +++++++++++++++++++ .../Sources/CoreUI/EmptyStateView.swift | 16 ++++++++ IosClientKit/Sources/CoreUI/ErrorView.swift | 22 ++++++++++ IosClientKit/Sources/CoreUI/LoadingView.swift | 11 +++++ .../Sources/CoreUI/PlaceholderView.swift | 16 ++++++++ .../Sources/DesignSystem/DesignTokens.swift | 25 ++++++++++++ 14 files changed, 294 insertions(+) create mode 100644 IosClientKit/Sources/CoreCommon/Logger.swift create mode 100644 IosClientKit/Sources/CoreDatabase/PersistenceController.swift create mode 100644 IosClientKit/Sources/CoreDomain/HomeRepository.swift create mode 100644 IosClientKit/Sources/CoreDomain/HomeUseCase.swift create mode 100644 IosClientKit/Sources/CoreModel/Habit.swift create mode 100644 IosClientKit/Sources/CoreModel/HomeData.swift create mode 100644 IosClientKit/Sources/CoreNetwork/APIClient.swift create mode 100644 IosClientKit/Sources/CoreNetwork/HomeDTO.swift create mode 100644 IosClientKit/Sources/CoreRepository/FakeHomeRepository.swift create mode 100644 IosClientKit/Sources/CoreUI/EmptyStateView.swift create mode 100644 IosClientKit/Sources/CoreUI/ErrorView.swift create mode 100644 IosClientKit/Sources/CoreUI/LoadingView.swift create mode 100644 IosClientKit/Sources/CoreUI/PlaceholderView.swift create mode 100644 IosClientKit/Sources/DesignSystem/DesignTokens.swift diff --git a/IosClientKit/Sources/CoreCommon/Logger.swift b/IosClientKit/Sources/CoreCommon/Logger.swift new file mode 100644 index 0000000..9543a53 --- /dev/null +++ b/IosClientKit/Sources/CoreCommon/Logger.swift @@ -0,0 +1,14 @@ +/// 全局日志工具(Android 端 Timber 的轻量替代)。 +public enum Logger { + public static func info(_ message: @autoclosure () -> String) { + #if DEBUG + print("[INFO] \(message())") + #endif + } + + public static func error(_ message: @autoclosure () -> String) { + #if DEBUG + print("[ERROR] \(message())") + #endif + } +} diff --git a/IosClientKit/Sources/CoreDatabase/PersistenceController.swift b/IosClientKit/Sources/CoreDatabase/PersistenceController.swift new file mode 100644 index 0000000..ac17cda --- /dev/null +++ b/IosClientKit/Sources/CoreDatabase/PersistenceController.swift @@ -0,0 +1,28 @@ +import Foundation +import SwiftData + +/// 打卡习惯的持久化实体(对应 Android Room 的 @Entity)。 +@Model +public final class HabitEntity { + public var id: String + public var title: String + public var detail: String + public var streakDays: Int + public var createdAt: Date + + public init(id: String, title: String, detail: String, streakDays: Int, createdAt: Date) { + self.id = id + self.title = title + self.detail = detail + self.streakDays = streakDays + self.createdAt = createdAt + } +} + +/// SwiftData 容器工厂(对应 Android RoomDatabase)。 +public enum PersistenceController { + public static func makeModelContainer(inMemory: Bool = false) throws -> ModelContainer { + let configuration = ModelConfiguration(isStoredInMemoryOnly: inMemory) + return try ModelContainer(for: HabitEntity.self, configurations: configuration) + } +} diff --git a/IosClientKit/Sources/CoreDomain/HomeRepository.swift b/IosClientKit/Sources/CoreDomain/HomeRepository.swift new file mode 100644 index 0000000..8644765 --- /dev/null +++ b/IosClientKit/Sources/CoreDomain/HomeRepository.swift @@ -0,0 +1,8 @@ +import CoreModel + +/// Home 数据仓库协议(定义在 domain 层,实现放 CoreRepository)。 +/// Kotlin: interface HomeRepository { fun observeHome(): Flow } +public protocol HomeRepository: Sendable { + /// Kotlin: Flow → Swift: AsyncThrowingStream + func observeHome() -> AsyncThrowingStream +} diff --git a/IosClientKit/Sources/CoreDomain/HomeUseCase.swift b/IosClientKit/Sources/CoreDomain/HomeUseCase.swift new file mode 100644 index 0000000..5b190cd --- /dev/null +++ b/IosClientKit/Sources/CoreDomain/HomeUseCase.swift @@ -0,0 +1,16 @@ +import CoreModel + +/// 获取 Home 数据的用例。 +/// Kotlin: class HomeUseCase(...) { suspend operator fun invoke(): HomeData } +public struct HomeUseCase: Sendable { + private let repository: HomeRepository + + public init(repository: HomeRepository) { + self.repository = repository + } + + /// Kotlin: Flow → Swift: AsyncThrowingStream + public func observeHome() -> AsyncThrowingStream { + repository.observeHome() + } +} diff --git a/IosClientKit/Sources/CoreModel/Habit.swift b/IosClientKit/Sources/CoreModel/Habit.swift new file mode 100644 index 0000000..a0dc84a --- /dev/null +++ b/IosClientKit/Sources/CoreModel/Habit.swift @@ -0,0 +1,27 @@ +import Foundation + +/// 一个打卡习惯(对应 Android 端 Habit)。 +public struct Habit: Identifiable, Hashable, Sendable { + public let id: String + public let title: String + /// 备注 / 目标描述。 + public let detail: String + /// 今日是否已打卡。 + public let isCheckedInToday: Bool + /// 连续打卡天数。 + public let streakDays: Int + + public init( + id: String, + title: String, + detail: String, + isCheckedInToday: Bool, + streakDays: Int + ) { + self.id = id + self.title = title + self.detail = detail + self.isCheckedInToday = isCheckedInToday + self.streakDays = streakDays + } +} diff --git a/IosClientKit/Sources/CoreModel/HomeData.swift b/IosClientKit/Sources/CoreModel/HomeData.swift new file mode 100644 index 0000000..a76e73f --- /dev/null +++ b/IosClientKit/Sources/CoreModel/HomeData.swift @@ -0,0 +1,19 @@ +import Foundation + +/// Home 页面的聚合数据(对应 Android 端 HomeData)。 +public struct HomeData: Identifiable, Hashable, Sendable { + public let id: String + /// 顶部问候语,例如「早上好,Andy」。 + public let greeting: String + /// 今日打卡摘要,例如「已完成 2 / 4」。 + public let summary: String + /// 今日待打卡的习惯列表。 + public let habits: [Habit] + + public init(id: String, greeting: String, summary: String, habits: [Habit]) { + self.id = id + self.greeting = greeting + self.summary = summary + self.habits = habits + } +} diff --git a/IosClientKit/Sources/CoreNetwork/APIClient.swift b/IosClientKit/Sources/CoreNetwork/APIClient.swift new file mode 100644 index 0000000..e4017b3 --- /dev/null +++ b/IosClientKit/Sources/CoreNetwork/APIClient.swift @@ -0,0 +1,37 @@ +import Foundation + +/// 网络错误。 +public enum NetworkError: Error, Sendable { + case invalidResponse + case http(statusCode: Int) + case decoding(String) +} + +/// 通用 HTTP 客户端(对应 Android Retrofit 的最小抽象)。 +public protocol APIClient: Sendable { + func get(_ url: URL) async throws -> T +} + +/// 基于 URLSession 的 APIClient 实现。 +public struct URLSessionAPIClient: APIClient { + private let session: URLSession + + public init(session: URLSession = .shared) { + self.session = session + } + + public func get(_ url: URL) async throws -> T { + let (data, response) = try await session.data(from: url) + guard let http = response as? HTTPURLResponse else { + throw NetworkError.invalidResponse + } + guard (200..<300).contains(http.statusCode) else { + throw NetworkError.http(statusCode: http.statusCode) + } + do { + return try JSONDecoder().decode(T.self, from: data) + } catch { + throw NetworkError.decoding(error.localizedDescription) + } + } +} diff --git a/IosClientKit/Sources/CoreNetwork/HomeDTO.swift b/IosClientKit/Sources/CoreNetwork/HomeDTO.swift new file mode 100644 index 0000000..a020b4b --- /dev/null +++ b/IosClientKit/Sources/CoreNetwork/HomeDTO.swift @@ -0,0 +1,15 @@ +import Foundation + +/// 示例网络 DTO(与领域 Model 分离,对应 Kotlin @Serializable 的 data class)。 +/// 注意:DTO 只承载接口字段,不直接进入 UI;领域层用 CoreModel.HomeData。 +public struct HomeDTO: Codable, Sendable { + public let id: String + public let greeting: String + public let habits: [HabitDTO] +} + +public struct HabitDTO: Codable, Sendable { + public let id: String + public let title: String + public let checkedIn: Bool +} diff --git a/IosClientKit/Sources/CoreRepository/FakeHomeRepository.swift b/IosClientKit/Sources/CoreRepository/FakeHomeRepository.swift new file mode 100644 index 0000000..b822687 --- /dev/null +++ b/IosClientKit/Sources/CoreRepository/FakeHomeRepository.swift @@ -0,0 +1,40 @@ +import CoreCommon +import CoreDomain +import CoreModel + +/// Fake 仓库实现(骨架阶段返回假数据,后续替换为 OfflineFirst 实现)。 +public struct FakeHomeRepository: HomeRepository { + public init() {} + + public func observeHome() -> AsyncThrowingStream { + AsyncThrowingStream { continuation in + let task = Task { + do { + // 模拟网络延迟。 + try await Task.sleep(for: .milliseconds(400)) + try Task.checkCancellation() + Logger.info("FakeHomeRepository 返回假数据") + continuation.yield(Self.fakeData) + continuation.finish() + } catch { + continuation.finish(throwing: error) + } + } + continuation.onTermination = { _ in + task.cancel() + } + } + } + + private static let fakeData = HomeData( + id: "home-1", + greeting: "早上好,Andy", + summary: "今日已完成 2 / 4", + habits: [ + Habit(id: "habit-1", title: "晨跑 5 公里", detail: "保持运动习惯", isCheckedInToday: true, streakDays: 12), + Habit(id: "habit-2", title: "阅读 30 分钟", detail: "每天进步一点", isCheckedInToday: true, streakDays: 8), + Habit(id: "habit-3", title: "喝水 8 杯", detail: "健康补水", isCheckedInToday: false, streakDays: 0), + Habit(id: "habit-4", title: "写日记", detail: "记录今日所思", isCheckedInToday: false, streakDays: 3), + ] + ) +} diff --git a/IosClientKit/Sources/CoreUI/EmptyStateView.swift b/IosClientKit/Sources/CoreUI/EmptyStateView.swift new file mode 100644 index 0000000..6ed9656 --- /dev/null +++ b/IosClientKit/Sources/CoreUI/EmptyStateView.swift @@ -0,0 +1,16 @@ +import SwiftUI + +/// 空状态占位视图。 +public struct EmptyStateView: View { + private let title: String + private let systemImage: String + + public init(title: String, systemImage: String = "tray") { + self.title = title + self.systemImage = systemImage + } + + public var body: some View { + ContentUnavailableView(title, systemImage: systemImage) + } +} diff --git a/IosClientKit/Sources/CoreUI/ErrorView.swift b/IosClientKit/Sources/CoreUI/ErrorView.swift new file mode 100644 index 0000000..43539ec --- /dev/null +++ b/IosClientKit/Sources/CoreUI/ErrorView.swift @@ -0,0 +1,22 @@ +import SwiftUI + +/// 错误占位视图(带重试)。 +public struct ErrorView: View { + private let message: String + private let retry: () -> Void + + public init(message: String, retry: @escaping () -> Void) { + self.message = message + self.retry = retry + } + + public var body: some View { + ContentUnavailableView { + Label("出错了", systemImage: "exclamationmark.triangle") + } description: { + Text(message) + } actions: { + Button("重试", action: retry) + } + } +} diff --git a/IosClientKit/Sources/CoreUI/LoadingView.swift b/IosClientKit/Sources/CoreUI/LoadingView.swift new file mode 100644 index 0000000..3005dec --- /dev/null +++ b/IosClientKit/Sources/CoreUI/LoadingView.swift @@ -0,0 +1,11 @@ +import SwiftUI + +/// 加载中占位视图。 +public struct LoadingView: View { + public init() {} + + public var body: some View { + ProgressView("加载中…") + .frame(maxWidth: .infinity, maxHeight: .infinity) + } +} diff --git a/IosClientKit/Sources/CoreUI/PlaceholderView.swift b/IosClientKit/Sources/CoreUI/PlaceholderView.swift new file mode 100644 index 0000000..945171e --- /dev/null +++ b/IosClientKit/Sources/CoreUI/PlaceholderView.swift @@ -0,0 +1,16 @@ +import SwiftUI + +/// 尚未实现的 Tab 占位视图。 +public struct PlaceholderView: View { + private let title: String + private let systemImage: String + + public init(_ title: String, systemImage: String) { + self.title = title + self.systemImage = systemImage + } + + public var body: some View { + ContentUnavailableView(title, systemImage: systemImage) + } +} diff --git a/IosClientKit/Sources/DesignSystem/DesignTokens.swift b/IosClientKit/Sources/DesignSystem/DesignTokens.swift new file mode 100644 index 0000000..2be3ef5 --- /dev/null +++ b/IosClientKit/Sources/DesignSystem/DesignTokens.swift @@ -0,0 +1,25 @@ +import SwiftUI + +/// 设计 tokens(颜色 / 间距 / 圆角),对应 Android DesignSystem。 +public enum DS { + public enum Color { + public static let accent = SwiftUI.Color.accentColor + public static let success = SwiftUI.Color.green + public static let danger = SwiftUI.Color.red + public static let warning = SwiftUI.Color.orange + } + + public enum Spacing { + public static let xs: CGFloat = 4 + public static let sm: CGFloat = 8 + public static let md: CGFloat = 16 + public static let lg: CGFloat = 24 + public static let xl: CGFloat = 32 + } + + public enum Radius { + public static let sm: CGFloat = 8 + public static let md: CGFloat = 12 + public static let lg: CGFloat = 16 + } +} From b5de8ddfbd12d04778f60a12fd2c11539d064334 Mon Sep 17 00:00:00 2001 From: AndyChen1024 Date: Mon, 14 Sep 2026 21:10:44 +0800 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20feature=20=E5=A4=9A=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E9=AA=A8=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/FeatureAuth/AuthScreen.swift | 10 +++ .../Sources/FeatureHome/HomeScreen.swift | 77 +++++++++++++++++++ .../Sources/FeatureHome/HomeUiState.swift | 9 +++ .../Sources/FeatureHome/HomeViewModel.swift | 34 ++++++++ 4 files changed, 130 insertions(+) create mode 100644 IosClientKit/Sources/FeatureAuth/AuthScreen.swift create mode 100644 IosClientKit/Sources/FeatureHome/HomeScreen.swift create mode 100644 IosClientKit/Sources/FeatureHome/HomeUiState.swift create mode 100644 IosClientKit/Sources/FeatureHome/HomeViewModel.swift diff --git a/IosClientKit/Sources/FeatureAuth/AuthScreen.swift b/IosClientKit/Sources/FeatureAuth/AuthScreen.swift new file mode 100644 index 0000000..c1f3cd3 --- /dev/null +++ b/IosClientKit/Sources/FeatureAuth/AuthScreen.swift @@ -0,0 +1,10 @@ +import SwiftUI + +/// FeatureAuth 占位(后续实现登录 / 注册)。 +public struct AuthScreen: View { + public init() {} + + public var body: some View { + ContentUnavailableView("登录", systemImage: "person.crop.circle") + } +} diff --git a/IosClientKit/Sources/FeatureHome/HomeScreen.swift b/IosClientKit/Sources/FeatureHome/HomeScreen.swift new file mode 100644 index 0000000..79aa4a1 --- /dev/null +++ b/IosClientKit/Sources/FeatureHome/HomeScreen.swift @@ -0,0 +1,77 @@ +import SwiftUI +import CoreModel +import CoreUI +import DesignSystem + +public struct HomeScreen: View { + private let viewModel: HomeViewModel + + public init(viewModel: HomeViewModel) { + self.viewModel = viewModel + } + + public var body: some View { + content + .navigationTitle("首页") + .task { viewModel.load() } + } + + @ViewBuilder + private var content: some View { + switch viewModel.uiState { + case .loading: + LoadingView() + case .empty: + EmptyStateView(title: "今天还没有待打卡的习惯", systemImage: "checkmark.circle") + case .success(let data): + successContent(data) + case .error(let message): + ErrorView(message: message) { viewModel.load() } + } + } + + private func successContent(_ data: HomeData) -> some View { + List { + Section { + VStack(alignment: .leading, spacing: DS.Spacing.sm) { + Text(data.greeting) + .font(.largeTitle.bold()) + Text(data.summary) + .font(.subheadline) + .foregroundStyle(.secondary) + } + .padding(.vertical, DS.Spacing.sm) + } + + Section("今日打卡") { + ForEach(data.habits) { habit in + HabitRow(habit: habit) + } + } + } + } +} + +private struct HabitRow: View { + let habit: Habit + + var body: some View { + HStack { + Image(systemName: habit.isCheckedInToday ? "checkmark.circle.fill" : "circle") + .foregroundStyle(habit.isCheckedInToday ? DS.Color.success : DS.Color.warning) + .font(.title3) + VStack(alignment: .leading, spacing: 2) { + Text(habit.title) + .font(.headline) + Text(habit.detail) + .font(.caption) + .foregroundStyle(.secondary) + } + Spacer() + Text("🔥 \(habit.streakDays) 天") + .font(.caption) + .foregroundStyle(.secondary) + } + .padding(.vertical, DS.Spacing.xs) + } +} diff --git a/IosClientKit/Sources/FeatureHome/HomeUiState.swift b/IosClientKit/Sources/FeatureHome/HomeUiState.swift new file mode 100644 index 0000000..3926a18 --- /dev/null +++ b/IosClientKit/Sources/FeatureHome/HomeUiState.swift @@ -0,0 +1,9 @@ +import CoreModel + +/// Kotlin: sealed interface HomeUiState → Swift: enum 关联值 +public enum HomeUiState { + case loading + case success(HomeData) + case empty + case error(String) +} diff --git a/IosClientKit/Sources/FeatureHome/HomeViewModel.swift b/IosClientKit/Sources/FeatureHome/HomeViewModel.swift new file mode 100644 index 0000000..ff3b237 --- /dev/null +++ b/IosClientKit/Sources/FeatureHome/HomeViewModel.swift @@ -0,0 +1,34 @@ +import CoreDomain +import CoreModel +import Observation + +@MainActor +@Observable +public final class HomeViewModel { + public private(set) var uiState: HomeUiState = .loading + + private let homeUseCase: HomeUseCase + private var loadTask: Task? + + public init(homeUseCase: HomeUseCase) { + self.homeUseCase = homeUseCase + } + + public func load() { + // flatMapLatest 的等价:新 load 前先取消旧的观察。 + loadTask?.cancel() + loadTask = Task { [weak self] in + guard let self else { return } + self.uiState = .loading + do { + for try await data in self.homeUseCase.observeHome() { + self.uiState = data.habits.isEmpty ? .empty : .success(data) + } + } catch is CancellationError { + // 被更新的 load() 取代,保持当前状态即可。 + } catch { + self.uiState = .error(error.localizedDescription) + } + } + } +} From 653caa0b84e15863ed65568061f55b5f1c6f7259 Mon Sep 17 00:00:00 2001 From: AndyChen1024 Date: Mon, 14 Sep 2026 21:10:44 +0800 Subject: [PATCH 06/10] =?UTF-8?q?refactor:=20app=20=E7=BB=84=E5=90=88?= =?UTF-8?q?=E6=A0=B9=E4=B8=8E=E5=AF=BC=E8=88=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IosClientKit/Sources/App/AppContainer.swift | 14 ++++++++ IosClientKit/Sources/App/AppRootView.swift | 36 +++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 IosClientKit/Sources/App/AppContainer.swift create mode 100644 IosClientKit/Sources/App/AppRootView.swift diff --git a/IosClientKit/Sources/App/AppContainer.swift b/IosClientKit/Sources/App/AppContainer.swift new file mode 100644 index 0000000..356801d --- /dev/null +++ b/IosClientKit/Sources/App/AppContainer.swift @@ -0,0 +1,14 @@ +import CoreDomain +import CoreRepository +import FeatureHome + +/// 手写组合根 / DI 容器(对应 Android Hilt 的 AppContainer)。 +@MainActor +public enum AppContainer { + /// 装配 FeatureHome 依赖并返回 ViewModel。 + public static func makeHomeViewModel() -> HomeViewModel { + let repository: HomeRepository = FakeHomeRepository() + let useCase = HomeUseCase(repository: repository) + return HomeViewModel(homeUseCase: useCase) + } +} diff --git a/IosClientKit/Sources/App/AppRootView.swift b/IosClientKit/Sources/App/AppRootView.swift new file mode 100644 index 0000000..33e4b69 --- /dev/null +++ b/IosClientKit/Sources/App/AppRootView.swift @@ -0,0 +1,36 @@ +import SwiftUI +import CoreUI +import FeatureAuth +import FeatureHome + +public struct AppRootView: View { + @State private var homeViewModel: HomeViewModel + + public init() { + _homeViewModel = State(initialValue: AppContainer.makeHomeViewModel()) + } + + public var body: some View { + TabView { + NavigationStack { + HomeScreen(viewModel: homeViewModel) + } + .tabItem { Label("首页", systemImage: "house.fill") } + + NavigationStack { + PlaceholderView("计划", systemImage: "calendar") + } + .tabItem { Label("计划", systemImage: "calendar") } + + NavigationStack { + PlaceholderView("打卡", systemImage: "checkmark.seal.fill") + } + .tabItem { Label("打卡", systemImage: "checkmark.seal.fill") } + + NavigationStack { + AuthScreen() + } + .tabItem { Label("我的", systemImage: "person.crop.circle") } + } + } +} From ec19d3bf98dfad37df51728faf2ae3e280a500ee Mon Sep 17 00:00:00 2001 From: AndyChen1024 Date: Mon, 14 Sep 2026 21:10:44 +0800 Subject: [PATCH 07/10] =?UTF-8?q?chore:=20=E6=B7=BB=E5=8A=A0=20SwiftLint?= =?UTF-8?q?=20=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .swiftlint.yml | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .swiftlint.yml diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..3b2b143 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,38 @@ +# SwiftLint 配置 + +disabled_rules: + - trailing_whitespace + +opt_in_rules: + - anyobject_protocol + - array_init + - closure_spacing + - empty_count + - empty_string + - redundant_nil_coalescing + - vertical_whitespace_closing_braces + +excluded: + - .build + - .swiftpm + - DerivedData + - tmp + - ios-client/Assets.xcassets + +line_length: + warning: 120 + error: 160 + ignores_comments: true + ignores_urls: true + +type_name: + min_length: 2 + max_length: 60 + +identifier_name: + min_length: 2 + max_length: 60 + +file_length: + warning: 500 + error: 1000 From ae763ae3a11908dedd0e4d3c548340da7272c906 Mon Sep 17 00:00:00 2001 From: AndyChen1024 Date: Mon, 14 Sep 2026 21:10:44 +0800 Subject: [PATCH 08/10] =?UTF-8?q?docs:=20=E9=A1=B9=E7=9B=AE=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E4=B8=8E=E5=8D=8F=E4=BD=9C=E8=A7=84=E8=8C=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 81 +++++++++++++++++++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 31 +++++++++++++++++++ README.md | 57 ++++++++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 CLAUDE.md create mode 100644 CONTRIBUTING.md create mode 100644 README.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..6fab860 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,81 @@ +# CLAUDE.md — iOS 客户端项目 AI 协作规范 + +## 项目概览 + +SwiftUI + MVVM 单向数据流 + SPM 多 target 模块化的 iOS「打卡/习惯追踪」客户端,架构对齐 Android NIA(`core/*` + `feature/*`)。 + +## 目录结构 + +- `ios-client.xcodeproj` / `ios-client/`:App 宿主(薄壳,只含 `@main` 挂载 `AppRootView`) +- `IosClientKit/`:本地 SPM 包,承载所有模块 + - `Sources/App`:组合根(`AppContainer` DI + `AppRootView` 导航) + - `Sources/CoreModel`:领域模型(`Sendable` 值类型,叶子) + - `Sources/CoreCommon`:通用工具(叶子) + - `Sources/CoreDomain`:Repository 协议 + UseCase(纯 Swift,不 import SwiftUI) + - `Sources/CoreRepository`:Repository 实现(Fake + 未来 OfflineFirst) + - `Sources/CoreNetwork`:DTO + APIClient(URLSession) + - `Sources/CoreDatabase`:SwiftData(`@Model` + `ModelContainer`) + - `Sources/DesignSystem`:设计 tokens + - `Sources/CoreUI`:可复用 SwiftUI 组件 + - `Sources/FeatureHome` / `Sources/FeatureAuth`:Feature 模块 + +## 依赖方向(硬约束,违反即编译不过) + +单向无环:`App → feature → core`;`App` 额外 → `CoreRepository`(装配具体实现)。 + +- 领域模型只在 `CoreModel`,其余模块只能 import `CoreModel`。 +- Repository 协议只定义在 `CoreDomain`,实现在 `CoreRepository`。 +- `CoreDomain` 纯 Swift:禁止 `import SwiftUI` / 数据框架。 +- DTO 只在 `CoreNetwork`,领域 Model 只在 `CoreModel`,二者分离。 +- **禁止**命名任何模块为 `CoreData`(与苹果 `CoreData` 框架撞名,已统一为 `CoreRepository`)。 + +## 状态管理约定 + +- ViewModel:`@MainActor @Observable final class`,持有 `uiState`。 +- UiState:`enum` 关联值(对应 Kotlin `sealed interface`)。 +- Kotlin → Swift 映射: + +| Android (Kotlin) | iOS (Swift) | +|---|---| +| `Flow` | `AsyncThrowingStream` | +| `StateFlow` + `stateIn` | `@Observable` + `@MainActor` | +| `flatMapLatest` | `Task` 取消自动 switch-to-latest | +| `sealed interface UiState` | `enum UiState`(关联值)| +| Hilt `@Inject` / AppContainer | 手写 `AppContainer` | +| Retrofit + kotlinx.serialization | URLSession + Codable | +| Room | SwiftData | +| DataStore | `@AppStorage` / UserDefaults | + +## 代码风格 + +- 跨模块符号一律 `public`,并加 `///` 文档注释。 +- 领域模型标记 `Sendable`。 +- 命名:类型 PascalCase,变量/函数 camelCase;UI 文案与注释可用中文。 + +## 明确禁止 + +- ❌ TCA、Swinject 等 DI 框架、Moya 等网络封装、任何第三方大框架 +- ❌ UIKit(除非 SwiftUI 确无法覆盖的极少数场景) +- ❌ 循环依赖 / 反向 import + +## 构建 / 验证命令 + +```bash +# 编译包(同时做依赖环检测,SPM 遇环即报错) +swift build --package-path IosClientKit + +# 模拟器编译 App +xcodebuild -project ios-client.xcodeproj -scheme ios-client \ + -destination 'generic/platform=iOS Simulator' \ + CODE_SIGNING_ALLOWED=NO build + +# Lint +swiftlint lint --strict +``` + +## 新增 Feature 清单 + +1. 在 `IosClientKit/Package.swift` 的 `targets` 加 `FeatureXxx` 及依赖。 +2. 新建 `Sources/FeatureXxx/`:Screen + ViewModel + UiState(enum)。 +3. UseCase 放 `CoreDomain`,Repository 实现放 `CoreRepository`。 +4. 在 `App` 的 `AppContainer` 装配依赖、`AppRootView` 接入导航。 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..21367bf --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# 贡献指南 + +## 架构约束(提交前必须遵守) + +- 依赖单向:`App → feature → core`,禁止反向 import。 +- Repository 协议只在 `CoreDomain`,实现在 `CoreRepository`。 +- DTO 与领域 Model 分离(`CoreNetwork` / `CoreModel`)。 +- 禁止命名模块 `CoreData`(与苹果框架撞名,统一为 `CoreRepository`)。 + +## 代码规范 + +- 跨模块符号 `public` + `///` 文档注释。 +- 领域模型标记 `Sendable`。 +- ViewModel:`@MainActor @Observable`,UiState 用 `enum`。 +- 引入任何第三方依赖前,需在 PR 说明必要性(项目默认零第三方框架)。 + +## 提交流程 + +1. 从 `main` 新建分支:`feat/xxx`、`fix/xxx`。 +2. 本地跑通: + ```bash + swiftlint lint + xcodebuild -project ios-client.xcodeproj -scheme ios-client \ + -destination 'generic/platform=iOS Simulator' \ + CODE_SIGNING_ALLOWED=NO build + ``` +3. 发起 PR,至少一位 reviewer 通过后合并。 + +## Commit 规范 + +采用 Conventional Commits:`feat:` / `fix:` / `refactor:` / `docs:` / `chore:`。 diff --git a/README.md b/README.md new file mode 100644 index 0000000..0d80cef --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# ios-client + +SwiftUI + MVVM 单向数据流的 iOS「打卡/习惯追踪」客户端,采用 SPM 多 target 模块化,架构对齐 Android NIA(`core/*` + `feature/*`)。 + +## 架构 + +``` +App(组合根)──► feature/* ──► core/* + │ + └──► CoreRepository ──► CoreNetwork / CoreDatabase / CoreDomain +``` + +依赖单向无环,由 SPM target 依赖天然强制(import 不到即编不过)。 + +## 模块 + +| 模块 | 职责 | +|---|---| +| CoreModel | 领域模型(叶子,`Sendable` 值类型)| +| CoreCommon | 通用工具(日志等)| +| CoreDomain | Repository 协议 + UseCase(纯 Swift)| +| CoreRepository | Repository 实现(Fake / OfflineFirst)| +| CoreNetwork | DTO + URLSession APIClient | +| CoreDatabase | SwiftData(`@Model` + `ModelContainer`)| +| DesignSystem | 设计 tokens(颜色/间距/圆角)| +| CoreUI | 可复用 SwiftUI 组件 | +| FeatureHome | 首页(打卡列表)| +| FeatureAuth | 登录 / 我的(占位)| +| App | 组合根(DI + 导航)| + +## 环境要求 + +- Xcode 26.6+ +- iOS 17.0+(`@Observable` / SwiftData 下限) + +## 构建 + +```bash +xcodebuild -project ios-client.xcodeproj -scheme ios-client \ + -destination 'generic/platform=iOS Simulator' \ + CODE_SIGNING_ALLOWED=NO build +``` + +## Lint + +```bash +brew install swiftlint # 首次 +swiftlint lint +``` + +## 目录与协作规范 + +见 [CLAUDE.md](CLAUDE.md) 与 [CONTRIBUTING.md](CONTRIBUTING.md)。 + +## License + +[MIT](LICENSE) From ecb6a82f88a6a0fa98c45201d7ae1ba2668cf2d1 Mon Sep 17 00:00:00 2001 From: AndyChen1024 Date: Mon, 14 Sep 2026 21:10:44 +0800 Subject: [PATCH 09/10] =?UTF-8?q?chore:=20=E6=B7=BB=E5=8A=A0=20LICENSE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4447a97 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Andy Chen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 6fd1df0503abd148edb2d456e7ed3243fb9837d4 Mon Sep 17 00:00:00 2001 From: AndyChen1024 Date: Mon, 14 Sep 2026 21:10:44 +0800 Subject: [PATCH 10/10] =?UTF-8?q?refactor:=20=E8=BF=81=E7=A7=BB=20App=20?= =?UTF-8?q?=E5=85=A5=E5=8F=A3=E5=B9=B6=E6=8E=A5=E5=85=A5=20IosClientKit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios-client.xcodeproj/project.pbxproj | 24 +++++- .../xcschemes/ios-client.xcscheme | 76 +++++++++++++++++++ ios-client/ContentView.swift | 24 ------ ios-client/IOSClientApp.swift | 11 +++ ios-client/ios_clientApp.swift | 17 ----- 5 files changed, 109 insertions(+), 43 deletions(-) create mode 100644 ios-client.xcodeproj/xcshareddata/xcschemes/ios-client.xcscheme delete mode 100644 ios-client/ContentView.swift create mode 100644 ios-client/IOSClientApp.swift delete mode 100644 ios-client/ios_clientApp.swift diff --git a/ios-client.xcodeproj/project.pbxproj b/ios-client.xcodeproj/project.pbxproj index 80a5e1c..ffd7a8f 100644 --- a/ios-client.xcodeproj/project.pbxproj +++ b/ios-client.xcodeproj/project.pbxproj @@ -65,6 +65,7 @@ ); name = "ios-client"; packageProductDependencies = ( + A10000000000000000000002 /* App */, ); productName = "ios-client"; productReference = A1890A533057E82C003D5BE4 /* ios-client.app */; @@ -94,6 +95,9 @@ ); mainGroup = A1890A4A3057E82C003D5BE4; minimizedProjectReferenceProxies = 1; + packageReferences = ( + A10000000000000000000001 /* XCLocalSwiftPackageReference "IosClientKit" */, + ); preferredProjectObjectVersion = 77; productRefGroup = A1890A543057E82C003D5BE4 /* Products */; projectDirPath = ""; @@ -246,6 +250,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 972VPTZ33X; ENABLE_APP_SANDBOX = YES; ENABLE_PREVIEWS = YES; ENABLE_USER_SELECTED_FILES = readonly; @@ -270,7 +275,7 @@ REGISTER_APP_GROUPS = YES; SDKROOT = auto; STRING_CATALOG_GENERATE_SYMBOLS = YES; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; SWIFT_EMIT_LOC_STRINGS = YES; @@ -288,6 +293,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 972VPTZ33X; ENABLE_APP_SANDBOX = YES; ENABLE_PREVIEWS = YES; ENABLE_USER_SELECTED_FILES = readonly; @@ -312,7 +318,7 @@ REGISTER_APP_GROUPS = YES; SDKROOT = auto; STRING_CATALOG_GENERATE_SYMBOLS = YES; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SWIFT_APPROACHABLE_CONCURRENCY = YES; SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; SWIFT_EMIT_LOC_STRINGS = YES; @@ -325,6 +331,20 @@ }; /* End XCBuildConfiguration section */ +/* Begin XCLocalSwiftPackageReference section */ + A10000000000000000000001 /* XCLocalSwiftPackageReference "IosClientKit" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = IosClientKit; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + A10000000000000000000002 /* App */ = { + isa = XCSwiftPackageProductDependency; + productName = App; + }; +/* End XCSwiftPackageProductDependency section */ + /* Begin XCConfigurationList section */ A1890A4E3057E82C003D5BE4 /* Build configuration list for PBXProject "ios-client" */ = { isa = XCConfigurationList; diff --git a/ios-client.xcodeproj/xcshareddata/xcschemes/ios-client.xcscheme b/ios-client.xcodeproj/xcshareddata/xcschemes/ios-client.xcscheme new file mode 100644 index 0000000..87df22d --- /dev/null +++ b/ios-client.xcodeproj/xcshareddata/xcschemes/ios-client.xcscheme @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios-client/ContentView.swift b/ios-client/ContentView.swift deleted file mode 100644 index 17af27c..0000000 --- a/ios-client/ContentView.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// ContentView.swift -// ios-client -// -// Created by Andy on 2026/9/14. -// - -import SwiftUI - -struct ContentView: View { - var body: some View { - VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundStyle(.tint) - Text("Hello, world!") - } - .padding() - } -} - -#Preview { - ContentView() -} diff --git a/ios-client/IOSClientApp.swift b/ios-client/IOSClientApp.swift new file mode 100644 index 0000000..47fa289 --- /dev/null +++ b/ios-client/IOSClientApp.swift @@ -0,0 +1,11 @@ +import SwiftUI +import App + +@main +struct IOSClientApp: App { + var body: some Scene { + WindowGroup { + AppRootView() + } + } +} diff --git a/ios-client/ios_clientApp.swift b/ios-client/ios_clientApp.swift deleted file mode 100644 index ca853d1..0000000 --- a/ios-client/ios_clientApp.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// ios_clientApp.swift -// ios-client -// -// Created by Andy on 2026/9/14. -// - -import SwiftUI - -@main -struct ios_clientApp: App { - var body: some Scene { - WindowGroup { - ContentView() - } - } -}