ToastProvider.java

package no.ntnu.idatt1002.demo.view.components;

import java.util.LinkedList;
import java.util.Queue;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import javafx.animation.FadeTransition;
import javafx.animation.TranslateTransition;
import javafx.concurrent.Task;
import javafx.scene.layout.AnchorPane;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import no.ntnu.idatt1002.demo.Logger;

// TODO: finish this javadoc
/**
 * A provider for toasts.
 */
public class ToastProvider extends AnchorPane {
  private static ToastProvider instance;
  private final Queue<Toast> toastQueue = new LinkedList<>();
  private Toast currenToast;
  private MediaPlayer mediaPlayer;

  private ToastProvider() {
    super();
    this.setPickOnBounds(false);
  }

  private void next() {
    this.getChildren().clear();
    this.currenToast = null;

    if (!toastQueue.isEmpty()) {
      this.currenToast = toastQueue.poll();
      this.getChildren().add(this.currenToast);
      AnchorPane.setBottomAnchor(this.currenToast, 25.0);
      AnchorPane.setRightAnchor(this.currenToast, 25.0);
      this.animateIn();

      // Play sound
      try {
        Media sound = new Media(
            getClass().getResource("/no/ntnu/idatt1002/sounds/"
                + this.currenToast.getType().name().toLowerCase() + ".mp3")
                .toExternalForm());

        mediaPlayer = new MediaPlayer(sound);
        mediaPlayer.play();
      } catch (Exception e) {
        Logger.error("Could not play sound "
            + this.currenToast.getType().name().toLowerCase() + ".mp3");
      }

      this.currenToast.setOnClose(() -> {
        this.next();
      });
    }

    if (this.currenToast == null) {
      return;
    }

    Task<Void> delay = new Task<Void>() {
      @Override
      protected Void call() throws Exception {
        try {
          Thread.sleep(Toast.DELFAULT_TOAST_DURATION);
        } catch (InterruptedException e) {
          Logger.error(e.getMessage());
        }
        return null;
      }
    };

    delay.setOnSucceeded(event -> {
      if (this.currenToast != null) {
        this.currenToast.animateOut();
      }
    });

    // Schedule the delay task
    Thread thread = new Thread(delay);

    // Make the thread a daemon thread so it doesn't prevent the application from
    // closing
    thread.setDaemon(true);
    thread.start();
  }

  /**
   * Gets the current instance of the toast provider.
   *
   * @return the instance
   */
  public static ToastProvider getInstance() {
    if (instance == null) {
      instance = new ToastProvider();
    }
    return instance;
  }

  private void animateIn() {
    final int animationTime = 400;

    // Fade in the toast
    FadeTransition fadeTransition = new FadeTransition();
    fadeTransition.setNode(currenToast);
    fadeTransition.setFromValue(0);
    fadeTransition.setToValue(1);
    fadeTransition.setDuration(javafx.util.Duration.millis(animationTime));
    fadeTransition.play();

    // Move the toast down out of view
    this.setTranslateY(100);

    // Move the toast up into view
    TranslateTransition translateTransition = new TranslateTransition();
    translateTransition.setNode(currenToast);
    translateTransition.setByY(-100);
    translateTransition.setDuration(javafx.util.Duration.millis(animationTime));
    translateTransition.setInterpolator(javafx.animation.Interpolator.EASE_OUT);
    translateTransition.play();
  }

  /**
   * Enqueues a toast.
   *
   * @param toast the toast to enqueue
   */
  public static void enqueue(Toast toast) {
    getInstance().toastQueue.add(toast);

    // If no toasts are currently being displayed, display the next one
    if (getInstance().currenToast == null) {
      getInstance().next();
    }
  }

}