99import de .doubleslash .keeptime .model .Work ;
1010import de .doubleslash .keeptime .rest .integration .heimat .model .HeimatTask ;
1111import javafx .animation .Animation ;
12- import javafx .animation .PauseTransition ;
12+ import javafx .animation .KeyFrame ;
1313import javafx .animation .RotateTransition ;
14+ import javafx .animation .Timeline ;
1415import javafx .application .Platform ;
1516import javafx .beans .binding .Bindings ;
1617import javafx .beans .binding .BooleanBinding ;
2627import javafx .scene .Node ;
2728import javafx .scene .control .*;
2829import javafx .scene .effect .GaussianBlur ;
29- import javafx .scene .layout .AnchorPane ;
30- import javafx .scene .layout .HBox ;
31- import javafx .scene .layout .VBox ;
30+ import javafx .scene .layout .*;
3231import javafx .scene .paint .Color ;
3332import javafx .scene .shape .Circle ;
3433import javafx .scene .shape .SVGPath ;
4544import java .time .format .FormatStyle ;
4645import java .util .Collections ;
4746import java .util .List ;
47+ import java .util .concurrent .atomic .AtomicInteger ;
4848import java .util .function .Consumer ;
4949import java .util .function .Predicate ;
5050
@@ -76,6 +76,8 @@ public class ExternalProjectsSyncController {
7676
7777 @ FXML
7878 private Hyperlink externalSystemLink ;
79+ @ FXML
80+ private Hyperlink externalSystemLinkLoadingScreen ;
7981
8082 @ FXML
8183 private VBox loadingScreen ;
@@ -85,6 +87,10 @@ public class ExternalProjectsSyncController {
8587
8688 @ FXML
8789 private Label loadingMessage ;
90+ @ FXML
91+ private Label loadingClosingMessage ;
92+ @ FXML
93+ private Region syncingIconRegion ;
8894
8995 @ FXML
9096 private ComboBox <HeimatTask > heimatTaskComboBox ;
@@ -97,13 +103,18 @@ public class ExternalProjectsSyncController {
97103 0.1 , 0.1 );
98104 private final SVGPath loadingFailure = SvgNodeProvider .getSvgNodeWithScale (Resources .RESOURCE .SVG_XMARK_SOLID , 0.1 ,
99105 0.1 );
106+ private final Color colorLoadingSpinner = Color .valueOf ("#00A5E1" );
107+ private final Color colorLoadingSuccess = Color .valueOf ("#74a317" );
108+ private final Color colorLoadingFailure = Color .valueOf ("#c63329" );
100109
101110 private final LocalTimeStringConverter localTimeStringConverter = new LocalTimeStringConverter (FormatStyle .MEDIUM );
102111 private ObservableList <TableRow > items ;
103112
104113 private LocalDate currentReportDate ;
105114 private Stage thisStage ;
106115 private final HeimatController heimatController ;
116+ private final RotateTransition loadingSpinnerAnimation = new RotateTransition (Duration .seconds (1 ),
117+ syncingIconRegion );
107118
108119 public ExternalProjectsSyncController (final HeimatController heimatController ) {
109120 this .heimatController = heimatController ;
@@ -158,16 +169,17 @@ public void initForDate(LocalDate currentReportDate, List<Work> currentWorkItems
158169
159170 saveButton .disableProperty ().bind (projectsValidProperty );
160171 externalSystemLink .setOnAction (ae -> BrowserHelper .openURL (heimatController .getUrlForDay (currentReportDate )));
172+ externalSystemLinkLoadingScreen .setOnAction (
173+ ae -> BrowserHelper .openURL (heimatController .getUrlForDay (currentReportDate )));
161174
162175 final List <HeimatTask > tasksForDay = heimatController .getTasks (currentReportDate );
163176 final FilteredList <HeimatTask > tasksNotInList = new FilteredList <>(FXCollections .observableArrayList (tasksForDay ),
164177 (task ) -> items .stream ().noneMatch (tr -> task .id () == tr .mapping .heimatTaskId ()));
165- items .addListener (
166- (ListChangeListener <? super TableRow >) c -> {
167- final Predicate <? super HeimatTask > predicate = tasksNotInList .getPredicate ();
168- tasksNotInList .setPredicate (null );
169- tasksNotInList .setPredicate (predicate );
170- });
178+ items .addListener ((ListChangeListener <? super TableRow >) c -> {
179+ final Predicate <? super HeimatTask > predicate = tasksNotInList .getPredicate ();
180+ tasksNotInList .setPredicate (null );
181+ tasksNotInList .setPredicate (predicate );
182+ });
171183 heimatTaskComboBox .setItems (tasksNotInList );
172184 addHeimatTaskButton .disableProperty ()
173185 .bind (heimatTaskComboBox .getSelectionModel ().selectedItemProperty ().isNull ());
@@ -408,7 +420,9 @@ protected List<HeimatController.HeimatErrors> call() {
408420 task .setOnSucceeded (e -> {
409421 LOG .error ("Task successfull" );
410422 final List <HeimatController .HeimatErrors > errors = task .getValue ();
423+ int closingSeconds = 5 ;
411424 if (!errors .isEmpty ()) {
425+ closingSeconds = 10 ;
412426 loadingScreenShowSyncing ("Something did not work :(" , loadingFailure );
413427 List <String > a = errors .stream ().map (error -> {
414428 final List <Project > projects = error .mapping ().mapping ().projects ();
@@ -423,32 +437,44 @@ protected List<HeimatController.HeimatErrors> call() {
423437
424438 showErrorDialog (a );
425439 } else {
426- loadingScreenShowSyncing ("Successfully synced!" , loadingSuccess );
440+ loadingScreenShowSyncing (
441+ "Successfully synced!\n Please always validate that everything worked like expected." ,
442+ loadingSuccess );
427443 }
428444
429- PauseTransition delay = new PauseTransition (Duration .seconds (2 ));
430- // TODO maybe show countdown in UI with option to "Open day in HEIMAT"? could add 1 second again ;)
431- delay .setOnFinished (event -> {
432- showLoadingScreen (false );
433- thisStage .close ();
434- });
435- delay .play ();
445+ final AtomicInteger remainingSeconds = new AtomicInteger (closingSeconds );
446+ loadingClosingMessage .setText ("Closing in " + remainingSeconds + " seconds..." );
447+ loadingClosingMessage .setVisible (true );
448+ Timeline timeline = new Timeline (new KeyFrame (Duration .seconds (1 ), event -> {
449+ remainingSeconds .getAndDecrement ();
450+ loadingClosingMessage .setText ("Closing in " + remainingSeconds + " seconds..." );
451+ if (remainingSeconds .get () <= 0 ) {
452+ showLoadingScreen (false );
453+ thisStage .close ();
454+ loadingClosingMessage .setVisible (false );
455+ }
456+ }));
457+ timeline .setCycleCount (remainingSeconds .get ());
458+ timeline .play ();
436459 });
437460
438461 task .setOnFailed (e -> {
439462 final Throwable exception = task .getException ();
440- LOG .error ("Task failed" , exception );
441- loadingScreenShowSyncing ("Something did not work :(" , loadingFailure );
463+ LOG .error ("Task failed unexpectedly. " , exception );
464+ loadingScreenShowSyncing ("Something very unexpected has happened :(" , loadingFailure );
442465
443- showErrorDialog (Collections .singletonList ("ERROR " + exception .getMessage ()));
466+ showErrorDialog (Collections .singletonList ("Error was: " + exception .getMessage ()));
444467 showLoadingScreen (false );
445468 thisStage .close ();
446469 });
447470 loadingScreenShowSyncing ("Syncing..." , loadingSpinner );
448471 Platform .runLater (() -> new Thread (task ).start ());
449472 });
450473
451- cancelButton .setOnAction (ae -> thisStage .close ());
474+ cancelButton .setOnAction (ae -> {
475+ showLoadingScreen (false );
476+ thisStage .close ();
477+ });
452478 }
453479
454480 private static void markNodeValidOrNot (final Node textArea , final boolean isValid ) {
@@ -466,33 +492,39 @@ private static boolean areSecondsOfDayValid(final long seconds) {
466492 }
467493
468494 private void initializeLoadingScreen () {
469- loadingScreen .getChildren ().add (0 , loadingSpinner );
470-
471- loadingSuccess .setFill (Color .GREEN );
495+ showLoadingScreen (false );
496+ loadingSuccess .setFill (colorLoadingSuccess );
472497 loadingSuccess .prefHeight (50 );
473498 loadingSuccess .prefWidth (50 );
474499
475- loadingFailure .setFill (Color . RED );
500+ loadingFailure .setFill (colorLoadingFailure );
476501 loadingFailure .prefHeight (50 );
477502 loadingFailure .prefWidth (50 );
478503
504+ loadingSpinner .setFill (colorLoadingSpinner );
479505 loadingSpinner .prefHeight (50 );
480506 loadingSpinner .prefWidth (50 );
481507
482- RotateTransition rotateTransition = new RotateTransition (Duration .seconds (1 ), loadingSpinner );
483- rotateTransition .setByAngle (360 );
484- rotateTransition .setCycleCount (Animation .INDEFINITE );
485- rotateTransition .play ();
508+ loadingSpinnerAnimation .setNode (syncingIconRegion );
509+ loadingSpinnerAnimation .setByAngle (360 );
510+ loadingSpinnerAnimation .setCycleCount (Animation .INDEFINITE );
486511 }
487512
488513 private void loadingScreenShowSyncing (String statusMessage , SVGPath icon ) {
489- final ObservableList <Node > children = loadingScreen .getChildren ();
490- children .remove (0 );
491- children .add (0 , icon );
514+ if (icon == loadingSpinner ) {
515+ loadingSpinnerAnimation .play ();
516+ } else {
517+ loadingSpinnerAnimation .stop ();
518+ }
519+ syncingIconRegion .setShape (icon );
520+ syncingIconRegion .setBackground (new Background (new BackgroundFill (icon .getFill (), null , null )));
492521 loadingMessage .setText (statusMessage );
493522 }
494523
495524 private void showLoadingScreen (final boolean show ) {
525+ if (!show )
526+ loadingSpinnerAnimation .stop ();
527+ loadingClosingMessage .setVisible (false );
496528 pane .setDisable (show );
497529 loadingScreen .setVisible (show );
498530 pane .setEffect (show ? new GaussianBlur () : null );
0 commit comments