From b6823aebfa6b4f7da2f0e50706be394b65014817 Mon Sep 17 00:00:00 2001 From: Rohan Sircar Date: Tue, 1 Sep 2020 20:26:12 +0530 Subject: [PATCH] Modularized code Moved all resource creation code to modules (thin cake pattern) Cleanup Added test actor running on fx thread --- src/main/resources/application.conf | 2 +- src/main/scala/nova/monadic_sfx/Main.scala | 143 +----------------- .../scala/nova/monadic_sfx/MainModule.scala | 7 + .../nova/monadic_sfx/actors/ActorModule.scala | 49 ++++++ .../nova/monadic_sfx/actors/TestActor.scala | 34 +++++ .../executors/ExecutorsModule.scala | 5 + .../monadic_sfx/executors/GUIExecutor.scala | 2 +- .../scala/nova/monadic_sfx/http/Backend.scala | 10 -- .../nova/monadic_sfx/http/HttpModule.scala | 28 ++++ .../http/requests/DummyRequest.scala | 26 +++- .../nova/monadic_sfx/modules/MainModule.scala | 79 ---------- .../nova/monadic_sfx/pages/HomePage.scala | 39 ----- .../nova/monadic_sfx/pages/LoginPage.scala | 102 ------------- .../scala/nova/monadic_sfx/ui/MyFxApp.scala | 90 +++++++++++ .../scala/nova/monadic_sfx/ui/UiModule.scala | 37 +++++ .../monadic_sfx/ui/screens/HomeScreen.scala | 45 ++++++ .../monadic_sfx/ui/screens/LoginScreen.scala | 108 +++++++++++++ .../nova/monadic_sfx/ui/screens/Screen.scala | 13 ++ .../scala/nova/monadic_sfx/util/Action.scala | 23 +++ 19 files changed, 467 insertions(+), 375 deletions(-) create mode 100644 src/main/scala/nova/monadic_sfx/MainModule.scala create mode 100644 src/main/scala/nova/monadic_sfx/actors/ActorModule.scala create mode 100644 src/main/scala/nova/monadic_sfx/actors/TestActor.scala create mode 100644 src/main/scala/nova/monadic_sfx/executors/ExecutorsModule.scala delete mode 100644 src/main/scala/nova/monadic_sfx/http/Backend.scala create mode 100644 src/main/scala/nova/monadic_sfx/http/HttpModule.scala delete mode 100644 src/main/scala/nova/monadic_sfx/modules/MainModule.scala delete mode 100644 src/main/scala/nova/monadic_sfx/pages/HomePage.scala delete mode 100644 src/main/scala/nova/monadic_sfx/pages/LoginPage.scala create mode 100644 src/main/scala/nova/monadic_sfx/ui/MyFxApp.scala create mode 100644 src/main/scala/nova/monadic_sfx/ui/UiModule.scala create mode 100644 src/main/scala/nova/monadic_sfx/ui/screens/HomeScreen.scala create mode 100644 src/main/scala/nova/monadic_sfx/ui/screens/LoginScreen.scala create mode 100644 src/main/scala/nova/monadic_sfx/ui/screens/Screen.scala create mode 100644 src/main/scala/nova/monadic_sfx/util/Action.scala diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index a368af4..929257c 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -1,5 +1,5 @@ javafx-dispatcher { type = "Dispatcher" - executor = "akka.dispatch.gui.JavaFXEventThreadExecutorServiceConfigurator" + executor = "nova.monadic_sfx.executors.JavaFXEventThreadExecutorServiceConfigurator" throughput = 1 } \ No newline at end of file diff --git a/src/main/scala/nova/monadic_sfx/Main.scala b/src/main/scala/nova/monadic_sfx/Main.scala index b8372eb..1ec02ea 100644 --- a/src/main/scala/nova/monadic_sfx/Main.scala +++ b/src/main/scala/nova/monadic_sfx/Main.scala @@ -1,60 +1,22 @@ package nova.monadic_sfx -import scalafx.application.JFXApp -import scalafx.application.JFXApp.PrimaryStage import monix.eval.Task -import monix.execution.Scheduler // import sttp.client.asynchttpclient.monix.AsyncHttpClientMonixBackend // import sttp.client._ // import sttp.client.circe._ // import io.circe.generic.auto._ -import scala.util.Failure -import scala.util.Success -import akka.{actor => classic} import nova.monadic_sfx.executors._ import cats.effect.Resource -import nova.monadic_sfx.models._ -import nova.monadic_sfx.ui.DefaultUI -import nova.monadic_sfx.http.Backend -import nova.monadic_sfx.modules.MainModule -import scalafx.stage.Stage -import scalafx.scene.layout.FlowPane -import nova.monadic_sfx.pages.LoginPage -import scala.concurrent.duration._ import sttp.client.asynchttpclient.monix.AsyncHttpClientMonixBackend -import scalafx.Includes._ -import scala.concurrent.Await -import akka.actor.typed.scaladsl.adapter._ -import scalafx.application.Platform import io.odin.syntax._ -// import io.odin._ import io.odin.monix._ -import nova.monadic_sfx.pages.HomePage -import monix.execution.Callback import monix.eval.TaskApp import cats.effect.ExitCode -import scalafx.scene.layout.HBox -import scalafx.scene.Scene import cats.implicits._ -import cats.effect.Clock -import cats.effect.Sync -object Main extends TaskApp { +object Main extends MainModule with TaskApp { - // lazy val schedulers: Schedulers = new Schedulers() - // override implicit def scheduler: Scheduler = schedulers.fx override def run(args: List[String]): Task[ExitCode] = { - // val logger = consoleLogger().withAsyncUnsafe() - - // lazy val backendTask = AsyncHttpClientMonixBackend() - // lazy val actorSystemTask = Task { - // classic.ActorSystem( - // name = "FXActorSystem" - // ) - // } - - // implicit lazy val FSync = Sync[Task] - // implicit lazy val FClock = Clock[Task] // val startTime = Task.clock // .monotonic(scala.concurrent.duration.MILLISECONDS) // .map(Duration.fromNanos(_)) @@ -62,108 +24,13 @@ object Main extends TaskApp { // clock <- Resource.liftF(Task(Task.clock)) logger <- consoleLogger().withAsync() backend <- AsyncHttpClientMonixBackend.resource() - actorSystem <- - Resource.make(logger.info("Creating Actor System") >> Task { - classic.ActorSystem( - name = "FXActorSystem" - ) - })(sys => - logger.info("Shutting down actor system") >> Task.fromFuture( - sys.terminate() - ) >> logger.info("Actor System terminated") - ) - // _ <- Resource.liftF(logger.info(Thread.currentThread().getName())) - fxApp <- Resource.make(logger.info("Creating FX Application") >> Task { - // val appStage = makePrimaryStage(backend, actorSystem) - // stage = appStage - // val stage2 = new PrimaryStage { - // scene = new Scene(new HBox()) - // } - val app: JFXApp = new JFXApp { - lazy val schedulers: Schedulers = new Schedulers() - - implicit lazy val defaultScheduler: Scheduler = schedulers.fx - - val application = - for { - appStage <- logger.info("Inside JFX application stage") >> Task( - makePrimaryStage(backend, actorSystem) - ) - _ <- Task { stage = appStage } - _ <- Task.sleep(2.seconds) - loginScene <- LoginPage(appStage, backend, actorSystem) - _ <- Task { - // appStage.maximized = true - appStage.height = 800 - appStage.width = 800 - appStage - .scene() - .setRoot( - loginScene - ) - } - } yield () - application.timed.runAsync( - new Callback[Throwable, (FiniteDuration, Unit)] { - - override def onSuccess(value: (FiniteDuration, Unit)): Unit = { - val (duration, _) = value - println( - s"Application started successfully in ${duration.toSeconds} seconds" - ) - } - - override def onError(e: Throwable): Unit = { - println("Application start failed. Reason -") - e.printStackTrace() - } - - } - ) - - override def stopApp() = { - Platform.exit() - // System.exit(0) - } - } - app - })(app => logger.info("Stopping FX Application") >> Task(app.stopApp())) - // _ <- Resource.liftF(Task.unit.executeOn(defaultScheduler)) - _ <- Resource.liftF(logger.info(Thread.currentThread().getName())) + actorSystem <- actorResource(logger) + reqs <- Resource.liftF(Task(requesters(backend, actorSystem))) + schedulers <- Resource.liftF(Task(new Schedulers())) + fxApp <- fxAppResource(logger, backend, actorSystem, reqs, schedulers) } yield (fxApp) -// >> logger.info("test") appResource .use(fxApp => Task(fxApp.main(args.toArray))) .as(ExitCode.Success) } - - def makePrimaryStage( - backend: AppTypes.HttpBackend, - actorSystem: classic.ActorSystem - ) = { - new PrimaryStage { - scene = new DefaultUI().scene - // onCloseRequest = () => { - // val f2 = actorSystem.terminate() - // val f1 = backend.close().runToFuture - - // println("Closing backend") - // Await.result(f1, 3.seconds) - // println("Closing actor system") - // println(Thread.currentThread().getName()) - // Await.result(f2, 3.seconds) - // println("Actor system closed") - // Platform.exit() - // System.exit(0) - // } - } - } } - -// class MyFxApp extends javafx.application.Application { - -// override def start(stage: javafx.stage.Stage): Unit = { -// stage.show() -// } - -// } diff --git a/src/main/scala/nova/monadic_sfx/MainModule.scala b/src/main/scala/nova/monadic_sfx/MainModule.scala new file mode 100644 index 0000000..93b5cca --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/MainModule.scala @@ -0,0 +1,7 @@ +package nova.monadic_sfx + +import nova.monadic_sfx.actors.ActorModule +import nova.monadic_sfx.ui.UiModule +import nova.monadic_sfx.http.HttpModule + +trait MainModule extends ActorModule with UiModule with HttpModule diff --git a/src/main/scala/nova/monadic_sfx/actors/ActorModule.scala b/src/main/scala/nova/monadic_sfx/actors/ActorModule.scala new file mode 100644 index 0000000..091b036 --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/actors/ActorModule.scala @@ -0,0 +1,49 @@ +package nova.monadic_sfx.actors + +import io.odin.Logger +import monix.eval.Task +import cats.effect.Resource +import akka.actor._ +import akka.actor.typed.scaladsl.adapter._ +import akka.actor.typed.scaladsl.Behaviors +import com.softwaremill.macwire._ +import akka.actor.typed.Behavior +import akka.actor.typed.DispatcherSelector + +trait ActorModule { + def actorResource(logger: Logger[Task]): Resource[Task, ActorSystem] = + Resource.make(logger.info("Creating Actor System") >> Task { + ActorSystem( + name = "FXActorSystem" + ) + })(sys => + logger.info("Shutting down actor system") >> Task.fromFuture( + sys.terminate() + ) >> logger.info("Actor System terminated") + ) + + def testActor( + system: ActorSystem + ): akka.actor.typed.ActorRef[Counter.Command] = { + val behaviour: Behavior[Counter.Command] = + Behaviors.setup(context => wire[Counter]) + system.spawn( + behaviour, + "CounterActor", + DispatcherSelector.fromConfig("javafx-dispatcher") + ) + } + + def testActorL( + system: ActorSystem + ): Task[akka.actor.typed.ActorRef[Counter.Command]] = + Task { + val behaviour: Behavior[Counter.Command] = + Behaviors.setup(context => wire[Counter]) + system.spawn( + behaviour, + "CounterActor", + DispatcherSelector.fromConfig("javafx-dispatcher") + ) + } +} diff --git a/src/main/scala/nova/monadic_sfx/actors/TestActor.scala b/src/main/scala/nova/monadic_sfx/actors/TestActor.scala new file mode 100644 index 0000000..e28ad2a --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/actors/TestActor.scala @@ -0,0 +1,34 @@ +package nova.monadic_sfx.actors + +import akka.actor.typed._ +import akka.actor.typed.scaladsl._ + +object Counter { + sealed trait Command + case object Increment extends Command + final case class GetValue(replyTo: ActorRef[Value]) extends Command + final case class Value(n: Int) + + def apply(): Behavior[Command] = { + Behaviors.setup(context => new Counter(context)) + } +} + +class Counter(context: ActorContext[Counter.Command]) + extends AbstractBehavior[Counter.Command](context) { + import Counter._ + + private var n = 0 + + override def onMessage(msg: Command): Behavior[Counter.Command] = { + msg match { + case Increment => + n += 1 + context.log.debug("Incremented counter to [{}]", n) + this + case GetValue(replyTo) => + replyTo ! Value(n) + Behaviors.stopped + } + } +} diff --git a/src/main/scala/nova/monadic_sfx/executors/ExecutorsModule.scala b/src/main/scala/nova/monadic_sfx/executors/ExecutorsModule.scala new file mode 100644 index 0000000..e672377 --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/executors/ExecutorsModule.scala @@ -0,0 +1,5 @@ +package nova.monadic_sfx.executors + +trait ExecutorsModule { + lazy val schedulers = new Schedulers() +} diff --git a/src/main/scala/nova/monadic_sfx/executors/GUIExecutor.scala b/src/main/scala/nova/monadic_sfx/executors/GUIExecutor.scala index 735762e..98085ac 100644 --- a/src/main/scala/nova/monadic_sfx/executors/GUIExecutor.scala +++ b/src/main/scala/nova/monadic_sfx/executors/GUIExecutor.scala @@ -20,7 +20,7 @@ import scala.concurrent.ExecutionContext import java.util.concurrent.Executor // First we wrap invokeLater/runLater as an ExecutorService -abstract class GUIExecutorService extends AbstractExecutorService { +trait GUIExecutorService extends AbstractExecutorService { def execute(command: Runnable): Unit def shutdown(): Unit = () diff --git a/src/main/scala/nova/monadic_sfx/http/Backend.scala b/src/main/scala/nova/monadic_sfx/http/Backend.scala deleted file mode 100644 index 158c578..0000000 --- a/src/main/scala/nova/monadic_sfx/http/Backend.scala +++ /dev/null @@ -1,10 +0,0 @@ -package nova.monadic_sfx.http - -import sttp.client.asynchttpclient.monix.AsyncHttpClientMonixBackend -import nova.monadic_sfx.AppTypes - -object Backend { - val backend = AsyncHttpClientMonixBackend - .resource() - def apply() = { backend } -} diff --git a/src/main/scala/nova/monadic_sfx/http/HttpModule.scala b/src/main/scala/nova/monadic_sfx/http/HttpModule.scala new file mode 100644 index 0000000..850d2aa --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/http/HttpModule.scala @@ -0,0 +1,28 @@ +package nova.monadic_sfx.http + +import nova.monadic_sfx.http.requests.DummyRequest +import nova.monadic_sfx.AppTypes + +trait HttpModule { + def requesters( + backend: AppTypes.HttpBackend, + system: akka.actor.ActorSystem + ): Requesters = { + import com.softwaremill.macwire._ + val dummyRequester = wire[DummyRequest] + wire[Requesters] + } +} + +class Requesters(val dummyRequester: DummyRequest) + +// object Requesters { +// def apply( +// backend: AppTypes.HttpBackend, +// system: akka.actor.ActorSystem +// ): Requesters = { +// import com.softwaremill.macwire._ +// val dummyRequester = wire[DummyRequest] +// wire[Requesters] +// } +// } diff --git a/src/main/scala/nova/monadic_sfx/http/requests/DummyRequest.scala b/src/main/scala/nova/monadic_sfx/http/requests/DummyRequest.scala index 132411f..f3fa76b 100644 --- a/src/main/scala/nova/monadic_sfx/http/requests/DummyRequest.scala +++ b/src/main/scala/nova/monadic_sfx/http/requests/DummyRequest.scala @@ -8,20 +8,36 @@ import sttp.client._ import sttp.client.circe._ import io.circe.generic.auto._ import nova.monadic_sfx.models._ +import cats.data.EitherT class DummyRequest(backend: HttpBackend) extends AppTypes { private implicit val _backend = backend def send() = { Task - .suspend( - (for { + .suspend { + for { req <- basicRequest .get(uri"https://httpbin.org/get") .response(asJson[HttpBinResponse]) .send() - } yield println(req)) >> - Task(println(Thread.currentThread().getName())) - ) + } yield (req) + } + + } + + def test() = { + for { + res <- send() + res3 <- Task { res.body } + res2 <- Task { + res3.fold( + err => { + err.toString() + }, + value => value.toString() + ) + } + } yield (res3) } } diff --git a/src/main/scala/nova/monadic_sfx/modules/MainModule.scala b/src/main/scala/nova/monadic_sfx/modules/MainModule.scala deleted file mode 100644 index 182b7fc..0000000 --- a/src/main/scala/nova/monadic_sfx/modules/MainModule.scala +++ /dev/null @@ -1,79 +0,0 @@ -package nova.monadic_sfx.modules - -import nova.monadic_sfx.executors.Schedulers -import monix.execution.Scheduler -import nova.monadic_sfx.http.requests.DummyRequest -import monix.eval.Task -import akka.{actor => classic} -import cats.effect.Resource -import nova.monadic_sfx.AppTypes - -trait MainModule { - import com.softwaremill.macwire._ -// val schedulers: Schedulers = new Schedulers() - -// implicit val defaultScheduler: Scheduler = schedulers.fx - -// val program = -// for { -// backend <- Backend() -// fxActorSystem <- -// Resource -// .make(Task { -// classic.ActorSystem( -// name = "FXActorSystem" -// ) -// })(sys => Task(sys.terminate())) - -// } yield { -// val dummyRequester = wire[DummyRequest] -// dummyRequester.send() -// } - -// val program = Backend().use(backend => -// Resource -// .make(Task { -// classic.ActorSystem( -// name = "FXActorSystem" -// ) -// })(sys => Task(sys.terminate())) -// .use { implicit system => -// // system.spa -// // system.typed -// // val javaFxActor = system.actorOf( -// // Props[JavaFxActor]().withDispatcher("javafx-dispatcher"), -// // "javaFxActor" -// // ) -// // val swingActor = system.actorOf( -// // Props[SwingActor]().withDispatcher("swing-dispatcher"), -// // "swingActor" -// // ) -// Task(new DummyRequest(backend)) >> -// Task.unit -// } -// ) - def schedulers: Schedulers - - def defaultScheduler: Scheduler - - def backendTask: Task[AppTypes.HttpBackend] - def actorSystemTask: Task[classic.ActorSystem] - - def deps = - for { - backend <- backendTask - actorSystem <- actorSystemTask - dummyRequesterTask <- Task { - wireDeps(backend, actorSystem) - } - } yield dummyRequesterTask - - def wireDeps( - backend: AppTypes.HttpBackend, - system: classic.ActorSystem - ): DummyRequest = { - wire[DummyRequest] - // new DummyRequest(backend) - } - -} diff --git a/src/main/scala/nova/monadic_sfx/pages/HomePage.scala b/src/main/scala/nova/monadic_sfx/pages/HomePage.scala deleted file mode 100644 index ccc8213..0000000 --- a/src/main/scala/nova/monadic_sfx/pages/HomePage.scala +++ /dev/null @@ -1,39 +0,0 @@ -package nova.monadic_sfx.pages -import nova.monadic_sfx.AppTypes -import scalafx.scene.control.TextField -import scalafx.scene.control._ -import scalafx.scene.layout.VBox -import scalafx.scene.Node -import scalafx.Includes._ -import scalafx.scene.layout.HBox -import scalafx.scene.text.Text -import scalafx.scene.Parent -import scalafx.application.JFXApp.PrimaryStage - -class HomePage( - backend: AppTypes.HttpBackend, - system: akka.actor.ActorSystem, - onLogout: () => Unit -) { - private lazy val root = new HBox { - children = List( - new Text { - text = "hello" - }, - new Button { - text = "logout" - onAction = () => onLogout() - } - ) - } - def render = root -} - -object HomePage { - def apply( - backend: AppTypes.HttpBackend, - system: akka.actor.ActorSystem, - onLogout: () => Unit - ): Parent = - new HomePage(backend, system, onLogout).render -} diff --git a/src/main/scala/nova/monadic_sfx/pages/LoginPage.scala b/src/main/scala/nova/monadic_sfx/pages/LoginPage.scala deleted file mode 100644 index cb76bfc..0000000 --- a/src/main/scala/nova/monadic_sfx/pages/LoginPage.scala +++ /dev/null @@ -1,102 +0,0 @@ -package nova.monadic_sfx.pages -import nova.monadic_sfx.AppTypes -import scalafx.scene.control.TextField -import scalafx.scene.control._ -import scalafx.scene.layout.VBox -import scalafx.scene.Node -import scalafx.Includes._ -import scalafx.scene.Parent -import scalafx.application.JFXApp.PrimaryStage -import nova.monadic_sfx.http.requests.DummyRequest -import monix.eval.Task -import monix.execution.Scheduler -// import io.odin.syntax._ -// import _root_.monix.eval.Task -// import io.odin.monix._ -// import javafx.beans.property.ObjectProperty -// import javafx.event.{ActionEvent, EventHandler} -class LoginPage( - appStage: PrimaryStage, - backend: AppTypes.HttpBackend, - system: akka.actor.ActorSystem -) { - - val dummyRequester = new DummyRequest(backend) - - //pure function callbacks, but with side effects still - private def onLogout(stage: PrimaryStage) = - for { - _ <- Task { println("logging out") } - root <- render - _ <- Task(stage.scene().setRoot(root)) - } yield () - - private def onLogin(stage: PrimaryStage) = - Task.deferAction { implicit Scheduler => - for { - _ <- Task(println("logging in")) - root <- Task { - stage - .scene() - .setRoot( - HomePage( - backend, - system, - () => runFxTask(onLogout(appStage)) - ) - ) - } - } yield () - } - - private lazy val root = Task.deferAction(implicit scheduler => - Task { - new VBox { - children = Seq( - new Label { - text = "username" - }, - new TextField(), - new Label { - text = "password" - }, - new TextField(), - new Button { - text = "Login" - onAction = () => - runFxTask { - Task - .parZip2( - dummyRequester - .send(), - // .executeOn(Scheduler.global) - onLogin(appStage) - ) - } - } - ) - } - } - ) - def render: Task[Parent] = root - - /** - * Implicitly runs monix task as fire and forget. \ - * For use in ScalaFX callbacks. - * - * @param task - * @param s - */ - def runFxTask[T](task: => Task[T])(implicit s: Scheduler) = { - task.runAsyncAndForget - } - -} - -object LoginPage { - def apply( - appStage: PrimaryStage, - backend: AppTypes.HttpBackend, - system: akka.actor.ActorSystem - ) = new LoginPage(appStage, backend, system).render -} diff --git a/src/main/scala/nova/monadic_sfx/ui/MyFxApp.scala b/src/main/scala/nova/monadic_sfx/ui/MyFxApp.scala new file mode 100644 index 0000000..a5ce284 --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/ui/MyFxApp.scala @@ -0,0 +1,90 @@ +package nova.monadic_sfx.ui + +import scalafx.application.JFXApp +import nova.monadic_sfx.executors.Schedulers +import monix.execution.Scheduler +import monix.eval.Task +import nova.monadic_sfx.screens.LoginScreen +import nova.monadic_sfx.AppTypes +import scalafx.application.Platform +import scala.concurrent.duration._ +import io.odin.Logger +import monix.execution.Callback +import com.softwaremill.macwire._ +import nova.monadic_sfx.http.Requesters + +import akka.actor._ +import akka.actor.typed.Behavior +import akka.actor.typed.scaladsl.adapter._ +import akka.actor.typed.scaladsl.Behaviors +import nova.monadic_sfx.actors.Counter +import akka.actor.typed.DispatcherSelector + +class MyFxApp( + logger: Logger[Task], + backend: AppTypes.HttpBackend, + actorSystem: akka.actor.ActorSystem, + requesters: Requesters, + schedulers: Schedulers +) extends JFXApp { + + implicit lazy val defaultScheduler: Scheduler = schedulers.fx + + lazy val application = + for { + appStage <- Task( + UiModule.makePrimaryStage(backend, actorSystem) + ) + // _ <- Task { + // val counterActor = testActor(actorSystem) + // counterActor ! (Counter.Increment) + // } + _ <- Task { stage = appStage } + _ <- Task.sleep(2.seconds) + loginScene <- wire[LoginScreen].render + _ <- Task { + // appStage.maximized = true + appStage.height = 800 + appStage.width = 800 + appStage + .scene() + .setRoot( + loginScene + ) + } + } yield () + + def testActor( + system: ActorSystem + ): akka.actor.typed.ActorRef[Counter.Command] = { + val behaviour: Behavior[Counter.Command] = + Behaviors.setup(context => wire[Counter]) + system.spawn( + behaviour, + "CounterActor", + DispatcherSelector.fromConfig("javafx-dispatcher") + ) + } + + application.timed.runAsync( + new Callback[Throwable, (FiniteDuration, Unit)] { + + override def onSuccess(value: (FiniteDuration, Unit)): Unit = { + val (duration, _) = value + println( + s"Application started successfully in ${duration.toSeconds} seconds" + ) + } + + override def onError(e: Throwable): Unit = { + println("Application start failed. Reason -") + e.printStackTrace() + } + + } + ) + + override def stopApp() = { + Platform.exit() + } +} diff --git a/src/main/scala/nova/monadic_sfx/ui/UiModule.scala b/src/main/scala/nova/monadic_sfx/ui/UiModule.scala new file mode 100644 index 0000000..486e7f2 --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/ui/UiModule.scala @@ -0,0 +1,37 @@ +package nova.monadic_sfx.ui + +import scalafx.application.JFXApp +import monix.eval.Task +import nova.monadic_sfx.AppTypes +import scalafx.application.JFXApp.PrimaryStage +import io.odin.Logger +import cats.effect.Resource +import com.softwaremill.macwire._ +import nova.monadic_sfx.http.Requesters +import nova.monadic_sfx.executors.Schedulers + +trait UiModule { + def fxAppResource( + logger: Logger[Task], + backend: AppTypes.HttpBackend, + actorSystem: akka.actor.ActorSystem, + requesters: Requesters, + schedulers: Schedulers + ): Resource[Task, JFXApp] = + Resource.make(logger.info("Creating FX Application") >> Task { + val app: JFXApp = wire[MyFxApp] + app + })(app => logger.info("Stopping FX Application") >> Task(app.stopApp())) + +} + +object UiModule { + def makePrimaryStage( + backend: AppTypes.HttpBackend, + actorSystem: akka.actor.ActorSystem + ) = { + new PrimaryStage { + scene = new DefaultUI().scene + } + } +} diff --git a/src/main/scala/nova/monadic_sfx/ui/screens/HomeScreen.scala b/src/main/scala/nova/monadic_sfx/ui/screens/HomeScreen.scala new file mode 100644 index 0000000..72862b7 --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/ui/screens/HomeScreen.scala @@ -0,0 +1,45 @@ +package nova.monadic_sfx.screens +import nova.monadic_sfx.AppTypes +import scalafx.scene.control.TextField +import scalafx.scene.control._ +import scalafx.scene.layout.VBox +import scalafx.scene.Node +import scalafx.Includes._ +import scalafx.scene.layout.HBox +import scalafx.scene.text.Text +import scalafx.scene.Parent +import scalafx.application.JFXApp.PrimaryStage +import monix.eval.Task +import nova.monadic_sfx.util.Action + +class HomeScreen( + backend: AppTypes.HttpBackend, + system: akka.actor.ActorSystem, + onLogout: () => Task[Unit] +) { + private lazy val root = Task.deferAction { implicit s => + Task { + new HBox { + children = List( + new Text { + text = "hello" + }, + new Button { + text = "logout" + onAction = () => Action.asyncT(onLogout()) + } + ) + } + } + } + def render = root +} + +object HomeScreen { + def apply( + backend: AppTypes.HttpBackend, + system: akka.actor.ActorSystem, + onLogout: () => Task[Unit] + ): Task[Parent] = + new HomeScreen(backend, system, onLogout).render +} diff --git a/src/main/scala/nova/monadic_sfx/ui/screens/LoginScreen.scala b/src/main/scala/nova/monadic_sfx/ui/screens/LoginScreen.scala new file mode 100644 index 0000000..d505ad0 --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/ui/screens/LoginScreen.scala @@ -0,0 +1,108 @@ +package nova.monadic_sfx.screens +import nova.monadic_sfx.AppTypes +import scalafx.scene.control.TextField +import scalafx.scene.control._ +import scalafx.scene.layout.VBox +import scalafx.scene.Node +import scalafx.Includes._ +import scalafx.scene.Parent +import scalafx.application.JFXApp.PrimaryStage +import nova.monadic_sfx.http.requests.DummyRequest +import monix.eval.Task +import monix.execution.Scheduler +import cats.effect.Effect +import cats.effect.implicits._ +import nova.monadic_sfx.util.Action +import io.odin.Logger +import nova.monadic_sfx.http.Requesters +import sttp.client.Response +import nova.monadic_sfx.models.HttpBinResponse +import sttp.client.ResponseError +import nova.monadic_sfx.executors.Schedulers +import nova.monadic_sfx.ui.screens.Screen +// import io.odin.syntax._ +// import _root_.monix.eval.Task +// import io.odin.monix._ +// import javafx.beans.property.ObjectProperty +// import javafx.event.{ActionEvent, EventHandler} +class LoginScreen( + override protected val appStage: PrimaryStage, + logger: Logger[Task], + backend: AppTypes.HttpBackend, + system: akka.actor.ActorSystem, + requesters: Requesters, + schedulers: Schedulers +) extends Screen { + val dummyRequester: DummyRequest = requesters.dummyRequester + //pure function callbacks, but with side effects still + + // lazy val hs = { + // import com.softwaremill.macwire._ + // lazy val action = () => onLogout(appStage) + // wire[HomeScreen] + // } + + private def onLogout(stage: PrimaryStage) = + for { + _ <- logger.info("Logging out") + root <- render + _ <- changeRootL(root) + } yield () + + private def onLogin(stage: PrimaryStage) = + for { + _ <- logger.info("Logging in") + homeScreen <- HomeScreen( + backend, + system, + () => onLogout(appStage) + ) + _ <- Task { stage.scene().setRoot(homeScreen) } + } yield () + + private lazy val root = Task.deferAction(implicit scheduler => + Task { + new VBox { + children = Seq( + new Label { + text = "username" + }, + new TextField(), + new Label { + text = "password" + }, + new TextField(), + new Button { + text = "Login" + onAction = () => + Action.asyncT { + Task + .parSequence( + List( + testRequest, + // .executeOn(Scheduler.global) + onLogin(appStage) + ) + ) + } + } + ) + } + } + ) + def render: Task[Parent] = root + + val testRequest = for { + res <- dummyRequester.send() + _ <- logger.info(res.body.toString()) + } yield () + +} + +// object LoginScreen { +// def apply( +// appStage: PrimaryStage, +// backend: AppTypes.HttpBackend, +// system: akka.actor.ActorSystem +// ) = new LoginScreen(appStage, backend, system).render +// } diff --git a/src/main/scala/nova/monadic_sfx/ui/screens/Screen.scala b/src/main/scala/nova/monadic_sfx/ui/screens/Screen.scala new file mode 100644 index 0000000..aa09a6e --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/ui/screens/Screen.scala @@ -0,0 +1,13 @@ +package nova.monadic_sfx.ui.screens + +import scalafx.application.JFXApp.PrimaryStage +import scalafx.scene.Parent +import monix.eval.Task + +trait Screen { + protected def appStage: PrimaryStage + def changeRoot(root: Parent): Unit = { + appStage.scene().setRoot(root) + } + def changeRootL(root: Parent): Task[Unit] = Task(changeRoot(root)) +} diff --git a/src/main/scala/nova/monadic_sfx/util/Action.scala b/src/main/scala/nova/monadic_sfx/util/Action.scala new file mode 100644 index 0000000..7367265 --- /dev/null +++ b/src/main/scala/nova/monadic_sfx/util/Action.scala @@ -0,0 +1,23 @@ +package nova.monadic_sfx.util + +import monix.eval.Task +import monix.execution.Scheduler +import cats.effect.Effect +import cats.effect.implicits._ + +object Action { + + /** + * Implicitly runs monix task as fire and forget. \ + * For use in ScalaFX callbacks. + * + * @param task + * @param s + */ + def asyncT[T](task: => Task[T])(implicit s: Scheduler): Unit = { + task.runAsyncAndForget + } + + def asyncF[F[_]: Effect, T](cb: => F[T]): Unit = + cb.toIO.unsafeRunAsyncAndForget() +}