From 0afb682db138d6c4b644426bccb5c93008d35094 Mon Sep 17 00:00:00 2001 From: Kyren223 Date: Fri, 28 Feb 2025 21:23:43 +0200 Subject: Added the ability to view user profiles --- internal/client/ui/core/banlist/banlist.go | 11 +- internal/client/ui/core/banview/banview.go | 6 - internal/client/ui/core/chat/chat.go | 11 ++ internal/client/ui/core/core.go | 20 +++- internal/client/ui/core/memberlist/memberlist.go | 8 +- internal/client/ui/core/profile/profile.go | 145 +++++++++++++++++++++++ internal/client/ui/core/signallist/signallist.go | 12 ++ 7 files changed, 202 insertions(+), 11 deletions(-) create mode 100644 internal/client/ui/core/profile/profile.go (limited to 'internal/client/ui/core') diff --git a/internal/client/ui/core/banlist/banlist.go b/internal/client/ui/core/banlist/banlist.go index 286d453..7daff55 100644 --- a/internal/client/ui/core/banlist/banlist.go +++ b/internal/client/ui/core/banlist/banlist.go @@ -165,13 +165,20 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.SetIndex(m.index + m.height/2) case "p": - // TODO: profile + if 0 <= m.index && m.index < len(members) { + member := members[m.index] + return m, func() tea.Msg { + return ui.ProfilePopupMsg{ + User: member.UserID, + } + } + } case "v", "V": if 0 <= m.index && m.index < len(members) { member := members[m.index] return m, func() tea.Msg { - return ui.BanViewPopupmsg{ + return ui.BanViewPopupMsg{ Network: m.networkId, User: member.UserID, } diff --git a/internal/client/ui/core/banview/banview.go b/internal/client/ui/core/banview/banview.go index a6ff91b..bcfd05b 100644 --- a/internal/client/ui/core/banview/banview.go +++ b/internal/client/ui/core/banview/banview.go @@ -13,12 +13,6 @@ import ( var width = 48 -const ( - BanReasonField = iota - BanField - FieldCount -) - type Model struct { banReason string name string diff --git a/internal/client/ui/core/chat/chat.go b/internal/client/ui/core/chat/chat.go index c7735f7..9b0f240 100644 --- a/internal/client/ui/core/chat/chat.go +++ b/internal/client/ui/core/chat/chat.go @@ -413,6 +413,17 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.SetIndex(Unselected) } + case "p": + if m.selectedMessage == nil { + return m, nil + } + + return m, func() tea.Msg { + return ui.ProfilePopupMsg{ + User: m.selectedMessage.SenderID, + } + } + case "x", "d": if m.selectedMessage == nil { return m, nil diff --git a/internal/client/ui/core/core.go b/internal/client/ui/core/core.go index fce8414..ba02ba9 100644 --- a/internal/client/ui/core/core.go +++ b/internal/client/ui/core/core.go @@ -27,6 +27,7 @@ import ( "github.com/kyren223/eko/internal/client/ui/core/networkjoin" "github.com/kyren223/eko/internal/client/ui/core/networklist" "github.com/kyren223/eko/internal/client/ui/core/networkupdate" + "github.com/kyren223/eko/internal/client/ui/core/profile" "github.com/kyren223/eko/internal/client/ui/core/signaladd" "github.com/kyren223/eko/internal/client/ui/core/signallist" "github.com/kyren223/eko/internal/client/ui/core/state" @@ -80,6 +81,7 @@ type Model struct { banReasonPopup *banreason.Model banViewPopup *banview.Model signalAddPopup *signaladd.Model + profilePopup *profile.Model networkList networklist.Model signalList signallist.Model frequencyList frequencylist.Model @@ -171,6 +173,8 @@ func (m Model) View() string { popup = m.banViewPopup.View() } else if m.signalAddPopup != nil { popup = m.signalAddPopup.View() + } else if m.profilePopup != nil { + popup = m.profilePopup.View() } else { assert.Never("missing handling of a popup!") } @@ -349,10 +353,14 @@ func (m *Model) updateConnected(message tea.Msg) tea.Cmd { popup := banreason.New(msg.User, msg.Network) m.banReasonPopup = &popup - case ui.BanViewPopupmsg: + case ui.BanViewPopupMsg: popup := banview.New(msg.User, msg.Network) m.banViewPopup = &popup + case ui.ProfilePopupMsg: + popup := profile.New(msg.User) + m.profilePopup = &popup + case tea.KeyMsg: switch msg.String() { case "n": @@ -482,6 +490,7 @@ func (m *Model) updateConnected(message tea.Msg) tea.Cmd { m.banReasonPopup = nil m.banViewPopup = nil m.signalAddPopup = nil + m.profilePopup = nil } case "enter": @@ -538,6 +547,8 @@ func (m *Model) updateConnected(message tea.Msg) tea.Cmd { m.signalList.SetIndex(i) } return cmd + } else if m.profilePopup != nil { + m.profilePopup = nil } default: @@ -689,6 +700,10 @@ func (m *Model) updatePopups(msg tea.Msg) tea.Cmd { popup, cmd := m.signalAddPopup.Update(msg) m.signalAddPopup = &popup return cmd + } else if m.profilePopup != nil { + popup, cmd := m.profilePopup.Update(msg) + m.profilePopup = &popup + return cmd } return nil } @@ -703,7 +718,8 @@ func (m *Model) HasPopup() bool { m.networkJoinPopup != nil || m.banReasonPopup != nil || m.banViewPopup != nil || - m.signalAddPopup != nil + m.signalAddPopup != nil || + m.profilePopup != nil } func calculateNotifications() { diff --git a/internal/client/ui/core/memberlist/memberlist.go b/internal/client/ui/core/memberlist/memberlist.go index 3e07446..14ff342 100644 --- a/internal/client/ui/core/memberlist/memberlist.go +++ b/internal/client/ui/core/memberlist/memberlist.go @@ -200,7 +200,13 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.SetIndex(m.index + m.height/2) case "p": - // TODO: profile + member := m.Members()[m.index] + + return m, func() tea.Msg { + return ui.ProfilePopupMsg{ + User: member.UserID, + } + } // Normal case "T": diff --git a/internal/client/ui/core/profile/profile.go b/internal/client/ui/core/profile/profile.go new file mode 100644 index 0000000..601b99c --- /dev/null +++ b/internal/client/ui/core/profile/profile.go @@ -0,0 +1,145 @@ +package profile + +import ( + "crypto/ed25519" + "strconv" + "strings" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/kyren223/eko/internal/client/ui/colors" + "github.com/kyren223/eko/internal/client/ui/core/state" + "github.com/kyren223/eko/pkg/assert" + "github.com/kyren223/eko/pkg/snowflake" + "golang.org/x/crypto/ssh" +) + +var width = 70 + +type Model struct { + id snowflake.ID +} + +func New(userId snowflake.ID) Model { + return Model{ + id: userId, + } +} + +func (m Model) Init() tea.Cmd { + return nil +} + +func (m Model) View() string { + user := state.State.Users[m.id] + + var builder strings.Builder + + headerStyle := lipgloss.NewStyle(). + PaddingLeft(2). + Width(width). + Background(colors.Background). + Foreground(colors.Focus) + + name := user.Name + name = lipgloss.NewStyle(). + MarginBackground(colors.Background). + Background(colors.Gold). + Foreground(colors.Background). + Bold(true). + Render(name) + paddingLeft := lipgloss.NewStyle(). + MarginLeft(2). + Background(colors.Background). + Foreground(colors.Gold). + Render("") + paddingRight := lipgloss.NewStyle(). + MarginRight(2). + Background(colors.Background). + Foreground(colors.Gold). + Render("") + builder.WriteString(paddingLeft) + builder.WriteString(name) + builder.WriteString(paddingRight) + builder.WriteByte('\n') + builder.WriteByte('\n') + + publicDMs := "Private DMs" + if user.IsPublicDM { + publicDMs = "Public DMs" + } + + id := strconv.FormatInt(int64(user.ID), 10) + info := lipgloss.NewStyle(). + PaddingLeft(2). + Width(width). + Background(colors.Background). + Foreground(colors.LightGray). + Render("ID: " + id + " | " + publicDMs) + builder.WriteString(info) + builder.WriteByte('\n') + builder.WriteByte('\n') + + publicKeyHeader := headerStyle.Render("Public Key (ssh-ed25519)") + publicKey := lipgloss.NewStyle(). + PaddingLeft(2). + Width(width). + Render(publicKeyToSshString(user.PublicKey)) + builder.WriteString(publicKeyHeader) + builder.WriteByte('\n') + builder.WriteByte('\n') + builder.WriteString(publicKey) + builder.WriteByte('\n') + + aboutMeHeader := headerStyle.Render("About me") + builder.WriteString(aboutMeHeader) + builder.WriteByte('\n') + builder.WriteByte('\n') + + textBg := colors.BackgroundHighlight + textFg := colors.White + bg := colors.Background + + description := user.Description + if description == "" { + description = lipgloss.NewStyle(). + Foreground(colors.Purple). + Render("No description was provided") + } + + excessWidth := 4 - 2 + bgStyle := lipgloss.NewStyle().Background(textBg).Foreground(bg) + topMiddle := strings.Repeat(" ", width-excessWidth) + top := bgStyle.Render("🭠🭘" + topMiddle + "🭣🭕") + middle := lipgloss.NewStyle().Width(width+2).Padding(0, 2). + Background(textBg).Foreground(textFg).Render(description) + bgStyle2 := lipgloss.NewStyle().Foreground(textBg) + bottomMiddle := strings.Repeat("█", width-excessWidth) + bottom := bgStyle2.Render("🭥🭓" + bottomMiddle + "🭞🭚") + aboutMe := lipgloss.JoinVertical(lipgloss.Left, top, middle, bottom) + + builder.WriteString(aboutMe) + + content := builder.String() + + return lipgloss.NewStyle(). + Border(lipgloss.ThickBorder()). + Padding(1, 2). + Align(lipgloss.Left, lipgloss.Center). + BorderBackground(colors.Background). + BorderForeground(colors.White). + Background(colors.Background). + Foreground(colors.White). + Render(content) +} + +func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { + return m, nil +} + +func publicKeyToSshString(pub ed25519.PublicKey) string { + sshPubKey, err := ssh.NewPublicKey(pub) + assert.NoError(err, "converting to ssh key should never fail") + sshPubKeyBytes := ssh.MarshalAuthorizedKey(sshPubKey) + return strings.Split(string(sshPubKeyBytes), " ")[1] +} diff --git a/internal/client/ui/core/signallist/signallist.go b/internal/client/ui/core/signallist/signallist.go index f20724d..b8476ec 100644 --- a/internal/client/ui/core/signallist/signallist.go +++ b/internal/client/ui/core/signallist/signallist.go @@ -185,6 +185,18 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { case "i": _ = clipboard.WriteAll(strconv.FormatInt(int64(*state.UserID), 10)) + case "p": + if m.index == -1 { + return m, nil + } + userId := state.Data.Signals[m.index] + + return m, func() tea.Msg { + return ui.ProfilePopupMsg{ + User: userId, + } + } + case "c": if m.index == -1 { return m, nil -- cgit v1.3.1