1
0
forked from sunglocto/pi-im

18 Commits

Author SHA1 Message Date
ff121b42d9 Fix splits taking up half the screen 2025-10-09 05:25:10 +01:00
d00f266a4e add status to member list, @here, more delta menu buttons, and choice to suspend to desktop when hitting close 2025-09-30 19:29:31 +01:00
934d06b6b0 Remove MucsToJoin from README.md 2025-09-30 06:34:55 +01:00
d612d16e52 remove unnecesary code 2025-09-28 15:56:08 +01:00
e9c1f8dbe1 Make it so that the bookmarks button replaces the content of the first tab 2025-09-28 15:54:34 +01:00
7ec143bc98 Bookmark management 2025-09-28 09:45:56 +01:00
adf86896e9 Add basic member lists, fixes #8 2025-09-27 13:10:49 +01:00
3f157d06c7 Update to match current oasisSdk version, fix bookmarks, update deps, and introduce a new bug 2025-09-27 09:45:53 +01:00
f4d3f76841 Revert last commit 2025-09-14 13:52:20 +01:00
d2ec518846 i forgor 2025-09-14 13:51:21 +01:00
d63fc073de Fix timeline glitches 2025-09-07 08:10:47 +01:00
0c26b7a8cf Convert newlines to symbol in reply text 2025-09-04 18:27:51 +01:00
d45ed57572 refactor 2025-09-04 18:11:37 +01:00
c98cbe81ed Format code 2025-09-04 18:02:53 +01:00
ea47f10b5c Add reply text fallback for systems which do not have UTF-8 2025-09-03 13:23:41 +01:00
91215094e4 Merge branch 'master' of https://forge.sunglocto.net/sunglocto/pi-im 2025-09-03 09:32:34 +01:00
082d2757f6 Fix image uploading 2025-09-03 09:32:24 +01:00
596bd6bdba Merge pull request 'Add an issue template' (#20) from nyx/pi-im:nyx-patch-4 into master
Reviewed-on: sunglocto/pi-im#20
2025-09-03 08:02:17 +00:00
4 changed files with 456 additions and 227 deletions

View File

@@ -32,8 +32,6 @@ If you want to add MUCs or DMs, you must configure the program by editing the pi
<DisplayName>sunglocto</DisplayName> <DisplayName>sunglocto</DisplayName>
<TLSoff>false</TLSoff> <TLSoff>false</TLSoff>
<StartTLS>true</StartTLS> <StartTLS>true</StartTLS>
<MucsToJoin>room1@muc.example.com</MucsToJoin>
<MucsToJoin>room2@muc.example.com</MucsToJoin>
</Login> </Login>
<Notifications>true</Notifications> <Notifications>true</Notifications>
<DMs>person1@example.com</DMs> <DMs>person1@example.com</DMs>

6
go.mod
View File

@@ -1,15 +1,15 @@
module pi-im module pi-im
go 1.24.6 go 1.25.1
require ( require (
fyne.io/fyne/v2 v2.6.3 fyne.io/fyne/v2 v2.6.3
fyne.io/x/fyne v0.0.0-20250827163406-39fd826f385e fyne.io/x/fyne v0.0.0-20250910205345-ecc79984d005
github.com/makeworld-the-better-one/go-isemoji v1.3.0 github.com/makeworld-the-better-one/go-isemoji v1.3.0
github.com/rrivera/identicon v0.0.0-20240116195454-d5ba35832c0d github.com/rrivera/identicon v0.0.0-20240116195454-d5ba35832c0d
github.com/shreve/musicwand v0.0.1 github.com/shreve/musicwand v0.0.1
mellium.im/xmpp v0.22.0 mellium.im/xmpp v0.22.0
pain.agency/oasis-sdk v0.0.0-20250831105702-85385dca3a95 pain.agency/oasis-sdk v0.0.0-20250918002549-5a45c8afedcd
) )
require ( require (

8
go.sum
View File

@@ -2,8 +2,8 @@ fyne.io/fyne/v2 v2.6.3 h1:cvtM2KHeRuH+WhtHiA63z5wJVBkQ9+Ay0UMl9PxFHyA=
fyne.io/fyne/v2 v2.6.3/go.mod h1:NGSurpRElVoI1G3h+ab2df3O5KLGh1CGbsMMcX0bPIs= fyne.io/fyne/v2 v2.6.3/go.mod h1:NGSurpRElVoI1G3h+ab2df3O5KLGh1CGbsMMcX0bPIs=
fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg= fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg=
fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
fyne.io/x/fyne v0.0.0-20250827163406-39fd826f385e h1:oJM+HGkpSuq1J+JqUq/jo7KPrKj2K2/VIZlyus04w3Y= fyne.io/x/fyne v0.0.0-20250910205345-ecc79984d005 h1:CmdApAnt07juL0dhcFReFGpADUdRjjm0eDVJDS01uKE=
fyne.io/x/fyne v0.0.0-20250827163406-39fd826f385e/go.mod h1:u3LF1EkElytjOT8OHxft16trctGndF9qpsoH6YIDOUU= fyne.io/x/fyne v0.0.0-20250910205345-ecc79984d005/go.mod h1:kQFmF5meMIXnyCioLoCrXol5opruSS/PHYGKMBIE3SU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
@@ -113,5 +113,5 @@ mellium.im/xmlstream v0.15.4 h1:gLKxcWl4rLMUpKgtzrTBvr4OexPeO/edYus+uK3F6ZI=
mellium.im/xmlstream v0.15.4/go.mod h1:yXaCW2++fmVO4L9piKVkyLDqnCmictVYF7FDQW8prb4= mellium.im/xmlstream v0.15.4/go.mod h1:yXaCW2++fmVO4L9piKVkyLDqnCmictVYF7FDQW8prb4=
mellium.im/xmpp v0.22.0 h1:UthQVSwEAr7SNrmyc90c2ykGpVHxjn/3yw8Ey4+Im8s= mellium.im/xmpp v0.22.0 h1:UthQVSwEAr7SNrmyc90c2ykGpVHxjn/3yw8Ey4+Im8s=
mellium.im/xmpp v0.22.0/go.mod h1:WSjq12nhREFD88Vy/0WD6Q8inE8t6a8w7QjzwivWitw= mellium.im/xmpp v0.22.0/go.mod h1:WSjq12nhREFD88Vy/0WD6Q8inE8t6a8w7QjzwivWitw=
pain.agency/oasis-sdk v0.0.0-20250831105702-85385dca3a95 h1:BcB7/hnMnQIU+pERvQRGFMt9i0/o8XCnHbK1kYG3/K4= pain.agency/oasis-sdk v0.0.0-20250918002549-5a45c8afedcd h1:oLKI4XqaHpJeegwRxRYH9hepFO4GYKCr6C7cLwqXTK8=
pain.agency/oasis-sdk v0.0.0-20250831105702-85385dca3a95/go.mod h1:eyvDgfpHo+9bdB/AkMEMZ3ETeoSONTULVx9X4w9kGAU= pain.agency/oasis-sdk v0.0.0-20250918002549-5a45c8afedcd/go.mod h1:eyvDgfpHo+9bdB/AkMEMZ3ETeoSONTULVx9X4w9kGAU=

641
main.go
View File

@@ -2,10 +2,10 @@ package main
import ( import (
//core - required //core - required
"context"
"encoding/xml" "encoding/xml"
"errors" "errors"
"fmt" "fmt"
_ "image/color"
"io" "io"
"log" "log"
"math/rand/v2" "math/rand/v2"
@@ -20,6 +20,7 @@ import (
"fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/driver/desktop"
"fyne.io/fyne/v2/storage" "fyne.io/fyne/v2/storage"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
@@ -29,21 +30,20 @@ import (
"github.com/shreve/musicwand/pkg/mpris" "github.com/shreve/musicwand/pkg/mpris"
// xmpp - required // xmpp - required
_ "mellium.im/xmpp/disco" "mellium.im/xmpp/bookmarks"
"mellium.im/xmpp/jid" "mellium.im/xmpp/jid"
"mellium.im/xmpp/muc" "mellium.im/xmpp/muc"
_ "mellium.im/xmpp/stanza"
oasisSdk "pain.agency/oasis-sdk" oasisSdk "pain.agency/oasis-sdk"
// gui - optional
// catppuccin "github.com/mbaklor/fyne-catppuccin"
// TODO: integrated theme switcher // TODO: integrated theme switcher
) )
var version string = "3i" var version string = "3.1i"
var statBar widget.Label var statBar widget.Label
var chatInfo fyne.Container var chatInfo fyne.Container
var chatSidebar fyne.Container var chatSidebar fyne.Container
var replyNameIcon string = ">"
var replyBodyIcon string = ">"
var newlineIcon string = " |-> "
var agreesToSendingHotFuckIntoChannel bool = false var agreesToSendingHotFuckIntoChannel bool = false
// by sunglocto // by sunglocto
@@ -58,6 +58,7 @@ type Message struct {
Raw oasisSdk.XMPPChatMessage Raw oasisSdk.XMPPChatMessage
Important bool Important bool
Readers []jid.JID Readers []jid.JID
Reactions map[string]string
} }
type ChatTab struct { type ChatTab struct {
@@ -67,6 +68,7 @@ type ChatTab struct {
isMuc bool isMuc bool
Muc *muc.Channel Muc *muc.Channel
UpdateSidebar bool UpdateSidebar bool
Members map[string]oasisSdk.UserPresence
} }
type ChatTabUI struct { type ChatTabUI struct {
@@ -79,6 +81,21 @@ type CustomMultiLineEntry struct {
widget.Entry widget.Entry
} }
func isUTF8Locale() bool {
localeVars := []string{"LC_ALL", "LC_CTYPE", "LANG"}
for _, envVar := range localeVars {
value := os.Getenv(envVar)
if strings.Contains(strings.ToLower(value), "utf-8") {
return true
}
}
return false
}
func isWindows() bool {
return os.PathSeparator == '\\' && os.PathListSeparator == ';'
}
func NewCustomMultiLineEntry() *CustomMultiLineEntry { func NewCustomMultiLineEntry() *CustomMultiLineEntry {
entry := &CustomMultiLineEntry{} entry := &CustomMultiLineEntry{}
entry.ExtendBaseWidget(entry) entry.ExtendBaseWidget(entry)
@@ -119,31 +136,12 @@ var DMs []string
var chatTabs = make(map[string]*ChatTab) var chatTabs = make(map[string]*ChatTab)
var UITabs = make(map[string]*ChatTabUI) var UITabs = make(map[string]*ChatTabUI)
var AppTabs *container.AppTabs var AppTabs *container.DocTabs
var selectedId widget.ListItemID var selectedId widget.ListItemID
var replying bool = false var replying bool = false
var notifications bool var notifications bool
var connection bool = true var connection bool = true
/*
func (m myTheme) Font(style fyne.TextStyle) fyne.Resource {
return resourceAppleColorEmojiTtf
}
func (m myTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) color.Color {
return adwaita.AdwaitaTheme().Color(name, fyne.CurrentApp().Settings().ThemeVariant())
}
func (m myTheme) Icon(name fyne.ThemeIconName) fyne.Resource {
return theme.DefaultTheme().Icon(name)
}
/*
func (m myTheme) Font(style fyne.TextStyle) fyne.Resource {
return theme.DefaultTheme().Font(style)
}
*/
var scrollDownOnNewMessage bool = true var scrollDownOnNewMessage bool = true
var w fyne.Window var w fyne.Window
var a fyne.App var a fyne.App
@@ -162,17 +160,24 @@ func CreateUITab(chatJidStr string) ChatTabUI {
ico.FillMode = canvas.ImageFillOriginal ico.FillMode = canvas.ImageFillOriginal
author := widget.NewLabel("author") author := widget.NewLabel("author")
author.TextStyle.Bold = true author.TextStyle.Bold = true
author.Selectable = true
content := widget.NewLabel("content") content := widget.NewLabel("content")
content.Wrapping = fyne.TextWrapWord content.Wrapping = fyne.TextWrapWord
content.Selectable = true content.Selectable = true
content.Importance = widget.MediumImportance
//content.SizeName = fyne.ThemeSizeName(theme.SizeNameText)
icon := theme.FileVideoIcon() icon := theme.FileVideoIcon()
replytext := widget.NewLabel(">fallback reply text") replytext := widget.NewLabel(">fallback reply text")
replytext.Hide() replytext.Hide()
replytext.Importance = widget.SuccessImportance replytext.Importance = widget.SuccessImportance
replytext.Selectable = true
btn := widget.NewButtonWithIcon("View media", icon, func() { btn := widget.NewButtonWithIcon("View media", icon, func() {
}) })
return container.NewVBox(replytext, container.NewHBox(ico, author), content, btn)
reactions := container.NewHBox()
return container.NewVBox(replytext, container.NewHBox(ico, author), content, btn, reactions)
}, },
func(i widget.ListItemID, co fyne.CanvasObject) { func(i widget.ListItemID, co fyne.CanvasObject) {
vbox := co.(*fyne.Container) vbox := co.(*fyne.Container)
@@ -192,16 +197,25 @@ func CreateUITab(chatJidStr string) ChatTabUI {
author := authorBox.Objects[1].(*widget.Label) author := authorBox.Objects[1].(*widget.Label)
content := vbox.Objects[2].(*widget.Label) content := vbox.Objects[2].(*widget.Label)
btn := vbox.Objects[3].(*widget.Button) btn := vbox.Objects[3].(*widget.Button)
reactions := vbox.Objects[4].(*fyne.Container)
reactions = container.NewVBox()
for _, reaction := range chatTabs[chatJidStr].Messages[i].Reactions {
reactions.Add(widget.NewLabel(reaction))
}
reactions.Refresh()
if chatTabs[chatJidStr].Messages[i].Important { if chatTabs[chatJidStr].Messages[i].Important {
//content.Importance = widget.DangerImportance TODO: Fix highlighting messages with mentions, it's currently broken content.Importance = widget.DangerImportance
} }
btn.Hidden = true // Hide by default btn.Hidden = true // Hide by default
msgContent := chatTabs[chatJidStr].Messages[i].Content msgContent := chatTabs[chatJidStr].Messages[i].Content
if chatTabs[chatJidStr].Messages[i].ImageURL != "" { if chatTabs[chatJidStr].Messages[i].Raw.OutOfBandMedia != nil {
btn.Hidden = false btn.Hidden = false
btn.OnTapped = func() { btn.OnTapped = func() {
go func() { go func() {
u, err := storage.ParseURI(chatTabs[chatJidStr].Messages[i].ImageURL) u, err := storage.ParseURI(chatTabs[chatJidStr].Messages[i].Raw.OutOfBandMedia.URL)
if err != nil { if err != nil {
fyne.Do(func() { fyne.Do(func() {
dialog.ShowError(err, w) dialog.ShowError(err, w)
@@ -210,7 +224,7 @@ func CreateUITab(chatJidStr string) ChatTabUI {
} }
if strings.HasSuffix(chatTabs[chatJidStr].Messages[i].ImageURL, "mp4") || strings.HasSuffix(chatTabs[chatJidStr].Messages[i].ImageURL, "mp3") || strings.HasSuffix(chatTabs[chatJidStr].Messages[i].ImageURL, "gif") || strings.HasSuffix(chatTabs[chatJidStr].Messages[i].ImageURL, "webp") { // FIXME: This code is fucking terrible // TODO: Could check mime? if strings.HasSuffix(chatTabs[chatJidStr].Messages[i].ImageURL, "mp4") || strings.HasSuffix(chatTabs[chatJidStr].Messages[i].ImageURL, "mp3") || strings.HasSuffix(chatTabs[chatJidStr].Messages[i].ImageURL, "gif") || strings.HasSuffix(chatTabs[chatJidStr].Messages[i].ImageURL, "webp") { // FIXME: This code is fucking terrible // TODO: Could check mime?
url, err := url.Parse(chatTabs[chatJidStr].Messages[i].ImageURL) url, err := url.Parse(chatTabs[chatJidStr].Messages[i].Raw.OutOfBandMedia.URL)
if err != nil { if err != nil {
fyne.Do(func() { fyne.Do(func() {
dialog.ShowError(err, w) dialog.ShowError(err, w)
@@ -249,13 +263,13 @@ func CreateUITab(chatJidStr string) ChatTabUI {
for i := len(chatTabs[chatJidStr].Messages) - 1; i >= 0; i-- { for i := len(chatTabs[chatJidStr].Messages) - 1; i >= 0; i-- {
if reply.ID == chatTabs[chatJidStr].Messages[i].Raw.StanzaID.ID { if reply.ID == chatTabs[chatJidStr].Messages[i].Raw.StanzaID.ID {
replytext.Show() replytext.Show()
replytext.SetText(fmt.Sprintf(">%s", chatTabs[chatJidStr].Messages[i].Content)) replytext.SetText(fmt.Sprintf("%s %s", replyBodyIcon, strings.ReplaceAll(chatTabs[chatJidStr].Messages[i].Content, "\n", newlineIcon)))
guy = chatTabs[chatJidStr].Messages[i].Author guy = chatTabs[chatJidStr].Messages[i].Author
break break
} }
} }
author.SetText(fmt.Sprintf("%s > %s", chatTabs[chatJidStr].Messages[i].Author, guy)) author.SetText(fmt.Sprintf("%s %s %s", chatTabs[chatJidStr].Messages[i].Author, replyNameIcon, guy))
} else { } else {
author.SetText(chatTabs[chatJidStr].Messages[i].Author) author.SetText(chatTabs[chatJidStr].Messages[i].Author)
replytext.Hide() replytext.Hide()
@@ -274,7 +288,13 @@ func CreateUITab(chatJidStr string) ChatTabUI {
} }
scroller.SetItemHeight(i, vbox.MinSize().Height) scroller.SetItemHeight(i, vbox.MinSize().Height)
scroller.CreateItem().Refresh()
vbox.Refresh() vbox.Refresh()
/*
fyne.Do(func() {
scroller.RefreshItem(i)
})
*/
}, },
) )
@@ -308,6 +328,7 @@ func addChatTab(isMuc bool, chatJid jid.JID, nick string) {
Nick: nick, Nick: nick,
Messages: []Message{}, Messages: []Message{},
isMuc: isMuc, isMuc: isMuc,
Members: make(map[string]oasisSdk.UserPresence),
} }
myUITab := CreateUITab(chatJid.String()) myUITab := CreateUITab(chatJid.String())
@@ -323,7 +344,8 @@ func addChatTab(isMuc bool, chatJid jid.JID, nick string) {
} }
fyne.Do(func() { fyne.Do(func() {
AppTabs.Append(container.NewTabItemWithIcon(chatJid.String(), icon, myUITab.Scroller)) myTab := container.NewTabItemWithIcon(chatJid.String(), icon, myUITab.Scroller)
AppTabs.Append(myTab)
}) })
} }
@@ -395,10 +417,8 @@ func dropToSignInPage(reason string) {
func main() { func main() {
muc.Since(time.Now()) muc.Since(time.Now())
config = piConfig{} config = piConfig{}
a = app.NewWithID("pi-im") a = app.NewWithID("pi-im")
//a.Settings().SetTheme(&myTheme{})
reader, err := a.Storage().Open("pi.xml") reader, err := a.Storage().Open("pi.xml")
if err != nil { if err != nil {
dropToSignInPage(err.Error()) dropToSignInPage(err.Error())
@@ -422,12 +442,18 @@ func main() {
login = config.Login login = config.Login
notifications = config.Notifications notifications = config.Notifications
client, err := oasisSdk.CreateClient( if isUTF8Locale() {
&login) replyBodyIcon = "↱"
replyNameIcon = "→ "
newlineIcon = " ⮡ "
}
client, err := oasisSdk.CreateClient(&login)
client.SetDmHandler(func(client *oasisSdk.XmppClient, msg *oasisSdk.XMPPChatMessage) { client.SetDmHandler(func(client *oasisSdk.XmppClient, msg *oasisSdk.XMPPChatMessage) {
correction := false correction := false
userJidStr := msg.From.Bare().String() userJidStr := msg.From.Bare().String()
fmt.Println(userJidStr)
tab, ok := chatTabs[userJidStr] tab, ok := chatTabs[userJidStr]
if ok { if ok {
str := *msg.CleanedBody str := *msg.CleanedBody
@@ -463,13 +489,14 @@ func main() {
if msg.Reply == nil { if msg.Reply == nil {
replyID = "PICLIENT:UNAVAILABLE" replyID = "PICLIENT:UNAVAILABLE"
} else { } else {
fmt.Println("Received reply in DM")
replyID = msg.Reply.ID replyID = msg.Reply.ID
} }
if correction { if correction {
for i := len(tab.Messages) - 1; i > 0; i-- { for i := len(tab.Messages) - 1; i > 0; i-- {
if tab.Messages[i].Raw.From.String() == msg.From.String() { if tab.Messages[i].Raw.From.String() == msg.From.String() {
tab.Messages[i].Content = *msg.CleanedBody + " (edited)" tab.Messages[i].Content = *msg.CleanedBody + " (corrected)"
fyne.Do(func() { fyne.Do(func() {
UITabs[userJidStr].Scroller.Refresh() UITabs[userJidStr].Scroller.Refresh()
}) })
@@ -485,21 +512,22 @@ func main() {
ReplyID: replyID, ReplyID: replyID,
Raw: *msg, Raw: *msg,
ImageURL: img, ImageURL: img,
Reactions: make(map[string]string),
} }
tab.Messages = append(tab.Messages, myMessage) tab.Messages = append(tab.Messages, myMessage)
fyne.Do(func() { fyne.Do(func() {
UITabs[userJidStr].Scroller.Refresh() //UITabs[userJidStr].Scroller.Refresh()
if scrollDownOnNewMessage { if scrollDownOnNewMessage {
UITabs[userJidStr].Scroller.ScrollToBottom() //UITabs[userJidStr].Scroller.ScrollToBottom()
} }
}) })
} }
}) })
client.SetGroupChatHandler(func(client *oasisSdk.XmppClient, muc *muc.Channel, msg *oasisSdk.XMPPChatMessage) { client.SetGroupChatHandler(func(client *oasisSdk.XmppClient, muc *muc.Channel, msg *oasisSdk.XMPPChatMessage) {
// HACK: IGNORING ALL MESSAGES FROM CLASSIC MUC HISTORY IN PREPARATION OF MAM SUPPORT
ignore := false ignore := false
reaction := false
correction := false correction := false
important := false important := false
donotnotify := false donotnotify := false
@@ -517,15 +545,21 @@ func main() {
correction = true correction = true
break // dont need to look at more fields break // dont need to look at more fields
} }
if v.XMLName.Local == "reactions" {
reaction = true
break
}
} }
var ImageID string = "" var ImageID string = ""
mucJidStr := msg.From.Bare().String() mucJidStr := msg.From.Bare().String()
if tab, ok := chatTabs[mucJidStr]; ok { if tab, ok := chatTabs[mucJidStr]; ok {
chatInfo.Objects[0] = widget.NewLabel(fmt.Sprintf("[!] %s", mucJidStr))
chatTabs[mucJidStr].Muc = muc chatTabs[mucJidStr].Muc = muc
str := *msg.CleanedBody str := *msg.CleanedBody
if strings.Contains(str, login.DisplayName) { if strings.Contains(str, login.DisplayName) {
fmt.Println(str) fmt.Println(str, login.DisplayName)
important = true important = true
} }
if !donotnotify && !ignore && notifications { if !donotnotify && !ignore && notifications {
@@ -548,9 +582,7 @@ func main() {
lines[i] = strings.Join(s, " ") lines[i] = strings.Join(s, " ")
} }
str = strings.Join(lines, " ") str = strings.Join(lines, " ")
fmt.Println(str)
} }
fmt.Println(msg.ID)
var replyID string var replyID string
if msg.Reply == nil { if msg.Reply == nil {
replyID = "PICLIENT:UNAVAILABLE" replyID = "PICLIENT:UNAVAILABLE"
@@ -558,10 +590,22 @@ func main() {
replyID = msg.Reply.To replyID = msg.Reply.To
} }
if reaction {
for i := len(tab.Messages) - 1; i > 0; i-- {
if tab.Messages[i].Raw.StanzaID.ID == msg.Reply.ID {
tab.Messages[i].Reactions[msg.From.String()] = *msg.CleanedBody
fyne.Do(func() {
UITabs[mucJidStr].Scroller.Refresh()
})
return
}
}
}
if correction { if correction {
for i := len(tab.Messages) - 1; i > 0; i-- { for i := len(tab.Messages) - 1; i > 0; i-- {
if tab.Messages[i].Raw.From.String() == msg.From.String() { if tab.Messages[i].Raw.From.String() == msg.From.String() {
tab.Messages[i].Content = *msg.CleanedBody + " (edited)" tab.Messages[i].Content = *msg.CleanedBody + " (corrected)"
fyne.Do(func() { fyne.Do(func() {
UITabs[mucJidStr].Scroller.Refresh() UITabs[mucJidStr].Scroller.Refresh()
}) })
@@ -578,10 +622,12 @@ func main() {
Raw: *msg, Raw: *msg,
ImageURL: ImageID, ImageURL: ImageID,
Important: important, Important: important,
Reactions: make(map[string]string),
} }
if !ignore { if !ignore {
tab.Messages = append(tab.Messages, myMessage) tab.Messages = append(tab.Messages, myMessage)
} }
important = false
fyne.Do(func() { fyne.Do(func() {
UITabs[mucJidStr].Scroller.Refresh() UITabs[mucJidStr].Scroller.Refresh()
if scrollDownOnNewMessage { if scrollDownOnNewMessage {
@@ -616,6 +662,39 @@ func main() {
}) })
} }
}) })
client.SetPresenceHandler(func(client *oasisSdk.XmppClient, from jid.JID, p oasisSdk.UserPresence) {
bareAcc := from.Bare()
tab, ok := chatTabs[bareAcc.String()]
if !ok {
// User presence
addChatTab(false, bareAcc, client.Login.DisplayName)
return
}
if tab.isMuc {
tab.Members[from.String()] = p
}
})
client.SetBookmarkHandler(false, func(client *oasisSdk.XmppClient, bookmark bookmarks.Channel) {
// FIXME
if bookmark.JID.String() == "conversations-offtopic-reloaded@conference.trashserver.net" {
return
}
if bookmark.Autojoin {
if bookmark.Nick == "" {
fmt.Println("WARNING: Bookmark has no name")
bookmark.Nick = client.Login.DisplayName
}
addChatTab(true, bookmark.JID, client.Login.DisplayName)
_, err := client.ConnectMuc(bookmark, oasisSdk.MucLegacyHistoryConfig{}, context.TODO())
if err != nil {
fmt.Println("ERROR: " + err.Error())
return
}
}
})
client.SetDeliveryReceiptHandler( client.SetDeliveryReceiptHandler(
func(_ *oasisSdk.XmppClient, from jid.JID, id string) { func(_ *oasisSdk.XmppClient, from jid.JID, id string) {
fmt.Printf("Delivered %s to %s", id, from.String()) fmt.Printf("Delivered %s to %s", id, from.String())
@@ -625,7 +704,6 @@ func main() {
func(_ *oasisSdk.XmppClient, from jid.JID, id string) { func(_ *oasisSdk.XmppClient, from jid.JID, id string) {
for _, tab := range chatTabs { for _, tab := range chatTabs {
for i := len(tab.Messages) - 1; i >= 0; i-- { for i := len(tab.Messages) - 1; i >= 0; i-- {
fmt.Println(tab.Messages[i])
if tab.Messages[i].Raw.StanzaID == nil { if tab.Messages[i].Raw.StanzaID == nil {
continue continue
} }
@@ -635,7 +713,6 @@ func main() {
} }
} }
} }
fmt.Printf("%s has seen %s\n", from.String(), id)
}) })
if err != nil { if err != nil {
@@ -647,11 +724,13 @@ func main() {
err = client.Connect() err = client.Connect()
if err != nil { if err != nil {
responseChan := make(chan bool) responseChan := make(chan bool)
fyne.Do(func() {
//fyne.Do(func() {
fmt.Println(err)
dialog.ShowConfirm("disconnected", fmt.Sprintf("the client disconnected. would you like to try and reconnect?\nreason:\n%s", err.Error()), func(b bool) { dialog.ShowConfirm("disconnected", fmt.Sprintf("the client disconnected. would you like to try and reconnect?\nreason:\n%s", err.Error()), func(b bool) {
responseChan <- b responseChan <- b
}, w) }, w)
}) //})
if !<-responseChan { if !<-responseChan {
connection = false connection = false
} }
@@ -660,8 +739,20 @@ func main() {
}() }()
a = app.New() a = app.New()
//a.Settings().SetTheme(&myTheme{})
w = a.NewWindow("pi") w = a.NewWindow("pi")
w.SetCloseIntercept(func() {
w.RequestFocus()
dialog.ShowConfirm("Close pi", "You hit the close button. Do you want Pi to close completely (confirm) or minimize to the tray? (cancel)", func(b bool) {
if b {
w.Close()
a.Quit()
log.Fatalln("Goodbye!")
} else {
w.Hide()
}
}, w)
})
w.Resize(fyne.NewSize(500, 500)) w.Resize(fyne.NewSize(500, 500))
entry := NewCustomMultiLineEntry() entry := NewCustomMultiLineEntry()
@@ -697,7 +788,6 @@ func main() {
go func() { go func() {
if replying { if replying {
m := chatTabs[activeMucJid].Messages[selectedId].Raw m := chatTabs[activeMucJid].Messages[selectedId].Raw
fmt.Println(selectedId)
err = client.ReplyToEvent(&m, text) err = client.ReplyToEvent(&m, text)
if err != nil { if err != nil {
dialog.ShowError(err, w) dialog.ShowError(err, w)
@@ -714,14 +804,39 @@ func main() {
dialog.ShowError(err, w) dialog.ShowError(err, w)
} }
return return
} } else {
}, w)
}
err = client.SendText(jid.MustParse(activeMucJid).Bare(), text) err = client.SendText(jid.MustParse(activeMucJid).Bare(), text)
if err != nil { if err != nil {
dialog.ShowError(err, w) dialog.ShowError(err, w)
} }
}
}, w)
} else if text == "@here" && chatTabs[activeMucJid].isMuc {
tab := chatTabs[activeMucJid]
dialog.ShowConfirm("WARNING", fmt.Sprintf("There are %d members in this room.\nYou are about to mention every single one of them.\nYou may be punished if you are not a moderator of this chat.\nWould you like to continue?", len(tab.Members)), func(b bool) {
if b {
text = ""
for name := range tab.Members {
text = fmt.Sprintf("%s %s", text, jid.MustParse(name).Resourcepart())
}
err = client.SendText(jid.MustParse(activeMucJid).Bare(), text)
if err != nil {
dialog.ShowError(err, w)
}
}
}, w)
} else {
err = client.SendText(jid.MustParse(activeMucJid).Bare(), text)
if err != nil {
dialog.ShowError(err, w)
}
}
}() }()
if !isMuc { if !isMuc {
@@ -788,14 +903,6 @@ func main() {
}, w) }, w)
}) })
mis := fyne.NewMenuItem("clear chat window", func() {
dialog.ShowConfirm("clear chat window", "are you sure you want to clear the chat window?", func(b bool) {
if b {
fmt.Println("clearing chat")
}
}, w)
})
jtb := fyne.NewMenuItem("jump to bottom", func() { jtb := fyne.NewMenuItem("jump to bottom", func() {
selectedScroller, ok := AppTabs.Selected().Content.(*widget.List) selectedScroller, ok := AppTabs.Selected().Content.(*widget.List)
if !ok { if !ok {
@@ -905,7 +1012,7 @@ func main() {
}, w) }, w)
}) })
leaveRoom := fyne.NewMenuItem("Leave current room (experimental)", func() { leaveRoom := fyne.NewMenuItem("disconnect from current room", func() {
selectedScroller, ok := AppTabs.Selected().Content.(*widget.List) selectedScroller, ok := AppTabs.Selected().Content.(*widget.List)
if !ok { if !ok {
return return
@@ -917,14 +1024,112 @@ func main() {
break break
} }
} }
if !chatTabs[activeMucJid].isMuc {
return
}
AppTabs.Selected().Text = fmt.Sprintf("%s (disconnected)", AppTabs.Selected().Text) AppTabs.Selected().Text = fmt.Sprintf("%s (disconnected)", AppTabs.Selected().Text)
AppTabs.Items = append(AppTabs.Items[:AppTabs.SelectedIndex()], AppTabs.Items[AppTabs.SelectedIndex()+1:]...)
AppTabs.SelectIndex(0) AppTabs.SelectIndex(0)
delete(client.MucChannels, activeMucJid) err := client.DisconnectMuc(activeMucJid, "user left on own accord", context.TODO())
//delete(chatTabs, activeMucJid) if err != nil {
dialog.ShowError(err, w)
}
delete(UITabs, activeMucJid)
}) })
joinARoom := fyne.NewMenuItem("Join a room", func() { manageBookmarks := fyne.NewMenuItem("manage bookmarks", func() {
dialog.ShowEntryDialog("Join a room", "JID:", func(s string) { bookmarks := client.BookmarkCache()
box := container.NewVBox()
box.Add(widget.NewRichTextFromMarkdown("# Manage Bookmarks"))
for address, bookmark := range bookmarks {
bookmarkWidget := container.NewGridWithColumns(5)
nameLabel := widget.NewLabel(bookmark.Name)
nameLabel.Wrapping = fyne.TextWrapBreak
bookmarkWidget.Add(nameLabel)
bookmarkJidWidget := widget.NewLabel(address)
bookmarkJidWidget.TextStyle.Monospace = true
bookmarkJidWidget.Selectable = true
bookmarkJidWidget.Wrapping = fyne.TextWrapWord
bookmarkWidget.Add(bookmarkJidWidget)
var autojoinCheck *widget.Check
var deleteBookmarkButton *widget.Button
var joinRoomButton *widget.Button
joinRoomButton = widget.NewButtonWithIcon("Join", theme.AccountIcon(), func() {
fyne.Do(func() { joinRoomButton.Disable() })
go func() {
if bookmark.Nick == "" {
bookmark.Nick = client.Login.DisplayName
}
var zero uint64 = uint64(0)
addChatTab(true, bookmark.JID, bookmark.Nick)
_, err := client.ConnectMuc(bookmark, oasisSdk.MucLegacyHistoryConfig{MaxCount: &zero}, context.TODO())
if err != nil {
fyne.Do(func() {
joinRoomButton.Enable()
dialog.ShowError(err, w)
})
return
}
client.RefreshBookmarks(false)
fyne.Do(func() { joinRoomButton.Enable() })
}()
})
autojoinCheck = widget.NewCheck("Autojoin", func(b bool) {
go func() {
fyne.Do(func() { autojoinCheck.Disable() })
bookmark.Autojoin = b
err := client.PublishBookmark(bookmark, context.TODO())
if err != nil {
fyne.Do(func() {
autojoinCheck.Enable()
dialog.ShowError(err, w)
})
return
}
client.RefreshBookmarks(false)
fyne.Do(func() { autojoinCheck.Enable() })
}()
})
autojoinCheck.Checked = bookmark.Autojoin
deleteBookmarkButton = widget.NewButtonWithIcon("Delete", theme.CancelIcon(), func() {
go func() {
err := client.DeleteBookmark(bookmark.JID, context.TODO())
if err != nil {
fyne.Do(func() {
dialog.ShowError(err, w)
})
return
}
client.RefreshBookmarks(false)
bookmarkWidget.RemoveAll()
}()
})
bookmarkWidget.Add(autojoinCheck)
bookmarkWidget.Add(deleteBookmarkButton)
bookmarkWidget.Add(joinRoomButton)
box.Add(bookmarkWidget)
}
myScroller := container.NewScroll(box)
AppTabs.Items[0].Content = myScroller
AppTabs.Items[0].Text = "Bookmarks"
AppTabs.SelectIndex(0)
chatSidebar.Hidden = true
//d := dialog.NewCustom("manage bookmarks", "cancel", myScroller, w)
//d.Show()
})
joinARoom := fyne.NewMenuItem("connect to a room", func() {
dialog.ShowEntryDialog("connect to a room", "JID:", func(s string) {
i := resourcePiloadingGif i := resourcePiloadingGif
gif, err := extraWidgets.NewAnimatedGifFromResource(i) gif, err := extraWidgets.NewAnimatedGifFromResource(i)
if err != nil { if err != nil {
@@ -941,20 +1146,20 @@ func main() {
dialog.ShowError(err, w) dialog.ShowError(err, w)
return return
} }
joinjid, err := myjid.WithResource(login.DisplayName)
if err != nil { mychannel := new(bookmarks.Channel)
d.Hide() mychannel.JID = myjid
dialog.ShowError(err, w) mychannel.Nick = login.DisplayName
return //ch, err := client.MucClient.Join(client.Ctx, joinjid, client.Session)
}
ch, err := client.MucClient.Join(client.Ctx, joinjid, client.Session)
if err != nil {
d.Hide()
dialog.ShowError(err, w)
return
}
client.MucChannels[s] = ch
addChatTab(true, myjid, login.DisplayName) addChatTab(true, myjid, login.DisplayName)
num := uint64(0)
_, err = client.ConnectMuc(*mychannel, oasisSdk.MucLegacyHistoryConfig{MaxCount: &num}, context.TODO())
if err != nil {
d.Hide()
dialog.ShowError(err, w)
return
}
//client.MucChannels[s] = ch
d.Hide() d.Hide()
}() }()
}, w) }, w)
@@ -985,109 +1190,10 @@ func main() {
}) })
/* menu_help := fyne.NewMenu("π", mit, reconnect, licensesbtn)
servDisc := fyne.NewMenuItem("Disco features", func() {
//var search jid.JID
dialog.ShowEntryDialog("Disco features", "JID: ", func(s string) { // TODO: replace with undeprecated widgetd
d := dialog.NewCustom("Please wait", "Close", widget.NewLabel("..."), w)
d.Show()
go func() {
//search, err = jid.Parse(s)
//if err != nil {
// d.Hide()
// dialog.ShowError(err, w)
// return
//}
txt := `<iq from='ringen@muc.isekai.rocks/sunglocto'
to='ringen@muc.iskai.rocks/snit'
type='get'
id='vc2'>
<vCard xmlns='vcard-temp'/>
</iq>`
var stan stanza.IQ
xml.Unmarshal([]byte(txt), &stan)
if err != nil {
d.Hide()
dialog.ShowError(err, w)
return
}
r, err := client.Session.EncodeIQ(client.Ctx, stan)
if err != nil {
d.Hide()
dialog.ShowError(err, w)
return
}
ra, _ := r.Token()
t, _ := xml.MarshalIndent(ra, "", "\t")
fmt.Println(string(t))
d.Hide()
/*
myBox := container.NewGridWithColumns(1, widget.NewLabel("Items")) menu_changeroom := fyne.NewMenu("Α", mic, beginADM, joinARoom, leaveRoom, manageBookmarks)
info, err := disco.GetInfo(client.Ctx, "", search, client.Session) menu_configureview := fyne.NewMenu("Β", mia, jtt, jtb)
if err != nil {
d.Hide()
dialog.ShowError(err, w)
return
}
m := info.Identity
bytes, err := xml.MarshalIndent(m, "", "\t")
if err != nil {
d.Hide()
dialog.ShowError(err, w)
return
}
fyne.Do(func() {d.Hide()})
myBox.Objects = append(myBox.Objects, widget.NewLabel(string(bytes)))
dialog.ShowCustom("Service discovery", "cancel", myBox, w)
}()
}, w)
})
*/
savedata := fyne.NewMenuItem("DEBUG: Save tab data to disk", func() {
d := []ChatTab{}
for _, v := range chatTabs {
d = append(d, *v)
}
b, err := xml.Marshal(d)
if err != nil {
dialog.ShowError(err, w)
return
}
os.Create("test.xml")
os.WriteFile("text.xml", b, os.ModeAppend)
})
jbookmarks := fyne.NewMenuItem("Join rooms in bookmarks", func() {
// FIXME: Race condition
client.FetchBookmarks()
rooms := client.BookmarkCache()
for _, v := range rooms {
go func() {
if v.Autojoin == true {
joinjid, err := v.JID.WithResource(login.DisplayName)
if err != nil {
dialog.ShowError(err, w)
return
}
room, err := client.MucClient.Join(client.Ctx, joinjid, client.Session)
if err != nil {
dialog.ShowError(err, w)
return
}
client.MucChannels[v.JID.String()] = room
addChatTab(true, v.JID, login.DisplayName)
}
}()
}
})
menu_help := fyne.NewMenu("π", mit, reconnect, licensesbtn, savedata)
menu_changeroom := fyne.NewMenu("Α", mic, beginADM, joinARoom, leaveRoom, jbookmarks)
menu_configureview := fyne.NewMenu("Β", mia, mis, jtt, jtb)
hafjag := fyne.NewMenuItem("Hafjag", func() { hafjag := fyne.NewMenuItem("Hafjag", func() {
entry.Text = "Hafjag" entry.Text = "Hafjag"
SendCallback() SendCallback()
@@ -1119,6 +1225,40 @@ func main() {
entry.Text = old entry.Text = old
}) })
kai := fyne.NewMenuItem("kai cenat beg", func() {
old := entry.Text
entry.Text = "chat will you subscribe to save the kai cenat mafiathon 3"
SendCallback()
entry.Text = old
})
mipipiemi := fyne.NewMenuItem("mi pipi e mi", func() {
old := entry.Text
entry.Text = "mi pipi e mi"
SendCallback()
entry.Text = old
})
exposed_suffix := fyne.NewMenuItem(".exposed", func() {
selectedScroller, ok := AppTabs.Selected().Content.(*widget.List)
if !ok {
return
}
var activeChatJid string
for jid, tabData := range UITabs {
if tabData.Scroller == selectedScroller {
activeChatJid = jid
}
}
LatestMessage := chatTabs[activeChatJid].Messages[len(chatTabs[activeChatJid].Messages)-1]
old := entry.Text
entry.Text = fmt.Sprintf("%s.exposed", LatestMessage.Content)
SendCallback()
entry.Text = old
})
mycurrenttime := fyne.NewMenuItem("Current time", func() { mycurrenttime := fyne.NewMenuItem("Current time", func() {
entry.Text = fmt.Sprintf("It is currently %s", time.Now().Format(time.RFC850)) entry.Text = fmt.Sprintf("It is currently %s", time.Now().Format(time.RFC850))
SendCallback() SendCallback()
@@ -1126,8 +1266,8 @@ func main() {
mycurrentplayingsong := fyne.NewMenuItem("Get currently playing song", func() { mycurrentplayingsong := fyne.NewMenuItem("Get currently playing song", func() {
// BEGIN PLATFORM SPECIFIC CODE // BEGIN PLATFORM SPECIFIC CODE
if os.PathSeparator == '\\' && os.PathListSeparator == ';' { // Windows if isWindows() {
dialog.ShowError(errors.New("This feature is not supported on your operating system"), w) dialog.ShowError(errors.New("this feature is not supported on your operating system"), w)
return return
} }
// END PLATFORM SPECIFIC CODE // END PLATFORM SPECIFIC CODE
@@ -1160,7 +1300,7 @@ func main() {
} else if a_ok { } else if a_ok {
newtext = fmt.Sprintf("I'm currently listening to a song by %s", artists[0]) newtext = fmt.Sprintf("I'm currently listening to a song by %s", artists[0])
} else { } else {
dialog.ShowError(errors.New("error: There's a playing song, but we could not get any information."), w) dialog.ShowError(errors.New("error: There's a playing song, but we could not get any information"), w)
return return
} }
@@ -1175,7 +1315,7 @@ func main() {
} }
}) })
menu_jokes := fyne.NewMenu("Δ", mycurrenttime, hafjag, hotfuck, agree, mycurrentplayingsong) menu_jokes := fyne.NewMenu("Δ", mycurrenttime, mycurrentplayingsong, hafjag, hotfuck, agree, kai, mipipiemi, exposed_suffix)
bit := fyne.NewMenuItem("mark selected message as read", func() { bit := fyne.NewMenuItem("mark selected message as read", func() {
selectedScroller, ok := AppTabs.Selected().Content.(*widget.List) selectedScroller, ok := AppTabs.Selected().Content.(*widget.List)
if !ok { if !ok {
@@ -1264,17 +1404,47 @@ func main() {
ma := fyne.NewMainMenu(menu_help, menu_changeroom, menu_configureview, menu_messageoptions, menu_jokes) ma := fyne.NewMainMenu(menu_help, menu_changeroom, menu_configureview, menu_messageoptions, menu_jokes)
w.SetMainMenu(ma) w.SetMainMenu(ma)
AppTabs = container.NewAppTabs( desk, ok := a.(desktop.App)
container.NewTabItem("τίποτα", widget.NewLabel(` if ok {
desk.SetSystemTrayMenu(fyne.NewMenu("", fyne.NewMenuItem("show", w.Show)))
}
AppTabs = container.NewDocTabs(
container.NewTabItem("...", widget.NewLabel(`
pi pi
This tab will be used for displaying certain actions, such as
managing your bookmarks and configuring rooms.
`)), `)),
) )
for _, mucJidStr := range login.MucsToJoin { AppTabs.CloseIntercept = func(ti *container.TabItem) {
mucJid, err := jid.Parse(mucJidStr) go func() {
if err == nil { var activeChatJid string
addChatTab(true, mucJid, login.DisplayName) scroller, ok := ti.Content.(*widget.List)
if !ok {
return
} }
for jid, tabData := range UITabs {
if tabData.Scroller == scroller {
activeChatJid = jid
break
}
}
if !chatTabs[activeChatJid].isMuc {
return
}
err := client.DisconnectMuc(activeChatJid, "user left on own accord", context.TODO())
if err != nil {
dialog.ShowError(err, w)
}
AppTabs.Selected().Text = fmt.Sprintf("%s (disconnected)", AppTabs.Selected().Text)
AppTabs.Remove(ti)
delete(UITabs, activeChatJid)
}()
} }
for _, userJidStr := range DMs { for _, userJidStr := range DMs {
@@ -1286,6 +1456,10 @@ func main() {
} }
AppTabs.OnSelected = func(ti *container.TabItem) { AppTabs.OnSelected = func(ti *container.TabItem) {
if AppTabs.Selected() == AppTabs.Items[0] {
chatSidebar.Hidden = true
return
}
selectedScroller, ok := AppTabs.Selected().Content.(*widget.List) selectedScroller, ok := AppTabs.Selected().Content.(*widget.List)
if !ok { if !ok {
return return
@@ -1301,21 +1475,78 @@ func main() {
tab := chatTabs[activeChatJid] tab := chatTabs[activeChatJid]
UITab := UITabs[activeChatJid] UITab := UITabs[activeChatJid]
if tab.isMuc {
//chatInfo = *container.NewHBox(widget.NewLabel(tab.Muc.Addr().String()))
} else {
chatInfo = *container.NewHBox(widget.NewLabel(tab.Jid.String()))
}
chatSidebar = *UITab.Sidebar chatSidebar = *UITab.Sidebar
old := chatSidebar.Position() if tab.isMuc {
chatSidebar.Refresh() nameLabel := widget.NewRichTextFromMarkdown("# " + activeChatJid)
chatSidebar.Move(old) nameLabel.Truncation = fyne.TextTruncateEllipsis
box := container.NewVBox(nameLabel, widget.NewLabel(fmt.Sprintf("%d members ", len(tab.Members))))
chatSidebar.Objects = []fyne.CanvasObject{}
for name, p := range tab.Members {
gen, _ := identicon.New("github", 5, 3)
userjid, err := jid.Parse(name)
if err != nil {
fmt.Println("ERROR: " + err.Error())
continue // unrecoverable
}
nickname := userjid.Resourcepart()
if nickname == "" {
continue // we got the MUC presence, do not include it in the member list
}
ii, err := gen.Draw(nickname)
mention := func() {
entry.SetText(fmt.Sprintf("%s %s", entry.Text, nickname))
}
if err != nil {
fmt.Println("ERROR: " + err.Error())
box.Add(container.NewHBox(widget.NewLabel(nickname), widget.NewButton("Mention", mention)))
} else {
im := ii.Image(15)
imageWidget := canvas.NewImageFromImage(im)
imageWidget.FillMode = canvas.ImageFillOriginal
imageWidget.Refresh()
nickLabel := widget.NewLabel(nickname)
nickLabel.Selectable = true
box.Add(container.NewHBox(imageWidget, nickLabel))
if p.Status != "" {
s := widget.NewLabel(fmt.Sprintf("\"%s\"", p.Status))
s.Importance = widget.WarningImportance
s.TextStyle = fyne.TextStyle{
Bold: false,
Italic: true,
Monospace: false,
}
s.Truncation = fyne.TextTruncateEllipsis
box.Add(s)
}
} }
// HACK - disable chatsidebar because it's currently very buggy }
chatSidebar.Hidden = true chatSidebar = *container.NewGridWithColumns(1, container.NewVScroll(box))
} else {
chatSidebar = *container.NewVBox(widget.NewRichTextFromMarkdown("# " + activeChatJid))
}
chatSidebar.Hidden = false
chatSidebar.Refresh()
}
chatSidebar.Hidden = false
statBar.SetText("") statBar.SetText("")
w.SetContent(container.NewVSplit(container.NewVSplit(AppTabs, container.NewHSplit(entry, container.NewGridWithRows(1, sendbtn, replybtn))), container.NewHSplit(&statBar, &chatInfo))) chatInfo = *container.NewHBox(widget.NewLabel(""))
MainSplit := container.NewHSplit(AppTabs, &chatSidebar)
DownSplit := container.NewHSplit(entry, container.NewGridWithRows(1, sendbtn, replybtn))
BigSplit := container.NewVSplit(MainSplit, DownSplit)
BigSplit.SetOffset(1)
SmallSplit := container.NewHSplit(&statBar, &chatInfo)
MasterSplit := container.NewVSplit(BigSplit, SmallSplit)
MasterSplit.SetOffset(1)
w.SetContent(MasterSplit)
w.ShowAndRun() w.ShowAndRun()
} }