feat: feature 多模块骨架
This commit is contained in:
10
IosClientKit/Sources/FeatureAuth/AuthScreen.swift
Normal file
10
IosClientKit/Sources/FeatureAuth/AuthScreen.swift
Normal file
@@ -0,0 +1,10 @@
|
||||
import SwiftUI
|
||||
|
||||
/// FeatureAuth 占位(后续实现登录 / 注册)。
|
||||
public struct AuthScreen: View {
|
||||
public init() {}
|
||||
|
||||
public var body: some View {
|
||||
ContentUnavailableView("登录", systemImage: "person.crop.circle")
|
||||
}
|
||||
}
|
||||
77
IosClientKit/Sources/FeatureHome/HomeScreen.swift
Normal file
77
IosClientKit/Sources/FeatureHome/HomeScreen.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
9
IosClientKit/Sources/FeatureHome/HomeUiState.swift
Normal file
9
IosClientKit/Sources/FeatureHome/HomeUiState.swift
Normal 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)
|
||||
}
|
||||
34
IosClientKit/Sources/FeatureHome/HomeViewModel.swift
Normal file
34
IosClientKit/Sources/FeatureHome/HomeViewModel.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user