feat(tui): show voice activity with green color and speaker icon when users talk

This commit is contained in:
Jose Luis Montañes Ojados
2026-01-16 19:50:44 +01:00
parent 13f444193d
commit c55ace0c00
4 changed files with 129 additions and 14 deletions

View File

@@ -48,6 +48,9 @@ func main() {
// Create Bubble Tea program
p := tea.NewProgram(m, tea.WithAltScreen())
// Pass program reference to model for event handlers
m.SetProgram(p)
// Set up log capture
r, w, _ := os.Pipe()
if debugFile != nil {

View File

@@ -71,6 +71,12 @@ type Model struct {
// Error handling
lastError string
// Voice activity
talkingClients map[uint16]bool // ClientID -> isTalking
// Program reference for sending messages from event handlers
program *tea.Program
}
// addLog adds a message to the log panel
@@ -86,15 +92,21 @@ func (m *Model) addLog(format string, args ...any) {
// NewModel creates a new TUI model
func NewModel(serverAddr, nickname string) *Model {
return &Model{
serverAddr: serverAddr,
nickname: nickname,
focus: FocusChannels,
channels: []ChannelNode{},
chatMessages: []ChatMessage{},
logMessages: []string{"Starting..."},
serverAddr: serverAddr,
nickname: nickname,
focus: FocusChannels,
channels: []ChannelNode{},
chatMessages: []ChatMessage{},
logMessages: []string{"Starting..."},
talkingClients: make(map[uint16]bool),
}
}
// SetProgram sets the tea.Program reference for sending messages from event handlers
func (m *Model) SetProgram(p *tea.Program) {
m.program = p
}
// Init is called when the program starts
func (m *Model) Init() tea.Cmd {
return tea.Batch(
@@ -151,6 +163,11 @@ type chatMsg struct {
message string
}
type talkingStatusMsg struct {
clientID uint16
talking bool
}
type errorMsg struct {
err string
}
@@ -172,6 +189,16 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.client = msg.client
m.connecting = true
// Register event handlers that need to send messages to the TUI
if m.program != nil {
m.client.On(ts3client.EventTalkingStatus, func(e *ts3client.TalkingStatusEvent) {
m.program.Send(talkingStatusMsg{
clientID: e.ClientID,
talking: e.Talking,
})
})
}
// Connect asynchronously
m.client.ConnectAsync()
@@ -229,6 +256,15 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.lastError = msg.err
return m, nil
case talkingStatusMsg:
// Update talking status for client
if msg.talking {
m.talkingClients[msg.clientID] = true
} else {
delete(m.talkingClients, msg.clientID)
}
return m, nil
case logMsg:
// External log message
m.addLog("%s", string(msg))
@@ -264,6 +300,7 @@ func (m *Model) updateChannelList(channels []*ts3client.Channel) {
ID: cl.ID,
Nickname: cl.Nickname,
IsMe: cl.ID == m.selfID,
Talking: m.talkingClients[cl.ID],
})
}
}
@@ -478,10 +515,16 @@ func (m *Model) renderChannels() string {
for _, user := range ch.Users {
userPrefix := " └─ "
userStyle := lipgloss.NewStyle().Faint(true)
if user.IsMe {
// Talking users get bright green color and speaker icon
if user.Talking {
userStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("46")) // Bright green
userPrefix = " └─ 🔊 "
} else if user.IsMe {
userStyle = userStyle.Foreground(lipgloss.Color("82"))
userPrefix = " └─ ► "
}
lines = append(lines, userStyle.Render(userPrefix+user.Nickname))
}
}