Refactor iOS codebase with SOLID/DRY patterns
Core Infrastructure: - Add StateFlowObserver for reusable Kotlin StateFlow observation - Add ValidationRules for centralized form validation - Add ActionState enum for tracking async operations - Add KotlinTypeExtensions with .asKotlin helpers - Add Dependencies factory for dependency injection - Add ViewState, FormField, and FormState for view layer - Add LoadingOverlay and AsyncContentView components - Add form state containers (Task, Residence, Contractor, Document) ViewModel Updates (9 files): - Refactor all ViewModels to use StateFlowObserver pattern - Add optional DI support via initializer parameters - Reduce boilerplate by ~330 lines across ViewModels View Updates (4 files): - Update ResidencesListView to use ListAsyncContentView - Update ContractorsListView to use ListAsyncContentView - Update WarrantiesTabContent to use ListAsyncContentView - Update DocumentsTabContent to use ListAsyncContentView Net reduction: -332 lines (1007 removed, 675 added) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -5,74 +5,67 @@ import Combine
|
||||
@MainActor
|
||||
class TaskViewModel: ObservableObject {
|
||||
// MARK: - Published Properties
|
||||
@Published var isLoading: Bool = false
|
||||
@Published var actionState: ActionState<TaskActionType> = .idle
|
||||
@Published var errorMessage: String?
|
||||
@Published var taskCreated: Bool = false
|
||||
@Published var taskUpdated: Bool = false
|
||||
@Published var taskCancelled: Bool = false
|
||||
@Published var taskUncancelled: Bool = false
|
||||
@Published var taskMarkedInProgress: Bool = false
|
||||
@Published var taskArchived: Bool = false
|
||||
@Published var taskUnarchived: Bool = false
|
||||
|
||||
// MARK: - Computed Properties (Backward Compatibility)
|
||||
|
||||
var isLoading: Bool { actionState.isLoading }
|
||||
var taskCreated: Bool { actionState.isSuccess(.create) }
|
||||
var taskUpdated: Bool { actionState.isSuccess(.update) }
|
||||
var taskCancelled: Bool { actionState.isSuccess(.cancel) }
|
||||
var taskUncancelled: Bool { actionState.isSuccess(.uncancel) }
|
||||
var taskMarkedInProgress: Bool { actionState.isSuccess(.markInProgress) }
|
||||
var taskArchived: Bool { actionState.isSuccess(.archive) }
|
||||
var taskUnarchived: Bool { actionState.isSuccess(.unarchive) }
|
||||
|
||||
// MARK: - Private Properties
|
||||
private let sharedViewModel: ComposeApp.TaskViewModel
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
// MARK: - Initialization
|
||||
init() {
|
||||
self.sharedViewModel = ComposeApp.TaskViewModel()
|
||||
init(sharedViewModel: ComposeApp.TaskViewModel? = nil) {
|
||||
self.sharedViewModel = sharedViewModel ?? ComposeApp.TaskViewModel()
|
||||
}
|
||||
|
||||
// MARK: - Public Methods
|
||||
func createTask(request: TaskCreateRequest, completion: @escaping (Bool) -> Void) {
|
||||
isLoading = true
|
||||
actionState = .loading(.create)
|
||||
errorMessage = nil
|
||||
taskCreated = false
|
||||
|
||||
sharedViewModel.createNewTask(request: request)
|
||||
|
||||
// Observe the state
|
||||
Task {
|
||||
for await state in sharedViewModel.taskAddNewCustomTaskState {
|
||||
if state is ApiResultLoading {
|
||||
await MainActor.run {
|
||||
self.isLoading = true
|
||||
}
|
||||
} else if let success = state as? ApiResultSuccess<CustomTask> {
|
||||
await MainActor.run {
|
||||
self.isLoading = false
|
||||
self.taskCreated = true
|
||||
}
|
||||
sharedViewModel.resetAddTaskState()
|
||||
completion(true)
|
||||
break
|
||||
} else if let error = state as? ApiResultError {
|
||||
await MainActor.run {
|
||||
self.errorMessage = ErrorMessageParser.parse(error.message)
|
||||
self.isLoading = false
|
||||
}
|
||||
sharedViewModel.resetAddTaskState()
|
||||
completion(false)
|
||||
break
|
||||
StateFlowObserver.observeWithCompletion(
|
||||
sharedViewModel.taskAddNewCustomTaskState,
|
||||
loadingSetter: { [weak self] loading in
|
||||
if loading { self?.actionState = .loading(.create) }
|
||||
},
|
||||
errorSetter: { [weak self] error in
|
||||
if let error = error {
|
||||
self?.actionState = .error(.create, error)
|
||||
self?.errorMessage = error
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onSuccess: { [weak self] (_: CustomTask) in
|
||||
self?.actionState = .success(.create)
|
||||
},
|
||||
completion: completion,
|
||||
resetState: { [weak self] in self?.sharedViewModel.resetAddTaskState() }
|
||||
)
|
||||
}
|
||||
|
||||
func cancelTask(id: Int32, completion: @escaping (Bool) -> Void) {
|
||||
isLoading = true
|
||||
actionState = .loading(.cancel)
|
||||
errorMessage = nil
|
||||
taskCancelled = false
|
||||
|
||||
sharedViewModel.cancelTask(taskId: id) { success in
|
||||
Task { @MainActor in
|
||||
self.isLoading = false
|
||||
if success.boolValue {
|
||||
self.taskCancelled = true
|
||||
self.actionState = .success(.cancel)
|
||||
completion(true)
|
||||
} else {
|
||||
self.errorMessage = "Failed to cancel task"
|
||||
let errorMsg = "Failed to cancel task"
|
||||
self.actionState = .error(.cancel, errorMsg)
|
||||
self.errorMessage = errorMsg
|
||||
completion(false)
|
||||
}
|
||||
}
|
||||
@@ -80,18 +73,18 @@ class TaskViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
func uncancelTask(id: Int32, completion: @escaping (Bool) -> Void) {
|
||||
isLoading = true
|
||||
actionState = .loading(.uncancel)
|
||||
errorMessage = nil
|
||||
taskUncancelled = false
|
||||
|
||||
sharedViewModel.uncancelTask(taskId: id) { success in
|
||||
Task { @MainActor in
|
||||
self.isLoading = false
|
||||
if success.boolValue {
|
||||
self.taskUncancelled = true
|
||||
self.actionState = .success(.uncancel)
|
||||
completion(true)
|
||||
} else {
|
||||
self.errorMessage = "Failed to uncancel task"
|
||||
let errorMsg = "Failed to uncancel task"
|
||||
self.actionState = .error(.uncancel, errorMsg)
|
||||
self.errorMessage = errorMsg
|
||||
completion(false)
|
||||
}
|
||||
}
|
||||
@@ -99,18 +92,18 @@ class TaskViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
func markInProgress(id: Int32, completion: @escaping (Bool) -> Void) {
|
||||
isLoading = true
|
||||
actionState = .loading(.markInProgress)
|
||||
errorMessage = nil
|
||||
taskMarkedInProgress = false
|
||||
|
||||
sharedViewModel.markInProgress(taskId: id) { success in
|
||||
Task { @MainActor in
|
||||
self.isLoading = false
|
||||
if success.boolValue {
|
||||
self.taskMarkedInProgress = true
|
||||
self.actionState = .success(.markInProgress)
|
||||
completion(true)
|
||||
} else {
|
||||
self.errorMessage = "Failed to mark task in progress"
|
||||
let errorMsg = "Failed to mark task in progress"
|
||||
self.actionState = .error(.markInProgress, errorMsg)
|
||||
self.errorMessage = errorMsg
|
||||
completion(false)
|
||||
}
|
||||
}
|
||||
@@ -118,18 +111,18 @@ class TaskViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
func archiveTask(id: Int32, completion: @escaping (Bool) -> Void) {
|
||||
isLoading = true
|
||||
actionState = .loading(.archive)
|
||||
errorMessage = nil
|
||||
taskArchived = false
|
||||
|
||||
sharedViewModel.archiveTask(taskId: id) { success in
|
||||
Task { @MainActor in
|
||||
self.isLoading = false
|
||||
if success.boolValue {
|
||||
self.taskArchived = true
|
||||
self.actionState = .success(.archive)
|
||||
completion(true)
|
||||
} else {
|
||||
self.errorMessage = "Failed to archive task"
|
||||
let errorMsg = "Failed to archive task"
|
||||
self.actionState = .error(.archive, errorMsg)
|
||||
self.errorMessage = errorMsg
|
||||
completion(false)
|
||||
}
|
||||
}
|
||||
@@ -137,18 +130,18 @@ class TaskViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
func unarchiveTask(id: Int32, completion: @escaping (Bool) -> Void) {
|
||||
isLoading = true
|
||||
actionState = .loading(.unarchive)
|
||||
errorMessage = nil
|
||||
taskUnarchived = false
|
||||
|
||||
sharedViewModel.unarchiveTask(taskId: id) { success in
|
||||
Task { @MainActor in
|
||||
self.isLoading = false
|
||||
if success.boolValue {
|
||||
self.taskUnarchived = true
|
||||
self.actionState = .success(.unarchive)
|
||||
completion(true)
|
||||
} else {
|
||||
self.errorMessage = "Failed to unarchive task"
|
||||
let errorMsg = "Failed to unarchive task"
|
||||
self.actionState = .error(.unarchive, errorMsg)
|
||||
self.errorMessage = errorMsg
|
||||
completion(false)
|
||||
}
|
||||
}
|
||||
@@ -156,18 +149,18 @@ class TaskViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
func updateTask(id: Int32, request: TaskCreateRequest, completion: @escaping (Bool) -> Void) {
|
||||
isLoading = true
|
||||
actionState = .loading(.update)
|
||||
errorMessage = nil
|
||||
taskUpdated = false
|
||||
|
||||
sharedViewModel.updateTask(taskId: id, request: request) { success in
|
||||
Task { @MainActor in
|
||||
self.isLoading = false
|
||||
if success.boolValue {
|
||||
self.taskUpdated = true
|
||||
self.actionState = .success(.update)
|
||||
completion(true)
|
||||
} else {
|
||||
self.errorMessage = "Failed to update task"
|
||||
let errorMsg = "Failed to update task"
|
||||
self.actionState = .error(.update, errorMsg)
|
||||
self.errorMessage = errorMsg
|
||||
completion(false)
|
||||
}
|
||||
}
|
||||
@@ -176,16 +169,13 @@ class TaskViewModel: ObservableObject {
|
||||
|
||||
func clearError() {
|
||||
errorMessage = nil
|
||||
if case .error = actionState {
|
||||
actionState = .idle
|
||||
}
|
||||
}
|
||||
|
||||
func resetState() {
|
||||
taskCreated = false
|
||||
taskUpdated = false
|
||||
taskCancelled = false
|
||||
taskUncancelled = false
|
||||
taskMarkedInProgress = false
|
||||
taskArchived = false
|
||||
taskUnarchived = false
|
||||
actionState = .idle
|
||||
errorMessage = nil
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user