diff --git a/design/perfin-logo.svg b/design/perfin-logo.svg
new file mode 100644
index 0000000..2dcc35f
--- /dev/null
+++ b/design/perfin-logo.svg
@@ -0,0 +1,78 @@
+
+
+
+
diff --git a/src/main/java/com/andrewlalis/perfin/PerfinApp.java b/src/main/java/com/andrewlalis/perfin/PerfinApp.java
index 7d72d04..9b578e2 100644
--- a/src/main/java/com/andrewlalis/perfin/PerfinApp.java
+++ b/src/main/java/com/andrewlalis/perfin/PerfinApp.java
@@ -3,6 +3,7 @@ package com.andrewlalis.perfin;
import com.andrewlalis.javafx_scene_router.AnchorPaneRouterView;
import com.andrewlalis.javafx_scene_router.SceneRouter;
import com.andrewlalis.perfin.model.Profile;
+import com.andrewlalis.perfin.view.ImageCache;
import com.andrewlalis.perfin.view.SceneUtil;
import com.andrewlalis.perfin.view.StartupSplashScreen;
import javafx.application.Application;
@@ -20,6 +21,7 @@ import java.util.function.Consumer;
*/
public class PerfinApp extends Application {
public static final Path APP_DIR = Path.of(System.getProperty("user.home", "."), ".perfin");
+ public static PerfinApp instance;
/**
* The router that's used for navigating between different "pages" in the application.
@@ -32,6 +34,7 @@ public class PerfinApp extends Application {
@Override
public void start(Stage stage) {
+ instance = this;
var splashScreen = new StartupSplashScreen(List.of(
PerfinApp::defineRoutes,
PerfinApp::initAppDir,
@@ -51,6 +54,7 @@ public class PerfinApp extends Application {
Scene mainViewScene = SceneUtil.load("/main-view.fxml");
stage.setScene(mainViewScene);
stage.setTitle("Perfin");
+ stage.getIcons().add(ImageCache.getLogo64());
});
}
diff --git a/src/main/java/com/andrewlalis/perfin/control/AccountViewController.java b/src/main/java/com/andrewlalis/perfin/control/AccountViewController.java
index f66818a..f3e51b0 100644
--- a/src/main/java/com/andrewlalis/perfin/control/AccountViewController.java
+++ b/src/main/java/com/andrewlalis/perfin/control/AccountViewController.java
@@ -1,20 +1,16 @@
package com.andrewlalis.perfin.control;
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
-import com.andrewlalis.perfin.data.AccountHistoryItemRepository;
-import com.andrewlalis.perfin.data.util.CurrencyUtil;
import com.andrewlalis.perfin.data.util.DateUtil;
import com.andrewlalis.perfin.model.Account;
import com.andrewlalis.perfin.model.Profile;
import com.andrewlalis.perfin.model.history.AccountHistoryItem;
+import com.andrewlalis.perfin.view.component.AccountHistoryItemTile;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.*;
-import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
-import javafx.scene.text.Text;
-import javafx.scene.text.TextFlow;
import java.time.LocalDateTime;
import java.util.List;
@@ -39,7 +35,7 @@ public class AccountViewController implements RouteSelectionListener {
@Override
public void onRouteSelected(Object context) {
account = (Account) context;
- titleLabel.setText("Account: " + account.getAccountNumber());
+ titleLabel.setText("Account #" + account.getId());
accountNameField.setText(account.getName());
accountNumberField.setText(account.getAccountNumber());
@@ -105,42 +101,13 @@ public class AccountViewController implements RouteSelectionListener {
} else {
loadHistoryFrom = historyItems.getLast().getTimestamp();
}
- List nodes = historyItems.stream().map(item -> visualizeHistoryItem(item, historyRepo)).toList();
+ List extends Node> nodes = historyItems.stream()
+ .map(item -> new AccountHistoryItemTile(item, historyRepo))
+ .toList();
Platform.runLater(() -> historyItemsVBox.getChildren().addAll(nodes));
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
-
- private Node visualizeHistoryItem(AccountHistoryItem item, AccountHistoryItemRepository repo) {
- BorderPane containerPane = new BorderPane();
- containerPane.setStyle("""
- -fx-border-color: lightgray;
- -fx-border-radius: 5px;
- -fx-padding: 5px;
- """);
- Label timestampLabel = new Label(item.getTimestamp().format(DateUtil.DEFAULT_DATETIME_FORMAT));
- timestampLabel.setStyle("-fx-font-size: small;");
- containerPane.setTop(timestampLabel);
- containerPane.setCenter(switch (item.getType()) {
- case TEXT -> {
- var text = repo.getTextItem(item.getId());
- yield new TextFlow(new Text(text));
- }
- case ACCOUNT_ENTRY -> {
- var entry = repo.getAccountEntryItem(item.getId());
- Text amountText = new Text(CurrencyUtil.formatMoney(entry.getSignedAmount(), entry.getCurrency()));
- TextFlow text = new TextFlow(new Text("Entry added with value of "), amountText);
- yield text;
- }
- case BALANCE_RECORD -> {
- var balanceRecord = repo.getBalanceRecordItem(item.getId());
- Text amountText = new Text(CurrencyUtil.formatMoney(balanceRecord.getBalance(), balanceRecord.getCurrency()));
- TextFlow text = new TextFlow(new Text("Balance record added with value of "), amountText);
- yield text;
- }
- });
- return containerPane;
- }
}
diff --git a/src/main/java/com/andrewlalis/perfin/control/ProfilesViewController.java b/src/main/java/com/andrewlalis/perfin/control/ProfilesViewController.java
index b79dd12..21507d9 100644
--- a/src/main/java/com/andrewlalis/perfin/control/ProfilesViewController.java
+++ b/src/main/java/com/andrewlalis/perfin/control/ProfilesViewController.java
@@ -1,5 +1,6 @@
package com.andrewlalis.perfin.control;
+import com.andrewlalis.perfin.PerfinApp;
import com.andrewlalis.perfin.data.util.FileUtil;
import com.andrewlalis.perfin.model.Profile;
import com.andrewlalis.perfin.view.ProfilesStage;
@@ -95,6 +96,11 @@ public class ProfilesViewController {
openButton.setOnAction(event -> openProfile(profileName, false));
openButton.setDisable(isCurrent);
buttonBox.getChildren().add(openButton);
+ Button viewFilesButton = new Button("View Files");
+ viewFilesButton.setOnAction(event -> {
+ PerfinApp.instance.getHostServices().showDocument(Profile.getDir(profileName).toUri().toString());
+ });
+ buttonBox.getChildren().add(viewFilesButton);
Button deleteButton = new Button("Delete");
deleteButton.setOnAction(event -> deleteProfile(profileName));
buttonBox.getChildren().add(deleteButton);
diff --git a/src/main/java/com/andrewlalis/perfin/control/TransactionViewController.java b/src/main/java/com/andrewlalis/perfin/control/TransactionViewController.java
index 8cfe6b0..1ea948e 100644
--- a/src/main/java/com/andrewlalis/perfin/control/TransactionViewController.java
+++ b/src/main/java/com/andrewlalis/perfin/control/TransactionViewController.java
@@ -27,6 +27,8 @@ import static com.andrewlalis.perfin.PerfinApp.router;
public class TransactionViewController {
private Transaction transaction;
+ @FXML public Label titleLabel;
+
@FXML public Label amountLabel;
@FXML public Label timestampLabel;
@FXML public Label descriptionLabel;
@@ -41,6 +43,7 @@ public class TransactionViewController {
public void setTransaction(Transaction transaction) {
this.transaction = transaction;
if (transaction == null) return;
+ titleLabel.setText("Transaction #" + transaction.getId());
amountLabel.setText(CurrencyUtil.formatMoney(transaction.getAmount(), transaction.getCurrency()));
timestampLabel.setText(DateUtil.formatUTCAsLocalWithZone(transaction.getTimestamp()));
descriptionLabel.setText(transaction.getDescription());
diff --git a/src/main/java/com/andrewlalis/perfin/view/ImageCache.java b/src/main/java/com/andrewlalis/perfin/view/ImageCache.java
new file mode 100644
index 0000000..cd2051c
--- /dev/null
+++ b/src/main/java/com/andrewlalis/perfin/view/ImageCache.java
@@ -0,0 +1,40 @@
+package com.andrewlalis.perfin.view;
+
+import javafx.scene.image.Image;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class ImageCache {
+ public static final ImageCache instance = new ImageCache();
+
+ private final Map images = new ConcurrentHashMap<>();
+
+ public Image get(String resource, double width, double height, boolean preserveRatio, boolean smooth) {
+ final String cacheKey = getCacheKey(resource, width, height, preserveRatio, smooth);
+ Image stored = images.get(cacheKey);
+ if (stored != null) return stored;
+ try (var in = ImageCache.class.getResourceAsStream(resource)) {
+ if (in == null) throw new IOException("Could not load resource " + resource);
+ Image img = new Image(in, width, height, preserveRatio, smooth);
+ images.put(cacheKey, img);
+ return img;
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private String getCacheKey(String resource, double width, double height, boolean preserveRatio, boolean smooth) {
+ return resource + "_" +
+ "W" + width + "_" +
+ "H" + height + "_" +
+ "PR-" + preserveRatio + "_" +
+ "S-" + smooth;
+ }
+
+ public static Image getLogo64() {
+ return instance.get("/images/perfin-logo_64.png", 64, 64, true, true);
+ }
+}
diff --git a/src/main/java/com/andrewlalis/perfin/view/StartupSplashScreen.java b/src/main/java/com/andrewlalis/perfin/view/StartupSplashScreen.java
index 1e55ec9..3d2e476 100644
--- a/src/main/java/com/andrewlalis/perfin/view/StartupSplashScreen.java
+++ b/src/main/java/com/andrewlalis/perfin/view/StartupSplashScreen.java
@@ -26,6 +26,7 @@ public class StartupSplashScreen extends Stage implements Consumer {
setTitle("Starting Perfin...");
setResizable(false);
initStyle(StageStyle.UNDECORATED);
+ getIcons().add(ImageCache.getLogo64());
setScene(buildScene());
setOnShowing(event -> runTasks());
diff --git a/src/main/java/com/andrewlalis/perfin/view/component/AccountHistoryItemTile.java b/src/main/java/com/andrewlalis/perfin/view/component/AccountHistoryItemTile.java
new file mode 100644
index 0000000..b4f56c7
--- /dev/null
+++ b/src/main/java/com/andrewlalis/perfin/view/component/AccountHistoryItemTile.java
@@ -0,0 +1,57 @@
+package com.andrewlalis.perfin.view.component;
+
+import com.andrewlalis.perfin.data.AccountHistoryItemRepository;
+import com.andrewlalis.perfin.data.util.CurrencyUtil;
+import com.andrewlalis.perfin.data.util.DateUtil;
+import com.andrewlalis.perfin.model.AccountEntry;
+import com.andrewlalis.perfin.model.BalanceRecord;
+import com.andrewlalis.perfin.model.history.AccountHistoryItem;
+import javafx.scene.Node;
+import javafx.scene.control.Hyperlink;
+import javafx.scene.control.Label;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.text.Text;
+import javafx.scene.text.TextFlow;
+
+/**
+ * A tile that shows a brief bit of information about an account history item.
+ */
+public class AccountHistoryItemTile extends BorderPane {
+ public AccountHistoryItemTile(AccountHistoryItem item, AccountHistoryItemRepository repo) {
+ setStyle("""
+ -fx-border-color: lightgray;
+ -fx-border-radius: 5px;
+ -fx-padding: 5px;
+ """);
+
+ Label timestampLabel = new Label(DateUtil.formatUTCAsLocalWithZone(item.getTimestamp()));
+ timestampLabel.setStyle("-fx-font-size: small;");
+ setTop(timestampLabel);
+ setCenter(switch (item.getType()) {
+ case TEXT -> buildTextItem(repo.getTextItem(item.getId()));
+ case ACCOUNT_ENTRY -> buildAccountEntryItem(repo.getAccountEntryItem(item.getId()));
+ case BALANCE_RECORD -> buildBalanceRecordItem(repo.getBalanceRecordItem(item.getId()));
+ });
+ }
+
+ private Node buildTextItem(String text) {
+ return new TextFlow(new Text(text));
+ }
+
+ private Node buildAccountEntryItem(AccountEntry entry) {
+ Text amountText = new Text(CurrencyUtil.formatMoney(entry.getSignedAmount(), entry.getCurrency()));
+ Hyperlink transactionLink = new Hyperlink("Transaction #" + entry.getTransactionId());
+ return new TextFlow(
+ new Text("Entry added with value of "),
+ amountText,
+ new Text(", linked with "),
+ transactionLink,
+ new Text(".")
+ );
+ }
+
+ private Node buildBalanceRecordItem(BalanceRecord balanceRecord) {
+ Text amountText = new Text(CurrencyUtil.formatMoney(balanceRecord.getBalance(), balanceRecord.getCurrency()));
+ return new TextFlow(new Text("Balance record added with value of "), amountText);
+ }
+}
diff --git a/src/main/java/com/andrewlalis/perfin/view/component/AttachmentPreview.java b/src/main/java/com/andrewlalis/perfin/view/component/AttachmentPreview.java
index f211dce..2263dee 100644
--- a/src/main/java/com/andrewlalis/perfin/view/component/AttachmentPreview.java
+++ b/src/main/java/com/andrewlalis/perfin/view/component/AttachmentPreview.java
@@ -2,6 +2,7 @@ package com.andrewlalis.perfin.view.component;
import com.andrewlalis.perfin.model.Attachment;
import com.andrewlalis.perfin.model.Profile;
+import com.andrewlalis.perfin.view.ImageCache;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
@@ -43,13 +44,7 @@ public class AttachmentPreview extends BorderPane {
}
}
if (showDocIcon) {
- try (var in = AttachmentPreview.class.getResourceAsStream("/images/doc-icon.png")) {
- if (in == null) throw new NullPointerException("Missing /images/doc-icon.png resource.");
- Image img = new Image(in, IMAGE_SIZE, IMAGE_SIZE, true, true);
- contentContainer.setCenter(new ImageView(img));
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ contentContainer.setCenter(new ImageView(ImageCache.instance.get("/images/doc-icon.png", 64, 64, true, true)));
}
BorderPane hoverIndicatorPane = new BorderPane();
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 4ceaa78..3936074 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -7,10 +7,10 @@ module com.andrewlalis.perfin {
requires com.fasterxml.jackson.databind;
- requires java.sql;
-
requires com.github.f4b6a3.ulid;
+ requires java.sql;
+
exports com.andrewlalis.perfin to javafx.graphics;
exports com.andrewlalis.perfin.view to javafx.graphics;
exports com.andrewlalis.perfin.model to javafx.graphics;
diff --git a/src/main/resources/account-view.fxml b/src/main/resources/account-view.fxml
index 461a4f8..dab0c8b 100644
--- a/src/main/resources/account-view.fxml
+++ b/src/main/resources/account-view.fxml
@@ -14,37 +14,53 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
@@ -63,12 +79,4 @@
-
-
-
-
-
-
-
-
diff --git a/src/main/resources/images/perfin-logo_64.png b/src/main/resources/images/perfin-logo_64.png
new file mode 100644
index 0000000..5ca250b
Binary files /dev/null and b/src/main/resources/images/perfin-logo_64.png differ
diff --git a/src/main/resources/transaction-view.fxml b/src/main/resources/transaction-view.fxml
index 0eb5e2a..bb2f870 100644
--- a/src/main/resources/transaction-view.fxml
+++ b/src/main/resources/transaction-view.fxml
@@ -8,6 +8,11 @@
xmlns:fx="http://javafx.com/fxml"
fx:controller="com.andrewlalis.perfin.control.TransactionViewController"
>
+
+
+
+
+