package tui import ( "encoding/base64" "fmt" "log" "strings" "time" "whspbrd/pkg/cell_size" "whspbrd/pkg/clean_image" "whspbrd/pkg/render_image" "github.com/jroimartin/gocui" ) const ( chatViewColumn = 23 userIconPath = "./configs/icon.png" contactIconPathFmt = "./configs/servers/default/users/%s/icon.png" messageRowOffset = 2 messageRowIncrement = 3 ) func updateChatView(v *gocui.View) { v.Clear() clear := cleanimage.NewKittyImageCleaner() fmt.Print(clear.DeleteByColumn(chatViewColumn, false)) // Check if we have valid users if len(users) == 0 || selectedUserIdx >= len(users) { return } // Ensure selectedMessageIdx is within bounds if len(chatData.Messages) == 0 { return } if selectedMessageIdx < 0 { selectedMessageIdx = 0 } if selectedMessageIdx >= len(chatData.Messages) { selectedMessageIdx = len(chatData.Messages) - 1 } // Get view dimensions _, viewHeight := v.Size() // Calculate which messages should be visible // We want to show the selected message in the middle of the view when possible messagesPerView := viewHeight / messageRowIncrement if messagesPerView <= 0 { messagesPerView = 1 } // Calculate start index - show newest messages at bottom startIdx := max(0, min(selectedMessageIdx-(messagesPerView/2), len(chatData.Messages)-messagesPerView)) // Calculate scroll offset to position view correctly scrollOffset := startIdx * messageRowIncrement ox, _ := v.Origin() v.SetOrigin(ox, scrollOffset) // Get terminal cell size once for all messages w, h, err := cell_size.GetTerminalCellSizePixels() if err != nil { log.Println("Error getting terminal cell size:", err) w, h = 10, 20 // fallback values } if h > w { h = h * 2 w = 0 } else { w = w*3 - (w / 10) h = 0 } // Render only visible messages endIdx := min(startIdx+messagesPerView+1, len(chatData.Messages)) for i := startIdx; i < endIdx; i++ { msg := chatData.Messages[i] decoded, err := base64.StdEncoding.DecodeString(msg.Content) if err != nil { log.Printf("Error decoding message: %v", err) continue } decoded = []byte(strings.TrimSuffix(string(decoded), "\n")) t, err := time.Parse(time.RFC3339, msg.Timestamp) if err != nil { log.Printf("Error parsing timestamp: %v", err) t = time.Now() // fallback to current time } formattedTime := t.Format("2006-01-02 15:04") // Determine if this is a message from the user or the contact isUserMessage := !strings.EqualFold(msg.Sender, users[selectedUserIdx]) var senderLabel string var iconPath string if isUserMessage { senderLabel = Colors.Text(Colors.Base02) + "You (" + formattedTime + "):" + Colors.Reset iconPath = userIconPath } else { senderLabel = Colors.Text(Colors.Base05) + msg.Sender + " (" + formattedTime + "):" + Colors.Reset iconPath = fmt.Sprintf(contactIconPathFmt, strings.ToLower(msg.Sender)) } // Highlight selected message prefix := "\t\t\t\t\t" if i == selectedMessageIdx { prefix = "\x1b[7m\t\t\t\t\t" senderLabel = senderLabel + "\x1b[0m\x1b[7m" decoded = append([]byte("\x1b[0m\x1b[7m"), decoded...) decoded = append(decoded, []byte("\x1b[0m")...) } // Print message text fmt.Fprintf(v, "%s", prefix+senderLabel+"\n"+prefix+string(decoded)+"\n\n") // Calculate row position for image rendering rowPosition := i*messageRowIncrement + messageRowOffset - scrollOffset // Only render images that are visible in the view if rowPosition >= 0 && rowPosition < viewHeight { render_image.RenderImage(iconPath, rowPosition, chatViewColumn, w, h, false) } } } func sendMessage(g *gocui.Gui, v *gocui.View) error { if len(users) == 0 || selectedUserIdx >= len(users) { return nil } input := v.Buffer() v.Clear() v.SetCursor(0, 0) v.SetOrigin(0, 0) if err := WriteMessage(users[selectedUserIdx], "You", users[selectedUserIdx], input); err != nil { log.Printf("Error writing message: %v", err) return err } LoadMessages(users[selectedUserIdx]) chatView, err := g.View("chat") if err != nil { log.Printf("Error getting chat view: %v", err) return err } // Select newest message (at the end) if len(chatData.Messages) > 0 { selectedMessageIdx = len(chatData.Messages) - 1 } updateChatView(chatView) return nil } func scrollChatUp(g *gocui.Gui, v *gocui.View) error { if len(chatData.Messages) == 0 { return nil } chatView, err := g.View("chat") if err != nil { return err } // Move selection up (to older messages) if selectedMessageIdx > 0 { selectedMessageIdx-- updateChatView(chatView) } return nil } func scrollChatDown(g *gocui.Gui, v *gocui.View) error { if len(chatData.Messages) == 0 { return nil } chatView, err := g.View("chat") if err != nil { return err } // Move selection down (to newer messages) if selectedMessageIdx < len(chatData.Messages)-1 { selectedMessageIdx++ updateChatView(chatView) } return nil }