feat: feature 多模块骨架

This commit is contained in:
AndyChen1024
2026-09-14 21:10:44 +08:00
parent 4fe0a26432
commit b5de8ddfbd
4 changed files with 130 additions and 0 deletions

View File

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

View File

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

View File

@@ -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<Void, Never>?
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)
}
}
}
}