Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refine Account Views #172

Merged
merged 2 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions MMEX/Repository/AttachmentRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,20 @@ extension AttachmentRepository {
.order(Self.col_id)
)
}

func delete(refType: RefType, refId: DataId) -> Bool {
do {
let query = Self.table
.filter(Self.col_refType == refType.rawValue && Self.col_refId == Int64(refId))
.delete()
log.trace("DEBUG: AttachmentRepository.delete(): \(query.expression.description)")
try db.run(query)
log.info("INFO: AttachmentRepository.delete(\(Self.repositoryName))")
return true
} catch {
log.error("ERROR: AttachmentRepository.delete(\(Self.repositoryName)): \(error)")
return false
}

}
}
2 changes: 1 addition & 1 deletion MMEX/View/Account/AccountCreateView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ struct AccountCreateView: View {
}
ToolbarItem(placement: .confirmationAction) {
Button("Add") {
if let createError = vm.createAccount(&data) {
if let createError = vm.updateAccount(&data) {
alertMessage = createError
alertIsPresented = true
} else {
Expand Down
9 changes: 3 additions & 6 deletions MMEX/View/Account/AccountReadView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,12 @@ struct AccountReadView: View {
.sheet(isPresented: $updateViewIsPresented) {
AccountUpdateView(
vm: vm,
title: data.name,
data: data,
newData: $newData,
isPresented: $updateViewIsPresented
isPresented: $updateViewIsPresented,
dismiss: dismiss
)
.onDisappear {
if newData != nil {
dismiss()
}
}
}
.fileExporter(
isPresented: $exporterIsPresented,
Expand Down
7 changes: 5 additions & 2 deletions MMEX/View/Account/AccountUpdateView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import SwiftUI
struct AccountUpdateView: View {
@EnvironmentObject var env: EnvironmentManager
var vm: RepositoryViewModel
var title: String
@State var data: AccountData
@Binding var newData: AccountData?
@Binding var isPresented: Bool
var dismiss: DismissAction

@State private var alertIsPresented = false
@State private var alertMessage: String?
Expand All @@ -28,7 +30,7 @@ struct AccountUpdateView: View {
)
}
.textSelection(.enabled)
.navigationTitle(data.name)
.navigationTitle(title)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
Expand All @@ -37,13 +39,14 @@ struct AccountUpdateView: View {
}
ToolbarItem(placement: .confirmationAction) {
Button("Done") {
let updateError = vm.updateAccount(data)
let updateError = vm.updateAccount(&data)
if updateError != nil {
alertMessage = updateError
alertIsPresented = true
} else {
newData = data
isPresented = false
dismiss()
}
}
}
Expand Down
18 changes: 13 additions & 5 deletions MMEX/View/Repository/RepositoryListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ where GroupType.MainRepository == MainRepository,
Text(Image(systemName: "chevron.up.chevron.down"))
) } )
.onChange(of: groupChoice) {
vm.unloadGroup(for: vmGroup)
vm.loadGroup(for: vmGroup, groupChoice)
vm.searchGroup(for: vmGroup, search: search)
}
Expand Down Expand Up @@ -138,6 +139,7 @@ where GroupType.MainRepository == MainRepository,
await load()
} }
.refreshable {
vm.unloadGroup(for: vmGroup)
vm.unloadList(for: vmList)
await load()
}
Expand All @@ -147,7 +149,10 @@ where GroupType.MainRepository == MainRepository,
)
.onAppear { newData = nil }
.onDisappear {
if newData != nil { vm.reload(nil, newData) }
if newData != nil { Task {
await vm.reload(nil as MainData?, newData)
vm.searchGroup(for: vmGroup, search: search)
} }
}
}
}
Expand Down Expand Up @@ -201,10 +206,13 @@ where GroupType.MainRepository == MainRepository,
newData = nil
deleteData = false
}
.onDisappear {
if deleteData { vm.reload(data, nil) }
else if newData != nil { vm.reload(data, newData) }
}
.onDisappear {
log.debug("DEBUG: RepositoryListView.itemView.onDisappear")
if deleteData || newData != nil { Task {
await vm.reload(data, newData)
vm.searchGroup(for: vmGroup, search: search)
} }
}
) {
env.theme.item.view(
name: { itemName(data) },
Expand Down
139 changes: 83 additions & 56 deletions MMEX/ViewModel/AccountViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import SwiftUI
import SQLite

enum AccountGroupChoice: String, RepositoryGroupChoiceProtocol {
case all = "All"
Expand All @@ -14,13 +15,9 @@ enum AccountGroupChoice: String, RepositoryGroupChoiceProtocol {
case type = "Type"
case currency = "Currency"
case status = "Status"
case attachment = "Att." // full name does not fit in iPhoneSE display
case attachment = "Attachment"
static let defaultValue = Self.all

static let isSingleton: Set<Self> = [.all]
var fullName: String {
switch self { case .attachment: "Attachment"; default: rawValue }
}
}

struct AccountGroup: RepositoryLoadGroupProtocol {
Expand Down Expand Up @@ -67,6 +64,8 @@ struct AccountSearch: RepositorySearchProtocol {
extension RepositoryViewModel {
func loadAccountList() async {
log.trace("DEBUG: RepositoryViewModel.loadAccountList(main=\(Thread.isMainThread))")
guard case .idle = accountList.state else { return }
accountList.state = .loading
let queueOk = await withTaskGroup(of: Bool.self) { queue -> Bool in
load(queue: &queue, keyPath: \Self.accountData)
load(queue: &queue, keyPath: \Self.accountOrder)
Expand All @@ -77,17 +76,19 @@ extension RepositoryViewModel {
load(queue: &queue, keyPath: \Self.currencyOrder)
return await allOk(queue: queue)
}
accountList.state = queueOk ? .ready(()) : .error("Cannot load data.")
accountList.state = queueOk ? .ready(()) : .error("Cannot load.")
if queueOk {
log.info("INFO: RepositoryViewModel.loadAccountList(main=\(Thread.isMainThread)): Ready.")
} else {
log.debug("ERROR: RepositoryViewModel.loadAccountList(main=\(Thread.isMainThread)): Cannot load data.")
log.debug("ERROR: RepositoryViewModel.loadAccountList(main=\(Thread.isMainThread)): Cannot load.")
return
}
}

func unloadAccountList() {
log.trace("DEBUG: RepositoryViewModel.unloadAccountList(main=\(Thread.isMainThread))")
if case .loading = accountList.state { return }
accountList.state = .loading
accountData.unload()
accountOrder.unload()
accountUsed.unload()
Expand All @@ -97,16 +98,16 @@ extension RepositoryViewModel {
}

extension RepositoryViewModel {
func loadAccountGroup(env: EnvironmentManager, choice: AccountGroupChoice) -> Bool? {
func loadAccountGroup(env: EnvironmentManager, choice: AccountGroupChoice) {
log.trace("DEBUG: RepositoryViewModel.loadAccountGroup(\(choice.rawValue), main=\(Thread.isMainThread))")
if case .loading = accountGroup.state { return nil }
guard case .idle = accountGroup.state else { return }
guard
case .ready(_) = accountList.state,
case let .ready(dataDict) = accountData.state,
case let .ready(dataOrder) = accountOrder.state,
case let .ready(dataUsed) = accountUsed.state,
case let .ready(dataAtt) = accountAtt.state
else { return nil }
else { return }

accountGroup.state = .loading
accountGroup.choice = choice
Expand Down Expand Up @@ -158,16 +159,15 @@ extension RepositoryViewModel {
accountGroup.isVisible = groupTuple.isVisible
accountGroup.isExpanded = groupTuple.isExpanded
accountGroup.state = .ready(groupTuple.groupData)
return true
}

func unloadAccountGroup() -> Bool? {
func unloadAccountGroup() {
log.trace("DEBUG: RepositoryViewModel.unloadAccountGroup(main=\(Thread.isMainThread))")
if case .loading = accountGroup.state { return nil }
accountGroup.state = .idle
if case .loading = accountGroup.state { return }
accountGroup.state = .loading
accountGroup.isVisible = []
accountGroup.isExpanded = []
return true
accountGroup.state = .idle
}
}

Expand Down Expand Up @@ -201,13 +201,11 @@ extension RepositoryViewModel {
}

extension RepositoryViewModel {
func validateAccount(_ data: AccountData) -> String? {
func updateAccount(_ data: inout AccountData) -> String? {
if data.name.isEmpty {
return "Name is empty"
}

// TODO: data.name is unique

if case let .ready(currency) = currencyName.state {
if data.currencyId <= 0 {
return "No currency is selected"
Expand All @@ -218,38 +216,28 @@ extension RepositoryViewModel {
return "* currencyName is not loaded"
}

return nil
}
}

extension RepositoryViewModel {
func createAccount(_ data: inout AccountData) -> String? {
if let validateError = validateAccount(data) {
return validateError
}

guard let repository = AccountRepository(env) else {
typealias A = AccountRepository
guard let a = A(env) else {
return "* Database is not available"
}
guard repository.insert(&data) else {
return "* Cannot create new account"
}

return nil
}
}

extension RepositoryViewModel {
func updateAccount(_ data: AccountData) -> String? {
if let validateError = validateAccount(data) {
return validateError
guard let dataName = a.selectId(from: A.table.filter(
A.table[A.col_id] == Int64(data.id) || A.table[A.col_name] == data.name
) ) else {
return "* Cannot fetch from database"
}

guard let repository = AccountRepository(env) else {
return "* Database is not available"
guard dataName.count == (data.id <= 0 ? 0 : 1) else {
return "Account \(data.name) already exists"
}
guard repository.update(data) else {
return "* Cannot update account #\(data.id)"

if data.id <= 0 {
guard a.insert(&data) else {
return "* Cannot create new account"
}
} else {
guard a.update(data) else {
return "* Cannot update account #\(data.id)"
}
}

return nil
Expand All @@ -266,34 +254,73 @@ extension RepositoryViewModel {
return "* accountUsed is not loaded"
}

guard let repository = AccountRepository(env) else {
guard
let a = AccountRepository(env),
let ax = AttachmentRepository(env)
else {
return "* Database is not available"
}
guard repository.delete(data) else {
return "* Cannot delete account \(data.id)"

if case let .ready(att) = accountAtt.state {
if att[data.id] != nil {
guard ax.delete(refType: .account, refId: data.id) else {
return "* Cannot delete attachments for account #\(data.id)"
}
}
} else {
return "* accountAtt is not loaded"
}

guard a.delete(data) else {
return "* Cannot delete account #\(data.id)"
}

return nil
}
}

extension RepositoryViewModel {
func reload(_ oldData: AccountData?, _ newData: AccountData?) async {
func reloadAccount(_ oldData: AccountData?, _ newData: AccountData?) async {
log.trace("DEBUG: RepositoryViewModel.reloadAccount(main=\(Thread.isMainThread))")
if let newData {
if env.currencyCache[newData.currencyId] == nil {
// TODO: loadCurrency() -> addCurrency()
env.loadCurrency()
}
env.accountCache.update(id: newData.id, data: newData)
} else if let _ = oldData {
// TODO: loadAccount() -> removeAccount()
env.loadAccount()
} else if let oldData {
env.accountCache[oldData.id] = nil
}

// save isExpanded
let groupIsExpanded: [Bool]? = switch accountGroup.state {
case .ready(_): accountGroup.isExpanded
default: nil
}
var currencyIsExpanded: [DataId: Bool] = [:]
if let groupIsExpanded, case .currency = accountGroup.choice {
for (i, currencyId) in accountGroup.groupCurrency.enumerated() {
currencyIsExpanded[currencyId] = groupIsExpanded[i]
}
}

// TODO: update vm
_ = unloadAccountGroup()
// TODO: improve performance
unloadAccountGroup()
unloadAccountList()
await loadAccountList()
_ = loadAccountGroup(env: env, choice: accountGroup.choice)
loadAccountGroup(env: env, choice: accountGroup.choice)

// restore isExpanded
if let groupIsExpanded { switch accountGroup.choice {
case .currency:
for (i, currencyId) in accountGroup.groupCurrency.enumerated() {
if let isExpanded = currencyIsExpanded[currencyId] {
accountGroup.isExpanded[i] = isExpanded
}
}
default:
if accountGroup.isExpanded.count == groupIsExpanded.count {
accountGroup.isExpanded = groupIsExpanded
}
} }
}
}
Loading