21 Commits

Author SHA1 Message Date
4390763985 Add the ability to join rooms, as well as join 1:1 DMs inside the app 2025-08-10 09:50:24 +01:00
0e713ab417 Attempt to add loading wheel 2025-08-10 07:15:36 +01:00
sunglocto
a8b1160711 Delete emoji.go 2025-08-09 21:48:07 +00:00
187d8e750d Add the ability to join rooms 2025-08-09 22:45:39 +01:00
0578e6fafc idk 2025-08-09 18:10:32 +01:00
3571e5fad8 add ability to view readers of a message 2025-08-09 14:31:26 +01:00
7ed560e45d Update dependencies and add warning to Hot Fuck button 2025-08-09 10:07:05 +01:00
sunglocto
1f353ae258 Merge pull request #14 from 88572/master
Fix spelling and casing in one comment
2025-08-09 08:22:37 +01:00
sunglocto
f22cfb56c0 Merge pull request #12 from jjj333-p/patch-1
make /me parsing more efficient
2025-08-09 08:22:09 +01:00
sunglocto
f85caccbac Merge pull request #13 from jjj333-p/patch-2
make pointless loops no longer pointless
2025-08-09 08:21:22 +01:00
Nyx
09370be81a Fix spelling too 2025-08-08 20:46:36 -05:00
Nyx
c7664e6b96 fix: fix casing in comment 2025-08-08 20:45:48 -05:00
Joseph (Joe) Winkie
3aa619dd92 make pointless loops no longer pointless 2025-08-08 15:44:40 -10:00
Joseph (Joe) Winkie
dfc0b1088d make /me parsing more efficient
- only split once
- slicing a slice in go is efficient https://g.co/gemini/share/15cb7d25e6f3
2025-08-08 15:26:12 -10:00
147f10fe66 Merge branch 'master' of https://github.com/sunglocto/pi-im 2025-08-08 19:13:05 +01:00
4f0fe5cac7 Update all dependencies 2025-08-08 19:12:59 +01:00
sunglocto
4ae8efba21 Update README.md 2025-08-08 16:12:43 +00:00
sunglocto
35c4e76e0b Update README.md 2025-08-08 16:10:26 +00:00
sunglocto
3eee204bbf Update README.md 2025-08-08 16:08:19 +00:00
sunglocto
59d93da428 Update README.md 2025-08-08 16:07:17 +00:00
829438a3a5 lel 2025-08-08 11:24:36 +01:00
5 changed files with 308 additions and 88 deletions

View File

@@ -13,7 +13,7 @@ Experimental and extremely weird XMPP client written in Go. No solicitors.
pi is currently pre-pre-pre-pre alpha software which you should not use as your primary XMPP client.
pi uses [Fyne](https://fyne.io) for the frontend and uses the [Oasis SDK](https://github.com/jjj333-p/oasis-sdk) for XMPP functionality.
pi uses [Fyne](https://fyne.io) for the frontend and uses the [Oasis SDK](https://github.com/jjj333-p/oasis-sdk) by [Joseph Winkie](https://pain.agency) for XMPP functionality.
pi is an extremely opinionated client. It aims to have as little extra windows as possible, instead using alt-menus to perform many of the actions you'd see in a typical client.
@@ -38,12 +38,19 @@ If you want to add MUCs or DMs, you must configure the program by editing the pi
<MucsToJoin>room2@muc.example.com</MucsToJoin>
</Login>
<Notifications>true</Notifications>
<DMs>person1@example.com</DMs>
</piConfig>
```
The file is usually located at, on GNU/Linux systems:
```
~/.config/fyne/pi-im/Documents/pi.xml
```
This will be changed eventually, likely before a 3b release.
Currently joining and saving DM tabs is not supported, nor is getting avatars, reactions or encryption.
As of writing, pi supports basic message sending and receiving, replies and ~~file upload~~.
As of writing, pi supports basic message sending and receiving, replies, file upload and corrections.
## να χτίσω
@@ -51,23 +58,27 @@ As of writing, pi supports basic message sending and receiving, replies and ~~fi
To build pi, you will need the latest version of Go, at least 1.21. You can grab it [here](https://go.dev).
The build instructions are very simple. Simply clone the repo, fetch the repositories and build the program:
The build instructions are very simple. Simply clone the repo, fetch the repositories and build the program.
Here is a summary of the commands you would need to use to build and run the program:
Here is a summary of the commands you would need to use:
```bash
git clone https://github.com/sunglocto/pi
cd pi
git clone https://github.com/sunglocto/pi-im
cd pi-im
go mod tidy
go build .
./pi
./pi-im
```
> Uh, Windows???
Eventually. Don't count on it.
Fyne has first-class support for Windows and none of my dependencies are platform dependent. I've built this app for Android before. If you compile it, it will most likely work with no issues.
Fyne has first-class support for Windows and all of my dependencies are platform imdependent. I've built this app for Android before. If you compile it, it will most likely work with no issues.
Static executable snapshots are also provided for GNU/Linux systems, and CI runs on every commit, producing a binary on every successful build. You're welcome.
Static executable snapshots are also provided for GNU/Linux systems on every new version, and CI runs on every commit, producing a binary on every successful build. You're welcome.
## εγκατάσταση
(installation)
Pi currently has no consolidated way of installing it. There is an [Arch User Repository package available](https://aur.archlinux.org/pi-im), which is maintained by [snit](https://isekai.rocks/~snit).
## υποστήριξη
(support)

18
go.mod
View File

@@ -7,14 +7,14 @@ require (
fyne.io/x/fyne v0.0.0-20250418202416-58a230ad1acb
github.com/rrivera/identicon v0.0.0-20240116195454-d5ba35832c0d
mellium.im/xmpp v0.22.0
pain.agency/oasis-sdk v0.0.0-20250805052243-df6be3f9f629
pain.agency/oasis-sdk v0.0.0-20250809192709-a3e5dff1aa61
)
require (
fyne.io/systray v1.11.0 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fredbi/uri v1.1.0 // indirect
github.com/fredbi/uri v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fyne-io/gl-js v0.2.0 // indirect
github.com/fyne-io/glfw-js v0.3.0 // indirect
@@ -37,14 +37,14 @@ require (
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/yuin/goldmark v1.7.13 // indirect
golang.org/x/crypto v0.40.0 // indirect
golang.org/x/image v0.29.0 // indirect
golang.org/x/mod v0.26.0 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/image v0.30.0 // indirect
golang.org/x/mod v0.27.0 // indirect
golang.org/x/net v0.43.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/tools v0.35.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/text v0.28.0 // indirect
golang.org/x/tools v0.36.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
mellium.im/reader v0.1.0 // indirect
mellium.im/sasl v0.3.2 // indirect

36
go.sum
View File

@@ -10,8 +10,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
github.com/fredbi/uri v1.1.0 h1:OqLpTXtyRg9ABReqvDGdJPqZUxs8cyBDOMXBbskCaB8=
github.com/fredbi/uri v1.1.0/go.mod h1:aYTUoAXBOq7BLfVJ8GnKmfcuURosB1xyHDIfWeC/iW4=
github.com/fredbi/uri v1.1.1 h1:xZHJC08GZNIUhbP5ImTHnt5Ya0T8FI2VAwI/37kh2Ko=
github.com/fredbi/uri v1.1.1/go.mod h1:4+DZQ5zBjEwQCDmXW5JdIjz0PUA+yJbvtBv+u+adr5o=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fyne-io/gl-js v0.2.0 h1:+EXMLVEa18EfkXBVKhifYB6OGs3HwKO3lUElA0LlAjs=
@@ -70,22 +70,22 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/image v0.29.0 h1:HcdsyR4Gsuys/Axh0rDEmlBmB68rW1U9BUdB3UVHsas=
golang.org/x/image v0.29.0/go.mod h1:RVJROnf3SLK8d26OW91j4FrIHGbsJ8QnbEocVTOWQDA=
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/image v0.30.0 h1:jD5RhkmVAnjqaCUXfbGBrn3lpxbknfN9w2UhHHU+5B4=
golang.org/x/image v0.30.0/go.mod h1:SAEUTxCCMWSrJcCy/4HwavEsfZZJlYxeHLc6tTiAe/c=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -99,5 +99,5 @@ mellium.im/xmlstream v0.15.4 h1:gLKxcWl4rLMUpKgtzrTBvr4OexPeO/edYus+uK3F6ZI=
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/go.mod h1:WSjq12nhREFD88Vy/0WD6Q8inE8t6a8w7QjzwivWitw=
pain.agency/oasis-sdk v0.0.0-20250805052243-df6be3f9f629 h1:NE+Z2HQzc77nw7l7DsSDSi0x9l+YfLfXBYerK+GsPrQ=
pain.agency/oasis-sdk v0.0.0-20250805052243-df6be3f9f629/go.mod h1:eyvDgfpHo+9bdB/AkMEMZ3ETeoSONTULVx9X4w9kGAU=
pain.agency/oasis-sdk v0.0.0-20250809192709-a3e5dff1aa61 h1:7zb69SAfLAJhXoqXZaS0pq/p1Y9W19Pm4FjcwWjTVoE=
pain.agency/oasis-sdk v0.0.0-20250809192709-a3e5dff1aa61/go.mod h1:eyvDgfpHo+9bdB/AkMEMZ3ETeoSONTULVx9X4w9kGAU=

301
main.go
View File

@@ -4,9 +4,10 @@ import (
//core - required
"encoding/xml"
"fmt"
"image/color"
_ "image/color"
"io"
"log"
"math/rand/v2"
"net/url"
"os"
"strings"
@@ -22,24 +23,25 @@ import (
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"github.com/rrivera/identicon"
extraWidgets "fyne.io/x/fyne/widget"
// xmpp - required
"mellium.im/xmpp/disco"
"mellium.im/xmpp/jid"
"mellium.im/xmpp/muc"
oasisSdk "pain.agency/oasis-sdk"
// gui - optional
// catppuccin "github.com/mbaklor/fyne-catppuccin"
adwaita "fyne.io/x/fyne/theme"
// TODO: integrated theme switcher
)
var version string = "3.14a"
var version string = "3.142a"
var statBar widget.Label
var chatInfo fyne.Container
var chatSidebar fyne.Container
var agreesToSendingHotFuckIntoChannel bool = false
// by sunglocto
// license AGPL
@@ -51,6 +53,7 @@ type Message struct {
ImageURL string
Raw oasisSdk.XMPPChatMessage
Important bool
Readers []jid.JID
}
type ChatTab struct {
@@ -68,6 +71,36 @@ type ChatTabUI struct {
Sidebar *fyne.Container `xml:"-"`
}
type CustomMultiLineEntry struct {
widget.Entry
}
func NewCustomMultiLineEntry() *CustomMultiLineEntry {
entry := &CustomMultiLineEntry{}
entry.ExtendBaseWidget(entry)
entry.MultiLine = true
return entry
}
func (e *CustomMultiLineEntry) TypedShortcut(sc fyne.Shortcut) {
if sc.ShortcutName() == "CustomDesktop:Control+Return" {
e.Entry.TypedRune('\n')
return
}
e.Entry.TypedShortcut(sc)
}
func (e *CustomMultiLineEntry) TypedKey(ev *fyne.KeyEvent) {
if ev.Name == fyne.KeyReturn || ev.Name == fyne.KeyEnter {
// Normal Enter (no modifier) = submit
if e.OnSubmitted != nil {
e.OnSubmitted(e.Text)
}
} else {
e.Entry.TypedKey(ev)
}
}
type piConfig struct {
Login oasisSdk.LoginInfo
DMs []string
@@ -87,26 +120,24 @@ var replying bool = false
var notifications bool
var connection bool = true
type myTheme struct{}
/*
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, variant)
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)
}
func (m myTheme) Size(name fyne.ThemeSizeName) float32 {
if name == theme.SizeNameHeadingText {
return 18
}
return theme.DefaultTheme().Size(name)
}
*/
var scrollDownOnNewMessage bool = true
var w fyne.Window
@@ -160,25 +191,33 @@ func CreateUITab(chatJidStr string) ChatTabUI {
if chatTabs[chatJidStr].Messages[i].ImageURL != "" {
btn.Hidden = false
btn.OnTapped = func() {
fyne.Do(func() {
go func() {
u, err := storage.ParseURI(chatTabs[chatJidStr].Messages[i].ImageURL)
if err != nil {
dialog.ShowError(err, w)
fyne.Do(func() {
dialog.ShowError(err, w)
})
return
}
if strings.HasSuffix(chatTabs[chatJidStr].Messages[i].ImageURL, "mp4") || strings.HasSuffix(chatTabs[chatJidStr].Messages[i].ImageURL, "mp3") {
url, err := url.Parse(chatTabs[chatJidStr].Messages[i].ImageURL)
if err != nil {
dialog.ShowError(err, w)
fyne.Do(func() {
dialog.ShowError(err, w)
})
return
}
a.OpenURL(url)
fyne.Do(func() {
a.OpenURL(url)
})
return
}
image := canvas.NewImageFromURI(u)
image.FillMode = canvas.ImageFillOriginal
dialog.ShowCustom("Image", "Close", image, w)
})
fyne.Do(func() {
dialog.ShowCustom("Image", "Close", image, w)
})
}()
}
}
// Check if the message is a quote
@@ -198,11 +237,9 @@ func CreateUITab(chatJidStr string) ChatTabUI {
author.SetText(chatTabs[chatJidStr].Messages[i].Author)
}
if strings.Split(msgContent," ")[0] == "/me" {
sl := strings.Split(msgContent, " ")
sl[0] = ""
author.SetText(author.Text + strings.Join(sl, " "))
sl := strings.Split(msgContent, " ")
if sl[0] == "/me" {
author.SetText(author.Text + " " + strings.Join(sl[1:], " "))
content.SetText(" ")
}
@@ -218,11 +255,11 @@ func CreateUITab(chatJidStr string) ChatTabUI {
scroller.CreateItem()
myUITab.Scroller = scroller
gen, _ := identicon.New("github", 50, 20)
ii, _ := gen.Draw(chatJidStr)
im := ii.Image(250)
imw := canvas.NewImageFromImage(im)
imw.FillMode = canvas.ImageFillOriginal
gen, _ := identicon.New("github", 50, 20)
ii, _ := gen.Draw(chatJidStr)
im := ii.Image(250)
imw := canvas.NewImageFromImage(im)
imw.FillMode = canvas.ImageFillOriginal
myUITab.Sidebar = container.NewVBox(imw)
return myUITab
@@ -247,9 +284,15 @@ func addChatTab(isMuc bool, chatJid jid.JID, nick string) {
chatTabs[chatJidStr] = &myChatTab
UITabs[chatJidStr] = &myUITab
var icon fyne.Resource
if isMuc {
icon = theme.HomeIcon()
} else{
icon = theme.AccountIcon()
}
fyne.Do(func() {
AppTabs.Append(container.NewTabItem(chatJid.String(), myUITab.Scroller))
AppTabs.Append(container.NewTabItemWithIcon(chatJid.String(), icon, myUITab.Scroller))
})
}
@@ -406,15 +449,17 @@ func main() {
correction := false
important := false
for _, v := range msg.Unknown {
if v.XMLName.Local == "delay" { // CLasic history message
if v.XMLName.Local == "delay" { // Classic history message
//ignore = true
//fmt.Println("ignoring!")
//return //what is blud doing
}
}
for _, v := range msg.Unknown {
if v.XMLName.Local == "replace" {
correction = true
break // dont need to look at more fields
}
}
@@ -498,7 +543,7 @@ func main() {
case oasisSdk.ChatStatePaused:
fyne.Do(func() {
statBar.SetText(fmt.Sprintf("%s has stoped typing.", from.Resourcepart()))
statBar.SetText(fmt.Sprintf("%s has stopped typing.", from.Resourcepart()))
})
case oasisSdk.ChatStateInactive:
fyne.Do(func() {
@@ -518,7 +563,19 @@ func main() {
fmt.Printf("Delivered %s to %s", id, from.String())
},
func(_ *oasisSdk.XmppClient, from jid.JID, id string) {
fmt.Printf("%s has seen %s", from.String(), id)
for _, tab := range chatTabs {
for i := len(tab.Messages) - 1; i >= 0; i-- {
fmt.Println(tab.Messages[i])
if tab.Messages[i].Raw.StanzaID == nil {
continue
}
if tab.Messages[i].Raw.StanzaID.ID == id {
tab.Messages[i].Readers = append(tab.Messages[i].Readers, from)
break
}
}
}
fmt.Printf("%s has seen %s\n", from.String(), id)
},
)
if err != nil {
@@ -543,14 +600,14 @@ func main() {
}()
a = app.New()
a.Settings().SetTheme(myTheme{})
//a.Settings().SetTheme(myTheme{})
w = a.NewWindow("pi")
w.Resize(fyne.NewSize(500, 500))
entry := widget.NewMultiLineEntry()
entry.SetPlaceHolder("Say something, you know you want to.")
entry.OnChanged = func(s string) {
}
entry := NewCustomMultiLineEntry()
entry.SetPlaceHolder("Say something, you know you want to.\nCtrl+Enter for newline")
entry.Wrapping = fyne.TextWrapBreak
//entry.TypedShortcut()
SendCallback := func() {
text := entry.Text
@@ -580,6 +637,7 @@ func main() {
go func() {
if replying {
m := chatTabs[activeMucJid].Messages[selectedId].Raw
fmt.Println(selectedId)
err = client.ReplyToEvent(&m, text)
if err != nil {
dialog.ShowError(err, w)
@@ -589,7 +647,8 @@ func main() {
url, uerr := url.Parse(strings.Split(text, " ")[0])
if uerr == nil && strings.HasPrefix(strings.Split(text, " ")[0], "https://") {
err = client.SendImage(jid.MustParse(activeMucJid).Bare(), text, url.String(), &text)
//err = client.SendImage(jid.MustParse(activeMucJid).Bare(), text, url.String(), &text)
err = client.SendSingleFileMessage(jid.MustParse(activeMucJid).Bare(), url.String(), nil)
if err != nil {
dialog.ShowError(err, w)
}
@@ -618,6 +677,11 @@ func main() {
}
sendbtn := widget.NewButton("Send", SendCallback)
replybtn := widget.NewButton("Reply", func() {
replying = true
SendCallback()
replying = false
})
entry.OnSubmitted = func(s string) {
SendCallback()
// i fucking hate fyne
@@ -715,9 +779,6 @@ func main() {
})
//deb := fyne.NewMenuItem("DEBUG: Attempt to get MAM history from a user", func() {
//res, err := history.Fetch(client.Ctx, history.Query{}, jid.MustParse("ringen@muc.isekai.rocks"), client.Session)
//})
mic := fyne.NewMenuItem("upload a file", func() {
var link string
var toperr error
@@ -775,6 +836,86 @@ func main() {
}, w)
})
leaveRoom := fyne.NewMenuItem("Leave current room (experimental)", func() {
selectedScroller, ok := AppTabs.Selected().Content.(*widget.List)
if !ok {
return
}
var activeMucJid string
for jid, tabData := range UITabs {
if tabData.Scroller == selectedScroller {
activeMucJid = jid
break
}
}
AppTabs.Selected().Text = fmt.Sprintf("%s (disconnected)", AppTabs.Selected().Text)
AppTabs.SelectIndex(0)
delete(client.MucChannels, activeMucJid)
//delete(chatTabs, activeMucJid)
})
joinARoom := fyne.NewMenuItem("Join a room", func() {
dialog.ShowEntryDialog("Join a room", "JID:", func(s string) {
i := resourcePiloadingGif
gif, err := extraWidgets.NewAnimatedGifFromResource(i)
if err != nil {
panic(err)
}
gif.Start()
gif.Show()
d := dialog.NewCustom("Please wait", "Close", gif, w)
d.Show()
go func() {
myjid, err := jid.Parse(s)
if err != nil {
d.Hide()
dialog.ShowError(err, w)
return
}
joinjid, err := myjid.WithResource(login.DisplayName)
if err != nil {
d.Hide()
dialog.ShowError(err, w)
return
}
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)
d.Hide()
}()
}, w)
})
beginADM := fyne.NewMenuItem("Start a DM", func() {
dialog.ShowEntryDialog("Start a DM", "JID:", func(s string) {
i := resourcePiloadingGif
gif, err := extraWidgets.NewAnimatedGifFromResource(i)
if err != nil {
panic(err)
}
gif.Start()
gif.Show()
d := dialog.NewCustom("Please wait", "Close", gif, w)
d.Show()
go func() {
myjid, err := jid.Parse(s)
if err != nil {
d.Hide()
dialog.ShowError(err, w)
return
}
addChatTab(false, myjid, login.DisplayName)
d.Hide()
}()
}, w)
})
servDisc := fyne.NewMenuItem("Disco features", func() {
var search jid.JID
dialog.ShowEntryDialog("Disco features", "JID: ", func(s string) { // TODO: replace with undeprecated widget
@@ -784,7 +925,7 @@ func main() {
return
}
myBox := container.NewGridWithColumns(1, widget.NewLabel("Items\na\na\na\na\na"))
myBox := container.NewGridWithColumns(1, widget.NewLabel("Items"))
info, err := disco.GetInfo(client.Ctx, "", search, client.Session)
if err != nil {
dialog.ShowError(err, w)
@@ -815,7 +956,8 @@ func main() {
os.WriteFile("text.xml", b, os.ModeAppend)
})
menu_help := fyne.NewMenu("π", mit, reconnect, savedata)
menu_changeroom := fyne.NewMenu("Α", mic, servDisc)
menu_changeroom := fyne.NewMenu("Α", mic, servDisc, beginADM, joinARoom, leaveRoom)
menu_configureview := fyne.NewMenu("Β", mia, mis, jtt, jtb)
hafjag := fyne.NewMenuItem("Hafjag", func() {
entry.Text = "Hafjag"
@@ -824,16 +966,35 @@ func main() {
})
hotfuck := fyne.NewMenuItem("Hot Fuck", func() {
entry.Text = "Hot Fuck"
d := dialog.NewConfirm("WARNING", "This button will send the message \"Hot Fuck\" into your currently focused chat. Do you want to continue?", func(b bool) {
if b {
agreesToSendingHotFuckIntoChannel = true
entry.Text = "Hot Fuck"
SendCallback()
entry.Text = "Oh Yeah."
}
}, w)
if agreesToSendingHotFuckIntoChannel {
d.Confirm()
} else {
d.Show()
}
})
agree := fyne.NewMenuItem("Agree", func() {
old := entry.Text
entry.Text = strings.Repeat("^", rand.IntN(30))
SendCallback()
entry.Text = "Oh Yeah."
entry.Text = old
})
mycurrenttime := fyne.NewMenuItem("Current time", func() {
entry.Text = fmt.Sprintf("It is currently %s", time.Now().Format(time.RFC850))
SendCallback()
})
menu_jokes := fyne.NewMenu("δ", mycurrenttime, hafjag, hotfuck)
menu_jokes := fyne.NewMenu("Δ", mycurrenttime, hafjag, hotfuck, agree)
bit := fyne.NewMenuItem("mark selected message as read", func() {
selectedScroller, ok := AppTabs.Selected().Content.(*widget.List)
if !ok {
@@ -856,7 +1017,7 @@ func main() {
})
bic := fyne.NewMenuItem("show message XML", func() {
pre := widget.NewLabel("")
pre := widget.NewMultiLineEntry()
selectedScroller, ok := AppTabs.Selected().Content.(*widget.List)
if !ok {
@@ -878,11 +1039,47 @@ func main() {
return
}
pre.SetText(string(bytes))
pre.Selectable = true
pre.Refresh()
dialog.ShowCustom("Message", "Close", pre, w)
})
menu_messageoptions := fyne.NewMenu("Γ", bit, bia, bic)
red := fyne.NewMenuItem("show people who have read this message", func() {
pre := container.NewVBox()
selectedScroller, ok := AppTabs.Selected().Content.(*widget.List)
if !ok {
return
}
var activeChatJid string
for jid, tabData := range UITabs {
if tabData.Scroller == selectedScroller {
activeChatJid = jid
break
}
}
gen, _ := identicon.New("github", 5, 3)
m := chatTabs[activeChatJid].Messages[selectedId].Readers
for _, v := range m {
if chatTabs[activeChatJid].isMuc {
ii, _ := gen.Draw(v.Resourcepart())
im := ii.Image(25)
iw := canvas.NewImageFromImage(im)
iw.FillMode = canvas.ImageFillOriginal
pre.Add(container.NewHBox(iw, widget.NewLabel(v.Resourcepart())))
} else {
ii, _ := gen.Draw(v.Localpart())
im := ii.Image(25)
iw := canvas.NewImageFromImage(im)
iw.FillMode = canvas.ImageFillOriginal
pre.Add(container.NewHBox(iw, widget.NewLabel(v.Localpart())))
}
}
pre.Refresh()
dialog.ShowCustom("Message", "Close", pre, w)
})
menu_messageoptions := fyne.NewMenu("Γ", bit, bia, bic, red)
ma := fyne.NewMainMenu(menu_help, menu_changeroom, menu_configureview, menu_messageoptions, menu_jokes)
w.SetMainMenu(ma)
@@ -924,7 +1121,7 @@ func main() {
tab := chatTabs[activeChatJid]
UITab := UITabs[activeChatJid]
if tab.isMuc {
chatInfo = *container.NewHBox(widget.NewLabel(tab.Muc.Addr().String()))
//chatInfo = *container.NewHBox(widget.NewLabel(tab.Muc.Addr().String()))
} else {
chatInfo = *container.NewHBox(widget.NewLabel(tab.Jid.String()))
}
@@ -938,6 +1135,6 @@ func main() {
// HACK - disable chatsidebar because it's currently very buggy
chatSidebar.Hidden = true
statBar.SetText("")
w.SetContent(container.NewVSplit(container.NewVSplit(AppTabs, container.NewHSplit(entry, sendbtn)), container.NewHSplit(&statBar, &chatInfo)))
w.SetContent(container.NewVSplit(container.NewVSplit(AppTabs, container.NewHSplit(entry, container.NewGridWithRows(1, sendbtn, replybtn))), container.NewHSplit(&statBar, &chatInfo)))
w.ShowAndRun()
}

12
piloadinggif.go Normal file

File diff suppressed because one or more lines are too long