Skip to content

Commit

Permalink
Merge pull request #63 from moneymanagerex/cache_currency-1
Browse files Browse the repository at this point in the history
Cache currency formats in DataManager
  • Loading branch information
guanlisheng authored Sep 28, 2024
2 parents 559cdde + 8ccd37e commit 488c272
Show file tree
Hide file tree
Showing 28 changed files with 530 additions and 357 deletions.
4 changes: 2 additions & 2 deletions MMEX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -294,11 +294,11 @@
A39B1B3B2C9A66CB003E5562 /* Transactions */ = {
isa = PBXGroup;
children = (
A3C142A52C90417700D3CEC0 /* TransactionAddView2.swift */,
A3C1429D2C8FFCF100D3CEC0 /* TransactionAddView.swift */,
A3C142972C8FE62200D3CEC0 /* TransactionListView.swift */,
A3C142A92C90721800D3CEC0 /* TransactionListView2.swift */,
A3C142992C8FF77D00D3CEC0 /* TransactionDetailView.swift */,
A3C1429D2C8FFCF100D3CEC0 /* TransactionAddView.swift */,
A3C142A52C90417700D3CEC0 /* TransactionAddView2.swift */,
A3C142AB2C909C1C00D3CEC0 /* TransactionEditView.swift */,
);
path = Transactions;
Expand Down
29 changes: 22 additions & 7 deletions MMEX/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ struct ContentView: View {
@EnvironmentObject var dataManager: DataManager

var body: some View {
ZStack {
print("DEBUG: ContentView.body")
return ZStack {
if dataManager.isDatabaseConnected {
connectedView
} else {
Expand All @@ -45,7 +46,12 @@ struct ContentView: View {
NavigationSplitView {
SidebarView(selectedTab: $selectedTab)
} detail: {
TabContentView(selectedTab: $selectedTab, isDocumentPickerPresented: $isDocumentPickerPresented, isNewDocumentPickerPresented: $isNewDocumentPickerPresented, isSampleDocument: $isSampleDocument)
TabContentView(
selectedTab: $selectedTab,
isDocumentPickerPresented: $isDocumentPickerPresented,
isNewDocumentPickerPresented: $isNewDocumentPickerPresented,
isSampleDocument: $isSampleDocument
)
}
} else {
let infotableViewModel = InfotableViewModel(dataManager: dataManager)
Expand Down Expand Up @@ -140,8 +146,12 @@ struct ContentView: View {
// Management tab
private var managementTab: some View {
NavigationView {
ManagementView(isDocumentPickerPresented: $isDocumentPickerPresented, isNewDocumentPickerPresented: $isNewDocumentPickerPresented, isSampleDocument: $isSampleDocument)
.navigationBarTitle("Management", displayMode: .inline)
ManagementView(
isDocumentPickerPresented: $isDocumentPickerPresented,
isNewDocumentPickerPresented: $isNewDocumentPickerPresented,
isSampleDocument: $isSampleDocument
)
.navigationBarTitle("Management", displayMode: .inline)
}
.tabItem {
Image(systemName: "folder")
Expand Down Expand Up @@ -231,10 +241,11 @@ struct TabContentView: View {
@EnvironmentObject var dataManager: DataManager // Access DataManager from environment

var body: some View {
print("DEBUG: TabContentView.body")
// Use @StateObject to manage the lifecycle of InfotableViewModel
let infotableViewModel = InfotableViewModel(dataManager: dataManager)
// Here we ensure that there's no additional NavigationStack or NavigationView
Group {
return Group {
switch selectedTab {
case 0:
TransactionListView2(viewModel: infotableViewModel) // Summary and Edit feature
Expand All @@ -246,8 +257,12 @@ struct TabContentView: View {
TransactionAddView2(selectedTab: $selectedTab)
.navigationBarTitle("Add Transaction", displayMode: .inline)
case 3:
ManagementView(isDocumentPickerPresented: $isDocumentPickerPresented, isNewDocumentPickerPresented: $isNewDocumentPickerPresented, isSampleDocument: $isSampleDocument)
.navigationBarTitle("Management", displayMode: .inline)
ManagementView(
isDocumentPickerPresented: $isDocumentPickerPresented,
isNewDocumentPickerPresented: $isNewDocumentPickerPresented,
isSampleDocument: $isSampleDocument
)
.navigationBarTitle("Management", displayMode: .inline)
case 4:
SettingsView(viewModel: infotableViewModel)
.navigationBarTitle("Settings", displayMode: .inline)
Expand Down
24 changes: 21 additions & 3 deletions MMEX/DatabaseManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ class DataManager: ObservableObject {
@Published var isDatabaseConnected = false
private(set) var db: Connection?
private(set) var databaseURL: URL?


var currencyFormat: [Int64: CurrencyFormat] = [:]

init() {
connectToStoredDatabase()
}
}

extension DataManager {
func openDatabase(at url: URL) {
func openDatabase(at url: URL, isNew: Bool = false) {
if url.startAccessingSecurityScopedResource() {
defer { url.stopAccessingSecurityScopedResource() }
do {
Expand All @@ -42,6 +44,10 @@ extension DataManager {
isDatabaseConnected = false
databaseURL = nil
}

if !isNew {
loadCurrencyFormat()
}
}

/// Method to connect to a previously stored database path if available
Expand All @@ -55,7 +61,7 @@ extension DataManager {
}

func createDatabase(at url: URL, sampleData: Bool) {
openDatabase(at: url)
openDatabase(at: url, isNew: true)
guard let db else { return }

guard let tables = Bundle.main.url(forResource: "tables.sql", withExtension: "") else {
Expand Down Expand Up @@ -86,6 +92,7 @@ extension DataManager {
return
}
}
loadCurrencyFormat()
}

/// Closes the current database connection and resets related states.
Expand All @@ -94,6 +101,7 @@ extension DataManager {
isDatabaseConnected = false
db = nil
databaseURL = nil
currencyFormat = [:]
print("Database connection closed.")
}
}
Expand All @@ -108,6 +116,16 @@ extension DataManager {
}
}

extension DataManager {
func loadCurrencyFormat() {
currencyFormat = CurrencyRepository(db)?.dictionaryRefFormat() ?? [:]
}

func updateCurrencyFormat(id: Int64, value: CurrencyFormat) {
currencyFormat[id] = value
}
}

extension DataManager {
var repository : Repository? { Repository(db) }
var infotableRepository : InfotableRepository? { InfotableRepository(db) }
Expand Down
26 changes: 0 additions & 26 deletions MMEX/Models/AccountData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,29 +99,3 @@ extension AccountData {
),
]
}

// TODO: move to ViewModels
struct AccountWithCurrency: ExportableEntity {
var data: AccountData = AccountData()
var currency: CurrencyData?
var id: Int64 { data.id }
}
extension AccountData {
static let sampleDataWithCurrency : [AccountWithCurrency] = [
AccountWithCurrency(data: AccountData(
id: 1, name: "Cash Account", type: AccountType.cash,
status: AccountStatus.open, notes:"",
initialBal: 100.01, favoriteAcct: "TRUE", currencyId: 1
), currency: CurrencyData.sampleData[0]),
AccountWithCurrency(data: AccountData(
id: 2, name: "Chcking Account", type: AccountType.checking,
status: AccountStatus.open, notes:"",
initialBal: 200.02, favoriteAcct: "TRUE", currencyId: 2
), currency: CurrencyData.sampleData[1]),
AccountWithCurrency(data: AccountData(
id: 3, name: "Investment Account", type: AccountType.investment,
status: AccountStatus.open, notes:"",
initialBal: 300.03, favoriteAcct: "TRUE", currencyId: 2
), currency: CurrencyData.sampleData[1]),
]
}
74 changes: 57 additions & 17 deletions MMEX/Models/CurrencyData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,25 @@
import Foundation
import SQLite

struct CurrencyData: ExportableEntity {
var id : Int64 = 0
var name : String = ""
var prefixSymbol : String = ""
var suffixSymbol : String = ""
var decimalPoint : String = ""
var groupSeparator : String = ""
var unitName : String = ""
var centName : String = ""
var scale : Int = 0
var baseConvRate : Double = 0.0
var symbol : String = ""
var type : String = ""
enum CurrencyType: String, EnumCollateNoCase {
case fiat = "Fiat"
case crypto = "Crypto"
static let defaultValue = Self.fiat
}

struct CurrencyData: ExportableEntity, CurrencyFormatProtocol {
var id : Int64 = 0
var name : String = ""
var prefixSymbol : String = ""
var suffixSymbol : String = ""
var decimalPoint : String = ""
var groupSeparator : String = ""
var unitName : String = ""
var centName : String = ""
var scale : Int = 0
var baseConvRate : Double = 0.0
var symbol : String = ""
var type : CurrencyType = CurrencyType.defaultValue
}

extension CurrencyData: DataProtocol {
Expand All @@ -31,7 +37,40 @@ extension CurrencyData: DataProtocol {
}
}

extension CurrencyData {
// TODO: remove CurrencyFormat; use CurrencyData instead
protocol CurrencyFormatProtocol {
var name : String { get }
var prefixSymbol : String { get }
var suffixSymbol : String { get }
var decimalPoint : String { get }
var groupSeparator : String { get }
var scale : Int { get }
var baseConvRate : Double { get }
}

struct CurrencyFormat: CurrencyFormatProtocol {
let name : String
let prefixSymbol : String
let suffixSymbol : String
let decimalPoint : String
let groupSeparator : String
let scale : Int
let baseConvRate : Double
}

extension CurrencyFormatProtocol {
var toCurrencyFormat: CurrencyFormat { CurrencyFormat(
name : self.name,
prefixSymbol : self.prefixSymbol,
suffixSymbol : self.suffixSymbol,
decimalPoint : self.decimalPoint,
groupSeparator : self.groupSeparator,
scale : self.scale,
baseConvRate : self.baseConvRate
) }
}

extension CurrencyFormatProtocol {
/// A `NumberFormatter` configured specifically for the currency.
var formatter: NumberFormatter {
let nf = NumberFormatter()
Expand All @@ -54,6 +93,7 @@ extension CurrencyData {
}
}

// TODO: move to higher level where base currency is maintained
/// Helper method to convert and format the amount into base currency using the exchange rate.
func formatAsBaseCurrency(amount: Double, baseCurrencyRate: Double?) -> String {
let baseAmount = amount * (baseCurrencyRate ?? self.baseConvRate)
Expand All @@ -67,17 +107,17 @@ extension CurrencyData {
CurrencyData(
id: 1, name: "US dollar", prefixSymbol: "$", suffixSymbol: "",
decimalPoint: ".", groupSeparator: ",", unitName: "Dollar", centName: "Cent",
scale: 100, baseConvRate: 1.0, symbol: "USD", type: "Fiat"
scale: 100, baseConvRate: 1.0, symbol: "USD", type: .fiat
),
CurrencyData(
id: 2, name: "Euro", prefixSymbol: "", suffixSymbol: "",
decimalPoint: ".", groupSeparator: " ", unitName: "", centName: "",
scale: 100, baseConvRate: 1.0, symbol: "EUR", type: "Fiat"
scale: 100, baseConvRate: 1.0, symbol: "EUR", type: .fiat
),
CurrencyData(
id: 3, name: "British pound", prefixSymbol: "£", suffixSymbol: "",
decimalPoint: ".", groupSeparator: " ", unitName: "Pound", centName: "Pence",
scale: 100, baseConvRate: 1.0, symbol: "GBP", type: "Fiat"
scale: 100, baseConvRate: 1.0, symbol: "GBP", type: .fiat
),
]
}
33 changes: 2 additions & 31 deletions MMEX/Repositories/AccountRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -175,37 +175,8 @@ extension AccountRepository {
// load account of a stock
func pluck(for stock: StockData) -> AccountData? {
return pluck(
from: Self.table.filter(Self.col_id == stock.accountId),
key: "\(stock.accountId)"
key: "\(stock.accountId)",
from: Self.table.filter(Self.col_id == stock.accountId)
)
}
}

// TODO: move to ViewModels
extension AccountRepository {
// load all accounts and their currency
func loadWithCurrency() -> [AccountWithCurrency] {
// TODO via join?
// Create a lookup dictionary for currencies by currencyId
let currencies = CurrencyRepository(db).load();
let currencyDict = Dictionary(uniqueKeysWithValues: currencies.map { ($0.id, $0) })

do {
var data: [AccountWithCurrency] = []
for row in try db.prepare(Self.selectData(from: Self.table
.order(Self.col_name)
)) {
let account = Self.fetchData(row)
data.append(AccountWithCurrency(
data: account,
currency: currencyDict[account.currencyId]
) )
}
print("Successfull select from \(Self.repositoryName): \(data.count)")
return data
} catch {
print("Failed select from \(Self.repositoryName): \(error)")
return []
}
}
}
9 changes: 9 additions & 0 deletions MMEX/Repositories/AssetRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,13 @@ extension AssetRepository {
.order(Self.col_type, Self.col_status.desc, Self.col_name)
)
}

// load currencyId for all accounts
func loadCurrencyId() -> [Int64] {
return Repository(db).select(from: Self.table
.select(distinct: Self.col_currencyId)
) { row in
row[Self.col_currencyId] ?? 0
}
}
}
6 changes: 3 additions & 3 deletions MMEX/Repositories/CategoryRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,11 @@ extension CategoryRepository {
return select(from: Self.table)
}

// load category of a payeer
// load category of a payee
func pluck(for payee: PayeeData) -> CategoryData? {
return pluck(
from: Self.table.filter(Self.col_id == payee.categoryId),
key: "\(payee.categoryId)"
key: "\(payee.categoryId)",
from: Self.table.filter(Self.col_id == payee.categoryId)
)
}
}
Loading

0 comments on commit 488c272

Please sign in to comment.