Code Sketch


sniper_0718
By: Mhalsakant School
Category: Art
import scala.swing._
import scala.swing.event._
import java.awt.Color
import java.awt.Font
import java.awt.Graphics2D
import java.awt.event.MouseEvent
import java.net.URL
import javax.swing.ImageIcon
import scala.collection.mutable
import scala.concurrent.ExecutionContext.Implicits.global

object KojoMultiPCApp {

  val registeredUsers = mutable.Map[String, String](
    "bhimraj" -> "praxnya",
    "user1" -> "123", 
    "user2" -> "456"
  )
  val onlineUsers = mutable.Set[String]()
  val postsList = mutable.ListBuffer[(String, String)]()
  val friendRequests = mutable.Map[String, mutable.Set[String]]() 
  val friendsMap = mutable.Map[String, mutable.Set[String]]()
  
  val profileImages = mutable.Map[String, ImageIcon]()

  def main(args: Array[String]): Unit = {
    javax.swing.SwingUtilities.invokeLater(new Runnable {
      def run(): Unit = {
        val loginFrame = new LoginFrame()
        loginFrame.visible = true
      }
    })
  }

  class LoginFrame extends MainFrame {
    title = "Kojo Social - Login"
    resizable = false
    minimumSize = new Dimension(400, 240)
    centerOnScreen()

    val idField = new TextField(15)
    val passField = new PasswordField(15)
    val loginButton = new Button("Login / Register")
    val errorLabel = new Label("") { foreground = Color.RED }

    contents = new BoxPanel(Orientation.Vertical) {
      border = Swing.EmptyBorder(20, 20, 20, 20)
      // ?????? ?????? ?? ??? ????? ????? ???
      contents += new BoxPanel(Orientation.Horizontal) { contents += new Label("ID:   "); contents += idField }
      contents += Swing.VStrut(5)
      contents += new BoxPanel(Orientation.Horizontal) { contents += new Label("Pass: "); contents += passField }
      contents += Swing.VStrut(15)
      contents += loginButton
      contents += Swing.VStrut(10)
      contents += errorLabel
    }

    listenTo(loginButton)
    reactions += {
      case ButtonClicked(`loginButton`) =>
        val id = idField.text.trim
        val pass = new String(passField.peer.getPassword).trim
        if (id.nonEmpty && pass.nonEmpty) {
          if (registeredUsers.contains(id) && registeredUsers(id) != pass) {
            errorLabel.text = "Incorrect Password!"
          } else {
            registeredUsers(id) = pass
            onlineUsers += id
            val dashboard = new ThreeColumnDashboard(id)
            dashboard.visible = true
            this.dispose()
          }
        } else {
          errorLabel.text = "Please fill all fields!"
        }
    }
  }

  class ThreeColumnDashboard(currentUserId: String) extends MainFrame {
    title = s"Kojo Social Dashboard - Logged in as: $currentUserId"
    minimumSize = new Dimension(1150, 700)
    centerOnScreen()

    val usersListPanel = new BoxPanel(Orientation.Vertical)
    val usersScroll = new ScrollPane(usersListPanel) {
      preferredSize = new Dimension(250, 600)
    }

    def refreshOnlineUsers(): Unit = {
      usersListPanel.contents.clear()
      usersListPanel.contents += new Label("Active Users (Online):") { font = new Font("Arial", Font.BOLD, 13) }
      usersListPanel.contents += Swing.VStrut(10)
      
      for (user <- onlineUsers) {
        val displayName = if (user == currentUserId) s"$user (You)" else user
        val btn = new Button(displayName) {
          horizontalAlignment = Alignment.Left
          maximumSize = new Dimension(230, 30)
        }
        
        listenTo(btn)
        reactions += {
          case ButtonClicked(`btn`) =>
            if (user != currentUserId) {
              showRightColumnProfile(user)
            } else {
              Dialog.showMessage(usersListPanel, "This is your own profile.", "Info", Dialog.Message.Info)
            }
        }
        usersListPanel.contents += btn
        usersListPanel.contents += Swing.VStrut(5)
      }
      usersListPanel.revalidate()
      usersListPanel.repaint()
    }

    val postInput = new TextField(20)
    val postButton = new Button("Upload Post / Google Image URL")
    val feedPanel = new BoxPanel(Orientation.Vertical)
    val feedScroll = new ScrollPane(feedPanel) {
      preferredSize = new Dimension(520, 600)
    }

    def refreshFeed(): Unit = {
      feedPanel.contents.clear()
      feedPanel.contents += new Label("Live Feed & Posts (Right-click to add Post):") { font = new Font("Arial", Font.BOLD, 13) }
      feedPanel.contents += Swing.VStrut(10)
      
      for ((author, content) <- postsList.reverse) {
        val postBox = new BoxPanel(Orientation.Vertical) {
          border = Swing.CompoundBorder(Swing.LineBorder(Color.LIGHT_GRAY), Swing.EmptyBorder(10, 10, 10, 10))
          contents += new Label(s"Author: $author") { font = new Font("Arial", Font.BOLD, 12); foreground = Color.BLUE }
          contents += Swing.VStrut(5)
          
          if (content.startsWith("http") && (content.endsWith(".png") || content.endsWith(".jpg") || content.endsWith(".jpeg") || content.contains("img") || content.contains("google"))) {
            try {
              val imgIcon = new ImageIcon(new URL(content))
              val image = imgIcon.getImage.getScaledInstance(220, 160, java.awt.Image.SCALE_SMOOTH)
              contents += new Label { icon = new ImageIcon(image) }
            } catch {
              case _: Throwable => contents += new Label(content) { font = new Font("Arial", Font.PLAIN, 12) }
            }
          } else {
            contents += new Label(content) { font = new Font("Arial", Font.PLAIN, 12) }
          }
        }
        feedPanel.contents += postBox
        feedPanel.contents += Swing.VStrut(10)
      }
      feedPanel.revalidate()
      feedPanel.repaint()
    }

    feedPanel.peer.addMouseListener(new java.awt.event.MouseAdapter {
      override def mousePressed(e: MouseEvent): Unit = {
        if (e.isPopupTrigger || e.getButton() == MouseEvent.BUTTON3) {
          val urlInput = Dialog.showInput(feedPanel, "Paste Google Image URL or Text for Post:", "Add New Post", Dialog.Message.Question, null, Nil, "")
          urlInput match {
            case Some(text: String) if text.trim.nonEmpty =>
              postsList += ((currentUserId, text.trim))
              refreshFeed()
            case _ =>
          }
        }
      }
    })

    val rightPanel = new BoxPanel(Orientation.Vertical) {
      border = Swing.EmptyBorder(10, 10, 10, 10)
      contents += new Label("Click a user on the left to view profile") {
        font = new Font("Arial", Font.ITALIC, 12)
      }
    }
    val rightScroll = new ScrollPane(rightPanel) {
      preferredSize = new Dimension(320, 600)
    }

    def showRightColumnProfile(targetUser: String): Unit = {
      rightPanel.contents.clear()
      rightPanel.contents += new Label(s"Profile: $targetUser") { font = new Font("Arial", Font.BOLD, 14) }
      rightPanel.contents += Swing.VStrut(10)

      val circlePanel = new Panel {
        preferredSize = new Dimension(150, 150)
        maximumSize = new Dimension(150, 150)

        override def paintComponent(g: Graphics2D): Unit = {
          super.paintComponent(g)
          g.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING, java.awt.RenderingHints.VALUE_ANTIALIAS_ON)
          
          val sizeDim = Math.min(size.width, size.height)
          val x = (size.width - sizeDim) / 2
          val y = (size.height - sizeDim) / 2

          g.setColor(new Color(41, 128, 185))
          g.fillOval(x, y, sizeDim, sizeDim)

          profileImages.get(targetUser) match {
            case Some(icon) =>
              val img = icon.getImage.getScaledInstance(sizeDim, sizeDim, java.awt.Image.SCALE_SMOOTH)
              g.drawImage(img, x, y, null)
            case None =>
              g.setColor(Color.WHITE)
              g.setFont(new Font("Arial", Font.BOLD, 14))
              val metrics = g.getFontMetrics
              val txt = targetUser
              g.drawString(txt, x + (sizeDim - metrics.stringWidth(txt)) / 2, y + sizeDim / 2)
          }

          g.setColor(Color.DARK_GRAY)
          g.drawOval(x, y, sizeDim, sizeDim)
        }

        peer.addMouseListener(new java.awt.event.MouseAdapter {
          override def mousePressed(e: MouseEvent): Unit = {
            if (e.isPopupTrigger || e.getButton() == MouseEvent.BUTTON3) {
              val urlOpt = Dialog.showInput(rightPanel, "Right-Click: Paste Google Image URL for Profile Photo:", "Set Profile Photo", Dialog.Message.Question, null, Nil, "")
              urlOpt match {
                case Some(urlStr: String) if urlStr.trim.nonEmpty =>
                  try {
                    val icon = new ImageIcon(new URL(urlStr.trim))
                    profileImages(targetUser) = icon
                    repaint()
                    Dialog.showMessage(rightPanel, "Profile photo updated successfully!")
                  } catch {
                    case _: Throwable => Dialog.showMessage(rightPanel, "Invalid Image URL!", "Error", Dialog.Message.Error)
                  }
                case _ =>
              }
            } else if (e.getButton() == MouseEvent.BUTTON1) {
              showFullProfileDetails(targetUser)
            }
          }
        })
      }

      rightPanel.contents += circlePanel
      rightPanel.contents += new Label("(Left-click circle to open options & Chat)") { font = new Font("Arial", Font.ITALIC, 10); foreground = Color.GRAY }
      rightPanel.contents += new Label("(Right-click circle to set Google Photo)") { font = new Font("Arial", Font.ITALIC, 10); foreground = Color.GRAY }

      rightPanel.revalidate()
      rightPanel.repaint()
    }

    def showFullProfileDetails(targetUser: String): Unit = {
      rightPanel.contents.clear()
      rightPanel.contents += new Label(s"Profile: $targetUser") { font = new Font("Arial", Font.BOLD, 14) }
      rightPanel.contents += Swing.VStrut(10)

      val circlePanel = new Panel {
        preferredSize = new Dimension(150, 150)
        maximumSize = new Dimension(150, 150)

        override def paintComponent(g: Graphics2D): Unit = {
          super.paintComponent(g)
          g.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING, java.awt.RenderingHints.VALUE_ANTIALIAS_ON)
          val sizeDim = Math.min(size.width, size.height)
          val x = (size.width - sizeDim) / 2
          val y = (size.height - sizeDim) / 2
          g.setColor(new Color(41, 128, 185))
          g.fillOval(x, y, sizeDim, sizeDim)
          profileImages.get(targetUser) match {
            case Some(icon) =>
              val img = icon.getImage.getScaledInstance(sizeDim, sizeDim, java.awt.Image.SCALE_SMOOTH)
              g.drawImage(img, x, y, null)
            case None =>
              g.setColor(Color.WHITE)
              g.setFont(new Font("Arial", Font.BOLD, 14))
              val metrics = g.getFontMetrics
              val txt = targetUser
              g.drawString(txt, x + (sizeDim - metrics.stringWidth(txt)) / 2, y + sizeDim / 2)
          }
          g.setColor(Color.DARK_GRAY)
          g.drawOval(x, y, sizeDim, sizeDim)
        }

        peer.addMouseListener(new java.awt.event.MouseAdapter {
          override def mousePressed(e: MouseEvent): Unit = {
            if (e.isPopupTrigger || e.getButton() == MouseEvent.BUTTON3) {
              val urlOpt = Dialog.showInput(rightPanel, "Right-Click: Paste Google Image URL for Profile Photo:", "Set Profile Photo", Dialog.Message.Question, null, Nil, "")
              urlOpt match {
                case Some(urlStr: String) if urlStr.trim.nonEmpty =>
                  try {
                    val icon = new ImageIcon(new URL(urlStr.trim))
                    profileImages(targetUser) = icon
                    repaint()
                    Dialog.showMessage(rightPanel, "Profile photo updated successfully!")
                  } catch {
                    case _: Throwable => Dialog.showMessage(rightPanel, "Invalid Image URL!", "Error", Dialog.Message.Error)
                  }
                case _ =>
              }
            }
          }
        })
      }

      rightPanel.contents += circlePanel
      rightPanel.contents += Swing.VStrut(15)

      val reqButton = new Button()
      val chatButton = new Button("Open Live Chat") { enabled = false }

      val areFriends = friendsMap.getOrElse(currentUserId, mutable.Set()).contains(targetUser)
      val sentReqs = friendRequests.getOrElse(targetUser, mutable.Set())

      if (areFriends) {
        reqButton.text = "Connected (Friends)"
        reqButton.enabled = false
        chatButton.enabled = true
      } else if (sentReqs.contains(currentUserId)) {
        reqButton.text = "Request Sent"
        reqButton.enabled = false
      } else {
        reqButton.text = "Send Friend Request"
        reqButton.enabled = true
      }

      listenTo(reqButton)
      listenTo(chatButton)

      reactions += {
        case ButtonClicked(`reqButton`) =>
          friendRequests.getOrElseUpdate(targetUser, mutable.Set()) += currentUserId
          reqButton.text = "Request Sent"
          reqButton.enabled = false
          Dialog.showMessage(rightPanel, s"Friend request sent to $targetUser!")

        case ButtonClicked(`chatButton`) =>
          val chatWindow = new LiveChatWindow(currentUserId, targetUser)
          chatWindow.visible = true
      }

      rightPanel.contents += reqButton
      rightPanel.contents += Swing.VStrut(10)
      rightPanel.contents += chatButton

      rightPanel.contents += Swing.VStrut(20)
      rightPanel.contents += new Label("Incoming Requests:") { font = new Font("Arial", Font.BOLD, 12) }
      
      val myReqs = friendRequests.getOrElse(currentUserId, mutable.Set())
      for (requester <- myReqs) {
        val reqBox = new BoxPanel(Orientation.Horizontal) {
          contents += new Label(requester)
          val acceptBtn = new Button("Accept")
          val rejectBtn = new Button("Reject")
          listenTo(acceptBtn, rejectBtn)
          reactions += {
            case ButtonClicked(`acceptBtn`) =>
              myReqs -= requester
              friendsMap.getOrElseUpdate(currentUserId, mutable.Set()) += requester
              friendsMap.getOrElseUpdate(requester, mutable.Set()) += currentUserId
              Dialog.showMessage(rightPanel, s"You are now friends with $requester!")
              showFullProfileDetails(targetUser)
            case ButtonClicked(`rejectBtn`) =>
              myReqs -= requester
              showFullProfileDetails(targetUser)
          }
          contents += acceptBtn
          contents += rejectBtn
        }
        rightPanel.contents += reqBox
      }

      rightPanel.revalidate()
      rightPanel.repaint()
    }

    contents = new BoxPanel(Orientation.Horizontal) {
      contents += usersScroll
      contents += new Separator(Orientation.Vertical)
      contents += new BoxPanel(Orientation.Vertical) {
        border = Swing.EmptyBorder(10, 10, 10, 10)
        contents += new BoxPanel(Orientation.Horizontal) {
          contents += new Label("Post / Image URL: ")
          contents += postInput
          contents += postButton
        }
        contents += Swing.VStrut(10)
        contents += feedScroll
      }
      contents += new Separator(Orientation.Vertical)
      contents += rightScroll
    }

    listenTo(postButton)
    reactions += {
      case ButtonClicked(`postButton`) =>
        val content = postInput.text.trim
        if (content.nonEmpty) {
          postsList += ((currentUserId, content))
          postInput.text = ""
          refreshFeed()
        }
    }

    refreshOnlineUsers()
    refreshFeed()
  }

  class LiveChatWindow(userA: String, userB: String) extends MainFrame {
    title = s"Live Chat: $userA & $userB"
    minimumSize = new Dimension(400, 350)
    centerOnScreen()

    val chatArea = new TextArea(12, 30) { editable = false }
    val msgField = new TextField(20)
    val sendBtn = new Button("Send")

    contents = new BoxPanel(Orientation.Vertical) {
      border = Swing.EmptyBorder(10, 10, 10, 10)
      contents += new ScrollPane(chatArea)
      contents += Swing.VStrut(10)
      contents += new BoxPanel(Orientation.Horizontal) {
        contents += msgField
        contents += sendBtn
      }
    }

    listenTo(sendBtn)
    reactions += {
      case ButtonClicked(`sendBtn`) =>
        val text = msgField.text.trim
        if (text.nonEmpty) {
          chatArea.append(s"$userA:$text\n")
          msgField.text = ""
        }
    }
  }
}

KojoMultiPCApp.main(Array())