Compare commits
37 Commits
Author | SHA1 | Date | |
---|---|---|---|
4390763985 | |||
0e713ab417 | |||
![]() |
a8b1160711 | ||
187d8e750d | |||
0578e6fafc | |||
3571e5fad8 | |||
7ed560e45d | |||
![]() |
1f353ae258 | ||
![]() |
f22cfb56c0 | ||
![]() |
f85caccbac | ||
![]() |
09370be81a | ||
![]() |
c7664e6b96 | ||
![]() |
3aa619dd92 | ||
![]() |
dfc0b1088d | ||
147f10fe66 | |||
4f0fe5cac7 | |||
![]() |
4ae8efba21 | ||
![]() |
35c4e76e0b | ||
![]() |
3eee204bbf | ||
![]() |
59d93da428 | ||
829438a3a5 | |||
6c3195b029 | |||
5b5d4656aa | |||
![]() |
3afa1e7e38 | ||
ece04e1c36 | |||
52e38e7e66 | |||
59d83cb185 | |||
4015107de0 | |||
150f42bc58 | |||
3d6f835d4f | |||
922bc1d7cf | |||
a61d3090e1 | |||
215839d833 | |||
d7264e91f7 | |||
![]() |
93d3bb20d2 | ||
181b91edb4 | |||
ca6bda7950 |
2
.github/workflows/go.yml
vendored
2
.github/workflows/go.yml
vendored
@@ -30,5 +30,5 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: pi-binary
|
||||
path: pi
|
||||
path: pi-im
|
||||
|
||||
|
39
README.md
39
README.md
@@ -1,10 +1,10 @@
|
||||
<center>
|
||||
<img src="https://github.com/sunglocto/pi/blob/255bc3749c089e3945871ddf19dd17d14a83f9ff/pi.png">
|
||||
<img width="100" height="100" src="https://github.com/sunglocto/pi/blob/255bc3749c089e3945871ddf19dd17d14a83f9ff/pi.png">
|
||||
</center>
|
||||
|
||||
# π
|
||||
[](https://github.com/sunglocto/pi/actions/workflows/go.yml)
|
||||
<img width="1050" height="632" alt="image" src="https://github.com/user-attachments/assets/a5a3a7ab-8a85-49f0-81e9-ae5b977e7455" />
|
||||
<img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/9e2d9209-6ad5-4f22-94d0-4cc18c835372" />
|
||||
|
||||
|
||||
## the XMPP client from hell
|
||||
> it's 10% code. 20% ai
|
||||
@@ -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)
|
||||
@@ -90,6 +101,10 @@ From fellow insane and schizophrenic XMPP users:
|
||||
|
||||
> pi devstream when
|
||||
|
||||
<img width="361" height="66" alt="image" src="https://github.com/user-attachments/assets/5a926f6b-1005-4795-a6ef-4e0538bb4d5a" />
|
||||
<img width="316" height="73" alt="image" src="https://github.com/user-attachments/assets/52309c60-8110-43eb-9c45-56c9cfd82cc4" />
|
||||
|
||||
|
||||
## επιπλέον
|
||||
(extra)
|
||||
|
||||
|
21
go.mod
21
go.mod
@@ -1,19 +1,20 @@
|
||||
module pi
|
||||
module pi-im
|
||||
|
||||
go 1.24.5
|
||||
|
||||
require (
|
||||
fyne.io/fyne/v2 v2.6.2
|
||||
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
|
||||
@@ -36,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
|
||||
|
38
go.sum
38
go.sum
@@ -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=
|
||||
@@ -58,6 +58,8 @@ github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
|
||||
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rrivera/identicon v0.0.0-20240116195454-d5ba35832c0d h1:l3+2LWCbVxn5itfvXAfH9n4YL9jh8l1g5zcncbIc1cs=
|
||||
github.com/rrivera/identicon v0.0.0-20240116195454-d5ba35832c0d/go.mod h1:TbpErkob6SY7cyozRVSGoB3OlO2qOAgVN8O3KAJ4fMI=
|
||||
github.com/rymdport/portal v0.4.2 h1:7jKRSemwlTyVHHrTGgQg7gmNPJs88xkbKcIL3NlcmSU=
|
||||
github.com/rymdport/portal v0.4.2/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
|
||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
|
||||
@@ -68,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=
|
||||
@@ -97,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=
|
||||
|
634
main.go
634
main.go
@@ -4,10 +4,12 @@ import (
|
||||
//core - required
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"image/color"
|
||||
_ "image/color"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand/v2"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -20,43 +22,83 @@ import (
|
||||
"fyne.io/fyne/v2/storage"
|
||||
"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.1a"
|
||||
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
|
||||
|
||||
type Message struct {
|
||||
Author string
|
||||
Content string
|
||||
ID string
|
||||
ReplyID string
|
||||
ImageURL string
|
||||
Raw oasisSdk.XMPPChatMessage
|
||||
Author string
|
||||
Content string
|
||||
ID string
|
||||
ReplyID string
|
||||
ImageURL string
|
||||
Raw oasisSdk.XMPPChatMessage
|
||||
Important bool
|
||||
Readers []jid.JID
|
||||
}
|
||||
|
||||
type MucTab struct {
|
||||
Jid jid.JID
|
||||
Nick string
|
||||
Messages []Message
|
||||
Scroller *widget.List
|
||||
isMuc bool
|
||||
Muc *muc.Channel
|
||||
type ChatTab struct {
|
||||
Jid jid.JID
|
||||
Nick string
|
||||
Messages []Message
|
||||
isMuc bool
|
||||
Muc *muc.Channel
|
||||
UpdateSidebar bool
|
||||
}
|
||||
|
||||
type ChatTabUI struct {
|
||||
Internal *ChatTab
|
||||
Scroller *widget.List `xml:"-"`
|
||||
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 {
|
||||
@@ -69,97 +111,113 @@ var config piConfig
|
||||
var login oasisSdk.LoginInfo
|
||||
var DMs []string
|
||||
|
||||
var chatTabs = make(map[string]*MucTab)
|
||||
var tabs *container.AppTabs
|
||||
var chatTabs = make(map[string]*ChatTab)
|
||||
var UITabs = make(map[string]*ChatTabUI)
|
||||
|
||||
var AppTabs *container.AppTabs
|
||||
var selectedId widget.ListItemID
|
||||
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
|
||||
var a fyne.App
|
||||
|
||||
func addChatTab(isMuc bool, chatJid jid.JID, nick string) {
|
||||
mucJidStr := chatJid.String()
|
||||
if _, ok := chatTabs[mucJidStr]; ok {
|
||||
// Tab already exists
|
||||
return
|
||||
}
|
||||
|
||||
tabData := &MucTab{
|
||||
Jid: chatJid,
|
||||
Nick: nick,
|
||||
Messages: []Message{},
|
||||
isMuc: isMuc,
|
||||
}
|
||||
|
||||
func CreateUITab(chatJidStr string) ChatTabUI {
|
||||
var scroller *widget.List
|
||||
scroller = widget.NewList(
|
||||
func() int {
|
||||
return len(tabData.Messages)
|
||||
return len(chatTabs[chatJidStr].Messages)
|
||||
},
|
||||
func() fyne.CanvasObject {
|
||||
gen, _ := identicon.New("github", 5, 3)
|
||||
ii, _ := gen.Draw("default")
|
||||
im := ii.Image(25)
|
||||
ico := canvas.NewImageFromImage(im)
|
||||
ico.FillMode = canvas.ImageFillOriginal
|
||||
author := widget.NewLabel("author")
|
||||
author.TextStyle.Bold = true
|
||||
content := widget.NewRichTextWithText("content")
|
||||
content := widget.NewLabel("content")
|
||||
content.Wrapping = fyne.TextWrapWord
|
||||
content.Selectable = true
|
||||
icon := theme.FileVideoIcon()
|
||||
btn := widget.NewButtonWithIcon("View media", icon, func() {
|
||||
|
||||
})
|
||||
return container.NewVBox(author, content, btn)
|
||||
return container.NewVBox(container.NewHBox(ico, author), content, btn)
|
||||
},
|
||||
func(i widget.ListItemID, co fyne.CanvasObject) {
|
||||
vbox := co.(*fyne.Container)
|
||||
author := vbox.Objects[0].(*widget.Label)
|
||||
content := vbox.Objects[1].(*widget.RichText)
|
||||
authorBox := vbox.Objects[0].(*fyne.Container)
|
||||
// generate a Icon
|
||||
|
||||
gen, _ := identicon.New("github", 5, 3)
|
||||
ii, _ := gen.Draw(chatTabs[chatJidStr].Messages[i].Author)
|
||||
im := ii.Image(25)
|
||||
authorBox.Objects[0] = canvas.NewImageFromImage(im)
|
||||
authorBox.Objects[0].(*canvas.Image).FillMode = canvas.ImageFillOriginal
|
||||
authorBox.Objects[0].Refresh()
|
||||
|
||||
// Icon generate end
|
||||
|
||||
author := authorBox.Objects[1].(*widget.Label)
|
||||
content := vbox.Objects[1].(*widget.Label)
|
||||
btn := vbox.Objects[2].(*widget.Button)
|
||||
if chatTabs[chatJidStr].Messages[i].Important {
|
||||
//content.Importance = widget.DangerImportance TODO: Fix highlighting messages with mentions, it's currently broken
|
||||
}
|
||||
btn.Hidden = true // Hide by default
|
||||
msgContent := tabData.Messages[i].Content
|
||||
if tabData.Messages[i].ImageURL != "" {
|
||||
msgContent := chatTabs[chatJidStr].Messages[i].Content
|
||||
if chatTabs[chatJidStr].Messages[i].ImageURL != "" {
|
||||
btn.Hidden = false
|
||||
btn.OnTapped = func() {
|
||||
fyne.Do(func() {
|
||||
u, err := storage.ParseURI(tabData.Messages[i].ImageURL)
|
||||
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(tabData.Messages[i].ImageURL, "mp4") {
|
||||
url, err := url.Parse(tabData.Messages[i].ImageURL)
|
||||
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
|
||||
@@ -171,27 +229,71 @@ func addChatTab(isMuc bool, chatJid jid.JID, nick string) {
|
||||
}
|
||||
msgContent = strings.Join(lines, "\n")
|
||||
|
||||
content.ParseMarkdown(msgContent)
|
||||
if tabData.Messages[i].ReplyID != "PICLIENT:UNAVAILABLE" {
|
||||
author.SetText(fmt.Sprintf("%s > %s", tabData.Messages[i].Author, jid.MustParse(tabData.Messages[i].Raw.Reply.To).Resourcepart()))
|
||||
//content.ParseMarkdown(msgContent)
|
||||
content.SetText(msgContent)
|
||||
if chatTabs[chatJidStr].Messages[i].ReplyID != "PICLIENT:UNAVAILABLE" {
|
||||
author.SetText(fmt.Sprintf("%s > %s", chatTabs[chatJidStr].Messages[i].Author, jid.MustParse(chatTabs[chatJidStr].Messages[i].Raw.Reply.To).Resourcepart()))
|
||||
} else {
|
||||
author.SetText(tabData.Messages[i].Author)
|
||||
author.SetText(chatTabs[chatJidStr].Messages[i].Author)
|
||||
}
|
||||
|
||||
sl := strings.Split(msgContent, " ")
|
||||
if sl[0] == "/me" {
|
||||
author.SetText(author.Text + " " + strings.Join(sl[1:], " "))
|
||||
content.SetText(" ")
|
||||
}
|
||||
|
||||
scroller.SetItemHeight(i, vbox.MinSize().Height)
|
||||
},
|
||||
)
|
||||
|
||||
scroller.OnSelected = func(id widget.ListItemID) {
|
||||
selectedId = id
|
||||
}
|
||||
|
||||
myUITab := 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
|
||||
myUITab.Sidebar = container.NewVBox(imw)
|
||||
|
||||
tabData.Scroller = scroller
|
||||
return myUITab
|
||||
}
|
||||
func addChatTab(isMuc bool, chatJid jid.JID, nick string) {
|
||||
|
||||
chatTabs[mucJidStr] = tabData
|
||||
chatJidStr := chatJid.String()
|
||||
if _, ok := chatTabs[chatJidStr]; ok {
|
||||
// Tab already exists
|
||||
return
|
||||
}
|
||||
|
||||
tabItem := container.NewTabItem(chatJid.Localpart(), scroller)
|
||||
tabs.Append(tabItem)
|
||||
myChatTab := ChatTab{
|
||||
Jid: chatJid,
|
||||
Nick: nick,
|
||||
Messages: []Message{},
|
||||
isMuc: isMuc,
|
||||
}
|
||||
|
||||
myUITab := CreateUITab(chatJid.String())
|
||||
myUITab.Internal = &myChatTab
|
||||
|
||||
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.NewTabItemWithIcon(chatJid.String(), icon, myUITab.Scroller))
|
||||
})
|
||||
}
|
||||
|
||||
func dropToSignInPage(reason string) {
|
||||
@@ -227,8 +329,7 @@ func dropToSignInPage(reason string) {
|
||||
config.Login.User = userEntry.Text
|
||||
config.Login.Password = passwordEntry.Text
|
||||
config.Login.DisplayName = nicknameEntry.Text
|
||||
config.Notifications = true
|
||||
config.Login.MucsToJoin = append(config.Login.MucsToJoin, "ringen@muc.isekai.rocks") // DEBUG
|
||||
config.Notifications = false
|
||||
|
||||
bytes, err := xml.MarshalIndent(config, "", "\t")
|
||||
if err != nil {
|
||||
@@ -263,8 +364,9 @@ func dropToSignInPage(reason string) {
|
||||
|
||||
func main() {
|
||||
muc.Since(time.Now())
|
||||
|
||||
config = piConfig{}
|
||||
a = app.NewWithID("pi-ism")
|
||||
a = app.NewWithID("pi-im")
|
||||
reader, err := a.Storage().Open("pi.xml")
|
||||
if err != nil {
|
||||
dropToSignInPage(err.Error())
|
||||
@@ -305,10 +407,9 @@ func main() {
|
||||
lines := strings.Split(str, "\n")
|
||||
for i, line := range lines {
|
||||
s := strings.Split(line, " ")
|
||||
for j, v := range s {
|
||||
for _, v := range s {
|
||||
_, err := url.Parse(v)
|
||||
if err == nil && strings.HasPrefix(v, "https://") {
|
||||
s[j] = fmt.Sprintf("[%s](%s)", v, v)
|
||||
if strings.HasSuffix(v, ".png") || strings.HasSuffix(v, ".jpg") || strings.HasSuffix(v, ".jpeg") || strings.HasSuffix(v, ".webp") || strings.HasSuffix(v, ".mp4") {
|
||||
img = v
|
||||
}
|
||||
@@ -335,9 +436,9 @@ func main() {
|
||||
|
||||
tab.Messages = append(tab.Messages, myMessage)
|
||||
fyne.Do(func() {
|
||||
tab.Scroller.Refresh()
|
||||
UITabs[userJidStr].Scroller.Refresh()
|
||||
if scrollDownOnNewMessage {
|
||||
tab.Scroller.ScrollToBottom()
|
||||
UITabs[userJidStr].Scroller.ScrollToBottom()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -346,16 +447,19 @@ func main() {
|
||||
// HACK: IGNORING ALL MESSAGES FROM CLASSIC MUC HISTORY IN PREPARATION OF MAM SUPPORT
|
||||
ignore := false
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,8 +468,12 @@ func main() {
|
||||
if tab, ok := chatTabs[mucJidStr]; ok {
|
||||
chatTabs[mucJidStr].Muc = muc
|
||||
str := *msg.CleanedBody
|
||||
if strings.Contains(str, login.DisplayName) {
|
||||
fmt.Println(str)
|
||||
important = true
|
||||
}
|
||||
if !ignore && notifications {
|
||||
if !correction && strings.Contains(str, login.DisplayName) || (msg.Reply != nil && strings.Contains(msg.Reply.To, login.DisplayName)) {
|
||||
if !correction && msg.From.String() != client.JID.String() && strings.Contains(str, login.DisplayName) || (msg.Reply != nil && strings.Contains(msg.Reply.To, login.DisplayName)) {
|
||||
a.SendNotification(fyne.NewNotification(fmt.Sprintf("Mentioned in %s", mucJidStr), str))
|
||||
}
|
||||
}
|
||||
@@ -373,11 +481,10 @@ func main() {
|
||||
lines := strings.Split(str, "\n")
|
||||
for i, line := range lines {
|
||||
s := strings.Split(line, " ")
|
||||
for j, v := range s {
|
||||
for _, v := range s {
|
||||
_, err := url.Parse(v)
|
||||
if err == nil && strings.HasPrefix(v, "https://") {
|
||||
s[j] = fmt.Sprintf("[%s](%s)", v, v)
|
||||
if strings.HasSuffix(v, ".png") || strings.HasSuffix(v, ".jpg") || strings.HasSuffix(v, ".jpeg") || strings.HasSuffix(v, ".webp") || strings.HasSuffix(v, ".mp4") {
|
||||
if strings.HasSuffix(v, ".png") || strings.HasSuffix(v, ".jpg") || strings.HasSuffix(v, ".jpeg") || strings.HasSuffix(v, ".webp") || strings.HasSuffix(v, ".mp4") || strings.HasSuffix(v, ".mp3") {
|
||||
ImageID = v
|
||||
}
|
||||
}
|
||||
@@ -396,11 +503,11 @@ func main() {
|
||||
}
|
||||
|
||||
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() {
|
||||
tab.Messages[i].Content = *msg.CleanedBody + " (edited)"
|
||||
fyne.Do(func() {
|
||||
tab.Scroller.Refresh()
|
||||
UITabs[mucJidStr].Scroller.Refresh()
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -408,20 +515,21 @@ func main() {
|
||||
}
|
||||
|
||||
myMessage := Message{
|
||||
Author: msg.From.Resourcepart(),
|
||||
Content: str,
|
||||
ID: msg.ID,
|
||||
ReplyID: replyID,
|
||||
Raw: *msg,
|
||||
ImageURL: ImageID,
|
||||
Author: msg.From.Resourcepart(),
|
||||
Content: str,
|
||||
ID: msg.ID,
|
||||
ReplyID: replyID,
|
||||
Raw: *msg,
|
||||
ImageURL: ImageID,
|
||||
Important: important,
|
||||
}
|
||||
if !ignore {
|
||||
tab.Messages = append(tab.Messages, myMessage)
|
||||
}
|
||||
fyne.Do(func() {
|
||||
tab.Scroller.Refresh()
|
||||
UITabs[mucJidStr].Scroller.Refresh()
|
||||
if scrollDownOnNewMessage {
|
||||
tab.Scroller.ScrollToBottom()
|
||||
UITabs[mucJidStr].Scroller.ScrollToBottom()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -435,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() {
|
||||
@@ -447,7 +555,7 @@ func main() {
|
||||
})
|
||||
default:
|
||||
fyne.Do(func() {
|
||||
statBar.SetText(fmt.Sprint("Unknown state: ", state))
|
||||
statBar.SetText("")
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -455,36 +563,25 @@ 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 {
|
||||
log.Fatalln("Could not create client - " + err.Error())
|
||||
}
|
||||
|
||||
/*
|
||||
client.Session.Serve(xmpp.HandlerFunc(func(t xmlstream.TokenReadEncoder, start *xml.StartElement) error {
|
||||
d := xml.NewTokenDecoder(t)
|
||||
|
||||
// Ignore anything that's not a message.
|
||||
if start.Name.Local != "message" {
|
||||
return nil
|
||||
}
|
||||
|
||||
msg := struct {
|
||||
stanza.Message
|
||||
Body string `xml:"body"`
|
||||
}{}
|
||||
err := d.DecodeElement(&msg, start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if msg.Body != "" {
|
||||
log.Println("Got message: %q", msg.Body)
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
*/
|
||||
go func() {
|
||||
for connection {
|
||||
err = client.Connect()
|
||||
@@ -503,32 +600,32 @@ 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
|
||||
if tabs.Selected() == nil || tabs.Selected().Content == nil {
|
||||
if AppTabs.Selected() == nil || AppTabs.Selected().Content == nil || text == "" {
|
||||
return
|
||||
}
|
||||
|
||||
selectedScroller, ok := tabs.Selected().Content.(*widget.List)
|
||||
selectedScroller, ok := AppTabs.Selected().Content.(*widget.List)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var activeMucJid string
|
||||
var isMuc bool
|
||||
for jid, tabData := range chatTabs {
|
||||
for jid, tabData := range UITabs {
|
||||
if tabData.Scroller == selectedScroller {
|
||||
activeMucJid = jid
|
||||
isMuc = tabData.isMuc
|
||||
isMuc = chatTabs[activeMucJid].isMuc
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -540,11 +637,25 @@ func main() {
|
||||
go func() {
|
||||
if replying {
|
||||
m := chatTabs[activeMucJid].Messages[selectedId].Raw
|
||||
client.ReplyToEvent(&m, text)
|
||||
fmt.Println(selectedId)
|
||||
err = client.ReplyToEvent(&m, text)
|
||||
if err != nil {
|
||||
dialog.ShowError(err, w)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
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.SendSingleFileMessage(jid.MustParse(activeMucJid).Bare(), url.String(), nil)
|
||||
if err != nil {
|
||||
dialog.ShowError(err, w)
|
||||
}
|
||||
return
|
||||
}
|
||||
err = client.SendText(jid.MustParse(activeMucJid).Bare(), text)
|
||||
|
||||
if err != nil {
|
||||
dialog.ShowError(err, w)
|
||||
}
|
||||
@@ -558,7 +669,7 @@ func main() {
|
||||
})
|
||||
fyne.Do(func() {
|
||||
if scrollDownOnNewMessage {
|
||||
chatTabs[activeMucJid].Scroller.ScrollToBottom()
|
||||
UITabs[activeMucJid].Scroller.ScrollToBottom()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -566,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
|
||||
@@ -614,7 +730,7 @@ func main() {
|
||||
})
|
||||
|
||||
jtb := fyne.NewMenuItem("jump to bottom", func() {
|
||||
selectedScroller, ok := tabs.Selected().Content.(*widget.List)
|
||||
selectedScroller, ok := AppTabs.Selected().Content.(*widget.List)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -622,7 +738,7 @@ func main() {
|
||||
})
|
||||
|
||||
jtt := fyne.NewMenuItem("jump to top", func() {
|
||||
selectedScroller, ok := tabs.Selected().Content.(*widget.List)
|
||||
selectedScroller, ok := AppTabs.Selected().Content.(*widget.List)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -663,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
|
||||
@@ -691,7 +804,9 @@ func main() {
|
||||
progress := make(chan oasisSdk.UploadProgress)
|
||||
myprogressbar := widget.NewProgressBar()
|
||||
diag := dialog.NewCustom("Uploading file", "Hide", myprogressbar, w)
|
||||
diag.Show()
|
||||
fyne.Do(func() {
|
||||
diag.Show()
|
||||
})
|
||||
go func() {
|
||||
client.UploadFile(client.Ctx, reader.URI().Path(), progress)
|
||||
}()
|
||||
@@ -721,32 +836,172 @@ func main() {
|
||||
}, w)
|
||||
})
|
||||
|
||||
servDisc := fyne.NewMenuItem("Service discovery", func() {
|
||||
|
||||
myBox := container.NewVBox()
|
||||
info, err := disco.GetInfo(client.Ctx, "", jid.MustParse("ringen@muc.isekai.rocks"), client.Session)
|
||||
if err != nil {
|
||||
dialog.ShowError(err, w)
|
||||
}
|
||||
m := info.Features
|
||||
for _, v := range m {
|
||||
myBox.Objects = append(myBox.Objects, widget.NewLabel(v.Var))
|
||||
myBox.Refresh()
|
||||
}
|
||||
|
||||
dialog.ShowCustom("things", "cancel", myBox, w)
|
||||
})
|
||||
|
||||
menu_help := fyne.NewMenu("π", mit, reconnect, deb)
|
||||
menu_changeroom := fyne.NewMenu("β", mic, servDisc)
|
||||
menu_configureview := fyne.NewMenu("γ", mia, mis, jtt, jtb)
|
||||
bit := fyne.NewMenuItem("mark selected message as read", func() {
|
||||
selectedScroller, ok := tabs.Selected().Content.(*widget.List)
|
||||
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 chatTabs {
|
||||
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
|
||||
search, err = jid.Parse(s)
|
||||
if err != nil {
|
||||
dialog.ShowError(err, w)
|
||||
return
|
||||
}
|
||||
|
||||
myBox := container.NewGridWithColumns(1, widget.NewLabel("Items"))
|
||||
info, err := disco.GetInfo(client.Ctx, "", search, client.Session)
|
||||
if err != nil {
|
||||
dialog.ShowError(err, w)
|
||||
return
|
||||
}
|
||||
m := info.Features
|
||||
for _, v := range m {
|
||||
myBox.Add(widget.NewLabel(v.Var))
|
||||
myBox.Refresh()
|
||||
}
|
||||
|
||||
dialog.ShowCustom("Features", "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)
|
||||
})
|
||||
menu_help := fyne.NewMenu("π", mit, reconnect, savedata)
|
||||
|
||||
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"
|
||||
SendCallback()
|
||||
entry.Text = "Hafjag"
|
||||
})
|
||||
|
||||
hotfuck := fyne.NewMenuItem("Hot Fuck", func() {
|
||||
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 = 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, agree)
|
||||
bit := fyne.NewMenuItem("mark selected message as read", 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
|
||||
@@ -762,15 +1017,15 @@ func main() {
|
||||
})
|
||||
|
||||
bic := fyne.NewMenuItem("show message XML", func() {
|
||||
pre := widget.NewLabel("")
|
||||
pre := widget.NewMultiLineEntry()
|
||||
|
||||
selectedScroller, ok := tabs.Selected().Content.(*widget.List)
|
||||
selectedScroller, ok := AppTabs.Selected().Content.(*widget.List)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var activeChatJid string
|
||||
for jid, tabData := range chatTabs {
|
||||
for jid, tabData := range UITabs {
|
||||
if tabData.Scroller == selectedScroller {
|
||||
activeChatJid = jid
|
||||
break
|
||||
@@ -784,15 +1039,51 @@ 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)
|
||||
ma := fyne.NewMainMenu(menu_help, menu_changeroom, menu_configureview, menu_messageoptions)
|
||||
|
||||
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)
|
||||
|
||||
tabs = container.NewAppTabs(
|
||||
AppTabs = container.NewAppTabs(
|
||||
container.NewTabItem("τίποτα", widget.NewLabel(`
|
||||
pi
|
||||
`)),
|
||||
@@ -813,14 +1104,14 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
tabs.OnSelected = func(ti *container.TabItem) {
|
||||
selectedScroller, ok := tabs.Selected().Content.(*widget.List)
|
||||
AppTabs.OnSelected = func(ti *container.TabItem) {
|
||||
selectedScroller, ok := AppTabs.Selected().Content.(*widget.List)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var activeChatJid string
|
||||
for jid, tabData := range chatTabs {
|
||||
for jid, tabData := range UITabs {
|
||||
if tabData.Scroller == selectedScroller {
|
||||
activeChatJid = jid
|
||||
break
|
||||
@@ -828,19 +1119,22 @@ 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()))
|
||||
}
|
||||
|
||||
if tab.isMuc {
|
||||
chatSidebar = *container.NewStack(container.NewVScroll(container.NewVBox(widget.NewRichTextFromMarkdown(fmt.Sprintf("# %s", tab.Jid.String())), widget.NewRichTextFromMarkdown(tab.Muc.Addr().String()))))
|
||||
//chatSidebar.Refresh()
|
||||
}
|
||||
chatSidebar = *UITab.Sidebar
|
||||
old := chatSidebar.Position()
|
||||
chatSidebar.Refresh()
|
||||
chatSidebar.Move(old)
|
||||
}
|
||||
|
||||
statBar.SetText("nothing seems to be happening right now...")
|
||||
w.SetContent(container.NewVSplit(container.NewVSplit(container.NewHSplit(tabs, &chatSidebar), container.NewHSplit(entry, sendbtn)), container.NewHSplit(&statBar, &chatInfo)))
|
||||
// HACK - disable chatsidebar because it's currently very buggy
|
||||
chatSidebar.Hidden = true
|
||||
statBar.SetText("")
|
||||
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
12
piloadinggif.go
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user