Added scene router, main view for the entire app, and more preparations for a multi-page application.

This commit is contained in:
Andrew Lalis 2023-12-20 13:16:44 -05:00
parent 974fbe0b6e
commit 2959947acc
11 changed files with 216 additions and 22 deletions

View File

@ -1,10 +1,14 @@
package com.andrewlalis.perfin;
import com.andrewlalis.perfin.control.MainViewController;
import com.andrewlalis.perfin.view.SceneRouter;
import com.andrewlalis.perfin.view.SplashScreenStage;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.nio.file.Path;
import java.util.function.Consumer;
/**
* The class from which the JavaFX-based application starts.
@ -29,7 +33,12 @@ public class PerfinApp extends Application {
private void initMainScreen(Stage stage) {
stage.hide();
stage.setScene(SceneUtil.load("/accounts-view.fxml"));
Scene mainViewScene = SceneUtil.load("/main-view.fxml", (Consumer<MainViewController>) c -> {
c.router = new SceneRouter(c.mainContainer::setCenter)
.map("accounts", "/accounts-view.fxml")
.map("edit-account", "/edit-account.fxml");
});
stage.setScene(mainViewScene);
stage.setTitle("Perfin");
}
}

View File

@ -4,13 +4,17 @@ import com.andrewlalis.perfin.SceneUtil;
import com.andrewlalis.perfin.model.Account;
import com.andrewlalis.perfin.model.Profile;
import com.andrewlalis.perfin.view.BindingUtil;
import javafx.application.Platform;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
import java.util.List;
import java.util.function.Consumer;
public class AccountsViewController {
@ -18,27 +22,42 @@ public class AccountsViewController {
public BorderPane mainContainer;
@FXML
public FlowPane accountsPane;
@FXML
public Label noAccountsLabel;
private final ObservableList<Account> accountsList = FXCollections.observableArrayList();
@FXML
public void initialize() {
// Sync the size of the accounts pane to its container.
accountsPane.minWidthProperty().bind(mainContainer.widthProperty());
accountsPane.prefWidthProperty().bind(mainContainer.widthProperty());
accountsPane.prefWrapLengthProperty().bind(mainContainer.widthProperty());
accountsPane.maxWidthProperty().bind(mainContainer.widthProperty());
// Map each account in our list to an account tile element.
BindingUtil.mapContent(accountsPane.getChildren(), accountsList, account -> SceneUtil.loadNode(
"/account-tile.fxml",
(Consumer<AccountTileController>) c -> c.setAccount(account)
));
// Show the "no accounts" label when the accountsList is empty.
var listProp = new SimpleListProperty<>(accountsList);
noAccountsLabel.visibleProperty().bind(listProp.emptyProperty());
noAccountsLabel.managedProperty().bind(noAccountsLabel.visibleProperty());
accountsPane.visibleProperty().bind(listProp.emptyProperty().not());
accountsPane.managedProperty().bind(accountsPane.visibleProperty());
// Populate the list of accounts once a profile is available.
Profile.whenLoaded(profile -> {
populateAccounts(profile.getDataSource().getAccountRepository().findAll());
accountsList.setAll(profile.getDataSource().getAccountRepository().findAll());
});
}
private void populateAccounts(List<Account> accounts) {
this.accountsList.clear();
this.accountsList.addAll(accounts);
@FXML
public void createNewAccount() {
Stage mainStage = (Stage) mainContainer.getScene().getWindow();
Scene editAccountScene = SceneUtil.load("/edit-account.fxml");
mainStage.setScene(editAccountScene);
}
}

View File

@ -0,0 +1,24 @@
package com.andrewlalis.perfin.control;
import com.andrewlalis.perfin.view.SceneRouter;
import javafx.fxml.FXML;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
public class MainViewController {
public SceneRouter router;
@FXML
public BorderPane mainContainer;
@FXML
public HBox mainFooter;
@FXML
public void goToAccounts() {
router.goTo("accounts");
}
@FXML
public void goToEditAccounts() {
router.goTo("edit-account");
}
}

View File

@ -2,6 +2,7 @@ package com.andrewlalis.perfin.data;
import com.andrewlalis.perfin.model.Account;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
@ -9,4 +10,5 @@ public interface AccountRepository {
long insert(Account account);
List<Account> findAll();
Optional<Account> findById(long id);
BigDecimal deriveCurrentBalance(long id);
}

View File

@ -5,6 +5,7 @@ import com.andrewlalis.perfin.data.DbUtil;
import com.andrewlalis.perfin.model.Account;
import com.andrewlalis.perfin.model.AccountType;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
@ -39,6 +40,12 @@ public record JdbcAccountRepository(Connection conn) implements AccountRepositor
return DbUtil.findById(conn, "SELECT * FROM account WHERE id = ?", id, JdbcAccountRepository::parseAccount);
}
@Override
public BigDecimal deriveCurrentBalance(long id) {
// TODO: Implement this!
return BigDecimal.valueOf(0, 4);
}
private static Account parseAccount(ResultSet rs) throws SQLException {
long id = rs.getLong("id");
LocalDateTime createdAt = DbUtil.utcLDTFromTimestamp(rs.getTimestamp("created_at"));

View File

@ -0,0 +1,49 @@
package com.andrewlalis.perfin.view;
import com.andrewlalis.perfin.SceneUtil;
import javafx.application.Platform;
import javafx.scene.Parent;
import javafx.scene.layout.Pane;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
public class SceneRouter {
private final Consumer<Parent> setter;
private final Map<String, Parent> routeMap = new HashMap<>();
public Object context = null;
public SceneRouter(Pane pane) {
this(p -> pane.getChildren().setAll(p));
}
public SceneRouter(Consumer<Parent> setter) {
this.setter = setter;
}
public SceneRouter map(String route, Parent scene) {
routeMap.put(route, scene);
return this;
}
public SceneRouter map(String route, String fxml) {
return map(route, SceneUtil.loadNode(fxml));
}
public void goTo(String route, Object context) {
Parent node = routeMap.get(route);
if (node == null) throw new IllegalArgumentException("Route " + route + " is not mapped to any node.");
this.context = context;
Platform.runLater(() -> setter.accept(node));
}
public void goTo(String route) {
goTo(route, null);
}
@SuppressWarnings("unchecked")
public <T> T getContext() {
return (T) context;
}
}

View File

@ -1,8 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.FlowPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.Button?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<BorderPane
minHeight="400.0"
minWidth="600.0"
@ -12,20 +16,26 @@
fx:id="mainContainer"
stylesheets="@style/accounts-view.css"
>
<top>
<MenuBar BorderPane.alignment="CENTER">
<Menu mnemonicParsing="false" text="File">
<MenuItem mnemonicParsing="false" text="Close" />
</Menu>
<Menu mnemonicParsing="false" text="Edit">
<MenuItem mnemonicParsing="false" text="Delete" />
</Menu>
<Menu mnemonicParsing="false" text="Help">
<MenuItem mnemonicParsing="false" text="About" />
</Menu>
</MenuBar>
</top>
<!-- <top>-->
<!-- <MenuBar BorderPane.alignment="CENTER">-->
<!-- <Menu mnemonicParsing="false" text="File">-->
<!-- <MenuItem mnemonicParsing="false" text="Close" />-->
<!-- </Menu>-->
<!-- <Menu mnemonicParsing="false" text="Edit">-->
<!-- <MenuItem mnemonicParsing="false" text="Delete" />-->
<!-- </Menu>-->
<!-- <Menu mnemonicParsing="false" text="Help">-->
<!-- <MenuItem mnemonicParsing="false" text="About" />-->
<!-- </Menu>-->
<!-- </MenuBar>-->
<!-- </top>-->
<center>
<FlowPane fx:id="accountsPane" BorderPane.alignment="TOP_LEFT" vgap="5" hgap="5"/>
<VBox>
<HBox styleClass="actionsBar">
<Button text="Add an Account" onAction="#createNewAccount"/>
</HBox>
<FlowPane fx:id="accountsPane" BorderPane.alignment="TOP_LEFT" vgap="5" hgap="5"/>
<Label fx:id="noAccountsLabel" BorderPane.alignment="TOP_LEFT" text="No accounts have been added to this profile."/>
</VBox>
</center>
</BorderPane>

View File

@ -0,0 +1,33 @@
<?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" xmlns:fx="http://javafx.com/fxml/1">
<top>
<Label text="Edit Account"/>
</top>
<center>
<GridPane BorderPane.alignment="CENTER">
<columnConstraints>
<ColumnConstraints hgrow="NEVER" minWidth="10.0" />
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0" />
</columnConstraints>
<Label GridPane.columnIndex="0" GridPane.rowIndex="0" text="Name"/>
<TextField GridPane.columnIndex="1" GridPane.rowIndex="0" text="Bleh"/>
<Label GridPane.columnIndex="0" GridPane.rowIndex="1" text="Account Number"/>
<TextField GridPane.columnIndex="1" GridPane.rowIndex="1" text="1234"/>
<Label GridPane.columnIndex="0" GridPane.rowIndex="2" text="Currency"/>
<ChoiceBox GridPane.columnIndex="1" GridPane.rowIndex="2">
</ChoiceBox>
</GridPane>
</center>
<bottom>
<FlowPane BorderPane.alignment="CENTER_RIGHT">
<Button text="Save" />
<Button text="Cancel" />
</FlowPane>
</bottom>
</BorderPane>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<BorderPane
xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:id="mainContainer"
fx:controller="com.andrewlalis.perfin.control.MainViewController"
stylesheets="@style/main-view.css"
>
<top>
<HBox fx:id="mainHeader">
<Label text="TODO: Add breadcrumb navigation/header here!"/>
<Button text="Go to accounts!" onAction="#goToAccounts"/>
<Button text="Go to edit accounts!" onAction="#goToEditAccounts"/>
</HBox>
</top>
<bottom>
<HBox fx:id="mainFooter" spacing="5">
<Label text="Perfin v0.0.1"/>
<Label text="Copyright Bleh"/>
</HBox>
</bottom>
</BorderPane>

View File

@ -1,3 +1,11 @@
#accountsPane {
-fx-padding: 5px;
}
}
#noAccountsLabel {
-fx-padding: 5px;
}
.actionsBar {
-fx-padding: 5px;
}

View File

@ -0,0 +1,7 @@
#mainHeader {
-fx-padding: 3px;
}
#mainFooter {
-fx-padding: 3px;
}