Code Sketch


sniper_0718
By: Mhalsakant School
Category: Art
import scala.swing._
import scala.swing.event._
import java.awt.Color
import java.awt.Graphics2D
import java.awt.Font
import java.awt.event.MouseEvent
import java.awt.datatransfer.DataFlavor
import javax.swing.ImageIcon
import scala.collection.mutable

object KojoSocialApp {

  // Global databases
  private val registeredUsers = mutable.Map[String, String]()
  private val userPosts = mutable.Map[String, mutable.ListBuffer[PostData]]()
  
  // Follow requests: Map[TargetUserId, mutable.Set[RequesterId]]
  private val followRequests = mutable.Map[String, mutable.Set[String]]()
  
  // Friends / Accepted connections: Map[UserId, mutable.Set[FriendId]] (Mutual relationship helper)
  private val friendsMap = mutable.Map[String, mutable.Set[String]]()

  // Chat messages storage: Map[ChatPairKey, mutable.ListBuffer[ChatMessage]]
  // ChatPairKey ko alphabetically sort karke banayenge taaki dono users ke liye ek hi chat history rahe.
  private val chatHistories = mutable.Map[String, mutable.ListBuffer[ChatMessage]]()

  case class PostData(text: String, image: ImageIcon)
  case class ChatMessage(sender: String, text: String)

  def main(args: Array[String]): Unit = {
    // Dummy users testing ke liye
    registeredUsers("user1") = "Rahul123"
    registeredUsers("user2") = "Amit99"
    registeredUsers("user3") = "Priya2026"

    javax.swing.SwingUtilities.invokeLater(new Runnable {
      def run(): Unit = {
        val startFrame = new StartupSearchLoginFrame()
        startFrame.visible = true
      }
    })
  }

  // 1. STARTUP SEARCH & LOGIN FRAME
  class StartupSearchLoginFrame extends MainFrame {
    title = "Kojo Social App - Login & Register"
    resizable = false
    minimumSize = new Dimension(440, 330)
    centerOnScreen()

    val searchIdField = new TextField(10)
    val searchPasswordField = new PasswordField(10)
    val searchLoginButton = new Button("Login")
    
    val newIdField = new TextField(10)          
    val newPasswordField = new PasswordField(10)  
    val createAccountButton = new Button("Register New Account")

    val messageLabel = new Label("Enter your ID & Password to login or register.") {
      foreground = new Color(0, 100, 0)
      font = new Font("Arial", Font.BOLD, 12)
    }

    contents = new BoxPanel(Orientation.Vertical) {
      border = Swing.EmptyBorder(20, 20, 20, 20)
      contents += new Label("=== Welcome to Kojo Social ===") { font = new Font("Arial", Font.BOLD, 15) }
      contents += Swing.VStrut(15)
      
      contents += new Label("Already have an ID? Login here:")
      contents += Swing.VStrut(5)
      contents += new BoxPanel(Orientation.Horizontal) {
        contents += new Label("ID:   ")
        contents += searchIdField
        contents += Swing.HStrut(5)
        contents += new Label("Pass: ")
        contents += searchPasswordField
        contents += Swing.HStrut(10)
        contents += searchLoginButton
      }
      
      contents += Swing.VStrut(15)
      contents += new Separator()
      contents += Swing.VStrut(15)

      contents += new Label("New User? Register with ID & Password:")
      contents += Swing.VStrut(8)
      
      contents += new BoxPanel(Orientation.Horizontal) {
        contents += new Label("ID:   ")
        contents += newIdField
      }
      contents += Swing.VStrut(5)

      contents += new BoxPanel(Orientation.Horizontal) {
        contents += new Label("Pass: ")
        contents += newPasswordField
      }
      
      contents += Swing.VStrut(12)
      contents += createAccountButton
      contents += Swing.VStrut(10)
      contents += messageLabel
    }

    listenTo(searchLoginButton)
    listenTo(createAccountButton)

    reactions += {
      case ButtonClicked(`searchLoginButton`) =>
        val idToSearch = searchIdField.text.trim
        val enteredPass = new String(searchPasswordField.peer.getPassword).trim

        if (idToSearch.isEmpty || enteredPass.isEmpty) {
          messageLabel.text = "Please enter both ID and Password!"
          messageLabel.foreground = Color.RED
        } else if (registeredUsers.contains(idToSearch)) {
          val correctPass = registeredUsers(idToSearch)
          if (correctPass == enteredPass) {
            val dashboard = new FullScreenDashboard(idToSearch)
            dashboard.maximize()
            dashboard.visible = true
            this.dispose() 
          } else {
            messageLabel.text = "Incorrect Password!"
            messageLabel.foreground = Color.RED
          }
        } else {
          messageLabel.text = "ID not found! Please register below."
          messageLabel.foreground = Color.RED
        }

      case ButtonClicked(`createAccountButton`) =>
        val id = newIdField.text.trim
        val pass = new String(newPasswordField.peer.getPassword).trim

        if (id.isEmpty || pass.isEmpty) {
          messageLabel.text = "Enter both ID and Password to register!"
          messageLabel.foreground = Color.RED
        } else if (registeredUsers.contains(id)) {
          messageLabel.text = "This ID already exists! Try logging in above."
          messageLabel.foreground = Color.RED
        } else {
          registeredUsers(id) = pass
          val dashboard = new FullScreenDashboard(id)
          dashboard.maximize()
          dashboard.visible = true
          this.dispose()
        }
    }
  }

  // 2. FULL SCREEN DASHBOARD
  class FullScreenDashboard(currentUserId: String) extends MainFrame {
    title = "Kojo Application - Welcome ID: " + currentUserId
    minimumSize = new Dimension(1024, 728)

    val backButton = new Button("<-- Back")
    val searchField = new TextField(20)
    val searchButton = new Button("Search ID")
    val searchResultLabel = new Label("Search registered users here...") {
      foreground = Color.BLUE
      font = new Font("Arial", Font.BOLD, 14)
    }

    val topPanel = new BoxPanel(Orientation.Horizontal) {
      border = Swing.EmptyBorder(15, 20, 15, 20)
      contents += backButton
      contents += Swing.HStrut(15)
      contents += new Label("Search User ID: ") { font = new Font("Arial", Font.BOLD, 14) }
      contents += Swing.HStrut(10)
      contents += searchField
      contents += Swing.HStrut(10)
      contents += searchButton
      contents += Swing.HStrut(20)
      contents += searchResultLabel
    }

    val drawingCanvas = new Panel {
      background = new Color(245, 247, 250)

      var circleCenterX = 0
      var circleCenterY = 0
      var circleRadius = 0

      override def paintComponent(g: Graphics2D): Unit = {
        super.paintComponent(g)
        g.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING, java.awt.RenderingHints.VALUE_ANTIALIAS_ON)

        val w = size.width
        val h = size.height

        val diameter = (Math.min(w, h) * 0.70).toInt
        circleRadius = diameter / 2
        circleCenterX = w / 2
        circleCenterY = h / 2

        val circleX = circleCenterX - circleRadius
        val circleY = circleCenterY - circleRadius

        // Circle Shadow
        g.setColor(new Color(200, 200, 200, 120))
        g.fillOval(circleX + 10, circleY + 10, diameter, diameter)

        // Main Circle
        g.setColor(new Color(41, 128, 185))
        g.fillOval(circleX, circleY, diameter, diameter)

        // Circle Border
        g.setColor(new Color(21, 67, 96))
        g.setStroke(new java.awt.BasicStroke(5))
        g.drawOval(circleX, circleY, diameter, diameter)

        // Text inside Circle
        g.setColor(Color.WHITE)
        g.setFont(new Font("Arial", Font.BOLD, (diameter * 0.11).toInt))
        val idText = "ID: " + currentUserId
        val metrics = g.getFontMetrics
        val idX = circleCenterX - (metrics.stringWidth(idText) / 2)
        val idY = circleCenterY + 10
        g.drawString(idText, idX, idY)

        // Hint text
        g.setColor(Color.DARK_GRAY)
        g.setFont(new Font("Arial", Font.BOLD, 16))
        val hintText = "( Click anywhere on this big circle to open your profile & posts )"
        val hintMetrics = g.getFontMetrics
        g.drawString(hintText, circleCenterX - (hintMetrics.stringWidth(hintText) / 2), circleY + diameter + 30)
      }

      peer.addMouseListener(new java.awt.event.MouseAdapter {
        override def mouseClicked(e: MouseEvent): Unit = {
          val dx = e.getX - circleCenterX
          val dy = e.getY - circleCenterY
          if (dx * dx + dy * dy <= circleRadius * circleRadius) {
            val profileWin = new ProfileFrame(currentUserId, currentUserId)
            profileWin.visible = true
          }
        }
      })
    }

    contents = new BorderPanel {
      layout(topPanel) = BorderPanel.Position.North
      layout(drawingCanvas) = BorderPanel.Position.Center
    }

    listenTo(backButton)
    listenTo(searchButton)
    listenTo(searchField.keys)

    reactions += {
      case ButtonClicked(`backButton`) =>
        val loginScreen = new StartupSearchLoginFrame()
        loginScreen.visible = true
        this.dispose()

      case ButtonClicked(`searchButton`) =>
        performSearch()

      case KeyPressed(_, Key.Enter, _, _) =>
        performSearch()
    }

    def performSearch(): Unit = {
      val queryId = searchField.text.trim
      if (queryId.nonEmpty) {
        if (registeredUsers.contains(queryId)) {
          searchResultLabel.text = s"Found ID: $queryId. Opening profile..."
          searchResultLabel.foreground = new Color(39, 174, 96)
          
          val externalProfile = new ProfileFrame(queryId, currentUserId)
          externalProfile.visible = true
        } else {
          searchResultLabel.text = "No registered user found with this ID"
          searchResultLabel.foreground = Color.RED
        }
      } else {
        searchResultLabel.text = "Please enter an ID to search!"
        searchResultLabel.foreground = Color.RED
      }
    }
  }

  // 3. PROFILE WINDOW
  class ProfileFrame(profileUserId: String, viewerUserId: String) extends MainFrame {
    title = s"Profile Options - ID: $profileUserId"
    minimumSize = new Dimension(620, 550)
    centerOnScreen()

    val profileBackBtn = new Button("<-- Back")
    
    val captionLabel = new Label(s"Profile Details | ID: $profileUserId") {
      font = new Font("Arial", Font.BOLD, 14)
    }

    val postInputField = new TextArea(3, 30) {
      lineWrap = true
      wordWrap = true
      font = new Font("Arial", Font.PLAIN, 14)
    }

    var pastedImageIcon: ImageIcon = null
    val imageStatusLabel = new Label("No image attached. (Copy image from Google & Paste via Ctrl+V)") {
      font = new Font("Arial", Font.ITALIC, 12)
      foreground = Color.GRAY
    }

    postInputField.peer.addKeyListener(new java.awt.event.KeyAdapter {
      override def keyPressed(e: java.awt.event.KeyEvent): Unit = {
        if (e.isControlDown && e.getKeyCode == java.awt.event.KeyEvent.VK_V) {
          try {
            val clipboard = java.awt.Toolkit.getDefaultToolkit.getSystemClipboard
            if (clipboard.isDataFlavorAvailable(DataFlavor.imageFlavor)) {
              val img = clipboard.getData(DataFlavor.imageFlavor).asInstanceOf[java.awt.Image]
              pastedImageIcon = new ImageIcon(img.getScaledInstance(150, 150, java.awt.Image.SCALE_SMOOTH))
              imageStatusLabel.text = "Image attached successfully!"
              imageStatusLabel.foreground = new Color(39, 174, 96)
              e.consume()
            }
          } catch {
            case ex: Exception => 
              imageStatusLabel.text = "Failed to paste image!"
              imageStatusLabel.foreground = Color.RED
          }
        }
      }
    })

    val postButton = new Button("Post")
    
    // Dynamic buttons depending on whether it's own profile or someone else's profile
    val actionButton = new Button()
    val chatButton = new Button("? Open Chat") {
      // Chat button tabhi dikhega jab dono friends honge (ya khud ki profile pe ho)
      visible = false 
    }

    def updateButtonStates(): Unit = {
      if (profileUserId == viewerUserId) {
        actionButton.text = "View Follow Requests"
        chatButton.visible = false
      } else {
        val areFriends = friendsMap.getOrElse(viewerUserId, mutable.Set()).contains(profileUserId)
        if (areFriends) {
          actionButton.text = "Connected / Friends"
          actionButton.enabled = false
          chatButton.visible = true
        } else {
          val reqs = followRequests.getOrElse(profileUserId, mutable.Set())
          if (reqs.contains(viewerUserId)) {
            actionButton.text = "Request Sent"
            actionButton.enabled = false
          } else {
            actionButton.text = "Send Follow Request"
            actionButton.enabled = true
          }
          chatButton.visible = false
        }
      }
    }
    updateButtonStates()

    val statusMessage = new Label("") {
      font = new Font("Arial", Font.BOLD, 13)
      foreground = new Color(0, 128, 0)
    }

    val postsContainer = new BoxPanel(Orientation.Vertical) {
      border = Swing.EmptyBorder(10, 10, 10, 10)
    }
    val scrollPane = new ScrollPane(postsContainer) {
      preferredSize = new Dimension(570, 180)
    }

    def refreshPosts(): Unit = {
      postsContainer.contents.clear()
      val list = userPosts.getOrElse(profileUserId, mutable.ListBuffer())
      if (list.isEmpty) {
        postsContainer.contents += new Label("No posts yet.")
      } else {
        for (p <- list) {
          val panel = new BoxPanel(Orientation.Vertical) {
            border = Swing.CompoundBorder(Swing.LineBorder(Color.LIGHT_GRAY), Swing.EmptyBorder(8, 8, 8, 8))
            if (p.text.nonEmpty) {
              contents += new Label(p.text) { font = new Font("Arial", Font.PLAIN, 13) }
              contents += Swing.VStrut(5)
            }
            if (p.image != null) {
              contents += new Label() { icon = p.image }
            }
          }
          postsContainer.contents += panel
          postsContainer.contents += Swing.VStrut(10)
        }
      }
      postsContainer.revalidate()
      postsContainer.repaint()
    }

    refreshPosts()

    if (profileUserId != viewerUserId) {
      postInputField.editable = false
      postButton.enabled = false
      imageStatusLabel.text = "Viewing external profile (Posting disabled)"
    }

    contents = new BoxPanel(Orientation.Vertical) {
      border = Swing.EmptyBorder(15, 20, 20, 20)
      
      contents += new BoxPanel(Orientation.Horizontal) {
        contents += profileBackBtn
        contents += Swing.HStrut(15)
        contents += captionLabel
      }
      
      contents += Swing.VStrut(10)
      contents += new Label("Write caption / Paste Google Image (Ctrl+V) below:")
      contents += Swing.VStrut(5)
      contents += new ScrollPane(postInputField)
      contents += Swing.VStrut(3)
      contents += imageStatusLabel
      contents += Swing.VStrut(10)
      
      contents += new BoxPanel(Orientation.Horizontal) {
        if (profileUserId == viewerUserId) {
          contents += postButton
          contents += Swing.HStrut(15)
        }
        contents += actionButton
        contents += Swing.HStrut(10)
        contents += chatButton
      }
      
      contents += Swing.VStrut(10)
      contents += statusMessage
      contents += Swing.VStrut(5)
      contents += new Label("User Posts Feed:") { font = new Font("Arial", Font.BOLD, 13) }
      contents += Swing.VStrut(5)
      contents += scrollPane
    }

    listenTo(profileBackBtn)
    listenTo(postButton)
    listenTo(actionButton)
    listenTo(chatButton)

    reactions += {
      case ButtonClicked(`profileBackBtn`) =>
        this.close()

      case ButtonClicked(`postButton`) =>
        val content = postInputField.text.trim
        if (content.nonEmpty || pastedImageIcon != null) {
          val newPost = PostData(content, pastedImageIcon)
          userPosts.getOrElseUpdate(profileUserId, mutable.ListBuffer()) += newPost
          
          statusMessage.text = "Successfully Posted!"
          statusMessage.foreground = new Color(39, 174, 96)
          
          postInputField.text = ""
          pastedImageIcon = null
          imageStatusLabel.text = "No image attached."
          refreshPosts()
        } else {
          statusMessage.text = "Cannot post empty content!"
          statusMessage.foreground = Color.RED
        }

      case ButtonClicked(`actionButton`) =>
        if (profileUserId == viewerUserId) {
          // Open Follow Requests Management Dialog/Window
          val reqWindow = new FollowRequestsDialog(viewerUserId)
          reqWindow.visible = true
        } else {
          val reqs = followRequests.getOrElseUpdate(profileUserId, mutable.Set())
          if (!reqs.contains(viewerUserId)) {
            reqs += viewerUserId
            statusMessage.text = "Follow request sent successfully!"
            statusMessage.foreground = new Color(39, 174, 96)
            updateButtonStates()
          }
        }

      case ButtonClicked(`chatButton`) =>
        val chatWin = new LiveChatFrame(viewerUserId, profileUserId)
        chatWin.visible = true
    }
  }

  // 4. FOLLOW REQUESTS MANAGEMENT DIALOG (Accept / Reject)
  class FollowRequestsDialog(currentUserId: String) extends MainFrame {
    title = "Manage Follow Requests"
    minimumSize = new Dimension(400, 300)
    centerOnScreen()

    val requestsPanel = new BoxPanel(Orientation.Vertical) {
      border = Swing.EmptyBorder(10, 10, 10, 10)
    }
    val scroll = new ScrollPane(requestsPanel)

    def loadRequests(): Unit = {
      requestsPanel.contents.clear()
      val reqs = followRequests.getOrElse(currentUserId, mutable.Set())

      if (reqs.isEmpty) {
        requestsPanel.contents += new Label("No pending follow requests.") {
          font = new Font("Arial", Font.BOLD, 13)
        }
      } else {
        for (requester <- reqs) {
          val row = new BoxPanel(Orientation.Horizontal) {
            border = Swing.EmptyBorder(5, 5, 5, 5)
            contents += new Label(s"User ID: $requester") { font = new Font("Arial", Font.BOLD, 13) }
            contents += Swing.HStrut(20)
            
            val acceptBtn = new Button("Accept")
            val rejectBtn = new Button("Reject")

            listenTo(acceptBtn)
            listenTo(rejectBtn)

            reactions += {
              case ButtonClicked(`acceptBtn`) =>
                // Remove from requests
                reqs -= requester
                // Add mutual friends
                friendsMap.getOrElseUpdate(currentUserId, mutable.Set()) += requester
                friendsMap.getOrElseUpdate(requester, mutable.Set()) += currentUserId
                loadRequests()

              case ButtonClicked(`rejectBtn`) =>
                reqs -= requester
                loadRequests()
            }

            contents += acceptBtn
            contents += Swing.HStrut(5)
            contents += rejectBtn
          }
          requestsPanel.contents += row
          requestsPanel.contents += Swing.VStrut(5)
        }
      }
      requestsPanel.revalidate()
      requestsPanel.repaint()
    }

    loadRequests()

    val closeBtn = new Button("Close")
    contents = new BoxPanel(Orientation.Vertical) {
      border = Swing.EmptyBorder(15, 15, 15, 15)
      contents += new Label("Pending Requests:") { font = new Font("Arial", Font.BOLD, 15) }
      contents += Swing.VStrut(10)
      contents += scroll
      contents += Swing.VStrut(15)
      contents += closeBtn
    }

    listenTo(closeBtn)
    reactions += {
      case ButtonClicked(`closeBtn`) => this.close()
    }
  }

  // 5. LIVE CHAT WINDOW
  class LiveChatFrame(userA: String, userB: String) extends MainFrame {
    // Unique key generate karna taaki dono users ek hi chat room share karein
    private val chatKey = if (userA < userB) s"$userA-$userB" else s"$userB-$userA"

    title = s"Live Chat: $userA & $userB"
    minimumSize = new Dimension(450, 450)
    centerOnScreen()

    val chatArea = new TextArea(15, 35) {
      editable = false
      lineWrap = true
      wordWrap = true
      font = new Font("Arial", Font.PLAIN, 13)
    }

    val messageInput = new TextField(25)
    val sendButton = new Button("Send")

    def refreshChat(): Unit = {
      val messages = chatHistories.getOrElse(chatKey, mutable.ListBuffer())
      val sb = new StringBuilder
      for (msg <- messages) {
        sb.append(s"[${msg.sender}]:${msg.text}\n")
      }
      chatArea.text = sb.toString()
      // Scroll to bottom
      chatArea.peer.setCaretPosition(chatArea.text.length)
    }

    refreshChat()

    contents = new BoxPanel(Orientation.Vertical) {
      border = Swing.EmptyBorder(15, 15, 15, 15)
      contents += new Label(s"Chatting with: ${if (userA == currentUserId(userA, userB)) userB else userA}") {
        font = new Font("Arial", Font.BOLD, 14)
      }
      contents += Swing.VStrut(10)
      contents += new ScrollPane(chatArea)
      contents += Swing.VStrut(10)
      contents += new BoxPanel(Orientation.Horizontal) {
        contents += messageInput
        contents += Swing.HStrut(10)
        contents += sendButton
      }
    }

    def currentUserId(u1: String, u2: String): String = userA // Helper

    listenTo(sendButton)
    listenTo(messageInput.keys)

    reactions += {
      case ButtonClicked(`sendButton`) =>
        sendMessage()

      case KeyPressed(_, Key.Enter, _, _) =>
        sendMessage()
    }

    def sendMessage(): Unit = {
      val text = messageInput.text.trim
      if (text.nonEmpty) {
        val list = chatHistories.getOrElseUpdate(chatKey, mutable.ListBuffer())
        list += ChatMessage(userA, text)
        messageInput.text = ""
        refreshChat()
      }
    }
  }
}

KojoSocialApp.main(Array())