Added scene router, main view for the entire app, and more preparations for a multi-page application.
This commit is contained in:
parent
974fbe0b6e
commit
2959947acc
|
@ -1,10 +1,14 @@
|
||||||
package com.andrewlalis.perfin;
|
package com.andrewlalis.perfin;
|
||||||
|
|
||||||
|
import com.andrewlalis.perfin.control.MainViewController;
|
||||||
|
import com.andrewlalis.perfin.view.SceneRouter;
|
||||||
import com.andrewlalis.perfin.view.SplashScreenStage;
|
import com.andrewlalis.perfin.view.SplashScreenStage;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
|
import javafx.scene.Scene;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The class from which the JavaFX-based application starts.
|
* The class from which the JavaFX-based application starts.
|
||||||
|
@ -29,7 +33,12 @@ public class PerfinApp extends Application {
|
||||||
|
|
||||||
private void initMainScreen(Stage stage) {
|
private void initMainScreen(Stage stage) {
|
||||||
stage.hide();
|
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");
|
stage.setTitle("Perfin");
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,13 +4,17 @@ import com.andrewlalis.perfin.SceneUtil;
|
||||||
import com.andrewlalis.perfin.model.Account;
|
import com.andrewlalis.perfin.model.Account;
|
||||||
import com.andrewlalis.perfin.model.Profile;
|
import com.andrewlalis.perfin.model.Profile;
|
||||||
import com.andrewlalis.perfin.view.BindingUtil;
|
import com.andrewlalis.perfin.view.BindingUtil;
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.SimpleListProperty;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
import javafx.scene.layout.FlowPane;
|
import javafx.scene.layout.FlowPane;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public class AccountsViewController {
|
public class AccountsViewController {
|
||||||
|
@ -18,27 +22,42 @@ public class AccountsViewController {
|
||||||
public BorderPane mainContainer;
|
public BorderPane mainContainer;
|
||||||
@FXML
|
@FXML
|
||||||
public FlowPane accountsPane;
|
public FlowPane accountsPane;
|
||||||
|
@FXML
|
||||||
|
public Label noAccountsLabel;
|
||||||
|
|
||||||
private final ObservableList<Account> accountsList = FXCollections.observableArrayList();
|
private final ObservableList<Account> accountsList = FXCollections.observableArrayList();
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
|
// Sync the size of the accounts pane to its container.
|
||||||
accountsPane.minWidthProperty().bind(mainContainer.widthProperty());
|
accountsPane.minWidthProperty().bind(mainContainer.widthProperty());
|
||||||
accountsPane.prefWidthProperty().bind(mainContainer.widthProperty());
|
accountsPane.prefWidthProperty().bind(mainContainer.widthProperty());
|
||||||
accountsPane.prefWrapLengthProperty().bind(mainContainer.widthProperty());
|
accountsPane.prefWrapLengthProperty().bind(mainContainer.widthProperty());
|
||||||
accountsPane.maxWidthProperty().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(
|
BindingUtil.mapContent(accountsPane.getChildren(), accountsList, account -> SceneUtil.loadNode(
|
||||||
"/account-tile.fxml",
|
"/account-tile.fxml",
|
||||||
(Consumer<AccountTileController>) c -> c.setAccount(account)
|
(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 -> {
|
Profile.whenLoaded(profile -> {
|
||||||
populateAccounts(profile.getDataSource().getAccountRepository().findAll());
|
accountsList.setAll(profile.getDataSource().getAccountRepository().findAll());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void populateAccounts(List<Account> accounts) {
|
@FXML
|
||||||
this.accountsList.clear();
|
public void createNewAccount() {
|
||||||
this.accountsList.addAll(accounts);
|
Stage mainStage = (Stage) mainContainer.getScene().getWindow();
|
||||||
|
Scene editAccountScene = SceneUtil.load("/edit-account.fxml");
|
||||||
|
mainStage.setScene(editAccountScene);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package com.andrewlalis.perfin.data;
|
||||||
|
|
||||||
import com.andrewlalis.perfin.model.Account;
|
import com.andrewlalis.perfin.model.Account;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@ -9,4 +10,5 @@ public interface AccountRepository {
|
||||||
long insert(Account account);
|
long insert(Account account);
|
||||||
List<Account> findAll();
|
List<Account> findAll();
|
||||||
Optional<Account> findById(long id);
|
Optional<Account> findById(long id);
|
||||||
|
BigDecimal deriveCurrentBalance(long id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import com.andrewlalis.perfin.data.DbUtil;
|
||||||
import com.andrewlalis.perfin.model.Account;
|
import com.andrewlalis.perfin.model.Account;
|
||||||
import com.andrewlalis.perfin.model.AccountType;
|
import com.andrewlalis.perfin.model.AccountType;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
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);
|
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 {
|
private static Account parseAccount(ResultSet rs) throws SQLException {
|
||||||
long id = rs.getLong("id");
|
long id = rs.getLong("id");
|
||||||
LocalDateTime createdAt = DbUtil.utcLDTFromTimestamp(rs.getTimestamp("created_at"));
|
LocalDateTime createdAt = DbUtil.utcLDTFromTimestamp(rs.getTimestamp("created_at"));
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,12 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import javafx.scene.control.*?>
|
|
||||||
<?import javafx.scene.layout.BorderPane?>
|
<?import javafx.scene.layout.BorderPane?>
|
||||||
<?import javafx.scene.layout.FlowPane?>
|
<?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
|
<BorderPane
|
||||||
minHeight="400.0"
|
minHeight="400.0"
|
||||||
minWidth="600.0"
|
minWidth="600.0"
|
||||||
|
@ -12,20 +16,26 @@
|
||||||
fx:id="mainContainer"
|
fx:id="mainContainer"
|
||||||
stylesheets="@style/accounts-view.css"
|
stylesheets="@style/accounts-view.css"
|
||||||
>
|
>
|
||||||
<top>
|
<!-- <top>-->
|
||||||
<MenuBar BorderPane.alignment="CENTER">
|
<!-- <MenuBar BorderPane.alignment="CENTER">-->
|
||||||
<Menu mnemonicParsing="false" text="File">
|
<!-- <Menu mnemonicParsing="false" text="File">-->
|
||||||
<MenuItem mnemonicParsing="false" text="Close" />
|
<!-- <MenuItem mnemonicParsing="false" text="Close" />-->
|
||||||
</Menu>
|
<!-- </Menu>-->
|
||||||
<Menu mnemonicParsing="false" text="Edit">
|
<!-- <Menu mnemonicParsing="false" text="Edit">-->
|
||||||
<MenuItem mnemonicParsing="false" text="Delete" />
|
<!-- <MenuItem mnemonicParsing="false" text="Delete" />-->
|
||||||
</Menu>
|
<!-- </Menu>-->
|
||||||
<Menu mnemonicParsing="false" text="Help">
|
<!-- <Menu mnemonicParsing="false" text="Help">-->
|
||||||
<MenuItem mnemonicParsing="false" text="About" />
|
<!-- <MenuItem mnemonicParsing="false" text="About" />-->
|
||||||
</Menu>
|
<!-- </Menu>-->
|
||||||
</MenuBar>
|
<!-- </MenuBar>-->
|
||||||
</top>
|
<!-- </top>-->
|
||||||
<center>
|
<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>
|
</center>
|
||||||
</BorderPane>
|
</BorderPane>
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -1,3 +1,11 @@
|
||||||
#accountsPane {
|
#accountsPane {
|
||||||
-fx-padding: 5px;
|
-fx-padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#noAccountsLabel {
|
||||||
|
-fx-padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actionsBar {
|
||||||
|
-fx-padding: 5px;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
#mainHeader {
|
||||||
|
-fx-padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mainFooter {
|
||||||
|
-fx-padding: 3px;
|
||||||
|
}
|
Loading…
Reference in New Issue