start some transactions

main
Steve Ayerhart 2022-01-18 17:37:52 -05:00
parent 1b3203cc9f
commit 3af41dbeec
No known key found for this signature in database
GPG Key ID: 5C815FDF3A00B8BA
25 changed files with 284 additions and 74 deletions

View File

@ -1,7 +1,14 @@
gnome = import ('gnome')
reclaim_resources = gnome.compile_resources(
'reclaim-resources',
'reclaim.gresource.xml',
c_name: 'reclaim'
)
install_data(
meson.project_name() + '.gschema.xml',
install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas')
)
subdir('ui')

View File

@ -1,18 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/reclaim">
<file alias="application.css" compressed="true">application.css</file>
<gresource prefix="/reclaim">
<file alias="application.css" compressed="true">application.css</file>
<file compressed="true" preprocess="xml-stripblanks" alias="MainWindow.ui">ui/MainWindow.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="MainAccountListView.ui">ui/MainAccountListView.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="MainAccountListRow.ui">ui/MainAccountListRow.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="ContentView.ui">ui/ContentView.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="MainWindow.ui">ui/MainWindow.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="MainAccountListView.ui">ui/MainAccountListView.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="MainAccountListRow.ui">ui/MainAccountListRow.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="ContentView.ui">ui/ContentView.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="EditAccountsModal.ui">ui/EditAccountsModal.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="EditAcountListView.ui">ui/EditAccountListView.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="EditAccountRow.ui">ui/EditAccountRow.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="EditAccountRowContent.ui">ui/EditAccountRowContent.ui</file>
</gresource>
<file compressed="true" preprocess="xml-stripblanks" alias="EditAccountsModal.ui">ui/EditAccountsModal.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="EditAcountListView.ui">ui/EditAccountListView.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="EditAccountListRow.ui">ui/EditAccountListRow.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="EditAccountListRowContent.ui">ui/EditAccountListRowContent.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="AccountView.ui">ui/AccountView.ui</file>
</gresource>
</gresources>

View File

@ -1,21 +1,30 @@
CREATE TABLE account (
id INTEGER PRIMARY KEY AUTOINCREMENT,
id TEXT PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE transaction (
id INTEGER PRIMARY KEY AUTOINCREMENT,
id TEXT PRIMARY KEY,
description TEXT NOT NULL,
datetimestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
)
category_id INTEGER REFERENCES category(id),
);
CREATE TABLE journal (
id INTEGER PRIMARY KEY AUTOINCREMENT,
transaction_id INTEGER REFERENCES transaction(id),
account_id INTEGER REFERENCES account(id),
account_id TEXT REFERENCES account(id),
amount REAL NOT NULL,
is_credit BOOLEAN NOT NULL
)
);
CREATE TABLE category (
id INTEGER PRIMARY KEY AUTOINCREMENT,
parent_id INTEGER REFERENCES category(id) NOT NULL DEFAULT 0,
name TEXT NOT NULL,
DESCRIPTION TEXT
);

53
data/ui/AccountView.ui Normal file
View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="ReclaimAccountView" parent="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="AdwHeaderBar" id="titlebar">
<property name="hexpand">True</property>
<property name="show-start-title-buttons">False</property>
<property name="title-widget">
<object class="GtkLabel" id="title">
<property name="single-line-mode">True</property>
</object>
</property>
<child type="end">
<object class="GtkSearchEntry" id="search_entry"/>
</child>
</object>
</child>
<child>
<object class="GtkScrolledWindow" id="scroll">
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<child>
<object class="GtkColumnView" id="column_view">
<property name="show-column-separators">True</property>
<property name="show-row-separators">True</property>
<property name="reorderable">True</property>
<property name="enable-rubberband">True</property>
<child>
<object class="GtkColumnViewColumn" id="date_column">
<property name="title" translatable="true">Date</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn" id="category_column">
<property name="expand">True</property>
<property name="resizable">True</property>
<property name="title" translatable="true">Category</property>
</object>
</child>
<child>
<object class="GtkColumnViewColumn" id="payee_column">
<property name="resizable">True</property>
<property name="expand">True</property>
<property name="title" translatable="true">Payee</property>
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>

View File

@ -14,11 +14,12 @@
</object>
</child>
<style>
<class name="reclaim-titlebar"/>
<class name="reclaim-titlebar"/>
</style>
</object>
</child>
<!--stack?-->
<object class="GtkStack" id="accountViews">
</object>
</object>
</child>
</template>

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="child">
<object class="AdwBin">
<property name="child">
<object class="GtkBox">
<property name="orientation">horizontal</property>
<property name="valign">center</property>
<property name="spacing">12</property>
<child type="prefix">
<object class="GtkLabel" id="account_label">
<property name="valign">center</property>
<binding name="label">
<lookup name="name" type="ReclaimAccount">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
</object>
</child>
<child type="suffix">
<object class="GtkButton" id="delete_button">
<property name="icon-name">window-close-symbolic</property>
<property name="valign">center</property>
<signal name="clicked" handler="on_delete_button_clicked" />
<style>
<class name="flat" />
<class name="circular" />
</style>
</object>
</child>
</object>
</property>
</object>
</property>
</template>
</interface>

View File

@ -9,7 +9,11 @@
<child type="prefix">
<object class="GtkLabel" id="account_label">
<property name="valign">center</property>
<property name="hexpand">1</property>
<binding name="label">
<lookup name="name" type="ReclaimAccount">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>
</object>
</child>
<child type="suffix">

View File

@ -11,7 +11,7 @@
<object class="GtkListView" id="lv">
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="resource">/reclaim/EditAccountRow.ui</property>
<property name="resource">/reclaim/EditAccountListRow.ui</property>
</object>
</property>
<property name="model">

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="GtkListItem">
<property name="child">
<object class="ReclaimEditAccountRowContent">
<binding name="account">
<lookup name="item">GtkListItem</lookup>
</binding>
</object>
</property>
</template>
</interface>

View File

@ -67,6 +67,13 @@
<property name="margin-bottom">12</property>
<property name="vexpand">1</property>
<child>
<object class="ReclaimEditAccountListView" id="edit_account_list_view">
<binding name="accounts">
<lookup name="accounts" type="ReclaimAccountsViewModel">
<lookup name="accounts_view_model">ReclaimEditAccountsModal</lookup>
</lookup>
</binding>
</object>
</child>
</object>
</child>

View File

@ -7,14 +7,14 @@
<child>
<object class="GtkImage" id="account_badge">
<property name="valign">center</property>
<property name="icon-name">account-symbolic</property>
<property name="icon-name">notebook-symbolic</property>
</object>
</child>
<child>
<object class="GtkLabel" id="account_label">
<property name="valign">center</property>
<binding name="label">
<lookup name="title" type="ReclaimAccount">
<lookup name="name" type="ReclaimAccount">
<lookup name="item">GtkListItem</lookup>
</lookup>
</binding>

View File

@ -1,17 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="ReclaimAccountListView" parent="ReclaimView">
<template class="ReclaimMainAccountListView" parent="ReclaimView">
<child>
<object class="GtkScrolledWindow">
<property name="hscrollbar-policy">never</property>
<property name="width-request">250</property>
<property name="vexpand">1</property>
<child>
<object class="GtkListView" id="lv">
<style>
<class name="boxed-list"/>
<class name="content-sidebar-main"/>
</style>
<property name="factory">
<object class="GtkBuilderListItemFactory">
<property name="resource">/reclaim/MainAccountListRow.ui</property>
@ -20,7 +15,7 @@
<property name="model">
<object class="GtkNoSelection" id="selection_model">
<binding name="model">
<lookup name="accounts">ReclaimMainAcountListView</lookup>
<lookup name="accounts">ReclaimMainAccountListView</lookup>
</binding>
</object>
</property>

View File

@ -93,6 +93,7 @@
<property name="margin-bottom">6</property>
<property name="vexpand">1</property>
<child>
<object class="GtkStackSidebar" id="account_switcher"/>
</child>
</object>
</child>
@ -152,18 +153,7 @@
<child>
<object class="AdwLeafletPage">
<property name="child">
<object class="GtkStack" id="grid">
<child>
<object class="GtkStackPage">
<property name="name">content</property>
<property name="child">
<object class="ReclaimContentView">
<property name="hexpand">yes</property>
</object>
</property>
</object>
</child>
</object>
<object class="GtkStack" id="views"/>
</property>
</object>
</child>

View File

@ -1,7 +0,0 @@
gnome = import ('gnome')
reclaim_resources = gnome.compile_resources(
'reclaim-resources',
'reclaim.gresource.xml',
c_name: 'reclaim'
)

View File

@ -28,11 +28,13 @@ namespace Reclaim {
base.startup ();
// typeof (AccountListView).ensure ();
typeof (MainAccountListView).ensure ();
var account_repository = new SqliteAccountRepository ();
var accounts_view_model = new AccountsViewModel (account_repository);
typeof (EditAccountListRowContent).ensure ();
typeof (EditAccountListView).ensure ();
typeof (ContentView).ensure ();
new MainWindow (this, accounts_view_model);

View File

@ -12,8 +12,9 @@ source_files = files(
'ui/EditAccountsModal.vala',
'ui/EditAccountListView.vala',
'ui/EditAccountRowContent.vala',
'ui/EditAccountListRowContent.vala',
'ui/AccountView.vala',
'ui/ContentView.vala',
'ui/AccountsViewModel.vala',
'ui/MainAccountListView.vala',

View File

@ -17,17 +17,67 @@ namespace Reclaim {
}
public Gee.ArrayList<Account> read_all() {
const string query = "SELECT * FROM Account;";
const string query = "SELECT id, name FROM account;";
var accounts = new Gee.ArrayList<Account> ();
accounts.add(new Account());
Sqlite.Statement statement;
int status;
status = database.prepare_v2 (query, -1, out statement);
assert(status == Sqlite.OK);
while ((status = statement.step ()) == Sqlite.ROW) {
var account = new Account ();
account.id = statement.column_text (0);
account.name = statement.column_text (1);
accounts.add (account);
}
statement.reset ();
return accounts;
}
public Account read(string id) {
return new Account ();
const string query = "SELECT id, name FROM account WHERE id = ?;";
Sqlite.Statement statement;
int status;
Account account = new Account ();
status = database.prepare_v2 (query, -1, out statement);
assert (status == Sqlite.OK);
status = statement.bind_text (1, id);
assert (status == Sqlite.OK);
if (statement.step () == Sqlite.ROW) {
account.id = statement.column_text (0);
account.name = statement.column_text (1);
}
return account;
}
public void create(Account account) {
const string query = "INSERT OR IGNORE INTO account (id, name) VALUES (?, ?);";
Sqlite.Statement statement;
int status;
status = database.prepare_v2 (query, -1, out statement);
assert (status == Sqlite.OK);
status = statement.bind_text (1, account.id);
assert (status == Sqlite.OK);
status = statement.bind_text (2, account.name);
assert (status == Sqlite.OK);
if (statement.step () != Sqlite.DONE) {
warning ("Error: %d: %s", database.errcode (), database.errmsg ());
}
statement.reset ();
}
public void update(Account account) {

28
src/ui/AccountList.vala Normal file
View File

@ -0,0 +1,28 @@
namespace Reclaim {
public class AccountList : Object, ArrayList, ListModel {
private IAccountRepository repository { get; construct }
public AccountList (IAccountRepository repository) {
Object(repository: repository);
}
construct {
populate_accounts ();
}
public void create_new_account (Account account) {
repository.create (account);
accounts.add (account);
}
public void populate_accounts () {
var accounts = repository.read_all ();
this.accounts.add_all (accounts);
}
public ovverride
}
}

22
src/ui/AccountView.vala Normal file
View File

@ -0,0 +1,22 @@
namespace Reclaim {
[GtkTemplate (ui = "/reclaim/AccountView.ui")]
public class AccountView : Gtk.Box {
public unowned MainWindow main_window = null;
public Account account { get; set; }
[GtkChild]
private unowned Gtk.Label title;
[GtkChild]
private unowned Gtk.SearchEntry search_entry;
public AccountView (MainWindow main_window, Account account) {
Object (account: account);
this.main_window = main_window;
title.label = account.name;
search_entry.set_key_capture_widget (main_window);
search_entry.placeholder_text = "Search %s".printf (account.name);
}
}
}

View File

@ -1,7 +1,7 @@
namespace Reclaim {
public class AccountsViewModel : Object {
public ObservableList<Account> accounts { get; default = new ObservableList<Account> (); }
public IAccountRepository? repository { get; construct; }
public IAccountRepository repository { get; construct; }
public AccountsViewModel (IAccountRepository repository) {
Object(repository: repository);

View File

@ -2,7 +2,10 @@
namespace Reclaim {
public class EditAccountListRowContent : Adw.Bin {
public signal void clicked ();
public AccountsViewModel? accounts {get; set;}
public Account account { get; set;}
public EditAccountListRowContent (Account account) {
Object(account: account);
}
}
}

View File

@ -1,8 +1,8 @@
namespace Reclaim {
[GtkTemplate (ui = "/reclaim/EditAcountListView.ui")]
public class EditAccountListView : View {
public ObservableList<Account>? accounts { get; set; }
public AccountsViewModel? accounts_view_model { get; set; }
public ObservableList<Account> accounts { get; set; }
public AccountsViewModel accounts_view_model { get; set; }
public signal void new_account_requested (Account account);
public signal void account_removal_requested (Account account);

View File

@ -1,7 +1,7 @@
namespace Reclaim {
[GtkTemplate (ui = "/reclaim/MainAccountListView.ui")]
public class MainAccountListView : View {
public ObservableList<Account>? accounts { get; set; }
public AccountsViewModel? accounts_view_model { get; set; }
public ObservableList<Account> accounts { get; set; }
public AccountsViewModel accounts_view_model { get; set; }
}
}

View File

@ -6,8 +6,12 @@ namespace Reclaim {
public Adw.Leaflet? leaflet { get; set; }
public AccountsViewModel accounts_view_model {get; construct; }
public SimpleActionGroup actions { get; construct; }
[GtkChild]
private unowned Gtk.Stack views;
[GtkChild]
private unowned Gtk.StackSidebar account_switcher;
public SimpleActionGroup actions { get; construct; }
public MainWindow (Reclaim.Application app, AccountsViewModel accounts_view_model) {
GLib.Object (
@ -15,6 +19,13 @@ namespace Reclaim {
accounts_view_model: accounts_view_model,
title: "Reclaim"
);
foreach (var account in accounts_view_model.accounts) {
var accountView = new AccountView(this, account);
views.add_titled (accountView, account.name, account.name);
}
account_switcher.set_stack (views);
}
construct {

View File

@ -1,6 +1,10 @@
namespace Reclaim {
public class ObservableList<T> : Object, ListModel {
Gee.ArrayList<T> data = new Gee.ArrayList<T> ();
private Gee.ArrayList<T> data;
public ObservableList () {
data = new Gee.ArrayList<T> ();
}
public void add (T item) {
var position = data.size;
@ -41,6 +45,10 @@ namespace Reclaim {
return true;
}
public Gee.Iterator<T> iterator () {
return data.iterator ();
}
Object? get_item (uint position) {
return get ((int)position) as Object;
}