You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
412 lines
14 KiB
412 lines
14 KiB
package wow.doge.chatto.controller
|
|
|
|
import javafx.fxml.FXML
|
|
import javafx.scene.control.Label
|
|
import javafx.scene.control.Button
|
|
import javafx.scene.layout.FlowPane
|
|
import javafx.scene.control.TextArea
|
|
import javafx.scene.control.ListView
|
|
import javafx.scene.layout.HBox
|
|
import javafx.scene.layout.VBox
|
|
import scalafx.Includes._
|
|
import wow.doge.chatto.control.UserBox
|
|
import javafx.application.Platform
|
|
import javax.inject.Inject
|
|
import org.scalafx.extras._
|
|
import wow.doge.chatto.messagebuble.BubbledMDFXNode
|
|
import wow.doge.chatto.service.UserService
|
|
import scala.concurrent.ExecutionContext
|
|
import scala.concurrent.ExecutionContext.Implicits.global
|
|
import com.typesafe.scalalogging.LazyLogging
|
|
import com.sfxcode.sapphire.core.value.FXBean
|
|
import wow.doge.chatto.AppDataHandler
|
|
import com.jfoenix.controls.JFXListView
|
|
import scala.async.Async.{async, await}
|
|
import javafx.scene.paint.Color
|
|
import scalafx.collections.ObservableBuffer
|
|
import javafx.beans.property.SimpleListProperty
|
|
import wow.doge.chatto.control.UserBox2
|
|
import javafx.beans.value.ChangeListener
|
|
import com.sfxcode.sapphire.core.value.KeyBindings
|
|
import com.sfxcode.sapphire.core.value.FXBeanAdapter
|
|
import scalafx.collections.ObservableMap
|
|
import com.sfxcode.sapphire.core.value.BeanConversions
|
|
import javafx.util.converter.DateStringConverter
|
|
import javafx.beans.binding.Bindings
|
|
import wow.doge.chatto.service.ActiveUser
|
|
import scala.collection.mutable
|
|
import scala.collection.concurrent.TrieMap
|
|
import wow.doge.chatto.messagebuble.BubbleSpec
|
|
import javafx.scene.layout.Background
|
|
import javafx.scene.layout.BackgroundFill
|
|
import javafx.geometry.Pos
|
|
import scalafx.beans.property.ReadOnlyBufferProperty
|
|
import scalafx.beans.property.ReadOnlyBufferWrapper
|
|
import javafx.beans.property.ReadOnlyListProperty
|
|
import scalafx.beans.property.BufferProperty
|
|
import javafx.collections.FXCollections
|
|
import com.sandec.mdfx.MDFXNode
|
|
import javafx.scene.layout.BorderPane
|
|
import javafx.scene.layout.Priority
|
|
import net.synedra.validatorfx.Validator
|
|
import wow.doge.chatto.control.JFXSmoothScroll
|
|
import javafx.scene.control.ContextMenu
|
|
import javafx.scene.control.MenuItem
|
|
import javafx.scene.input.Clipboard
|
|
import javafx.scene.input.ClipboardContent
|
|
import scalafx.scene.input.KeyCodeCombination
|
|
import scalafx.scene.input.KeyCode
|
|
import scalafx.scene.input.KeyCombination
|
|
import javafx.scene.input.DataFormat
|
|
import wow.doge.chatto.control.MessageBox
|
|
import javafx.scene.control.SelectionMode
|
|
import scalafx.beans.property.BooleanProperty
|
|
|
|
class ChatController @Inject() (
|
|
userService: UserService,
|
|
appDataHandler: AppDataHandler
|
|
) extends AbstractViewController
|
|
with LazyLogging {
|
|
|
|
// @FXML private var label: Label = _
|
|
@FXML var chatMainPane: BorderPane = _
|
|
// @FXML private var flowPane: FlowPane = _
|
|
@FXML private var submitButton: Button = _
|
|
@FXML private var logoutButton: Button = _
|
|
// @FXML private var chatTextArea: TextArea = _
|
|
@FXML private var chatInput: TextArea = _
|
|
@FXML private var usersVBox: VBox = _
|
|
@FXML var usersListView: JFXListView[UserBox2] = _
|
|
@FXML var chatListView: JFXListView[MessageBox] = _
|
|
@FXML private var curUsr: Label = _
|
|
@FXML private var lastActiveLabel: Label = _
|
|
@FXML private var isOnlineLabel: Label = _
|
|
@FXML private var selectedUserBox: HBox = _
|
|
|
|
private val usersBuffer = ObservableBuffer.empty[UserBox2]
|
|
private val usersListProperty = BufferProperty(usersBuffer)
|
|
|
|
/**
|
|
* Readonly property wrapping an unmodifiable list.
|
|
* Synchronized with the internal users list property.
|
|
* Attemping to modify the internal list will throw an exception
|
|
*/
|
|
val usersListROProp: ReadOnlyListProperty[UserBox2] = BufferProperty(
|
|
FXCollections.unmodifiableObservableList(usersListProperty())
|
|
)
|
|
|
|
private val chatDataStore = TrieMap.empty[String, ChatDataProperty]
|
|
|
|
private lazy val chatDataAdapter = FXBeanAdapter[ChatData](this)
|
|
|
|
override def didGainVisibilityFirstTime(): Unit = {
|
|
super.didGainVisibilityFirstTime()
|
|
this.stage.resizable = true
|
|
chatMainPane.hgrow = Priority.ALWAYS
|
|
chatListView.selectionModel().selectionMode = SelectionMode.MULTIPLE
|
|
|
|
chatDataAdapter.set(FXBean(ChatData.empty))
|
|
|
|
Array(submitButton, chatInput).foreach(n => {
|
|
n.disableProperty() <== usersListView
|
|
.selectionModel()
|
|
.selectedItemProperty()
|
|
.isNull()
|
|
})
|
|
|
|
// curUserKeys.add("content", "${_self.data().content()}")
|
|
val chatDataAdapterKeys = KeyBindings()
|
|
chatDataAdapterKeys.add("currentUser", "${_self.userName()}")
|
|
// chatDataAdapterKeys.add(
|
|
// "lastActive",
|
|
// "${_self.lastActiveString()}"
|
|
// )
|
|
// chatDataAdapterKeys.add(
|
|
// "online",
|
|
// "${_self.onlineString()}"
|
|
// )
|
|
// curUserAdapter.addDateConverter()
|
|
// curUserAdapter.addBindings(curUserKeys)
|
|
chatDataAdapter.addBindings(chatDataAdapterKeys)
|
|
usersListView
|
|
.selectionModel()
|
|
.selectedItemProperty()
|
|
.addListener((_, _, newValue) => {
|
|
Option(newValue).foreach(nv => {
|
|
val maybeCDP = chatDataStore
|
|
.get(nv.username)
|
|
val chatDataBean = maybeCDP
|
|
.map(_.bean)
|
|
.getOrElse {
|
|
logger.error("Error null")
|
|
FXBean(ChatData.empty)
|
|
}
|
|
chatDataAdapter.set(chatDataBean)
|
|
maybeCDP.foreach(cdp => {
|
|
// lastActiveLabel.text <== cdp.lastActive
|
|
isOnlineLabel.text <== cdp.isActive.asString()
|
|
chatListView.items <== cdp.messageBubbles
|
|
})
|
|
})
|
|
})
|
|
|
|
val copyMessageMenuItem = new MenuItem("Copy Message")
|
|
copyMessageMenuItem.accelerator =
|
|
new KeyCodeCombination(KeyCode.C, KeyCombination.ControlDown)
|
|
copyMessageMenuItem.onAction = _ => {
|
|
val content = new ClipboardContent()
|
|
val x = chatListView.getSelectionModel().getSelectedItems().map(_.message)
|
|
// chatListView.selectionModel().selectedItem().message
|
|
Option(x).foreach(message => {
|
|
content.putString(message.mkString("\n"))
|
|
Clipboard.getSystemClipboard().setContent(content)
|
|
})
|
|
// val message = cdp.messageList(selectedIndex)
|
|
|
|
// val maybeCDP = chatDataStore.get(chatDataAdapter.get.bean.userName)
|
|
// maybeCDP.foreach(cdp => {
|
|
// // val message = Option(cdp.messageList().get(selectedIndex)).toRight {
|
|
// // "Unexpected error - message not found"
|
|
// // }
|
|
// // message.map(msg => {
|
|
// // // content.putString(msg)
|
|
// // // clipboard.setContent(content)
|
|
// // // val content = clipboard.content
|
|
// // // content.putString(msg)
|
|
// // // clipboard.content = content
|
|
// // clipboard.putString(msg)
|
|
// // })
|
|
// // message.left.map(err => logger.error(err))
|
|
// })
|
|
}
|
|
val chatListMenu = new ContextMenu()
|
|
|
|
chatListMenu.items += copyMessageMenuItem
|
|
chatListView.contextMenu = chatListMenu
|
|
|
|
usersListView.items <== usersListProperty
|
|
|
|
val validator = new Validator()
|
|
|
|
submitButton.disable <== validator.containsErrorsProperty()
|
|
|
|
submitButton.onAction = (e) => {
|
|
// val msgBox = ChatDataProperty.createMdMessageBox2(chatInput.text())
|
|
// chatListView.items() += msgBox
|
|
if (!chatInput.text().equals("") &&
|
|
!chatInput.text().equals(" ") &&
|
|
!chatInput.text().equals("\n")) {
|
|
val maybeCDP = chatDataStore.get(chatDataAdapter.get.bean.userName)
|
|
|
|
maybeCDP.foreach(cdp => {
|
|
cdp.messageBubbles += ChatDataProperty.createMdMessageBox3(
|
|
appDataHandler.appData.credentials.username,
|
|
cdp.username(),
|
|
chatInput.text()
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
validator
|
|
.createCheck()
|
|
.withMethod(c => {
|
|
val userName = chatInput.text()
|
|
if (!userName.toLowerCase().equals(userName)) {
|
|
c.error("Please use only lowercase letters.")
|
|
}
|
|
})
|
|
.dependsOn("chatInput", chatInput.text)
|
|
.decorates(chatInput)
|
|
.immediate()
|
|
}
|
|
|
|
override def didGainVisibility(): Unit = {
|
|
super.didGainVisibility()
|
|
chatInput.requestFocus()
|
|
|
|
async {
|
|
val willBeActiveUsers = userService
|
|
.getActiveUsers(appDataHandler.appData.credentials)
|
|
.map(_.body)
|
|
val maybeActiveUsers = await(willBeActiveUsers)
|
|
|
|
logger.debug(s"Received Users: $maybeActiveUsers")
|
|
|
|
val maybeUserBoxes = maybeActiveUsers.map(users => {
|
|
users.map(user => {
|
|
val chatData =
|
|
ChatData(user.userName, user, ObservableBuffer.empty[String])
|
|
chatDataStore.put(user.userName, new ChatDataProperty(chatData))
|
|
new UserBox2(user.userName, chatData) {
|
|
this.styleClass ++= Seq("text-white")
|
|
}
|
|
})
|
|
})
|
|
val messageBox = ChatDataProperty.createMdMessageBox2(
|
|
"""**Hello world qefwew yeeehay bwergqwevqcqe**
|
|
|**Hello world qefwew yeeehay bwergqwevqcqe**
|
|
|
|
|
| Hello World
|
|
""".stripMargin
|
|
)
|
|
onFX {
|
|
maybeUserBoxes.foreach(userBoxes => {
|
|
usersBuffer ++= userBoxes
|
|
})
|
|
chatListView.items() ++= Seq(
|
|
messageBox,
|
|
ChatDataProperty.createMdMessageBox2("hello"),
|
|
ChatDataProperty.createMdMessageBox2(
|
|
""" 1. Hello world qefwew yeeehay bwergqwevqcqe
|
|
|1. Hello world qefwew yeeehay bwergqwevqcqe
|
|
|1. Hello world qefwew yeeehay bwergqwevqcqe
|
|
|1. Hello world qefwew yeeehay bwergqwevqcqe""".stripMargin
|
|
)
|
|
)
|
|
// .map(node => {
|
|
// node.prefWidthProperty <== (chatListView.prefWidthProperty - 200)
|
|
// node
|
|
// })
|
|
// JFXSmoothScroll.smoothScrollingListView(chatListView, 0.1)
|
|
}
|
|
chatDataStore
|
|
.map { case (key, value) => value }
|
|
.foreach(cdp => {
|
|
cdp.messageBubbles ++= Seq(
|
|
ChatDataProperty.createMdMessageBox2("hi"),
|
|
ChatDataProperty.createMdMessageBox2("hello"),
|
|
ChatDataProperty.createMdMessageBox2("bye")
|
|
)
|
|
// .map(ChatDataProperty.createMdMessageBox)
|
|
})
|
|
}
|
|
}
|
|
def func() = {
|
|
val x = offFXAndWait {
|
|
2 + 3
|
|
}
|
|
x
|
|
}
|
|
|
|
def actionLogout = onFX {
|
|
offFXAndWait {
|
|
appDataHandler.clearCredentials()
|
|
}
|
|
// curUserAdapter.set(User.empty)
|
|
logger.debug(s"Logout - clearing credentials - ${appDataHandler.appData}")
|
|
chatDataAdapter.set(FXBean(ChatData.empty))
|
|
usersListView.items().clear()
|
|
chatListView.items().clear()
|
|
chatDataStore.clear()
|
|
usersBuffer.clear()
|
|
chatInput.clear()
|
|
this.stage.maximized = false
|
|
applicationController.logout()
|
|
}
|
|
|
|
implicit class MyClipboardExtension(clipboard: Clipboard) {
|
|
def putString(string: String) = {
|
|
// val content = Option(clipboard.getContent(DataFormat.PLAIN_TEXT))
|
|
// .getOrElse(new ClipboardContent())
|
|
// content.putString(string)
|
|
// clipboard.setContent(content)
|
|
}
|
|
}
|
|
}
|
|
|
|
final case class ChatData(
|
|
userName: String,
|
|
activeUser: ActiveUser,
|
|
messages: ObservableBuffer[String]
|
|
) {
|
|
lazy val lastActiveString =
|
|
activeUser.lastActive
|
|
.map(_.toString())
|
|
.getOrElse("User has not logged in yet")
|
|
lazy val onlineString = activeUser.online.toString()
|
|
}
|
|
object ChatData {
|
|
def empty = {
|
|
ChatData("empty", ActiveUser.empty, ObservableBuffer.empty[String])
|
|
}
|
|
}
|
|
class ChatDataProperty(chatData: ChatData) {
|
|
import ChatDataProperty._
|
|
val bean = FXBean(chatData)
|
|
val username = bean.getStringProperty("userName")
|
|
val isActive = bean.getBooleanProperty("activeUser.online")
|
|
val lastActive = bean.getStringProperty("lastActiveString")
|
|
lazy val messageBubbles = BufferProperty(
|
|
chatData.messages.map(ChatDataProperty.createMdMessageBox2)
|
|
)
|
|
def messageList = messageBubbles().map(_.message)
|
|
// lazy val messages = messagesBubbleProperty
|
|
// .get()
|
|
// .map(_.messageText)
|
|
}
|
|
object ChatDataProperty {
|
|
lazy val markdownStyleSheet =
|
|
getClass().getResource("/styles/markdown.css").toExternalForm()
|
|
|
|
def createMdMessageBox(mdfxText: String) = {
|
|
val mdfxNode = new BubbledMDFXNode(mdfxText);
|
|
mdfxNode
|
|
.getStylesheets()
|
|
.add(markdownStyleSheet);
|
|
mdfxNode.setBubbleSpec(BubbleSpec.FACE_RIGHT_CENTER);
|
|
mdfxNode.setBackground(
|
|
new Background(new BackgroundFill(Color.LIGHTSTEELBLUE, null, null))
|
|
);
|
|
val box = new HBox();
|
|
mdfxNode.setMinWidth(100.0);
|
|
box.setAlignment(Pos.TOP_RIGHT);
|
|
box.children += mdfxNode;
|
|
box
|
|
}
|
|
|
|
def createMdMessageBox2(mdfxText: String) = {
|
|
val mdfxNode = new MDFXNode(mdfxText);
|
|
mdfxNode
|
|
.getStylesheets()
|
|
.add(markdownStyleSheet)
|
|
mdfxNode.setMaxWidth(500)
|
|
mdfxNode.vgrow = Priority.ALWAYS
|
|
mdfxNode.setAlignment(Pos.CENTER)
|
|
mdfxNode.styleClass = Seq("chat-message-box")
|
|
|
|
val box = new MessageBox("", "", mdfxText)
|
|
box.setAlignment(Pos.CENTER_RIGHT)
|
|
// box.maxWidth(500)
|
|
box.hgrow = Priority.ALWAYS
|
|
box.vgrow = Priority.ALWAYS
|
|
box.children ++= Seq(mdfxNode)
|
|
box.fillHeight = true
|
|
box
|
|
}
|
|
|
|
def createMdMessageBox3(
|
|
sender: String,
|
|
receiver: String,
|
|
mdfxText: String
|
|
) = {
|
|
val mdfxNode = new MDFXNode(mdfxText);
|
|
mdfxNode
|
|
.getStylesheets()
|
|
.add(markdownStyleSheet)
|
|
mdfxNode.setMaxWidth(500)
|
|
mdfxNode.vgrow = Priority.ALWAYS
|
|
mdfxNode.setAlignment(Pos.CENTER)
|
|
mdfxNode.styleClass = Seq("chat-message-box")
|
|
|
|
val box = new MessageBox(sender, receiver, mdfxText)
|
|
box.setAlignment(Pos.CENTER_RIGHT)
|
|
// box.maxWidth(500)
|
|
box.hgrow = Priority.ALWAYS
|
|
box.vgrow = Priority.ALWAYS
|
|
box.children ++= Seq(mdfxNode)
|
|
box.fillHeight = true
|
|
box
|
|
}
|
|
|
|
}
|