feat(messageslist): play voice messages by click and seek by dragging
This commit is contained in:
parent
bba502cba8
commit
7e5c3dc62c
5 changed files with 56 additions and 18 deletions
2
go.mod
2
go.mod
|
|
@ -15,7 +15,7 @@ require (
|
|||
github.com/andybalholm/brotli v1.2.6
|
||||
github.com/ayn2op/arikawa/v3 v3.0.0-20261012034659-5b006907b2de
|
||||
github.com/ayn2op/ningen/v3 v3.0.1-0.20260929010920-29794d5fa70e
|
||||
github.com/ayn2op/tview v0.0.0-20261013011207-c8c627013874
|
||||
github.com/ayn2op/tview v0.0.0-20261013221052-78efeee1ac7c
|
||||
github.com/bogdanfinn/fhttp v0.6.9
|
||||
github.com/bogdanfinn/tls-client v1.16.0
|
||||
github.com/gdamore/tcell/v3 v3.6.0
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -14,8 +14,8 @@ github.com/ayn2op/arikawa/v3 v3.0.0-20261012034659-5b006907b2de h1:bO825TKutshlG
|
|||
github.com/ayn2op/arikawa/v3 v3.0.0-20261012034659-5b006907b2de/go.mod h1:YARcqZmk7hDjXB+lexlXX4wDFHULafbGnF0gsD0tuNw=
|
||||
github.com/ayn2op/ningen/v3 v3.0.1-0.20260929010920-29794d5fa70e h1:QAei/IVEyZV7GcpkpcFUh37+QRqwrt57z7OT3ciHrhI=
|
||||
github.com/ayn2op/ningen/v3 v3.0.1-0.20260929010920-29794d5fa70e/go.mod h1:RsvYiMzi4Si1F7vmml5NCL2DaKIZXkNUQC7QlGTMtCw=
|
||||
github.com/ayn2op/tview v0.0.0-20261013011207-c8c627013874 h1:zXM8mKeDfXx+dzGEVTTZ0Xfo9vCXNBiX2qEWz/6KMV0=
|
||||
github.com/ayn2op/tview v0.0.0-20261013011207-c8c627013874/go.mod h1:3aWH6S5uOqOkZv7JFePEVHukTj9hPb6Lbynm59Sks2E=
|
||||
github.com/ayn2op/tview v0.0.0-20261013221052-78efeee1ac7c h1:WgEp8OeS15d15kjPVPOPpnSsaKYJZYjTIKxBbr8b5kI=
|
||||
github.com/ayn2op/tview v0.0.0-20261013221052-78efeee1ac7c/go.mod h1:3aWH6S5uOqOkZv7JFePEVHukTj9hPb6Lbynm59Sks2E=
|
||||
github.com/bdandy/go-errors v1.2.2 h1:WdFv/oukjTJCLa79UfkGmwX7ZxONAihKu4V0mLIs11Q=
|
||||
github.com/bdandy/go-errors v1.2.2/go.mod h1:NkYHl4Fey9oRRdbB1CoC6e84tuqQHiqrOcZpqFEkBxM=
|
||||
github.com/bdandy/go-socks4 v1.2.3 h1:Q6Y2heY1GRjCtHbmlKfnwrKVU/k81LS8mRGLRlmDlic=
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/ayn2op/discordo/internal/voice"
|
||||
"github.com/ayn2op/ningen/v3"
|
||||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/button"
|
||||
"github.com/ayn2op/tview/column"
|
||||
tviewimage "github.com/ayn2op/tview/image"
|
||||
"github.com/ayn2op/tview/keybind"
|
||||
|
|
@ -69,6 +70,8 @@ type Model struct {
|
|||
// voicePlayer plays the voice message played last, and voiceStatus is what it last said of it.
|
||||
voicePlayer *voice.Player
|
||||
voiceStatus voice.PlayerStatus
|
||||
// seeking reports whether the waveform of the voice message is dragged, which is sought once it is let go of.
|
||||
seeking bool
|
||||
|
||||
// kitty reports whether previews are drawn with kitty's graphics protocol.
|
||||
// kittyImages counts the images sent to the terminal.
|
||||
|
|
@ -682,6 +685,9 @@ func (m Model) voiceMessageView(a discord.Attachment) tview.Widget {
|
|||
}
|
||||
|
||||
waveform := slider.New(0, duration, status.Position, onSeek).
|
||||
OnPress(func(position time.Duration) tview.Msg { return seekPressMsg(position) }).
|
||||
OnRelease(seekReleaseMsg{}).
|
||||
Dragging(loaded && m.seeking).
|
||||
Width(layout.Fixed(len(rail))).
|
||||
Rail(func(cell, handle int) (string, tview.Style) {
|
||||
if loaded && cell <= handle {
|
||||
|
|
@ -691,7 +697,10 @@ func (m Model) voiceMessageView(a discord.Attachment) tview.Widget {
|
|||
return rail[cell], style.Dim(true)
|
||||
})
|
||||
|
||||
return row.New(text.New(symbol), waveform, text.New(label)).Spacing(1).Height(layout.Fixed(1))
|
||||
width, _ := text.New(symbol).Size()
|
||||
play := button.New().Label(symbol).Width(width).OnClick(playMsg(a))
|
||||
|
||||
return row.New(play, waveform, text.New(label)).Spacing(1).Height(layout.Fixed(1))
|
||||
}
|
||||
|
||||
func (m Model) drawEmbeds(builder *richtext.Builder, message discord.Message, baseStyle tview.Style, width int, contentRoot ast.Node, contentSource []byte) {
|
||||
|
|
@ -980,7 +989,9 @@ func (m Model) Update(msg tview.Msg) (Model, tview.Cmd) {
|
|||
case keybind.Matches(msg, m.cfg.Keybinds.MessagesList.OpenInBrowser.Keybind):
|
||||
return m, m.openInBrowser()
|
||||
case keybind.Matches(msg, m.cfg.Keybinds.MessagesList.Play.Keybind):
|
||||
return m, m.play()
|
||||
if message, ok := m.selectedMessage(); ok && isVoiceMessage(*message) {
|
||||
return m, m.play(message.Attachments[0])
|
||||
}
|
||||
case keybind.Matches(msg, m.cfg.Keybinds.MessagesList.SeekBackward.Keybind):
|
||||
return m, m.seek(m.voiceStatus.Position - seekStep)
|
||||
case keybind.Matches(msg, m.cfg.Keybinds.MessagesList.SeekForward.Keybind):
|
||||
|
|
@ -1038,10 +1049,31 @@ func (m Model) Update(msg tview.Msg) (Model, tview.Cmd) {
|
|||
|
||||
m.items[index].previews[msg.proxy] = preview
|
||||
return m, cmd
|
||||
case playMsg:
|
||||
return m, m.play(discord.Attachment(msg))
|
||||
case seekPressMsg:
|
||||
m.seeking = true
|
||||
m.voiceStatus.Position = time.Duration(msg)
|
||||
return m, nil
|
||||
case seekMsg:
|
||||
return m, m.seek(time.Duration(msg))
|
||||
m.voiceStatus.Position = time.Duration(msg)
|
||||
if m.seeking {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
return m, m.seek(m.voiceStatus.Position)
|
||||
case seekReleaseMsg:
|
||||
m.seeking = false
|
||||
return m, m.seek(m.voiceStatus.Position)
|
||||
case voiceMsg:
|
||||
m.voiceStatus = m.voicePlayer.Status()
|
||||
status := m.voicePlayer.Status()
|
||||
// The waveform shows where it is dragged to rather than what is playing, while that voice message is the one loaded.
|
||||
m.seeking = m.seeking && status.ID == m.voiceStatus.ID
|
||||
if m.seeking {
|
||||
status.Position = m.voiceStatus.Position
|
||||
}
|
||||
|
||||
m.voiceStatus = status
|
||||
return m, m.Init()
|
||||
case tview.ResizeMsg:
|
||||
cols, rows := msg.Size()
|
||||
|
|
|
|||
|
|
@ -159,14 +159,13 @@ func isVoiceMessage(message discord.Message) bool {
|
|||
return message.Flags&discord.IsVoiceMessage != 0 && len(message.Attachments) == 1
|
||||
}
|
||||
|
||||
// play plays the selected voice message, or pauses or resumes it if it is the one playing.
|
||||
func (m Model) play() tview.Cmd {
|
||||
message, ok := m.selectedMessage()
|
||||
if !ok || !isVoiceMessage(*message) {
|
||||
return nil
|
||||
}
|
||||
// playMsg asks to play the voice message whose play symbol was clicked.
|
||||
type playMsg discord.Attachment
|
||||
|
||||
attachment := message.Attachments[0]
|
||||
func (playMsg) messagesList() {}
|
||||
|
||||
// play plays the voice message that is attachment, or pauses or resumes it if it is the one playing.
|
||||
func (m Model) play(attachment discord.Attachment) tview.Cmd {
|
||||
return func() tview.Msg {
|
||||
if err := m.playVoiceMessage(attachment); err != nil {
|
||||
return ui.ErrorDialog("play voice message", err)
|
||||
|
|
@ -196,10 +195,16 @@ func (m Model) playVoiceMessage(attachment discord.Attachment) error {
|
|||
return m.voicePlayer.Play(attachment.ID, audio)
|
||||
}
|
||||
|
||||
// seekMsg asks to seek to where the waveform of a voice message was clicked.
|
||||
type seekMsg time.Duration
|
||||
// seekPressMsg is where the waveform of a voice message was pressed, seekMsg where it was dragged to after, and seekReleaseMsg that it was let go of.
|
||||
type (
|
||||
seekPressMsg time.Duration
|
||||
seekMsg time.Duration
|
||||
seekReleaseMsg struct{}
|
||||
)
|
||||
|
||||
func (seekMsg) messagesList() {}
|
||||
func (seekPressMsg) messagesList() {}
|
||||
func (seekMsg) messagesList() {}
|
||||
func (seekReleaseMsg) messagesList() {}
|
||||
|
||||
// seek moves the voice message that is playing or paused to position.
|
||||
func (m Model) seek(position time.Duration) tview.Cmd {
|
||||
|
|
|
|||
|
|
@ -367,7 +367,8 @@ func (p paneWidget) Draw(screen tview.Screen, area tview.Rectangle) {
|
|||
|
||||
func (p paneWidget) Handle(msg tview.Msg, area tview.Rectangle) tview.Msg {
|
||||
out := p.child.Handle(msg, area)
|
||||
if mouse, ok := msg.(tview.MouseMsg); ok && area.Contains(mouse.Position()) {
|
||||
// Mouse input the pane leaves, but for a press, goes on to the other panes, for a slider dragged out of one to follow the pointer.
|
||||
if mouse, ok := msg.(tview.MouseMsg); ok && area.Contains(mouse.Position()) && (out != msg || mouse.Action == tview.MouseLeftDown) {
|
||||
return paneMsg{pane: p.pane, msg: out, focus: mouse.Action == tview.MouseLeftDown}
|
||||
}
|
||||
// Keys and paste reach only the focused pane, so what it makes of them goes to that pane through route.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue