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

Basic search for Category, redesign landing page and support close/open in management #53

Merged
merged 4 commits into from
Sep 27, 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
8 changes: 4 additions & 4 deletions MMEX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 9;
CURRENT_PROJECT_VERSION = 10;
DEVELOPMENT_ASSET_PATHS = "\"MMEX/Preview Content\"";
DEVELOPMENT_TEAM = 3N3NHU8X3F;
ENABLE_PREVIEWS = YES;
Expand All @@ -786,7 +786,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.1.5;
MARKETING_VERSION = 0.1.6;
PRODUCT_BUNDLE_IDENTIFIER = com.guangong.mmex;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand All @@ -803,7 +803,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 9;
CURRENT_PROJECT_VERSION = 10;
DEVELOPMENT_ASSET_PATHS = "\"MMEX/Preview Content\"";
DEVELOPMENT_TEAM = 3N3NHU8X3F;
ENABLE_PREVIEWS = YES;
Expand All @@ -821,7 +821,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.1.5;
MARKETING_VERSION = 0.1.6;
PRODUCT_BUNDLE_IDENTIFIER = com.guangong.mmex;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down
312 changes: 176 additions & 136 deletions MMEX/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,160 +10,202 @@ import SwiftUI
struct ContentView: View {
@State private var isDocumentPickerPresented = false
@State private var isNewDocumentPickerPresented = false
@State private var isSampleDocumnt = false
@State private var isSampleDocument = false
@State private var selectedTab = 0
@State private var selectedFileURL: URL?
@State private var isPresentingTransactionAddView = false
@Environment(\.horizontalSizeClass) var horizontalSizeClass
@Environment(\.verticalSizeClass) var verticalSizeClass

@EnvironmentObject var dataManager: DataManager // Access DataManager from environment
@EnvironmentObject var dataManager: DataManager

var body: some View {
ZStack {
if dataManager.isDatabaseConnected {
if horizontalSizeClass == .regular {
// && verticalSizeClass == .compact {
// iPad layout: Tabs on the side
NavigationSplitView {
SidebarView(selectedTab: $selectedTab)
} detail: {
TabContentView(selectedTab: $selectedTab, isDocumentPickerPresented: $isDocumentPickerPresented)
}
} else {
// Use @StateObject to manage the lifecycle of InfotableViewModel
let infotableViewModel = InfotableViewModel(dataManager: dataManager)
// iPhone layout: Tabs at the bottom
TabView(selection: $selectedTab) {
// Latest Transactions Tab
NavigationView {
TransactionListView2(viewModel: infotableViewModel) // Summary and Edit feature
.navigationBarTitle("Latest Transactions", displayMode: .inline)
}
.tabItem {
Image(systemName: "list.bullet")
Text("Checking")
}
.tag(0)

// Insights module
NavigationView {
InsightsView(viewModel: InsightsViewModel(dataManager: dataManager))
.navigationBarTitle("Reports and Insights", displayMode: .inline)
}
.tabItem {
Image(systemName: "arrow.up.right")
Text("Insights")
}
.tag(1)

// Add Transactions Tab
NavigationView {
TransactionAddView2(selectedTab: $selectedTab) // Reuse or new transaction add
.navigationBarTitle("Add Transaction", displayMode: .inline)
}
.tabItem {
Image(systemName: "plus.circle")
Text("Add Transaction")
}
.tag(2)

// Combined Management Tab
NavigationView {
ManagementView(isDocumentPickerPresented: $isDocumentPickerPresented)
.navigationBarTitle("Management", displayMode: .inline)
}
.tabItem {
Image(systemName: "folder")
Text("Management")
}
.tag(3)

// Settings Tab
NavigationView {
SettingsView(viewModel: infotableViewModel) // Payees, Accounts, Currency
.navigationBarTitle("Settings", displayMode: .inline)
}
.tabItem {
Image(systemName: "gearshape")
Text("Settings")
}
.tag(4)
}
.onChange(of: selectedTab) { tab in
if tab == 2 {
isPresentingTransactionAddView = true
}
}
}
connectedView
} else {
VStack(spacing: 20) {
Button("Open Database") {
isDocumentPickerPresented = true
}
Button("New Database") {
isNewDocumentPickerPresented = true
isSampleDocumnt = false
}
Button("Sample Database") {
isNewDocumentPickerPresented = true
isSampleDocumnt = true
}
}
disconnectedView
}
}
.fileImporter(
isPresented: $isDocumentPickerPresented,
allowedContentTypes: [.item],
allowsMultipleSelection: false
) { result in
switch result {
case .success(let urls):
if let selectedURL = urls.first {
if selectedURL.startAccessingSecurityScopedResource() {
dataManager.openDatabase(at: selectedURL)
UserDefaults.standard.set(selectedURL.path, forKey: "SelectedFilePath")
selectedURL.stopAccessingSecurityScopedResource()
} else {
print("Unable to access file at URL: \(selectedURL)")
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
selectedTab = 0
}
}
case .failure(let error):
print("Failed to pick a document: \(error.localizedDescription)")
}
}
) { handleFileImport($0) }
.fileExporter(
isPresented: $isNewDocumentPickerPresented,
document: MMEXDocument(),
contentType: .mmb,
defaultFilename: isSampleDocumnt ? "Sample.mmb" : "Untitled.mmb"
) { result in
switch result {
case .success(let url):
print("Successfully created new document: \(url)")
if url.startAccessingSecurityScopedResource() {
dataManager.openDatabase(at: url)
UserDefaults.standard.set(url.path, forKey: "SelectedFilePath")
url.stopAccessingSecurityScopedResource()
} else {
print("Unable to access file at URL: \(url)")
defaultFilename: isSampleDocument ? "Sample.mmb" : "Untitled.mmb"
) { handleFileExport($0) }
}

private var connectedView: some View {
Group {
if horizontalSizeClass == .regular {
NavigationSplitView {
SidebarView(selectedTab: $selectedTab)
} detail: {
TabContentView(selectedTab: $selectedTab, isDocumentPickerPresented: $isDocumentPickerPresented, isNewDocumentPickerPresented: $isNewDocumentPickerPresented, isSampleDocument: $isSampleDocument)
}
let repository = dataManager.getRepository()
guard let tables = Bundle.main.url(forResource: "tables.sql", withExtension: "") else {
print("Cannot find tables.sql in bundle")
return
} else {
let infotableViewModel = InfotableViewModel(dataManager: dataManager)
TabView(selection: $selectedTab) {
transactionTab(viewModel: infotableViewModel)
insightsTab(viewModel: InsightsViewModel(dataManager: dataManager))
addTransactionTab
managementTab
settingsTab(viewModel: infotableViewModel)
}
.onChange(of: selectedTab) { tab in
if tab == 2 { isPresentingTransactionAddView = true }
}
}
}
}

private var disconnectedView: some View {
VStack(spacing: 30) {
Image(systemName: "tray.fill")
.resizable()
.frame(width: 80, height: 80)
.foregroundColor(.accentColor)
Text("No Database Connected")
.font(.title)
.padding(.bottom, 10)
Text("Please open an existing database or create a new one to get started.")
.font(.body)
.multilineTextAlignment(.center)
.padding(.horizontal)

Button(action: { isDocumentPickerPresented = true }) {
Label("Open Database", systemImage: "folder.fill")
}
.buttonStyle(.borderedProminent)
.controlSize(.large)

Button(action: { isNewDocumentPickerPresented = true; isSampleDocument = false }) {
Label("Create New Database", systemImage: "plus.app.fill")
}
.buttonStyle(.bordered)
.controlSize(.large)

Button(action: { isNewDocumentPickerPresented = true; isSampleDocument = true }) {
Label("Use Sample Database", systemImage: "doc.text.fill")
}
.buttonStyle(.bordered)
.controlSize(.large)
}
.padding()
}

// Transaction tab
private func transactionTab(viewModel: InfotableViewModel) -> some View {
NavigationView {
TransactionListView2(viewModel: viewModel)
.navigationBarTitle("Latest Transactions", displayMode: .inline)
}
.tabItem {
Image(systemName: "list.bullet")
Text("Checking")
}
.tag(0)
}

// Insights tab
private func insightsTab(viewModel: InsightsViewModel) -> some View {
NavigationView {
InsightsView(viewModel: viewModel)
.navigationBarTitle("Reports and Insights", displayMode: .inline)
}
.tabItem {
Image(systemName: "arrow.up.right")
Text("Insights")
}
.tag(1)
}

// Add transaction tab
private var addTransactionTab: some View {
NavigationView {
TransactionAddView2(selectedTab: $selectedTab)
.navigationBarTitle("Add Transaction", displayMode: .inline)
}
.tabItem {
Image(systemName: "plus.circle")
Text("Add Transaction")
}
.tag(2)
}

// Management tab
private var managementTab: some View {
NavigationView {
ManagementView(isDocumentPickerPresented: $isDocumentPickerPresented, isNewDocumentPickerPresented: $isNewDocumentPickerPresented, isSampleDocument: $isSampleDocument)
.navigationBarTitle("Management", displayMode: .inline)
}
.tabItem {
Image(systemName: "folder")
Text("Management")
}
.tag(3)
}

// Settings tab
private func settingsTab(viewModel: InfotableViewModel) -> some View {
NavigationView {
SettingsView(viewModel: viewModel)
.navigationBarTitle("Settings", displayMode: .inline)
}
.tabItem {
Image(systemName: "gearshape")
Text("Settings")
}
.tag(4)
}

// File import handling
private func handleFileImport(_ result: Result<[URL], Error>) {
switch result {
case .success(let urls):
if let selectedURL = urls.first {
if selectedURL.startAccessingSecurityScopedResource() {
dataManager.openDatabase(at: selectedURL)
UserDefaults.standard.set(selectedURL.path, forKey: "SelectedFilePath")
selectedURL.stopAccessingSecurityScopedResource()
} else {
print("Unable to access file at URL: \(selectedURL)")
}
repository.execute(url: tables)
if isSampleDocumnt { repository.insertSampleData() }
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
selectedTab = 0
}
case .failure(let error):
print("Failed to create a new document: \(error.localizedDescription)")
}
case .failure(let error):
print("Failed to pick a document: \(error.localizedDescription)")
}
}

// File export handling
private func handleFileExport(_ result: Result<URL, Error>) {
switch result {
case .success(let url):
print("Successfully created new document: \(url)")
if url.startAccessingSecurityScopedResource() {
dataManager.openDatabase(at: url)
UserDefaults.standard.set(url.path, forKey: "SelectedFilePath")
url.stopAccessingSecurityScopedResource()
} else {
print("Unable to access file at URL: \(url)")
}
let repository = dataManager.getRepository()
if let tables = Bundle.main.url(forResource: "tables.sql", withExtension: "") {
repository.execute(url: tables)
if isSampleDocument { repository.insertSampleData() }
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
selectedTab = 0
}
case .failure(let error):
print("Failed to create a new document: \(error.localizedDescription)")
}
}
}
Expand Down Expand Up @@ -196,6 +238,8 @@ struct SidebarView: View {
struct TabContentView: View {
@Binding var selectedTab: Int
@Binding var isDocumentPickerPresented: Bool
@Binding var isNewDocumentPickerPresented: Bool
@Binding var isSampleDocument: Bool
@EnvironmentObject var dataManager: DataManager // Access DataManager from environment

var body: some View {
Expand All @@ -214,7 +258,7 @@ struct TabContentView: View {
TransactionAddView2(selectedTab: $selectedTab)
.navigationBarTitle("Add Transaction", displayMode: .inline)
case 3:
ManagementView(isDocumentPickerPresented: $isDocumentPickerPresented)
ManagementView(isDocumentPickerPresented: $isDocumentPickerPresented, isNewDocumentPickerPresented: $isNewDocumentPickerPresented, isSampleDocument: $isSampleDocument)
.navigationBarTitle("Management", displayMode: .inline)
case 4:
SettingsView(viewModel: infotableViewModel)
Expand All @@ -227,11 +271,7 @@ struct TabContentView: View {
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
// Initialize the DataManager and inject it into the preview
let dataManager = DataManager()
ContentView()
.environmentObject(dataManager) // Inject DataManager
}
#Preview(){
ContentView()
.environmentObject(DataManager()) // Inject DataManager
}
Loading