Added account tile, more model definitions, and improved scene and node loading.

This commit is contained in:
Andrew Lalis 2023-12-15 11:04:26 -05:00
parent 050f9c8a6b
commit 53ff257323
17 changed files with 305 additions and 28 deletions

17
pom.xml
View File

@ -28,9 +28,20 @@
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.44.1.0</version>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.16.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.16.0</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.224</version>
</dependency>
</dependencies>

View File

@ -1,28 +1,39 @@
package com.andrewlalis.perfin;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URL;
import java.util.function.Consumer;
public class SceneUtil {
public static Scene load(String fxml, Object controller) {
public static <T> Parent loadNode(String fxml, Consumer<T> controllerConfig) {
FXMLLoader loader = new FXMLLoader(SceneUtil.class.getResource(fxml));
if (controller != null) {
if (loader.getController() != null) {
throw new IllegalStateException("Cannot set loader for resource " + fxml + " because it has declared one already.");
}
loader.setController(controller);
}
try {
return new Scene(loader.load());
Parent p = loader.load();
if (controllerConfig != null) {
T controller = loader.getController();
if (controller == null) throw new NullPointerException("Could not get controller from " + fxml);
controllerConfig.accept(controller);
}
return p;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
public static Parent loadNode(String fxml) {
return loadNode(fxml, null);
}
public static <T> Scene load(String fxml, Consumer<T> controllerConfig) {
return new Scene(loadNode(fxml, controllerConfig));
}
public static Scene load(String fxml) {
return load(fxml, null);
}

View File

@ -0,0 +1,45 @@
package com.andrewlalis.perfin.control;
import com.andrewlalis.perfin.model.Account;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.event.EventType;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
public class AccountTileController {
private Account account;
@FXML
public VBox container;
@FXML
public Label accountNumberLabel;
@FXML
public Label accountBalanceLabel;
@FXML
public VBox accountNameBox;
@FXML
public Label accountNameLabel;
@FXML
public void initialize() {
ObservableValue<Boolean> accountNameTextPresent = accountNameLabel.textProperty().map(t -> t != null && !t.isBlank());
accountNameBox.visibleProperty().bind(accountNameTextPresent);
accountNameBox.managedProperty().bind(accountNameTextPresent);
}
public void setAccount(Account account) {
this.account = account;
Platform.runLater(() -> {
accountNumberLabel.setText(account.getAccountNumber());
accountBalanceLabel.setText(account.getCurrency().getSymbol() + " " + account.getCurrentBalance().toPlainString());
accountNameLabel.setText(account.getName());
container.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
System.out.println("Clicked on " + account.getAccountNumber());
});
});
}
}

View File

@ -1,10 +1,47 @@
package com.andrewlalis.perfin.control;
import com.andrewlalis.perfin.SceneUtil;
import com.andrewlalis.perfin.model.Account;
import com.andrewlalis.perfin.model.AccountType;
import javafx.fxml.FXML;
import javafx.scene.Parent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import java.math.BigDecimal;
import java.util.Currency;
import java.util.List;
import java.util.function.Consumer;
public class MainController {
@FXML
public BorderPane mainContainer;
@FXML
public FlowPane accountsPane;
@FXML
public void initialize() {
accountsPane.minWidthProperty().bind(mainContainer.widthProperty());
accountsPane.prefWidthProperty().bind(mainContainer.widthProperty());
accountsPane.prefWrapLengthProperty().bind(mainContainer.widthProperty());
accountsPane.maxWidthProperty().bind(mainContainer.widthProperty());
List<Account> tempAccounts = List.of(
new Account(AccountType.CHECKING, "1234-4324-4321-4143", BigDecimal.valueOf(3745.01), "Main Checking", Currency.getInstance("USD")),
new Account(AccountType.CHECKING, "1234-4324-4321-4143", BigDecimal.valueOf(3745.01), "Main Checking", Currency.getInstance("USD")),
new Account(AccountType.CHECKING, "1234-4324-4321-4143", BigDecimal.valueOf(3745.01), "Main Checking", Currency.getInstance("USD")),
new Account(AccountType.CHECKING, "1234-4324-4321-4143", BigDecimal.valueOf(3745.01), "Main Checking", Currency.getInstance("USD"))
);
populateAccounts(tempAccounts);
}
private void populateAccounts(List<Account> accounts) {
accountsPane.getChildren().clear();
for (var account : accounts) {
Parent node = SceneUtil.loadNode(
"/account-tile.fxml",
(Consumer<AccountTileController>) c -> c.setAccount(account)
);
accountsPane.getChildren().add(node);
}
}
}

View File

@ -34,7 +34,7 @@ public class StartupSplashScreenController {
}
printlnLater("Perfin initialized. Starting the app now.");
Thread.sleep(50000);
Thread.sleep(500);
Platform.runLater(() -> getSplashStage().setDone());
} catch (Exception e) {

View File

@ -0,0 +1,48 @@
package com.andrewlalis.perfin.model;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Currency;
/**
* The representation of a physical account of some sort (checking, savings,
* credit-card, etc.).
*/
public class Account {
private long id;
private LocalDateTime createdAt;
private AccountType type;
private String accountNumber;
private BigDecimal currentBalance;
private String name;
private Currency currency;
public Account(AccountType type, String accountNumber, BigDecimal currentBalance, String name, Currency currency) {
this.type = type;
this.accountNumber = accountNumber;
this.currentBalance = currentBalance;
this.name = name;
this.currency = currency;
}
public AccountType getType() {
return type;
}
public String getAccountNumber() {
return accountNumber;
}
public BigDecimal getCurrentBalance() {
return currentBalance;
}
public String getName() {
return name;
}
public Currency getCurrency() {
return currency;
}
}

View File

@ -0,0 +1,45 @@
package com.andrewlalis.perfin.model;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Currency;
/**
* A single entry depicting a credit or debit to an account.
* <p>
* The following rules apply in determining the type of an entry:
* </p>
* <ul>
* <li>A <em>debit</em> indicates an increase in assets or decrease in liability.</li>
* <li>A <em>credit</em> indicates a decrease in assets or increase in liability.</li>
* </ul>
*/
public class AccountEntry {
public enum Type {
CREDIT,
DEBIT
}
private long id;
private LocalDateTime timestamp;
private long accountId;
private BigDecimal amount;
private Type type;
private Currency currency;
public AccountEntry(long id, LocalDateTime timestamp, long accountId, BigDecimal amount, Type type, Currency currency) {
this.id = id;
this.timestamp = timestamp;
this.accountId = accountId;
this.amount = amount;
this.type = type;
this.currency = currency;
}
public AccountEntry(long accountId, BigDecimal amount, Type type, Currency currency) {
this.accountId = accountId;
this.amount = amount;
this.type = type;
this.currency = currency;
}
}

View File

@ -0,0 +1,7 @@
package com.andrewlalis.perfin.model;
public enum AccountType {
CHECKING,
SAVINGS,
CREDIT_CARD
}

View File

@ -0,0 +1,16 @@
package com.andrewlalis.perfin.model;
/**
* A profile is essentially a complete set of data that the application can
* operate on, sort of like a save file or user account. The profile contains
* a set of accounts, transaction records, attached documents, historical data,
* and more. A profile can be imported or exported easily from the application,
* and can be encrypted for additional security. Each profile also has its own
* settings.
* Practically, each profile is stored as its own isolated database file, with
* a name corresponding to the profile's name.
*/
public class Profile {
private String name;
}

View File

@ -3,9 +3,6 @@ package com.andrewlalis.perfin.view;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;

View File

@ -4,7 +4,7 @@ module com.andrewlalis.perfin {
requires javafx.fxml;
requires javafx.graphics;
requires org.xerial.sqlitejdbc;
requires com.fasterxml.jackson.databind;
exports com.andrewlalis.perfin to javafx.graphics;
opens com.andrewlalis.perfin.control to javafx.fxml;

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox
styleClass="account-tile-container"
prefHeight="100.0"
prefWidth="200.0"
stylesheets="@style/account-tile.css"
xmlns="http://javafx.com/javafx/17.0.2-ea"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.andrewlalis.perfin.control.AccountTileController"
fx:id="container"
>
<Label styleClass="main-label" text="Account Info" />
<Separator prefWidth="200.0" />
<Label text="Account Number" styleClass="property-label"/>
<Label fx:id="accountNumberLabel" styleClass="property-value" text="Account Number placeholder" />
<Label text="Account Balance" styleClass="property-label"/>
<Label fx:id="accountBalanceLabel" styleClass="property-value" text="account balance placeholder" />
<VBox fx:id="accountNameBox">
<Label text="Account Name" styleClass="property-label"/>
<Label fx:id="accountNameLabel" styleClass="property-value" text="account name placeholder"/>
</VBox>
</VBox>

View File

@ -1,13 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<BorderPane xmlns="http://javafx.com/javafx/17.0.2-ea"
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.FlowPane?>
<BorderPane
minHeight="400.0"
minWidth="600.0"
xmlns="http://javafx.com/javafx/17.0.2-ea"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.andrewlalis.perfin.control.MainController"
minWidth="600.0" minHeight="400.0">
fx:id="mainContainer"
stylesheets="@style/main.css"
>
<top>
<MenuBar BorderPane.alignment="CENTER">
<Menu mnemonicParsing="false" text="File">
@ -21,4 +25,7 @@
</Menu>
</MenuBar>
</top>
<center>
<FlowPane fx:id="accountsPane" BorderPane.alignment="TOP_LEFT" vgap="5" hgap="5"/>
</center>
</BorderPane>

View File

@ -8,7 +8,7 @@
xmlns="http://javafx.com/javafx/17.0.2-ea"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.andrewlalis.perfin.control.StartupSplashScreenController"
stylesheets="/startup-splash-screen.css"
stylesheets="@style/startup-splash-screen.css"
>
<center>
<TextArea fx:id="content" wrapText="true" editable="false" focusTraversable="false"/>

View File

@ -0,0 +1,24 @@
.account-tile-container {
-fx-border-color: lightgray;
-fx-border-width: 1px;
-fx-border-style: solid;
-fx-padding: 5px;
}
.account-tile-container:hover {
-fx-cursor: hand;
}
.main-label {
-fx-font-weight: bold;
-fx-font-size: large;
}
.property-label {
-fx-font-weight: bold;
}
.property-value {
-fx-font-family: monospace;
-fx-font-size: large;
}

View File

@ -0,0 +1,3 @@
#accountsPane {
-fx-padding: 5px;
}

View File

@ -1,5 +1,5 @@
#sceneRoot {
-fx-background-image: url("images/splash-screen.png");
-fx-background-image: url("../images/splash-screen.png");
-fx-background-repeat: stretch;
-fx-background-size: 400 200;
-fx-background-color: transparent;