Code Sketch


sniper_0718
By: Mhalsakant School
Category: Art
import javax.swing._
import java.awt._
import java.awt.event._
import java.io._
import java.net._
import javax.imageio.ImageIO
import javax.swing.filechooser.FileNameExtensionFilter
import scala.collection.mutable
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
import java.io.ByteArrayOutputStream
import java.io.ByteArrayInputStream
import java.util.Base64

// ==========================================
// SOCKET SERVER CLASS (All Features Synced)
// ==========================================
object KojoServer {
  val registeredUsers = mutable.Map[String, String]("bhimraj" -> "praxnya", "user1" -> "123", "user2" -> "456")
  val onlineUsers = mutable.Set[String]()
  val clientWriters = mutable.Map[String, PrintWriter]()
  val chatHistories = mutable.Map[(String, String), mutable.ListBuffer[(String, String)]]()
  val friendRequests = mutable.Map[String, mutable.Set[String]]()
  val friendsMap = mutable.Map[String, mutable.Set[String]]()
  val userPersonalPosts = mutable.Map[String, mutable.ListBuffer[String]]()
  val profileImages = mutable.Map[String, String]() // Base64 encoded string sathi

  def startServer(port: Int = 5000): Unit = {
    Future {
      try {
        val serverSocket = new ServerSocket(port)
        println("Kojo Server started on port " + port + "...")
        while (true) {
          val socket = serverSocket.accept()
          new ClientHandler(socket).start()
        }
      } catch {
        case e: Exception => println("Server already running or port busy: " + e.getMessage)
      }
    }
  }

  class ClientHandler(socket: Socket) extends Thread {
    val reader = new BufferedReader(new InputStreamReader(socket.getInputStream))
    val writer = new PrintWriter(socket.getOutputStream, true)
    var loggedInUser: String = ""

    override def run(): Unit = {
      try {
        var line: String = null
        while ({ line = reader.readLine(); line != null }) {
          val parts = line.split(":", 2)
          val cmd = parts(0)
          val data = if (parts.length > 1) parts(1) else ""

          cmd match {
            case "LOGIN" =>
              val creds = data.split(",")
              val id = creds(0)
              val pass = creds(1)
              if (registeredUsers.contains(id) && registeredUsers(id) != pass) {
                writer.println("ERROR:Incorrect Password")
              } else {
                registeredUsers(id) = pass
                loggedInUser = id
                onlineUsers += id
                clientWriters(id) = writer
                writer.println("LOGIN_SUCCESS")
                broadcastOnlineUsers()
              }

            case "CHAT" =>
              val chatParts = data.split(",", 3)
              val from = chatParts(0)
              val to = chatParts(1)
              val msg = chatParts(2)
              val chatKey = if (from < to) (from, to) else (to, from)
              chatHistories.getOrElseUpdate(chatKey, mutable.ListBuffer()) += ((from, msg))
              clientWriters.get(to).foreach(_.println("CHAT_MSG:" + from + "," + to + "," + msg))
              writer.println("CHAT_MSG:" + from + "," + to + "," + msg)

            case "REQ" =>
              val reqParts = data.split(",", 2)
              val from = reqParts(0)
              val to = reqParts(1)
              friendRequests.getOrElseUpdate(to, mutable.Set()) += from
              clientWriters.get(to).foreach(_.println("NOTIFY:" + from + " sent you a friend request!"))
              clientWriters.get(to).foreach(_.println("REFRESH_DATA"))
              writer.println("REQ_SENT")

            case "ACCEPT_REQ" =>
              val accParts = data.split(",", 2)
              val me = accParts(0)
              val requester = accParts(1)
              friendRequests.getOrElse(me, mutable.Set()) -= requester
              friendsMap.getOrElseUpdate(me, mutable.Set()) += requester
              friendsMap.getOrElseUpdate(requester, mutable.Set()) += me
              clientWriters.get(requester).foreach(_.println("NOTIFY:" + me + " accepted your friend request!"))
              clientWriters.get(requester).foreach(_.println("REFRESH_DATA"))
              writer.println("REFRESH_DATA")

            case "REJECT_REQ" =>
              val rejParts = data.split(",", 2)
              val me = rejParts(0)
              val requester = rejParts(1)
              friendRequests.getOrElse(me, mutable.Set()) -= requester
              clientWriters.get(requester).foreach(_.println("NOTIFY:" + me + " rejected your friend request."))
              clientWriters.get(requester).foreach(_.println("REFRESH_DATA"))
              writer.println("REFRESH_DATA")

            case "POST" =>
              val postParts = data.split(",", 2)
              val user = postParts(0)
              val postTxt = postParts(1)
              userPersonalPosts.getOrElseUpdate(user, mutable.ListBuffer()) += ("- " + postTxt)
              broadcastRefresh()

            case "UPLOAD_PHOTO" =>
              val photoParts = data.split(",", 2)
              val user = photoParts(0)
              val b64Img = photoParts(1)
              profileImages(user) = b64Img
              broadcastRefresh()

            case "GET_ALL_DATA" =>
              sendUserData(writer)

            case _ =>
          }
        }
      } catch {
        case _: Throwable => 
      } finally {
        if (loggedInUser.nonEmpty) {
          onlineUsers -= loggedInUser
          clientWriters -= loggedInUser
          broadcastOnlineUsers()
        }
        socket.close()
      }
    }

    def broadcastOnlineUsers(): Unit = {
      val usersList = onlineUsers.mkString(",")
      for ((_, w) <- clientWriters) {
        w.println("ONLINE_USERS:" + usersList)
      }
    }

    def broadcastRefresh(): Unit = {
      for ((_, w) <- clientWriters) {
        w.println("REFRESH_DATA")
      }
    }

    def sendUserData(w: PrintWriter): Unit = {
      // Send friend requests and posts info if needed
      w.println("REFRESH_DATA")
    }
  }
}

// ==========================================
// NETWORK CLIENT CLASS
// ==========================================
class NetworkClient(dashboard: Dashboard, serverIp: String, port: Int) {
  var socket: Socket = _
  var reader: BufferedReader = _
  var writer: PrintWriter = _

  def connect(id: String, pass: String): Boolean = {
    try {
      socket = new Socket(serverIp, port)
      reader = new BufferedReader(new InputStreamReader(socket.getInputStream))
      writer = new PrintWriter(socket.getOutputStream, true)

      writer.println("LOGIN:" + id + "," + pass)
      val resp = reader.readLine()
      if (resp == "LOGIN_SUCCESS") {
        startListening()
        true
      } else {
        false
      }
    } catch {
      case _: Throwable => false
    }
  }

  def startListening(): Unit = {
    Future {
      try {
        var line: String = null
        while ({ line = reader.readLine(); line != null }) {
          if (line.startsWith("ONLINE_USERS:")) {
            val users = line.substring(13).split(",").filter(_.nonEmpty).toSet
            SwingUtilities.invokeLater(() => dashboard.updateOnlineUsers(users))
          } else if (line.startsWith("CHAT_MSG:")) {
            val parts = line.substring(9).split(",", 3)
            dashboard.appendChatMessage(parts(0), parts(1), parts(2))
          } else if (line.startsWith("NOTIFY:")) {
            val msg = line.substring(7)
            SwingUtilities.invokeLater(() => JOptionPane.showMessageDialog(dashboard, msg, "Notification", JOptionPane.INFORMATION_MESSAGE))
          } else if (line == "REFRESH_DATA") {
            SwingUtilities.invokeLater(() => dashboard.setupRightPanel())
          }
        }
      } catch {
        case _: Throwable =>
      }
    }
  }

  def sendChat(from: String, to: String, msg: String): Unit = {
    if (writer != null) writer.println("CHAT:" + from + "," + to + "," + msg)
  }

  def sendFriendReq(from: String, to: String): Unit = {
    if (writer != null) writer.println("REQ:" + from + "," + to)
  }

  def acceptReq(me: String, requester: String): Unit = {
    if (writer != null) writer.println("ACCEPT_REQ:" + me + "," + requester)
  }

  def rejectReq(me: String, requester: String): Unit = {
    if (writer != null) writer.println("REJECT_REQ:" + me + "," + requester)
  }

  def sendPost(user: String, post: String): Unit = {
    if (writer != null) writer.println("POST:" + user + "," + post)
  }

  def sendPhoto(user: String, b64: String): Unit = {
    if (writer != null) writer.println("UPLOAD_PHOTO:" + user + "," + b64)
  }
}

// ==========================================
// LOGIN FRAME
// ==========================================
class LoginFrame extends JFrame {
  setTitle("Kojo Social - Login")
  setResizable(false)
  setSize(400, 280)
  setLocationRelativeTo(null)
  setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE)

  val ipField = new JTextField("127.0.0.1", 15)
  val idField = new JTextField("bhimraj", 15)
  val passField = new JPasswordField("praxnya", 15)
  val loginButton = new JButton("Login / Register & Start Server")
  val errorLabel = new JLabel("")
  errorLabel.setForeground(Color.RED)

  val panel = new JPanel()
  panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS))
  panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20))

  val ipPanel = new JPanel(new FlowLayout(FlowLayout.LEFT))
  ipPanel.add(new JLabel("Server IP:"))
  ipPanel.add(ipField)

  val idPanel = new JPanel(new FlowLayout(FlowLayout.LEFT))
  idPanel.add(new JLabel("ID:       "))
  idPanel.add(idField)

  val passPanel = new JPanel(new FlowLayout(FlowLayout.LEFT))
  passPanel.add(new JLabel("Pass:     "))
  passPanel.add(passField)

  panel.add(ipPanel)
  panel.add(idPanel)
  panel.add(passPanel)
  panel.add(Box.createVerticalStrut(15))
  panel.add(loginButton)
  panel.add(Box.createVerticalStrut(10))
  panel.add(errorLabel)

  add(panel)

  loginButton.addActionListener(new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = {
      val ip = ipField.getText.trim
      val id = idField.getText.trim
      val pass = new String(passField.getPassword).trim

      try {
        KojoServer.startServer(5000)
      } catch {
        case _: Throwable => 
      }

      Thread.sleep(300)

      val dashboard = new Dashboard(id, ip)
      if (dashboard.netClient.connect(id, pass)) {
        dashboard.setVisible(true)
        dispose()
      } else {
        errorLabel.setText("Connection Failed / Wrong Password!")
      }
    }
  })
}

// ==========================================
// DASHBOARD FRAME
// ==========================================
class Dashboard(val currentUserId: String, serverIp: String) extends JFrame {
  setTitle("Kojo Social Dashboard - Logged in as: " + currentUserId)
  setSize(1150, 680)
  setLocationRelativeTo(null)
  setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE)
  setLayout(new BorderLayout())

  var selectedTargetUser: String = ""
  val netClient = new NetworkClient(this, serverIp, 5000)
  var onlineUsersSet: Set[String] = Set()
  val localChatHistories = mutable.Map[(String, String), mutable.ListBuffer[(String, String)]]()

  val usersListPanel = new JPanel()
  usersListPanel.setLayout(new BoxLayout(usersListPanel, BoxLayout.Y_AXIS))
  val usersScroll = new JScrollPane(usersListPanel)
  usersScroll.setPreferredSize(new Dimension(240, 600))

  val centerPanel = new JPanel()
  centerPanel.setLayout(new BoxLayout(centerPanel, BoxLayout.Y_AXIS))
  centerPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10))
  
  val chatTitleLbl = new JLabel("Select a user from left to Start Live Chat")
  chatTitleLbl.setFont(new Font("Arial", Font.BOLD, 13))
  
  val chatArea = new JTextArea(16, 30)
  chatArea.setEditable(false)
  val chatScroll = new JScrollPane(chatArea)
  
  val msgField = new JTextField(20)
  val sendMsgBtn = new JButton("Send Message")
  val msgPanel = new JPanel(new FlowLayout(FlowLayout.LEFT))
  msgPanel.add(msgField)
  msgPanel.add(sendMsgBtn)
  msgPanel.setVisible(false)

  centerPanel.add(chatTitleLbl)
  centerPanel.add(Box.createVerticalStrut(10))
  centerPanel.add(chatScroll)
  centerPanel.add(Box.createVerticalStrut(10))
  centerPanel.add(msgPanel)

  val rightPanel = new JPanel()
  rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.Y_AXIS))
  rightPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10))
  val rightScroll = new JScrollPane(rightPanel)
  rightScroll.setPreferredSize(new Dimension(380, 600))

  def updateOnlineUsers(users: Set[String]): Unit = {
    onlineUsersSet = users
    refreshOnlineUsersUI()
  }

  def appendChatMessage(from: String, to: String, msg: String): Unit = {
    val chatKey = if (from < to) (from, to) else (to, from)
    localChatHistories.getOrElseUpdate(chatKey, mutable.ListBuffer()) += ((from, msg))
    if (selectedTargetUser.nonEmpty && (selectedTargetUser == from || selectedTargetUser == to)) {
      reloadChatArea()
    }
  }

  def reloadChatArea(): Unit = {
    if (selectedTargetUser.nonEmpty) {
      val chatKey = if (currentUserId < selectedTargetUser) (currentUserId, selectedTargetUser) else (selectedTargetUser, currentUserId)
      val history = localChatHistories.getOrElse(chatKey, mutable.ListBuffer())
      chatArea.setText(history.map { case (sender, m) => sender + ": " + m }.mkString("\n"))
    }
  }

  def refreshOnlineUsersUI(): Unit = {
    usersListPanel.removeAll()
    val titleLbl = new JLabel("Active Users (Online):")
    titleLbl.setFont(new Font("Arial", Font.BOLD, 13))
    usersListPanel.add(titleLbl)
    usersListPanel.add(Box.createVerticalStrut(10))
    
    for (user <- onlineUsersSet) {
      val displayName = if (user == currentUserId) user + " (You)" else user
      val btn = new JButton(displayName)
      btn.setHorizontalAlignment(SwingConstants.LEFT)
      btn.setMaximumSize(new Dimension(220, 30))
      
      btn.addActionListener(new ActionListener {
        override def actionPerformed(e: ActionEvent): Unit = {
          if (user != currentUserId) {
            selectedTargetUser = user
            chatTitleLbl.setText("Live Chat with: " + user)
            msgPanel.setVisible(true)
            reloadChatArea()
          } else {
            JOptionPane.showMessageDialog(usersListPanel, "This is your own profile.", "Info", JOptionPane.INFORMATION_MESSAGE)
          }
        }
      })
      usersListPanel.add(btn)
      usersListPanel.add(Box.createVerticalStrut(5))
    }
    usersListPanel.revalidate()
    usersListPanel.repaint()
  }

  def setupRightPanel(): Unit = {
    rightPanel.removeAll()

    // 0. Profile Photo & Circle Display
    val profilePanel = new JPanel(new FlowLayout(FlowLayout.LEFT))
    val avatarLabel = new JLabel()
    avatarLabel.setPreferredSize(new Dimension(60, 60))
    
    KojoServer.profileImages.get(currentUserId) match {
      case Some(b64) if b64.nonEmpty =>
        try {
          val bytes = Base64.getDecoder.decode(b64)
          val bimg = ImageIO.read(new ByteArrayInputStream(bytes))
          if (bimg != null) {
            val dimg = bimg.getScaledInstance(60, 60, Image.SCALE_SMOOTH)
            avatarLabel.setIcon(new ImageIcon(dimg))
          }
        } catch { case _: Throwable => }
      case _ =>
        avatarLabel.setText("[No Photo]")
    }
    profilePanel.add(avatarLabel)
    
    val uploadPhotoBtn = new JButton("Upload Photo")
    uploadPhotoBtn.addActionListener(new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = {
        val fileChooser = new JFileChooser()
        val result = fileChooser.showOpenDialog(rightPanel)
        if (result == JFileChooser.APPROVE_OPTION) {
          val file = fileChooser.getSelectedFile
          try {
            val img = ImageIO.read(file)
            if (img != null) {
              val baos = new ByteArrayOutputStream()
              ImageIO.write(img, "jpg", baos)
              val b64Str = Base64.getEncoder.encodeToString(baos.toByteArray)
              netClient.sendPhoto(currentUserId, b64Str)
              JOptionPane.showMessageDialog(rightPanel, "Photo uploaded successfully!")
            } else {
              JOptionPane.showMessageDialog(rightPanel, "Invalid image!", "Error", JOptionPane.ERROR_MESSAGE)
            }
          } catch {
            case _: Throwable => JOptionPane.showMessageDialog(rightPanel, "Failed to load image!", "Error", JOptionPane.ERROR_MESSAGE)
          }
        }
      }
    })
    profilePanel.add(uploadPhotoBtn)
    rightPanel.add(profilePanel)
    rightPanel.add(Box.createVerticalStrut(10))

    // 1. Friend Requests Section
    val frTitle = new JLabel("Friend Requests:")
    frTitle.setFont(new Font("Arial", Font.BOLD, 13))
    rightPanel.add(frTitle)
    rightPanel.add(Box.createVerticalStrut(5))

    val reqButton = new JButton("Send Friend Request to Selected User")
    reqButton.addActionListener(new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = {
        if (selectedTargetUser.nonEmpty && selectedTargetUser != currentUserId) {
          netClient.sendFriendReq(currentUserId, selectedTargetUser)
          reqButton.setText("Request Sent")
          reqButton.setEnabled(false)
          JOptionPane.showMessageDialog(rightPanel, "Friend request sent to " + selectedTargetUser + "!")
        } else {
          JOptionPane.showMessageDialog(rightPanel, "Please select another user from the left column first!")
        }
      }
    })
    rightPanel.add(reqButton)

    // Incoming Requests
    rightPanel.add(Box.createVerticalStrut(10))
    val incomingLbl = new JLabel("Incoming Requests:")
    incomingLbl.setFont(new Font("Arial", Font.BOLD, 11))
    rightPanel.add(incomingLbl)

    val myRecvReqs = KojoServer.friendRequests.getOrElse(currentUserId, mutable.Set())
    if (myRecvReqs.isEmpty) {
      rightPanel.add(new JLabel("No incoming requests."))
    } else {
      for (requester <- myRecvReqs) {
        val reqPanel = new JPanel(new FlowLayout(FlowLayout.LEFT))
        reqPanel.add(new JLabel(requester))
        val acceptBtn = new JButton("Accept")
        val rejectBtn = new JButton("Reject")

        acceptBtn.addActionListener(new ActionListener {
          override def actionPerformed(e: ActionEvent): Unit = {
            netClient.acceptReq(currentUserId, requester)
            JOptionPane.showMessageDialog(rightPanel, "You accepted " + requester + "'s request!")
          }
        })

        rejectBtn.addActionListener(new ActionListener {
          override def actionPerformed(e: ActionEvent): Unit = {
            netClient.rejectReq(currentUserId, requester)
            setupRightPanel()
          }
        })

        reqPanel.add(acceptBtn)
        reqPanel.add(rejectBtn)
        rightPanel.add(reqPanel)
      }
    }

    // Section 2: Personal Posts
    rightPanel.add(Box.createVerticalStrut(15))
    val myPostTitle = new JLabel("My Personal Posts:")
    myPostTitle.setFont(new Font("Arial", Font.BOLD, 12))
    rightPanel.add(myPostTitle)
    rightPanel.add(Box.createVerticalStrut(5))

    val postInput = new JTextField(18)
    val addPostBtn = new JButton("Add Post")
    val postInputPanel = new JPanel(new FlowLayout(FlowLayout.LEFT))
    postInputPanel.add(postInput)
    postInputPanel.add(addPostBtn)
    rightPanel.add(postInputPanel)

    val myPostsArea = new JTextArea(6, 25)
    myPostsArea.setEditable(false)

    val pList = KojoServer.userPersonalPosts.getOrElse(currentUserId, mutable.ListBuffer())
    myPostsArea.setText(pList.mkString("\n"))

    addPostBtn.addActionListener(new ActionListener {
      override def actionPerformed(e: ActionEvent): Unit = {
        val postTxt = postInput.getText.trim
        if (postTxt.nonEmpty) {
          netClient.sendPost(currentUserId, postTxt)
          postInput.setText("")
        }
      }
    })

    rightPanel.add(Box.createVerticalStrut(5))
    rightPanel.add(new JScrollPane(myPostsArea))

    rightPanel.revalidate()
    rightPanel.repaint()
  }

  setupRightPanel()

  val sendAction = new ActionListener {
    override def actionPerformed(e: ActionEvent): Unit = {
      val text = msgField.getText.trim
      if (text.nonEmpty && selectedTargetUser.nonEmpty) {
        netClient.sendChat(currentUserId, selectedTargetUser, text)
        msgField.setText("")
      }
    }
  }

  sendMsgBtn.addActionListener(sendAction)
  msgField.addActionListener(sendAction)

  add(usersScroll, BorderLayout.WEST)
  add(centerPanel, BorderLayout.CENTER)
  add(rightScroll, BorderLayout.EAST)
}

// Start Login Window
SwingUtilities.invokeLater(new Runnable {
  def run(): Unit = {
    new LoginFrame().setVisible(true)
  }
})