Added help pages, styled text implementation, and improved splash screen.
This commit is contained in:
parent
2a79afe1b5
commit
ce78df559e
|
@ -26,9 +26,9 @@
|
|||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:document-units="px"
|
||||
inkscape:zoom="2.0863221"
|
||||
inkscape:cx="143.55406"
|
||||
inkscape:cy="122.22466"
|
||||
inkscape:zoom="1.4752525"
|
||||
inkscape:cx="125.40226"
|
||||
inkscape:cy="94.899008"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1025"
|
||||
inkscape:window-x="1080"
|
||||
|
@ -36,6 +36,11 @@
|
|||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" /><defs
|
||||
id="defs1"><rect
|
||||
x="317.65446"
|
||||
y="235.76737"
|
||||
width="216.44269"
|
||||
height="83.027779"
|
||||
id="rect4" /><rect
|
||||
x="23.509132"
|
||||
y="17.511823"
|
||||
width="25.696689"
|
||||
|
@ -45,7 +50,12 @@
|
|||
y="17.511823"
|
||||
width="25.696689"
|
||||
height="28.301114"
|
||||
id="rect3" /></defs><g
|
||||
id="rect3" /><rect
|
||||
x="317.65446"
|
||||
y="235.76737"
|
||||
width="216.44269"
|
||||
height="83.027779"
|
||||
id="rect5" /></defs><g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"><rect
|
||||
|
@ -54,9 +64,26 @@
|
|||
width="105.83333"
|
||||
height="52.916664"
|
||||
x="0"
|
||||
y="0" /><g
|
||||
y="0" /><path
|
||||
style="display:inline;fill:#397526;fill-opacity:1;stroke:none;stroke-width:35.921;stroke-linecap:round"
|
||||
d="M -51.278833,56.497593 160.38037,45.238259 159.49177,-7.633004 c 0,0 -24.42624,1.512546 -47.38917,9.245796 -22.962932,7.733249 -18.862832,17.268118 -51.065942,24.86209 -32.20311,7.593969 -45.158576,4.517345 -74.188418,14.395906 -29.029843,9.87856 -38.127073,15.626805 -38.127073,15.626805 z"
|
||||
id="path5"
|
||||
sodipodi:nodetypes="ccczzzc" /><path
|
||||
style="display:inline;fill:#e7b300;fill-opacity:1;stroke:none;stroke-width:25.4;stroke-linecap:round"
|
||||
d="M -1.3473491,68.51775 104.48598,33.263506 v -52.916667 c 0,0 -12.21534,4.282234 -23.751883,14.636677 C 69.197554,5.3379594 71.165788,14.434953 55.014288,25.695895 38.862788,36.956837 32.416445,35.338519 17.831036,48.531087 3.2456269,61.723654 -1.3473491,68.51775 -1.3473491,68.51775 Z"
|
||||
id="path3"
|
||||
sodipodi:nodetypes="ccczzzc" /><path
|
||||
style="display:inline;fill:#ba8e00;fill-opacity:1;stroke:none;stroke-width:25.4;stroke-linecap:round"
|
||||
d="M 1.2353516e-7,65.117916 105.83333,40.715416 V -12.20125 c 0,0 -12.215341,3.0297196 -23.751884,12.20125 C 70.544903,9.1715303 72.513137,18.470338 56.361637,28.075167 40.210137,37.679996 33.763794,35.400695 19.178385,47.097731 4.5929761,58.794766 1.2353516e-7,65.117916 1.2353516e-7,65.117916 Z"
|
||||
id="path2"
|
||||
sodipodi:nodetypes="ccczzzc" /><path
|
||||
style="display:inline;fill:#ca9c00;fill-opacity:1;stroke:none;stroke-width:25.4;stroke-linecap:round"
|
||||
d="M 0,52.916666 H 105.83333 V 0 c 0,0 -12.215341,0.21316952 -23.751884,6.7246636 C 70.544903,13.236158 72.513137,22.988791 56.361637,28.869491 40.210137,34.750191 33.763794,30.984525 19.178385,39.318533 4.592976,47.65254 0,52.916666 0,52.916666 Z"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="ccczzzc" /><g
|
||||
id="logo-group"
|
||||
transform="translate(-30.013895,12.599684)"><path
|
||||
transform="translate(41.550528,32.475407)"
|
||||
style="display:inline"><path
|
||||
id="top-right-background"
|
||||
style="fill:#346b23;fill-opacity:1;stroke:none;stroke-width:0.79375;stroke-linecap:round"
|
||||
d="M 4.2333332,0 C 1.8880691,0 0,1.8880691 0,4.2333332 V 12.7 c 0,2.345264 1.8880691,4.233333 4.2333332,4.233333 H 12.7 c 2.345264,0 4.233333,-1.888069 4.233333,-4.233333 V 4.2333332 C 16.933333,1.8880691 15.045264,0 12.7,0 Z" /><path
|
||||
|
@ -71,16 +98,22 @@
|
|||
style="font-size:8px;line-height:8.64px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono';letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect3);display:inline;fill:#ca9c00;fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:round"
|
||||
d="m 25.689419,18.184344 v 0.507817 c -0.54427,0.01823 -0.959661,0.136597 -1.246119,0.355347 -0.283854,0.216146 -0.425622,0.518229 -0.425622,0.90625 0,0.244791 0.0468,0.455806 0.14055,0.632889 0.09635,0.177084 0.24613,0.32939 0.449255,0.456994 0.205729,0.125 0.527312,0.240838 0.964812,0.347609 l 0.117124,0.03137 v 0.115242 l 0.628916,-0.628916 c -0.04352,-0.0111 -0.08269,-0.02199 -0.128837,-0.03325 v -1.664003 c 0.283854,0.01823 0.509099,0.0976 0.675766,0.238223 0.158491,0.133727 0.2669,0.329437 0.326484,0.585622 l 0.527896,-0.527896 c -0.107669,-0.217411 -0.243902,-0.385244 -0.409099,-0.503008 -0.26302,-0.190104 -0.636672,-0.294241 -1.121047,-0.312471 v -0.507817 z m 0,1.0194 v 1.613387 c -0.309896,-0.08073 -0.523441,-0.153667 -0.640628,-0.218771 -0.117188,-0.06771 -0.207095,-0.150977 -0.269595,-0.249935 -0.0599,-0.101562 -0.08973,-0.22657 -0.08973,-0.375007 0,-0.236979 0.08464,-0.420695 0.253909,-0.550903 0.16927,-0.132812 0.417915,-0.20575 0.74604,-0.218771 z"
|
||||
transform="matrix(2.4707763,0,0,2.4707763,-55.488799,-44.26592)"
|
||||
sodipodi:nodetypes="cccscccccccccccccccccccscsc" /></g><path
|
||||
style="display:inline;fill:#e7b300;fill-opacity:1;stroke:none;stroke-width:25.4;stroke-linecap:round"
|
||||
d="M -1.3473491,68.51775 104.48598,33.263506 v -52.916667 c 0,0 -12.21534,4.282234 -23.751883,14.636677 C 69.197554,5.3379594 71.165788,14.434953 55.014288,25.695895 38.862788,36.956837 32.416445,35.338519 17.831036,48.531087 3.2456269,61.723654 -1.3473491,68.51775 -1.3473491,68.51775 Z"
|
||||
id="path3"
|
||||
sodipodi:nodetypes="ccczzzc" /><path
|
||||
style="display:inline;fill:#a78000;fill-opacity:1;stroke:none;stroke-width:25.4;stroke-linecap:round"
|
||||
d="M 1.2353516e-7,65.117916 105.83333,40.715416 V -12.20125 c 0,0 -12.215341,3.0297196 -23.751884,12.20125 C 70.544903,9.1715303 72.513137,18.470338 56.361637,28.075167 40.210137,37.679996 33.763794,35.400695 19.178385,47.097731 4.5929761,58.794766 1.2353516e-7,65.117916 1.2353516e-7,65.117916 Z"
|
||||
id="path2"
|
||||
sodipodi:nodetypes="ccczzzc" /><path
|
||||
style="display:inline;fill:#ca9c00;fill-opacity:1;stroke:none;stroke-width:25.4;stroke-linecap:round"
|
||||
d="M 0,52.916666 H 105.83333 V 0 c 0,0 -12.215341,0.21316952 -23.751884,6.7246636 C 70.544903,13.236158 72.513137,22.988791 56.361637,28.869491 40.210137,34.750191 33.763794,30.984525 19.178385,39.318533 4.592976,47.65254 0,52.916666 0,52.916666 Z"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="ccczzzc" /></g></svg>
|
||||
sodipodi:nodetypes="cccscccccccccccccccccccscsc" /></g><text
|
||||
xml:space="preserve"
|
||||
transform="matrix(0.26458333,0,0,0.26458333,-25.033368,-34.700052)"
|
||||
id="text4"
|
||||
style="font-weight:bold;font-size:64px;line-height:69.12px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono Bold';letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect4);display:inline;fill:#346b23;fill-opacity:1;stroke-width:96;stroke-linecap:round"><tspan
|
||||
x="317.6543"
|
||||
y="289.52758"
|
||||
id="tspan5"><tspan
|
||||
style="font-weight:normal;font-family:FreeSerif;-inkscape-font-specification:FreeSerif"
|
||||
id="tspan3">PerFin</tspan></tspan></text><text
|
||||
xml:space="preserve"
|
||||
transform="matrix(0.26458333,0,0,0.26458333,-24.420403,-19.32274)"
|
||||
id="text5"
|
||||
style="font-weight:bold;font-size:24px;line-height:25.92px;font-family:'Liberation Mono';-inkscape-font-specification:'Liberation Mono Bold';letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect5);display:inline;fill:#346b23;fill-opacity:1;stroke-width:96;stroke-linecap:round"><tspan
|
||||
x="317.6543"
|
||||
y="255.92758"
|
||||
id="tspan7"><tspan
|
||||
style="font-weight:normal;font-family:FreeSerif;-inkscape-font-specification:FreeSerif"
|
||||
id="tspan6">Personal Finance</tspan></tspan></text></g></svg>
|
||||
|
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 8.5 KiB |
|
@ -7,6 +7,7 @@ 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 com.andrewlalis.perfin.view.component.ScrollPaneRouterView;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Scene;
|
||||
|
@ -38,7 +39,7 @@ public class PerfinApp extends Application {
|
|||
* A router that controls which help page is being viewed in the side-pane.
|
||||
* Certain user actions may cause this router to navigate to certain pages.
|
||||
*/
|
||||
public static final SceneRouter helpRouter = new SceneRouter(new AnchorPaneRouterView(true));
|
||||
public static final SceneRouter helpRouter = new SceneRouter(new ScrollPaneRouterView());
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
|
@ -75,23 +76,24 @@ public class PerfinApp extends Application {
|
|||
});
|
||||
}
|
||||
|
||||
private static void mapResourceRoute(String route, String resource) {
|
||||
router.map(route, PerfinApp.class.getResource(resource));
|
||||
}
|
||||
|
||||
private static void defineRoutes(Consumer<String> msgConsumer) {
|
||||
msgConsumer.accept("Initializing application views.");
|
||||
Platform.runLater(() -> {
|
||||
mapResourceRoute("accounts", "/accounts-view.fxml");
|
||||
mapResourceRoute("account", "/account-view.fxml");
|
||||
mapResourceRoute("edit-account", "/edit-account.fxml");
|
||||
mapResourceRoute("transactions", "/transactions-view.fxml");
|
||||
mapResourceRoute("create-transaction", "/create-transaction.fxml");
|
||||
mapResourceRoute("create-balance-record", "/create-balance-record.fxml");
|
||||
// App pages.
|
||||
router.map("accounts", PerfinApp.class.getResource("/accounts-view.fxml"));
|
||||
router.map("account", PerfinApp.class.getResource("/account-view.fxml"));
|
||||
router.map("edit-account", PerfinApp.class.getResource("/edit-account.fxml"));
|
||||
router.map("transactions", PerfinApp.class.getResource("/transactions-view.fxml"));
|
||||
router.map("create-transaction", PerfinApp.class.getResource("/create-transaction.fxml"));
|
||||
router.map("create-balance-record", PerfinApp.class.getResource("/create-balance-record.fxml"));
|
||||
|
||||
// Map help pages.
|
||||
helpRouter.map("help-test", PerfinApp.class.getResource("/help-pages/help-test.fxml"));
|
||||
// Help pages.
|
||||
helpRouter.map("home", PerfinApp.class.getResource("/help-pages/home.fxml"));
|
||||
helpRouter.map("accounts", PerfinApp.class.getResource("/help-pages/accounts-view.fxml"));
|
||||
helpRouter.map("adding-an-account", PerfinApp.class.getResource("/help-pages/adding-an-account.fxml"));
|
||||
helpRouter.map("transactions", PerfinApp.class.getResource("/help-pages/transactions-view.fxml"));
|
||||
helpRouter.map("adding-a-transaction", PerfinApp.class.getResource("/help-pages/adding-a-transaction.fxml"));
|
||||
helpRouter.map("profiles", PerfinApp.class.getResource("/help-pages/profiles.fxml"));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -3,12 +3,13 @@ package com.andrewlalis.perfin.control;
|
|||
import com.andrewlalis.javafx_scene_router.AnchorPaneRouterView;
|
||||
import com.andrewlalis.perfin.view.BindingUtil;
|
||||
import com.andrewlalis.perfin.view.ProfilesStage;
|
||||
import com.andrewlalis.perfin.view.component.ScrollPaneRouterView;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import static com.andrewlalis.perfin.PerfinApp.helpRouter;
|
||||
import static com.andrewlalis.perfin.PerfinApp.router;
|
||||
|
@ -19,7 +20,8 @@ public class MainViewController {
|
|||
|
||||
@FXML public Button showManualButton;
|
||||
@FXML public Button hideManualButton;
|
||||
@FXML public VBox manualVBox;
|
||||
@FXML public BorderPane helpPane;
|
||||
@FXML public Button helpBackButton;
|
||||
|
||||
@FXML public void initialize() {
|
||||
AnchorPaneRouterView routerView = (AnchorPaneRouterView) router.getView();
|
||||
|
@ -41,15 +43,26 @@ public class MainViewController {
|
|||
router.navigate("accounts");
|
||||
|
||||
// Initialize the help manual components.
|
||||
manualVBox.managedProperty().bind(manualVBox.visibleProperty());
|
||||
manualVBox.setVisible(false);
|
||||
helpPane.managedProperty().bind(helpPane.visibleProperty());
|
||||
helpPane.setVisible(false);
|
||||
showManualButton.managedProperty().bind(showManualButton.visibleProperty());
|
||||
showManualButton.visibleProperty().bind(manualVBox.visibleProperty().not());
|
||||
showManualButton.visibleProperty().bind(helpPane.visibleProperty().not());
|
||||
hideManualButton.managedProperty().bind(hideManualButton.visibleProperty());
|
||||
hideManualButton.visibleProperty().bind(manualVBox.visibleProperty());
|
||||
hideManualButton.visibleProperty().bind(helpPane.visibleProperty());
|
||||
|
||||
AnchorPaneRouterView helpRouterView = (AnchorPaneRouterView) helpRouter.getView();
|
||||
manualVBox.getChildren().add(helpRouterView.getAnchorPane());
|
||||
helpBackButton.managedProperty().bind(helpBackButton.visibleProperty());
|
||||
helpRouter.currentRouteProperty().addListener((observable, oldValue, newValue) -> {
|
||||
helpBackButton.setVisible(helpRouter.getHistory().canGoBack());
|
||||
});
|
||||
helpBackButton.setOnAction(event -> helpRouter.navigateBack());
|
||||
|
||||
ScrollPaneRouterView helpRouterView = (ScrollPaneRouterView) helpRouter.getView();
|
||||
ScrollPane helpRouterScrollPane = helpRouterView.getScrollPane();
|
||||
helpRouterScrollPane.setMinWidth(200.0);
|
||||
helpRouterScrollPane.setMaxWidth(400.0);
|
||||
helpRouterScrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
|
||||
helpRouterScrollPane.getStyleClass().addAll("padding-extra");
|
||||
helpPane.setCenter(helpRouterScrollPane);
|
||||
|
||||
helpRouter.navigate("home");
|
||||
}
|
||||
|
@ -77,10 +90,25 @@ public class MainViewController {
|
|||
}
|
||||
|
||||
@FXML public void showManual() {
|
||||
manualVBox.setVisible(true);
|
||||
helpPane.setVisible(true);
|
||||
}
|
||||
|
||||
@FXML public void hideManual() {
|
||||
manualVBox.setVisible(false);
|
||||
helpPane.setVisible(false);
|
||||
}
|
||||
|
||||
@FXML public void helpViewHome() {
|
||||
helpRouter.getHistory().clear();
|
||||
helpRouter.navigate("home");
|
||||
}
|
||||
|
||||
@FXML public void helpViewAccounts() {
|
||||
helpRouter.getHistory().clear();
|
||||
helpRouter.navigate("accounts");
|
||||
}
|
||||
|
||||
@FXML public void helpViewTransactions() {
|
||||
helpRouter.getHistory().clear();
|
||||
helpRouter.navigate("transactions");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ import com.andrewlalis.perfin.data.ProfileLoadException;
|
|||
import com.andrewlalis.perfin.data.util.FileUtil;
|
||||
import com.andrewlalis.perfin.model.Profile;
|
||||
import com.andrewlalis.perfin.view.ProfilesStage;
|
||||
import javafx.beans.binding.BooleanExpression;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import com.andrewlalis.perfin.view.component.validation.ValidationApplier;
|
||||
import com.andrewlalis.perfin.view.component.validation.validators.PredicateValidator;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
|
@ -16,6 +16,8 @@ import javafx.scene.layout.HBox;
|
|||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
@ -24,22 +26,16 @@ import java.util.List;
|
|||
import static com.andrewlalis.perfin.PerfinApp.router;
|
||||
|
||||
public class ProfilesViewController {
|
||||
private static final Logger log = LoggerFactory.getLogger(ProfilesViewController.class);
|
||||
|
||||
@FXML public VBox profilesVBox;
|
||||
@FXML public TextField newProfileNameField;
|
||||
@FXML public Text newProfileNameErrorLabel;
|
||||
@FXML public Button addProfileButton;
|
||||
|
||||
@FXML public void initialize() {
|
||||
BooleanExpression newProfileNameValid = BooleanProperty.booleanExpression(newProfileNameField.textProperty()
|
||||
.map(text -> (
|
||||
text != null &&
|
||||
!text.isBlank() &&
|
||||
Profile.validateName(text) &&
|
||||
!Profile.getAvailableProfiles().contains(text)
|
||||
)));
|
||||
newProfileNameErrorLabel.managedProperty().bind(newProfileNameErrorLabel.visibleProperty());
|
||||
newProfileNameErrorLabel.visibleProperty().bind(newProfileNameValid.not().and(newProfileNameField.textProperty().isNotEmpty()));
|
||||
newProfileNameErrorLabel.wrappingWidthProperty().bind(newProfileNameField.widthProperty());
|
||||
var newProfileNameValid = new ValidationApplier<>(new PredicateValidator<String>()
|
||||
.addPredicate(s -> s == null || s.isBlank() || Profile.validateName(s), "Profile name should consist of only lowercase numbers.")
|
||||
).attachToTextField(newProfileNameField);
|
||||
addProfileButton.disableProperty().bind(newProfileNameValid.not());
|
||||
|
||||
refreshAvailableProfiles();
|
||||
|
@ -106,7 +102,7 @@ public class ProfilesViewController {
|
|||
}
|
||||
|
||||
private boolean openProfile(String name, boolean showPopup) {
|
||||
System.out.println("Opening profile: " + name);
|
||||
log.info("Opening profile \"{}\".", name);
|
||||
try {
|
||||
Profile.load(name);
|
||||
ProfilesStage.closeView();
|
||||
|
|
|
@ -62,10 +62,15 @@ public class StartupSplashScreen extends Stage implements Consumer<String> {
|
|||
|
||||
private void runTasks() {
|
||||
Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
for (var task : tasks) {
|
||||
try {
|
||||
task.accept(this);
|
||||
Thread.sleep(100);
|
||||
Thread.sleep(500);
|
||||
} catch (Exception e) {
|
||||
accept("Startup failed: " + e.getMessage());
|
||||
e.printStackTrace(System.err);
|
||||
|
@ -80,7 +85,7 @@ public class StartupSplashScreen extends Stage implements Consumer<String> {
|
|||
}
|
||||
accept("Startup successful!");
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package com.andrewlalis.perfin.view.component;
|
||||
|
||||
import com.andrewlalis.javafx_scene_router.RouterView;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
|
||||
public class ScrollPaneRouterView implements RouterView {
|
||||
private final ScrollPane scrollPane = new ScrollPane();
|
||||
|
||||
public ScrollPaneRouterView() {
|
||||
scrollPane.setFitToHeight(true);
|
||||
scrollPane.setFitToWidth(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showRouteNode(Parent node) {
|
||||
scrollPane.setContent(node);
|
||||
}
|
||||
|
||||
public ScrollPane getScrollPane() {
|
||||
return scrollPane;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
package com.andrewlalis.perfin.view.component;
|
||||
|
||||
import com.andrewlalis.perfin.PerfinApp;
|
||||
import javafx.beans.DefaultProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.property.StringPropertyBase;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.AccessibleAttribute;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.layout.Border;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.andrewlalis.perfin.PerfinApp.helpRouter;
|
||||
|
||||
/**
|
||||
* A component that renders markdown-ish text as a series of TextFlow elements,
|
||||
* styled according to some basic styles.
|
||||
*/
|
||||
@DefaultProperty("text")
|
||||
public class StyledText extends VBox {
|
||||
private StringProperty text;
|
||||
private boolean initialized = false;
|
||||
|
||||
public final void setText(String value) {
|
||||
if (value == null) value = "";
|
||||
textProperty().set(value);
|
||||
}
|
||||
|
||||
public final String getText() {
|
||||
return text == null ? "" : text.get();
|
||||
}
|
||||
|
||||
public final StringProperty textProperty() {
|
||||
if (text == null) {
|
||||
text = new StringPropertyBase("") {
|
||||
@Override public Object getBean() { return StyledText.this; }
|
||||
@Override public String getName() { return "text"; }
|
||||
@Override public void invalidated() {
|
||||
notifyAccessibleAttributeChanged(AccessibleAttribute.TEXT);
|
||||
}
|
||||
};
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void layoutChildren() {
|
||||
if (!initialized) {
|
||||
String s = getText();
|
||||
getChildren().clear();
|
||||
getChildren().addAll(renderText(s));
|
||||
getStyleClass().add("spacing-extra");
|
||||
initialized = true;
|
||||
}
|
||||
super.layoutChildren();
|
||||
}
|
||||
|
||||
private List<TextFlow> renderText(String text) {
|
||||
return new TextFlowBuilder().build(text);
|
||||
}
|
||||
|
||||
private static class TextFlowBuilder {
|
||||
private final List<TextFlow> flows = new ArrayList<>();
|
||||
private int idx = 0;
|
||||
private final StringBuilder currentRun = new StringBuilder();
|
||||
private TextFlow currentParagraph;
|
||||
|
||||
public List<TextFlow> build(String text) {
|
||||
flows.clear();
|
||||
idx = 0;
|
||||
currentRun.setLength(0);
|
||||
currentParagraph = new TextFlow();
|
||||
|
||||
while (idx < text.length()) {
|
||||
if (text.startsWith("**", idx)) {
|
||||
parseStyledText(text, "**", "bold-text");
|
||||
} else if (text.startsWith("*", idx)) {
|
||||
parseStyledText(text, "*", "italic-text");
|
||||
} else if (text.startsWith("`", idx)) {
|
||||
parseStyledText(text, "`", "mono-font");
|
||||
} else if (text.startsWith(" -- ", idx)) {
|
||||
parsePageBreak(text);
|
||||
} else if (text.startsWith("[", idx)) {
|
||||
parseLink(text);
|
||||
} else if (text.startsWith("#", idx) && (idx == 0 || (idx > 0 && text.charAt(idx - 1) == ' '))) {
|
||||
parseHeader(text);
|
||||
} else {
|
||||
currentRun.append(text.charAt(idx));
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
appendTextIfPresent();
|
||||
appendParagraphIfPresent();
|
||||
return flows;
|
||||
}
|
||||
|
||||
private void parsePageBreak(String text) {
|
||||
appendTextIfPresent();
|
||||
appendParagraphIfPresent();
|
||||
while (text.charAt(idx) == ' ') idx++;
|
||||
while (text.charAt(idx) == '-') idx++;
|
||||
while (text.charAt(idx) == ' ') idx++;
|
||||
}
|
||||
|
||||
private void parseStyledText(String text, String marker, String styleClass) {
|
||||
appendTextIfPresent();
|
||||
int endIdx = text.indexOf(marker, idx + marker.length());
|
||||
Text textItem = new Text(text.substring(idx + marker.length(), endIdx));
|
||||
textItem.getStyleClass().add(styleClass);
|
||||
currentParagraph.getChildren().add(textItem);
|
||||
idx = endIdx + marker.length();
|
||||
}
|
||||
|
||||
private void parseLink(String text) {
|
||||
appendTextIfPresent();
|
||||
int labelEndIdx = text.indexOf(']', idx);
|
||||
String label = text.substring(idx + 1, labelEndIdx);
|
||||
idx = labelEndIdx + 1;
|
||||
final String link;
|
||||
if (text.charAt(labelEndIdx + 1) == '(') {
|
||||
int linkEndIdx = text.indexOf(')', labelEndIdx + 2);
|
||||
link = text.substring(labelEndIdx + 2, linkEndIdx);
|
||||
idx = linkEndIdx + 1;
|
||||
} else {
|
||||
link = null;
|
||||
}
|
||||
Hyperlink hyperlink = new Hyperlink(label);
|
||||
if (link != null) {
|
||||
if (link.startsWith("http")) {
|
||||
hyperlink.setOnAction(event -> PerfinApp.instance.getHostServices().showDocument(link));
|
||||
} else if (link.startsWith("help:")) {
|
||||
hyperlink.setOnAction(event -> helpRouter.navigate(link.substring(5).strip()));
|
||||
}
|
||||
}
|
||||
hyperlink.setBorder(Border.EMPTY);
|
||||
hyperlink.setPadding(new Insets(0, 0, 0, 0));
|
||||
currentParagraph.getChildren().add(hyperlink);
|
||||
}
|
||||
|
||||
private void parseHeader(String text) {
|
||||
appendTextIfPresent();
|
||||
appendParagraphIfPresent();
|
||||
int size = 0;
|
||||
while (text.charAt(idx) == '#') {
|
||||
idx++;
|
||||
size++;
|
||||
}
|
||||
int endIdx = text.indexOf("#".repeat(size), idx);
|
||||
Text header = new Text(text.substring(idx, endIdx).strip());
|
||||
idx = endIdx + size;
|
||||
while (text.charAt(idx) == ' ') idx++;
|
||||
String styleClass = switch(size) {
|
||||
case 1 -> "large-font";
|
||||
case 2 -> "largest-font";
|
||||
default -> "largest-font";
|
||||
};
|
||||
header.getStyleClass().addAll(styleClass, "bold-text");
|
||||
currentParagraph.getChildren().add(header);
|
||||
appendParagraphIfPresent();
|
||||
}
|
||||
|
||||
private void appendTextIfPresent() {
|
||||
if (!currentRun.isEmpty()) {
|
||||
currentParagraph.getChildren().add(new Text(currentRun.toString()));
|
||||
currentRun.setLength(0);
|
||||
}
|
||||
}
|
||||
|
||||
private void appendParagraphIfPresent() {
|
||||
if (!currentParagraph.getChildren().isEmpty()) {
|
||||
flows.add(currentParagraph);
|
||||
currentParagraph = new TextFlow();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import com.andrewlalis.perfin.view.component.StyledText?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
styleClass="std-spacing"
|
||||
>
|
||||
<StyledText>
|
||||
## The Accounts View ##
|
||||
In the *Accounts view*, you'll see an overview of all the active
|
||||
accounts in your *Perfin* profile.
|
||||
--
|
||||
To add a new account, simply click on **Add an Account** from the menu
|
||||
at the top. [Read about how to add an account here.](help:adding-an-account)
|
||||
--
|
||||
Additionally, the top menu also shows an overview of the derived total
|
||||
balance for each currency you own. This adds up the balances of all
|
||||
accounts of like currency, and gives you a total of each.
|
||||
--
|
||||
Accounts are, by default, ordered according to the most recent activity.
|
||||
That means that your most active accounts will appear *first* in this
|
||||
page, and your least active accounts will appear *last*.
|
||||
|
||||
# Account Tiles #
|
||||
Each account's tile displays some basic information about the account
|
||||
up-front, like its name, number, balance, and type. Click on a tile to
|
||||
navigate to that account's page for more detailed information, and to
|
||||
make changes to the account.
|
||||
--
|
||||
The *current balance* shown in each account tile is derived from the
|
||||
account's last-known **balance record**, and all transactions that have
|
||||
happened between then and now.
|
||||
</StyledText>
|
||||
</VBox>
|
|
@ -0,0 +1,65 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import com.andrewlalis.perfin.view.component.StyledText?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
>
|
||||
<StyledText>
|
||||
## Adding a Transaction ##
|
||||
When you're adding a new transaction to your *Perfin* profile, there are
|
||||
some details that you should be aware of.
|
||||
|
||||
# Timestamp #
|
||||
The timestamp is the date and time at which the transaction took place,
|
||||
in your local time zone. Generally, it's encouraged to set this as the
|
||||
real timestamp at which the transaction took place (the time shown on a
|
||||
receipt or invoice, for example), rather than the timestamp provided by
|
||||
your financial institution once the payment has been processed.
|
||||
--
|
||||
It's formatted as `yyyy-mm-dd HH:MM:SS`, so for example, November 14th,
|
||||
2015 at 4:15pm would be written as `2015-11-14 16:15:00`. You can omit
|
||||
the seconds if you like, however.
|
||||
--
|
||||
Also note that you may not enter timestamps from the future; it just
|
||||
doesn't make sense to do so.
|
||||
|
||||
# Amount #
|
||||
The total amount of the transaction, as a positive decimal value. This
|
||||
is the final amount which you've paid or received, including any tax,
|
||||
tips, or transaction fees.
|
||||
|
||||
# Currency #
|
||||
The currency of the transaction. This should be the same currency as the
|
||||
account(s) that the transaction is linked to.
|
||||
|
||||
# Linked Debit and Credit Accounts #
|
||||
Every transaction, for it to mean something, needs to be linked to one
|
||||
(or sometimes two) of your accounts. A transaction's impact on an
|
||||
account depends on whether the transaction is being treated as a **Debit**
|
||||
or a **Credit** on the account.
|
||||
--
|
||||
The account linked as **Debit** is the one whose assets will
|
||||
**increase** as a result of the transaction. Some common examples of
|
||||
transactions with debit-linked accounts include deposits to checking
|
||||
or savings accounts, or refunds to credit cards.
|
||||
--
|
||||
The account linked as **Credit** is the one whose assets will
|
||||
**decrease** as a result of the transaction. Some common examples of
|
||||
transactions with credit-linked accounts include payments or purchases
|
||||
with a checking or savings account, or a credit card.
|
||||
--
|
||||
In short, if your account is *gaining money*, link it under **debit**.
|
||||
If your account is *losing money*, link it under **credit**.
|
||||
--
|
||||
For transfers between two accounts that are both tracked in *Perfin*,
|
||||
the *sending* account should be linked under **credit**, and the
|
||||
*receiving* account under **debit**.
|
||||
|
||||
# Attachments #
|
||||
Often, you'll have a receipt, invoice, bank statement, or some other
|
||||
document which acts as proof of a transaction. You can attach files to
|
||||
a transaction to save those files with it, as a reference. The files
|
||||
will be copied to your *Perfin* profile.
|
||||
</StyledText>
|
||||
</VBox>
|
|
@ -0,0 +1,45 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import com.andrewlalis.perfin.view.component.StyledText?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
>
|
||||
<StyledText>
|
||||
## Adding an Account ##
|
||||
When adding an account, you'll need to provide some basic information
|
||||
about the account so that Perfin can integrate it with its automatic
|
||||
balance calculations and other functions.
|
||||
|
||||
# Name #
|
||||
The name of the account is just a bit of text that you can use to
|
||||
identify the account from the rest of yours. Make sure it's unique, and
|
||||
to-the-point, since the account's name is used elsewhere in the app to
|
||||
refer to the account.
|
||||
|
||||
# Number #
|
||||
The number of the account is the unique account number provided by your
|
||||
financial institution. For checking and savings accounts, it'll be the
|
||||
account number provided by your bank, and for credit cards, it'll be the
|
||||
credit card's number.
|
||||
|
||||
# Currency #
|
||||
The currency is pretty self-explanatory; simply select the currency that
|
||||
your account uses. Perfin uses 3-character *currency codes* quite often,
|
||||
and you can read about them [here](https://en.wikipedia.org/wiki/ISO_4217#List_of_currency_codes).
|
||||
|
||||
# Account Type #
|
||||
The account's type determines how certain balance calculations and
|
||||
properties of the account work. For instance, a **Credit Card**
|
||||
account's balance is interpreted to be the amount you owe to the card's
|
||||
provider (the amount you need to pay off), while a **Checking**
|
||||
account's balance is naturally the amount of money in your account.
|
||||
|
||||
# Initial Balance #
|
||||
In order to accurately compute your account's balance (without needing
|
||||
to check back every hour with your financial institution), Perfin needs
|
||||
to know what your account's starting balance is. This way, it can use
|
||||
that balance, combined with any transactions you add, to tell you your
|
||||
balance at any moment in time.
|
||||
</StyledText>
|
||||
</VBox>
|
|
@ -1,15 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Text?>
|
||||
<?import javafx.scene.text.TextFlow?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
>
|
||||
<TextFlow>
|
||||
<Text>
|
||||
This is a testing help page, which is meant to be shown only for
|
||||
testing the help system and navigation.
|
||||
</Text>
|
||||
</TextFlow>
|
||||
</VBox>
|
|
@ -1,29 +1,35 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import com.andrewlalis.perfin.view.component.StyledText?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Text?>
|
||||
<?import javafx.scene.text.TextFlow?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
styleClass="std-spacing"
|
||||
>
|
||||
<Label styleClass="large-font,bold-text" text="Perfin Help Pages"/>
|
||||
<TextFlow>
|
||||
<Text>
|
||||
This is the homepage for Perfin's help pages: a manual for how to
|
||||
use Perfin safely and effectively to manage your personal finances.
|
||||
</Text>
|
||||
</TextFlow>
|
||||
<TextFlow>
|
||||
<Text>
|
||||
Search for a topic below, or click on the help icon beside some
|
||||
component to navigate to its page.
|
||||
</Text>
|
||||
</TextFlow>
|
||||
<StyledText>
|
||||
## Homepage ##
|
||||
This is the homepage for Perfin's help pages: a manual for how to use
|
||||
Perfin safely and effectively to manage your personal finances.
|
||||
--
|
||||
Search for a topic below, or follow one of the index links.
|
||||
</StyledText>
|
||||
<HBox>
|
||||
<Label text="Search!"/>
|
||||
<TextField/>
|
||||
<TextField disable="true" text="Searching is WIP" HBox.hgrow="ALWAYS"/>
|
||||
</HBox>
|
||||
<StyledText>
|
||||
# Help Pages Index #
|
||||
The following is a list of all help pages.
|
||||
--
|
||||
[Accounts View](help:accounts)
|
||||
--
|
||||
[Adding an Account](help:adding-an-account)
|
||||
--
|
||||
[Transactions View](help:transactions)
|
||||
--
|
||||
[Adding a Transaction](help:adding-a-transaction)
|
||||
--
|
||||
[Profiles](help:profiles)
|
||||
</StyledText>
|
||||
</VBox>
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import com.andrewlalis.perfin.view.component.StyledText?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
>
|
||||
<StyledText>
|
||||
## Profiles ##
|
||||
In *Perfin*, all the accounts, transactions, attachments, bank
|
||||
statements, and other financial data, are stored in a *profile*. You can
|
||||
think of it as a save file. It's a single folder that encapsulates all
|
||||
your data.
|
||||
--
|
||||
Perfin uses profiles to make it easy to work with multiple financial
|
||||
portfolios, export and import bulk data, and to let *you*, the user,
|
||||
access your data directly, as opposed to hiding it in some proprietary
|
||||
file format.
|
||||
--
|
||||
A running Perfin program always has one profile open at a time. You can
|
||||
change the active profile by clicking **Profiles** from the app's top
|
||||
menu, and then clicking **Open** next to the profile you'd like to use.
|
||||
--
|
||||
By default, when you first start Perfin, a profile named *default* is
|
||||
created for you to use. Most of the time, you won't need to worry about
|
||||
using other profiles, but if you do, now you know how.
|
||||
</StyledText>
|
||||
</VBox>
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import com.andrewlalis.perfin.view.component.StyledText?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
styleClass="std-spacing"
|
||||
>
|
||||
<StyledText>
|
||||
## The Transactions View ##
|
||||
In the *Transactions view*, you're shown a list of all transactions that
|
||||
have been added to your *Perfin* profile, along with some controls for
|
||||
navigating this list.
|
||||
--
|
||||
The transactions are ordered, by default, according to their timestamp,
|
||||
with recent transactions appearing before older ones.
|
||||
--
|
||||
Simply click on a transaction to view its details or make changes.
|
||||
--
|
||||
To add a new transaction, click **Add Transaction** in the top menu. You
|
||||
can read more about adding a transaction [here](help:adding-a-transaction).
|
||||
</StyledText>
|
||||
</VBox>
|
Binary file not shown.
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 15 KiB |
|
@ -5,30 +5,52 @@
|
|||
<BorderPane
|
||||
xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:id="mainContainer"
|
||||
fx:controller="com.andrewlalis.perfin.control.MainViewController"
|
||||
>
|
||||
<top>
|
||||
<VBox>
|
||||
<HBox styleClass="std-padding,std-spacing">
|
||||
<Button text="Back" onAction="#goBack"/>
|
||||
<Button text="Forward" onAction="#goForward"/>
|
||||
<Button text="Accounts" onAction="#goToAccounts"/>
|
||||
<Button text="Transactions" onAction="#goToTransactions"/>
|
||||
<Button text="Profiles" onAction="#viewProfiles"/>
|
||||
<center>
|
||||
<BorderPane fx:id="mainContainer">
|
||||
<!-- Top bar for the app -->
|
||||
<top>
|
||||
<VBox>
|
||||
<HBox styleClass="std-padding,std-spacing">
|
||||
<Button text="Back" onAction="#goBack"/>
|
||||
<Button text="Forward" onAction="#goForward"/>
|
||||
<Button text="Accounts" onAction="#goToAccounts"/>
|
||||
<Button text="Transactions" onAction="#goToTransactions"/>
|
||||
<Button text="Profiles" onAction="#viewProfiles"/>
|
||||
|
||||
<Button text="View Manual" fx:id="showManualButton" onAction="#showManual"/>
|
||||
<Button text="Hide Manual" fx:id="hideManualButton" onAction="#hideManual"/>
|
||||
</HBox>
|
||||
<HBox fx:id="breadcrumbHBox" styleClass="std-spacing,small-font"/>
|
||||
</VBox>
|
||||
</top>
|
||||
<bottom>
|
||||
<HBox styleClass="std-padding,std-spacing">
|
||||
<Label text="Perfin Version 1.2.0"/>
|
||||
</HBox>
|
||||
</bottom>
|
||||
<Button text="View Help" fx:id="showManualButton" onAction="#showManual"/>
|
||||
<Button text="Hide Help" fx:id="hideManualButton" onAction="#hideManual"/>
|
||||
</HBox>
|
||||
<HBox fx:id="breadcrumbHBox" styleClass="std-spacing,small-font"/>
|
||||
</VBox>
|
||||
</top>
|
||||
<!-- App footer -->
|
||||
<bottom>
|
||||
<HBox styleClass="std-padding,std-spacing">
|
||||
<Label text="Perfin Version 1.2.0"/>
|
||||
<AnchorPane>
|
||||
<Label text="© 2024 Andrew Lalis" styleClass="small-font,secondary-color-text-fill" AnchorPane.topAnchor="0" AnchorPane.bottomAnchor="0"/>
|
||||
</AnchorPane>
|
||||
</HBox>
|
||||
</bottom>
|
||||
</BorderPane>
|
||||
</center>
|
||||
|
||||
<!-- Right-side panel to show help info, at the top level of the whole app. -->
|
||||
<right>
|
||||
<VBox fx:id="manualVBox" style="-fx-min-width: 400px;" styleClass="padding-extra-1"/>
|
||||
<BorderPane fx:id="helpPane">
|
||||
<top>
|
||||
<VBox styleClass="padding-extra-1">
|
||||
<Label text="Perfin Help" styleClass="largest-font,bold-text"/>
|
||||
<HBox styleClass="std-spacing">
|
||||
<Hyperlink onAction="#helpViewHome">Home</Hyperlink>
|
||||
<Hyperlink onAction="#helpViewAccounts">Accounts</Hyperlink>
|
||||
<Hyperlink onAction="#helpViewTransactions">Transactions</Hyperlink>
|
||||
</HBox>
|
||||
<Button fx:id="helpBackButton" text="Back"/>
|
||||
</VBox>
|
||||
</top>
|
||||
</BorderPane>
|
||||
</right>
|
||||
</BorderPane>
|
||||
|
|
|
@ -39,19 +39,13 @@
|
|||
<bottom>
|
||||
<BorderPane>
|
||||
<left>
|
||||
<VBox styleClass="std-padding">
|
||||
<Label text="Add New Profile"/>
|
||||
</VBox>
|
||||
<AnchorPane styleClass="std-padding">
|
||||
<Label text="Add New Profile" styleClass="bold-text" AnchorPane.leftAnchor="0" AnchorPane.topAnchor="0" AnchorPane.bottomAnchor="0"/>
|
||||
</AnchorPane>
|
||||
</left>
|
||||
<center>
|
||||
<VBox styleClass="std-padding">
|
||||
<TextField fx:id="newProfileNameField" style="-fx-min-width: 50px; -fx-pref-width: 50px;"/>
|
||||
<Text
|
||||
fx:id="newProfileNameErrorLabel"
|
||||
styleClass="error-text"
|
||||
style="-fx-fill: red;"
|
||||
text="Invalid profile name. Profile names must only contain lowercase text."
|
||||
/>
|
||||
</VBox>
|
||||
</center>
|
||||
<right>
|
||||
|
|
Loading…
Reference in New Issue