Refactored styling in most views and components to follow base CSS.
This commit is contained in:
parent
a914197634
commit
02d392d6c7
|
@ -10,8 +10,13 @@ import com.andrewlalis.perfin.view.StartupSplashScreen;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.text.Font;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -21,6 +26,7 @@ import java.util.function.Consumer;
|
||||||
* The class from which the JavaFX-based application starts.
|
* The class from which the JavaFX-based application starts.
|
||||||
*/
|
*/
|
||||||
public class PerfinApp extends Application {
|
public class PerfinApp extends Application {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(PerfinApp.class);
|
||||||
public static final Path APP_DIR = Path.of(System.getProperty("user.home", "."), ".perfin");
|
public static final Path APP_DIR = Path.of(System.getProperty("user.home", "."), ".perfin");
|
||||||
public static PerfinApp instance;
|
public static PerfinApp instance;
|
||||||
|
|
||||||
|
@ -36,6 +42,7 @@ public class PerfinApp extends Application {
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage stage) {
|
public void start(Stage stage) {
|
||||||
instance = this;
|
instance = this;
|
||||||
|
loadFonts();
|
||||||
var splashScreen = new StartupSplashScreen(List.of(
|
var splashScreen = new StartupSplashScreen(List.of(
|
||||||
PerfinApp::defineRoutes,
|
PerfinApp::defineRoutes,
|
||||||
PerfinApp::initAppDir,
|
PerfinApp::initAppDir,
|
||||||
|
@ -97,4 +104,27 @@ public class PerfinApp extends Application {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void loadFonts() {
|
||||||
|
List<String> fontResources = List.of(
|
||||||
|
"/font/JetBrainsMono-2.304/fonts/ttf/JetBrainsMono-Medium.ttf",
|
||||||
|
"/font/Roboto/Roboto-Regular.ttf",
|
||||||
|
"/font/Roboto/Roboto-Bold.ttf",
|
||||||
|
"/font/Roboto/Roboto-Italic.ttf",
|
||||||
|
"/font/Roboto/Roboto-BoldItalic.ttf"
|
||||||
|
);
|
||||||
|
for (String res : fontResources) {
|
||||||
|
URL resourceUrl = PerfinApp.class.getResource(res);
|
||||||
|
if (resourceUrl == null) {
|
||||||
|
log.warn("Font resource {} was not found.", res);
|
||||||
|
} else {
|
||||||
|
Font font = Font.loadFont(PerfinApp.class.getResource(res).toExternalForm(), 10);
|
||||||
|
if (font == null) {
|
||||||
|
log.warn("Failed to load font {}.", res);
|
||||||
|
} else {
|
||||||
|
log.debug("Loaded font: Family = {}, Name = {}, Style = {}.", font.getFamily(), font.getName(), font.getStyle());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -25,7 +25,7 @@ public class MainViewController {
|
||||||
breadCrumb -> {
|
breadCrumb -> {
|
||||||
Label label = new Label("> " + breadCrumb.route());
|
Label label = new Label("> " + breadCrumb.route());
|
||||||
if (breadCrumb.current()) {
|
if (breadCrumb.current()) {
|
||||||
label.setStyle("-fx-font-weight: bold");
|
label.getStyleClass().add("bold-text");
|
||||||
}
|
}
|
||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,22 +66,15 @@ public class ProfilesViewController {
|
||||||
for (String profileName : profileNames) {
|
for (String profileName : profileNames) {
|
||||||
boolean isCurrent = profileName.equals(currentProfile);
|
boolean isCurrent = profileName.equals(currentProfile);
|
||||||
AnchorPane profilePane = new AnchorPane();
|
AnchorPane profilePane = new AnchorPane();
|
||||||
profilePane.setStyle("""
|
profilePane.getStyleClass().add("tile");
|
||||||
-fx-border-color: lightgray;
|
|
||||||
-fx-border-radius: 5px;
|
|
||||||
-fx-padding: 5px;
|
|
||||||
""");
|
|
||||||
|
|
||||||
Text nameTextElement = new Text(profileName);
|
Text nameTextElement = new Text(profileName);
|
||||||
nameTextElement.setStyle("-fx-font-size: large;");
|
nameTextElement.getStyleClass().add("large-font");
|
||||||
TextFlow nameLabel = new TextFlow(nameTextElement);
|
TextFlow nameLabel = new TextFlow(nameTextElement);
|
||||||
if (isCurrent) {
|
if (isCurrent) {
|
||||||
nameTextElement.setStyle("-fx-font-size: large; -fx-font-weight: bold;");
|
nameTextElement.getStyleClass().addAll("large-font", "bold-text");
|
||||||
Text currentProfileIndicator = new Text(" Currently Selected Profile");
|
Text currentProfileIndicator = new Text(" Currently Selected Profile");
|
||||||
currentProfileIndicator.setStyle("""
|
currentProfileIndicator.getStyleClass().addAll("small-font", "secondary-color-fill");
|
||||||
-fx-font-size: small;
|
|
||||||
-fx-fill: grey;
|
|
||||||
""");
|
|
||||||
nameLabel.getChildren().add(currentProfileIndicator);
|
nameLabel.getChildren().add(currentProfileIndicator);
|
||||||
}
|
}
|
||||||
AnchorPane.setLeftAnchor(nameLabel, 0.0);
|
AnchorPane.setLeftAnchor(nameLabel, 0.0);
|
||||||
|
|
|
@ -39,6 +39,16 @@ public class Account extends IdEntity {
|
||||||
return "..." + accountNumber.substring(accountNumber.length() - suffixLength);
|
return "..." + accountNumber.substring(accountNumber.length() - suffixLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getAccountNumberGrouped(int groupSize, char separator) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
int idx = 0;
|
||||||
|
while (idx < accountNumber.length()) {
|
||||||
|
sb.append(accountNumber.charAt(idx++));
|
||||||
|
if (idx % groupSize == 0 && idx < accountNumber.length()) sb.append(separator);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
public String getShortName() {
|
public String getShortName() {
|
||||||
String numberSuffix = getAccountNumberSuffix();
|
String numberSuffix = getAccountNumberSuffix();
|
||||||
return name + " (" + numberSuffix + ")";
|
return name + " (" + numberSuffix + ")";
|
||||||
|
|
|
@ -87,9 +87,10 @@ public class AccountEntry extends IdEntity {
|
||||||
* @return The effective value of this entry, either positive or negative.
|
* @return The effective value of this entry, either positive or negative.
|
||||||
*/
|
*/
|
||||||
public BigDecimal getEffectiveValue(AccountType accountType) {
|
public BigDecimal getEffectiveValue(AccountType accountType) {
|
||||||
return switch (accountType) {
|
if (accountType.areDebitsPositive()) {
|
||||||
case CHECKING, SAVINGS -> type == Type.DEBIT ? amount : amount.negate();
|
return type == Type.DEBIT ? amount : amount.negate();
|
||||||
case CREDIT_CARD -> type == Type.DEBIT ? amount.negate() : amount;
|
} else {
|
||||||
};
|
return type == Type.DEBIT ? amount.negate() : amount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,20 @@ package com.andrewlalis.perfin.model;
|
||||||
* Represents the different possible account types in Perfin.
|
* Represents the different possible account types in Perfin.
|
||||||
*/
|
*/
|
||||||
public enum AccountType {
|
public enum AccountType {
|
||||||
CHECKING("Checking"),
|
CHECKING("Checking", true),
|
||||||
SAVINGS("Savings"),
|
SAVINGS("Savings", true),
|
||||||
CREDIT_CARD("Credit Card");
|
CREDIT_CARD("Credit Card", false);
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
private final boolean debitsPositive;
|
||||||
|
|
||||||
AccountType(String name) {
|
AccountType(String name, boolean debitsPositive) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
this.debitsPositive = debitsPositive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean areDebitsPositive() {
|
||||||
|
return debitsPositive;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -23,7 +23,7 @@ public class AccountComboBoxCellFactory implements Callback<ListView<Account>, L
|
||||||
|
|
||||||
public AccountListCell(String emptyCellText) {
|
public AccountListCell(String emptyCellText) {
|
||||||
this.emptyCellText = emptyCellText;
|
this.emptyCellText = emptyCellText;
|
||||||
label.setStyle("-fx-text-fill: black;");
|
label.getStyleClass().add("normal-color-text-fill");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -53,7 +53,10 @@ public class StartupSplashScreen extends Stage implements Consumer<String> {
|
||||||
textArea.setFocusTraversable(false);
|
textArea.setFocusTraversable(false);
|
||||||
|
|
||||||
Scene scene = new Scene(root, 400.0, 200.0);
|
Scene scene = new Scene(root, 400.0, 200.0);
|
||||||
scene.getStylesheets().add(StartupSplashScreen.class.getResource("/style/startup-splash-screen.css").toExternalForm());
|
scene.getStylesheets().addAll(
|
||||||
|
StartupSplashScreen.class.getResource("/style/base.css").toExternalForm(),
|
||||||
|
StartupSplashScreen.class.getResource("/style/startup-splash-screen.css").toExternalForm()
|
||||||
|
);
|
||||||
return scene;
|
return scene;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,14 +12,10 @@ import javafx.scene.layout.BorderPane;
|
||||||
*/
|
*/
|
||||||
public abstract class AccountHistoryItemTile extends BorderPane {
|
public abstract class AccountHistoryItemTile extends BorderPane {
|
||||||
public AccountHistoryItemTile(AccountHistoryItem item) {
|
public AccountHistoryItemTile(AccountHistoryItem item) {
|
||||||
setStyle("""
|
getStyleClass().add("tile");
|
||||||
-fx-border-color: lightgray;
|
|
||||||
-fx-border-radius: 5px;
|
|
||||||
-fx-padding: 5px;
|
|
||||||
""");
|
|
||||||
|
|
||||||
Label timestampLabel = new Label(DateUtil.formatUTCAsLocalWithZone(item.getTimestamp()));
|
Label timestampLabel = new Label(DateUtil.formatUTCAsLocalWithZone(item.getTimestamp()));
|
||||||
timestampLabel.setStyle("-fx-font-size: small;");
|
timestampLabel.getStyleClass().add("small-font");
|
||||||
setTop(timestampLabel);
|
setTop(timestampLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
package com.andrewlalis.perfin.view.component;
|
package com.andrewlalis.perfin.view.component;
|
||||||
|
|
||||||
|
import com.andrewlalis.perfin.data.util.CurrencyUtil;
|
||||||
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 com.andrewlalis.perfin.model.MoneyValue;
|
||||||
import com.andrewlalis.perfin.model.Profile;
|
import com.andrewlalis.perfin.model.Profile;
|
||||||
|
import javafx.application.Platform;
|
||||||
import javafx.geometry.HPos;
|
import javafx.geometry.HPos;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
|
@ -14,6 +17,7 @@ import javafx.scene.layout.Priority;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.scene.text.Text;
|
import javafx.scene.text.Text;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static com.andrewlalis.perfin.PerfinApp.router;
|
import static com.andrewlalis.perfin.PerfinApp.router;
|
||||||
|
@ -30,14 +34,7 @@ public class AccountTile extends BorderPane {
|
||||||
|
|
||||||
public AccountTile(Account account) {
|
public AccountTile(Account account) {
|
||||||
setPrefWidth(350.0);
|
setPrefWidth(350.0);
|
||||||
setStyle("""
|
getStyleClass().addAll("tile", "hand-cursor");
|
||||||
-fx-border-color: lightgray;
|
|
||||||
-fx-border-width: 1px;
|
|
||||||
-fx-border-style: solid;
|
|
||||||
-fx-border-radius: 5px;
|
|
||||||
-fx-padding: 5px;
|
|
||||||
-fx-cursor: hand;
|
|
||||||
""");
|
|
||||||
|
|
||||||
setTop(getHeader(account));
|
setTop(getHeader(account));
|
||||||
setBottom(getFooter(account));
|
setBottom(getFooter(account));
|
||||||
|
@ -48,7 +45,7 @@ public class AccountTile extends BorderPane {
|
||||||
|
|
||||||
private Node getHeader(Account account) {
|
private Node getHeader(Account account) {
|
||||||
Text title = new Text("Account #" + account.id);
|
Text title = new Text("Account #" + account.id);
|
||||||
title.setStyle("-fx-font-size: large; -fx-font-weight: bold;");
|
title.getStyleClass().addAll("large-font", "bold-text");
|
||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +53,7 @@ public class AccountTile extends BorderPane {
|
||||||
Label currencyLabel = new Label(account.getCurrency().getCurrencyCode());
|
Label currencyLabel = new Label(account.getCurrency().getCurrencyCode());
|
||||||
Label typeLabel = new Label(account.getType().toString() + " Account");
|
Label typeLabel = new Label(account.getType().toString() + " Account");
|
||||||
HBox footerHBox = new HBox(currencyLabel, typeLabel);
|
HBox footerHBox = new HBox(currencyLabel, typeLabel);
|
||||||
footerHBox.setStyle("-fx-font-size: x-small; -fx-spacing: 3px;");
|
footerHBox.getStyleClass().addAll("std-spacing", "small-font");
|
||||||
return footerHBox;
|
return footerHBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,15 +70,33 @@ public class AccountTile extends BorderPane {
|
||||||
valueConstraints.setHalignment(HPos.RIGHT);
|
valueConstraints.setHalignment(HPos.RIGHT);
|
||||||
propertiesPane.getColumnConstraints().setAll(keyConstraints, valueConstraints);
|
propertiesPane.getColumnConstraints().setAll(keyConstraints, valueConstraints);
|
||||||
|
|
||||||
Label accountNameLabel = newPropertyValue(account.getName());
|
Label accountNameLabel = new Label(account.getName());
|
||||||
accountNameLabel.setWrapText(true);
|
accountNameLabel.setWrapText(true);
|
||||||
|
accountNameLabel.getStyleClass().add("italic-text");
|
||||||
|
|
||||||
Label accountTypeLabel = newPropertyValue(account.getType().toString());
|
Label accountNumberLabel = new Label(account.getAccountNumber());
|
||||||
|
accountNumberLabel.getStyleClass().add("mono-font");
|
||||||
|
|
||||||
|
Label accountTypeLabel = new Label(account.getType().toString());
|
||||||
accountTypeLabel.setTextFill(ACCOUNT_TYPE_COLORS.get(account.getType()));
|
accountTypeLabel.setTextFill(ACCOUNT_TYPE_COLORS.get(account.getType()));
|
||||||
accountTypeLabel.setStyle("-fx-font-weight: bold;");
|
accountTypeLabel.getStyleClass().add("bold-text");
|
||||||
|
|
||||||
Label balanceLabel = newPropertyValue("Computing balance...");
|
Label balanceLabel = new Label("Computing balance...");
|
||||||
|
balanceLabel.getStyleClass().addAll("mono-font");
|
||||||
balanceLabel.setDisable(true);
|
balanceLabel.setDisable(true);
|
||||||
|
Thread.ofVirtual().start(() -> Profile.getCurrent().getDataSource().useAccountRepository(repo -> {
|
||||||
|
BigDecimal balance = repo.deriveCurrentBalance(account.id);
|
||||||
|
String text = CurrencyUtil.formatMoney(new MoneyValue(balance, account.getCurrency()));
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
balanceLabel.setText(text);
|
||||||
|
if (account.getType().areDebitsPositive() && balance.compareTo(BigDecimal.ZERO) < 0) {
|
||||||
|
balanceLabel.getStyleClass().add("negative-color-text-fill");
|
||||||
|
} else if (!account.getType().areDebitsPositive() && balance.compareTo(BigDecimal.ZERO) < 0) {
|
||||||
|
balanceLabel.getStyleClass().add("positive-color-text-fill");
|
||||||
|
}
|
||||||
|
balanceLabel.setDisable(false);
|
||||||
|
});
|
||||||
|
}));
|
||||||
Profile.getCurrent().getDataSource().getAccountBalanceText(account, text -> {
|
Profile.getCurrent().getDataSource().getAccountBalanceText(account, text -> {
|
||||||
balanceLabel.setText(text);
|
balanceLabel.setText(text);
|
||||||
balanceLabel.setDisable(false);
|
balanceLabel.setDisable(false);
|
||||||
|
@ -91,7 +106,7 @@ public class AccountTile extends BorderPane {
|
||||||
newPropertyLabel("Account Name"),
|
newPropertyLabel("Account Name"),
|
||||||
accountNameLabel,
|
accountNameLabel,
|
||||||
newPropertyLabel("Account Number"),
|
newPropertyLabel("Account Number"),
|
||||||
newPropertyValue(account.getAccountNumber()),
|
accountNumberLabel,
|
||||||
newPropertyLabel("Account Type"),
|
newPropertyLabel("Account Type"),
|
||||||
accountTypeLabel,
|
accountTypeLabel,
|
||||||
newPropertyLabel("Current Balance"),
|
newPropertyLabel("Current Balance"),
|
||||||
|
@ -102,18 +117,7 @@ public class AccountTile extends BorderPane {
|
||||||
|
|
||||||
private static Label newPropertyLabel(String text) {
|
private static Label newPropertyLabel(String text) {
|
||||||
Label lbl = new Label(text);
|
Label lbl = new Label(text);
|
||||||
lbl.setStyle("""
|
lbl.getStyleClass().add("bold-text");
|
||||||
-fx-font-weight: bold;
|
|
||||||
""");
|
|
||||||
return lbl;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Label newPropertyValue(String text) {
|
|
||||||
Label lbl = new Label(text);
|
|
||||||
lbl.setStyle("""
|
|
||||||
-fx-font-family: monospace;
|
|
||||||
-fx-font-size: large;
|
|
||||||
""");
|
|
||||||
return lbl;
|
return lbl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ public class AttachmentPreview extends BorderPane {
|
||||||
public AttachmentPreview(Attachment attachment) {
|
public AttachmentPreview(Attachment attachment) {
|
||||||
BorderPane contentContainer = new BorderPane();
|
BorderPane contentContainer = new BorderPane();
|
||||||
Label nameLabel = new Label(attachment.getFilename());
|
Label nameLabel = new Label(attachment.getFilename());
|
||||||
nameLabel.setStyle("-fx-font-size: small;");
|
nameLabel.getStyleClass().add("small-font");
|
||||||
VBox nameContainer = new VBox(nameLabel);
|
VBox nameContainer = new VBox(nameLabel);
|
||||||
nameContainer.setPrefHeight(LABEL_SIZE);
|
nameContainer.setPrefHeight(LABEL_SIZE);
|
||||||
nameContainer.setMaxHeight(LABEL_SIZE);
|
nameContainer.setMaxHeight(LABEL_SIZE);
|
||||||
|
|
|
@ -27,42 +27,30 @@ import static com.andrewlalis.perfin.PerfinApp.router;
|
||||||
*/
|
*/
|
||||||
public class TransactionTile extends BorderPane {
|
public class TransactionTile extends BorderPane {
|
||||||
public final BooleanProperty selected = new SimpleBooleanProperty(false);
|
public final BooleanProperty selected = new SimpleBooleanProperty(false);
|
||||||
private static final String UNSELECTED_STYLE = """
|
|
||||||
-fx-border-color: lightgray;
|
|
||||||
-fx-border-width: 1px;
|
|
||||||
-fx-border-style: solid;
|
|
||||||
-fx-border-radius: 5px;
|
|
||||||
-fx-padding: 5px;
|
|
||||||
-fx-cursor: hand;
|
|
||||||
""";
|
|
||||||
private static final String SELECTED_STYLE = """
|
|
||||||
-fx-border-color: white;
|
|
||||||
-fx-border-width: 1px;
|
|
||||||
-fx-border-style: solid;
|
|
||||||
-fx-border-radius: 5px;
|
|
||||||
-fx-padding: 5px;
|
|
||||||
-fx-cursor: hand;
|
|
||||||
""";
|
|
||||||
|
|
||||||
public TransactionTile(Transaction transaction) {
|
public TransactionTile(Transaction transaction) {
|
||||||
setStyle(UNSELECTED_STYLE);
|
getStyleClass().addAll("tile", "hand-cursor");
|
||||||
|
|
||||||
setTop(getHeader(transaction));
|
setTop(getHeader(transaction));
|
||||||
setCenter(getBody(transaction));
|
setCenter(getBody(transaction));
|
||||||
setBottom(getFooter(transaction));
|
setBottom(getFooter(transaction));
|
||||||
|
|
||||||
styleProperty().bind(selected.map(value -> value ? SELECTED_STYLE : UNSELECTED_STYLE));
|
selected.addListener((observable, oldValue, newValue) -> {
|
||||||
|
if (newValue) {
|
||||||
|
getStyleClass().add("tile-border-selected");
|
||||||
|
} else {
|
||||||
|
getStyleClass().remove("tile-border-selected");
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private Node getHeader(Transaction transaction) {
|
private Node getHeader(Transaction transaction) {
|
||||||
Label currencyLabel = new Label(CurrencyUtil.formatMoney(transaction.getMoneyAmount()));
|
Label currencyLabel = new Label(CurrencyUtil.formatMoney(transaction.getMoneyAmount()));
|
||||||
currencyLabel.setStyle("-fx-font-family: monospace;");
|
currencyLabel.getStyleClass().add("mono-font");
|
||||||
HBox headerHBox = new HBox(
|
HBox headerHBox = new HBox(
|
||||||
currencyLabel
|
currencyLabel
|
||||||
);
|
);
|
||||||
headerHBox.setStyle("""
|
headerHBox.getStyleClass().addAll("std-spacing");
|
||||||
-fx-spacing: 3px;
|
|
||||||
""");
|
|
||||||
return headerHBox;
|
return headerHBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,14 +65,14 @@ public class TransactionTile extends BorderPane {
|
||||||
Hyperlink link = new Hyperlink(acc.getShortName());
|
Hyperlink link = new Hyperlink(acc.getShortName());
|
||||||
link.setOnAction(event -> router.navigate("account", acc));
|
link.setOnAction(event -> router.navigate("account", acc));
|
||||||
Text prefix = new Text("Credited from");
|
Text prefix = new Text("Credited from");
|
||||||
prefix.setFill(Color.RED);
|
prefix.getStyleClass().add("negative-color-fill");
|
||||||
Platform.runLater(() -> bodyVBox.getChildren().add(new TextFlow(prefix, link)));
|
Platform.runLater(() -> bodyVBox.getChildren().add(new TextFlow(prefix, link)));
|
||||||
});
|
});
|
||||||
accounts.ifDebit(acc -> {
|
accounts.ifDebit(acc -> {
|
||||||
Hyperlink link = new Hyperlink(acc.getShortName());
|
Hyperlink link = new Hyperlink(acc.getShortName());
|
||||||
link.setOnAction(event -> router.navigate("account", acc));
|
link.setOnAction(event -> router.navigate("account", acc));
|
||||||
Text prefix = new Text("Debited to");
|
Text prefix = new Text("Debited to");
|
||||||
prefix.setFill(Color.GREEN);
|
prefix.getStyleClass().add("positive-color-fill");
|
||||||
Platform.runLater(() -> bodyVBox.getChildren().add(new TextFlow(prefix, link)));
|
Platform.runLater(() -> bodyVBox.getChildren().add(new TextFlow(prefix, link)));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -96,10 +84,7 @@ public class TransactionTile extends BorderPane {
|
||||||
HBox footerHBox = new HBox(
|
HBox footerHBox = new HBox(
|
||||||
timestampLabel
|
timestampLabel
|
||||||
);
|
);
|
||||||
footerHBox.setStyle("""
|
footerHBox.getStyleClass().addAll("std-spacing", "small-font");
|
||||||
-fx-spacing: 3px;
|
|
||||||
-fx-font-size: small;
|
|
||||||
""");
|
|
||||||
return footerHBox;
|
return footerHBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
<VBox>
|
<VBox>
|
||||||
<Label text="Current Balance" styleClass="bold-text" fx:id="balanceLabel"/>
|
<Label text="Current Balance" styleClass="bold-text" fx:id="balanceLabel"/>
|
||||||
<Text
|
<Text
|
||||||
style="-fx-font-size: x-small; -fx-fill: grey"
|
styleClass="small-font,secondary-color-fill"
|
||||||
wrappingWidth="${balanceLabel.width}"
|
wrappingWidth="${balanceLabel.width}"
|
||||||
>Computed using the last recorded balance and all transactions since.</Text>
|
>Computed using the last recorded balance and all transactions since.</Text>
|
||||||
</VBox>
|
</VBox>
|
||||||
|
@ -65,7 +65,7 @@
|
||||||
<Label text="History" styleClass="bold-text,std-padding"/>
|
<Label text="History" styleClass="bold-text,std-padding"/>
|
||||||
<VBox>
|
<VBox>
|
||||||
<ScrollPane fitToHeight="true" fitToWidth="true">
|
<ScrollPane fitToHeight="true" fitToWidth="true">
|
||||||
<VBox fx:id="historyItemsVBox" style="-fx-padding: 10px; -fx-spacing: 10px;"/>
|
<VBox fx:id="historyItemsVBox" styleClass="padding-extra-1,spacing-extra-1"/>
|
||||||
</ScrollPane>
|
</ScrollPane>
|
||||||
<AnchorPane>
|
<AnchorPane>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<center>
|
<center>
|
||||||
<VBox>
|
<VBox>
|
||||||
<ScrollPane fitToHeight="true" fitToWidth="true" VBox.vgrow="ALWAYS">
|
<ScrollPane fitToHeight="true" fitToWidth="true" VBox.vgrow="ALWAYS">
|
||||||
<FlowPane fx:id="accountsPane" BorderPane.alignment="TOP_LEFT" vgap="5" hgap="5" styleClass="std-padding"/>
|
<FlowPane fx:id="accountsPane" BorderPane.alignment="TOP_LEFT" vgap="10" hgap="10" styleClass="padding-extra-1"/>
|
||||||
</ScrollPane>
|
</ScrollPane>
|
||||||
<Label fx:id="noAccountsLabel" BorderPane.alignment="TOP_LEFT" styleClass="std-padding" text="No accounts have been added to this profile."/>
|
<Label fx:id="noAccountsLabel" BorderPane.alignment="TOP_LEFT" styleClass="std-padding" text="No accounts have been added to this profile."/>
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
</VBox>
|
</VBox>
|
||||||
<VBox>
|
<VBox>
|
||||||
<Label text="Description" labelFor="${descriptionField}" styleClass="bold-text"/>
|
<Label text="Description" labelFor="${descriptionField}" styleClass="bold-text"/>
|
||||||
<Label text="Maximum of 255 characters." styleClass="small-text"/>
|
<Label text="Maximum of 255 characters." styleClass="small-font"/>
|
||||||
<TextArea
|
<TextArea
|
||||||
fx:id="descriptionField"
|
fx:id="descriptionField"
|
||||||
styleClass="mono-font"
|
styleClass="mono-font"
|
||||||
|
@ -57,7 +57,7 @@
|
||||||
<!-- Container for attachments -->
|
<!-- Container for attachments -->
|
||||||
<VBox fx:id="attachmentsVBox">
|
<VBox fx:id="attachmentsVBox">
|
||||||
<Label text="Attachments" styleClass="bold-text"/>
|
<Label text="Attachments" styleClass="bold-text"/>
|
||||||
<Label text="Attach receipts, invoices, or other content to this transaction." styleClass="small-text" wrapText="true"/>
|
<Label text="Attach receipts, invoices, or other content to this transaction." styleClass="small-font" wrapText="true"/>
|
||||||
<!-- FileSelectionArea inserted here! -->
|
<!-- FileSelectionArea inserted here! -->
|
||||||
</VBox>
|
</VBox>
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
<Button text="Transactions" onAction="#goToTransactions"/>
|
<Button text="Transactions" onAction="#goToTransactions"/>
|
||||||
<Button text="Profiles" onAction="#viewProfiles"/>
|
<Button text="Profiles" onAction="#viewProfiles"/>
|
||||||
</HBox>
|
</HBox>
|
||||||
<HBox fx:id="breadcrumbHBox" styleClass="std-spacing,small-text"/>
|
<HBox fx:id="breadcrumbHBox" styleClass="std-spacing,small-font"/>
|
||||||
</VBox>
|
</VBox>
|
||||||
</top>
|
</top>
|
||||||
<bottom>
|
<bottom>
|
||||||
|
|
|
@ -1,5 +1,19 @@
|
||||||
|
.root {
|
||||||
|
-fx-font-family: "Roboto", sans-serif;
|
||||||
|
-fx-font-size: 14px;
|
||||||
|
-fx-text-fill: rgb(26, 26, 26);
|
||||||
|
}
|
||||||
|
|
||||||
.mono-font {
|
.mono-font {
|
||||||
-fx-font-family: monospace;
|
-fx-font-family: "JetBrains Mono", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.large-font {
|
||||||
|
-fx-font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-font {
|
||||||
|
-fx-font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-text {
|
.error-text {
|
||||||
|
@ -11,8 +25,8 @@
|
||||||
-fx-font-weight: bold;
|
-fx-font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.small-text {
|
.italic-text {
|
||||||
-fx-font-size: small;
|
-fx-font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
.large-text {
|
.large-text {
|
||||||
|
@ -22,14 +36,62 @@
|
||||||
.std-padding {
|
.std-padding {
|
||||||
-fx-padding: 3px;
|
-fx-padding: 3px;
|
||||||
}
|
}
|
||||||
|
.padding-extra {
|
||||||
|
-fx-padding: 6px;
|
||||||
|
}
|
||||||
|
.padding-extra-1 {
|
||||||
|
-fx-padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.std-spacing {
|
.std-spacing {
|
||||||
-fx-spacing: 3px;
|
-fx-spacing: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.spacing-extra {
|
.spacing-extra {
|
||||||
-fx-spacing: 6px;
|
-fx-spacing: 6px;
|
||||||
}
|
}
|
||||||
|
.spacing-extra-1 {
|
||||||
|
-fx-spacing: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hand-cursor {
|
||||||
|
-fx-cursor: hand;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Standard "tile" styling. */
|
||||||
|
.tile {
|
||||||
|
-fx-border-color: lightgray;
|
||||||
|
-fx-border-width: 2px;
|
||||||
|
-fx-border-style: solid;
|
||||||
|
-fx-border-radius: 5px;
|
||||||
|
-fx-padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile-border-selected {
|
||||||
|
-fx-border-color: darkgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Standard colors */
|
||||||
|
.normal-color-text-fill {
|
||||||
|
-fx-text-fill: rgb(26, 26, 26);
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary-color-fill {
|
||||||
|
-fx-fill: rgb(99, 99, 99);
|
||||||
|
}
|
||||||
|
|
||||||
|
.negative-color-text-fill {
|
||||||
|
-fx-text-fill: rgb(247, 37, 69);
|
||||||
|
}
|
||||||
|
.negative-color-fill {
|
||||||
|
-fx-fill: rgb(247, 37, 69);
|
||||||
|
}
|
||||||
|
|
||||||
|
.positive-color-text-fill {
|
||||||
|
-fx-text-fill: rgb(43, 196, 77);
|
||||||
|
}
|
||||||
|
.positive-color-fill {
|
||||||
|
-fx-fill: rgb(43, 196, 77);
|
||||||
|
}
|
||||||
|
|
||||||
/* DEBUG BORDERS */
|
/* DEBUG BORDERS */
|
||||||
.debug-border-1 {
|
.debug-border-1 {
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
</top>
|
</top>
|
||||||
<center>
|
<center>
|
||||||
<ScrollPane fitToHeight="true" fitToWidth="true">
|
<ScrollPane fitToHeight="true" fitToWidth="true">
|
||||||
<VBox fx:id="transactionsVBox" styleClass="std-padding,spacing-extra"/>
|
<VBox fx:id="transactionsVBox" styleClass="padding-extra-1,spacing-extra-1"/>
|
||||||
</ScrollPane>
|
</ScrollPane>
|
||||||
</center>
|
</center>
|
||||||
</BorderPane>
|
</BorderPane>
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.andrewlalis.perfin.model;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Currency;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
public class AccountTest {
|
||||||
|
@Test
|
||||||
|
public void testGetAccountNumberSuffix() {
|
||||||
|
assertEquals("...1234", getTestAccountWithNumber("4328-1234").getAccountNumberSuffix());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetAccountNumberGrouped() {
|
||||||
|
assertEquals(
|
||||||
|
"1234-5678-9101-1121",
|
||||||
|
getTestAccountWithNumber("1234567891011121")
|
||||||
|
.getAccountNumberGrouped(4, '-')
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
"123-456-7",
|
||||||
|
getTestAccountWithNumber("1234567")
|
||||||
|
.getAccountNumberGrouped(3, '-')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Account getTestAccountWithNumber(String num) {
|
||||||
|
return new Account(
|
||||||
|
1,
|
||||||
|
LocalDateTime.now(),
|
||||||
|
false,
|
||||||
|
AccountType.CHECKING,
|
||||||
|
num,
|
||||||
|
"Testing Account",
|
||||||
|
Currency.getInstance("USD")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue