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)) // Reset scroll offset when switching contacts or if out of bounds if chatScrollOffset < 0 { chatScrollOffset = 0 } if chatScrollOffset > len(chatData.Messages) { chatScrollOffset = len(chatData.Messages) } // Set view origin based on scroll offset ox, _ := v.Origin() v.SetOrigin(ox, chatScrollOffset) // Get view dimensions to check if images are visible _, viewHeight := v.Size() for i, msg := range chatData.Messages { 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") w, h, err := cell_size.GetTerminalCellSizePixels() if err != nil { log.Println("Error getting terminal cell size:", err) continue } if h > w { h = h * 2 w = 0 } else { w = w*3 - (w / 10) h = 0 } if len(users) == 0 || selectedUserIdx >= len(users) { continue } // Calculate row position accounting for scroll offset // Each message takes 3 lines (messageRowIncrement) rowPosition := i*messageRowIncrement + messageRowOffset - chatScrollOffset // Only render images that are visible in the view if rowPosition >= 0 && rowPosition < viewHeight { if !strings.EqualFold(msg.Sender, users[selectedUserIdx]) { fmt.Fprintf(v, "%s", "\t\t\t\t\t"+Colors.Text(Colors.Base02)+"You ("+formattedTime+"):"+Colors.Reset+"\n\t\t\t\t\t"+string(decoded)+"\n\n") render_image.RenderImage(userIconPath, rowPosition, chatViewColumn, w, h, false) } else { fmt.Fprintf(v, "%s", "\t\t\t\t\t"+Colors.Text(Colors.Base05)+msg.Sender+" ("+formattedTime+"):"+Colors.Reset+"\n\t\t\t\t\t"+string(decoded)+"\n\n") iconPath := fmt.Sprintf(contactIconPathFmt, strings.ToLower(msg.Sender)) render_image.RenderImage(iconPath, rowPosition, chatViewColumn, w, h, false) } } else { // Still print the text even if image is not visible if !strings.EqualFold(msg.Sender, users[selectedUserIdx]) { fmt.Fprintf(v, "%s", "\t\t\t\t\t"+Colors.Text(Colors.Base02)+"You ("+formattedTime+"):"+Colors.Reset+"\n\t\t\t\t\t"+string(decoded)+"\n\n") } else { fmt.Fprintf(v, "%s", "\t\t\t\t\t"+Colors.Text(Colors.Base05)+msg.Sender+" ("+formattedTime+"):"+Colors.Reset+"\n\t\t\t\t\t"+string(decoded)+"\n\n") } } } } 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 } // Reset scroll to bottom when sending a new message chatScrollOffset = 0 updateChatView(chatView) return nil } func scrollChatUp(g *gocui.Gui, v *gocui.View) error { chatView, err := g.View("chat") if err != nil { return err } // Scroll up by 1 line if chatScrollOffset > 0 { chatScrollOffset-- updateChatView(chatView) } return nil } func scrollChatDown(g *gocui.Gui, v *gocui.View) error { chatView, err := g.View("chat") if err != nil { return err } // Scroll down by 1 line maxScroll := len(chatData.Messages) * messageRowIncrement if chatScrollOffset < maxScroll { chatScrollOffset++ updateChatView(chatView) } return nil }