Skip to content

Commit

Permalink
Add File message to chat
Browse files Browse the repository at this point in the history
The Chat Browser can now send and receive File messages to/from the
server. This enables users to share files. The files can be downloaded
by clicking on the file message. A new file can be send by selecting one
with the Attachment button.
  • Loading branch information
seternate committed Aug 18, 2024
1 parent 4fb9660 commit 9e03d49
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 26 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
github.com/rs/zerolog v1.31.0
github.com/seternate/go-lanty v0.1.0-beta.0.20240809132204-3be3d1587bdd
github.com/seternate/go-lanty v0.1.0-beta.0.20240818103544-c247788a1ae4
golang.design/x/clipboard v0.7.0
)

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,8 @@ github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWR
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/seternate/go-lanty v0.1.0-beta.0.20240809132204-3be3d1587bdd h1:vb22tGtb2yVepWmO/O6qHH6Zgqo8Ztd+PK47kg3xQCU=
github.com/seternate/go-lanty v0.1.0-beta.0.20240809132204-3be3d1587bdd/go.mod h1:QL66bV5xXhUysHVUI8VaX773dsvXJp0bDzrXWWq3IOs=
github.com/seternate/go-lanty v0.1.0-beta.0.20240818103544-c247788a1ae4 h1:vZbd/gyKrHFOBXz4htFCNR/AxD2c8avrPFazuYtMOdk=
github.com/seternate/go-lanty v0.1.0-beta.0.20240818103544-c247788a1ae4/go.mod h1:QL66bV5xXhUysHVUI8VaX773dsvXJp0bDzrXWWq3IOs=
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
Expand Down
48 changes: 48 additions & 0 deletions pkg/controller/chatcontroller.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package controller

import (
"fmt"
"net/url"
"slices"
"sync"
"time"
Expand Down Expand Up @@ -31,13 +33,59 @@ func NewChatController(parent *Controller) (controller *ChatController) {
return
}

func (controller *ChatController) DownloadFile(message chat.Message) {
if message.GetType() != chat.TYPE_FILE {
log.Warn().Interface("message", message).Msg("wrong message type provided for file download")
return
}
m := message.(*chat.FileMessage)
u, err := url.Parse(m.Message.URL)
if err != nil {
log.Error().Err(err).Interface("message", message).Msg("error parsing filemessage url")
controller.parent.Status.Error("Failed to start download", 3*time.Second)
return
}
download, err := controller.parent.client.File.GetFile(controller.parent.ctx, *u, controller.parent.settings.DownloadDirectory)
if err != nil {
log.Error().Err(err).Interface("message", message).Msg("error starting filemessage download")
controller.parent.Status.Error(fmt.Sprintf("Failed downloading %s", download.Filename()), 3*time.Second)
return
}
go func() {
<-download.Done
if download.Err != nil {
log.Error().Err(err).Str("file", download.Filename()).Msg("error downloading filemessage file")
controller.parent.Status.Error(fmt.Sprintf("Failed downloading %s", download.Filename()), 3*time.Second)
return
}
log.Debug().Str("file", download.Filename()).Msg("sucessfully downloaded filemessage file")
controller.parent.Status.Info(fmt.Sprintf("Downloaded \"%s\" to \"%s\"", download.Filename(), controller.parent.settings.DownloadDirectory), 3*time.Second)
}()
}

func (controller *ChatController) SendTextMessage(message string) {
err := controller.parent.client.Chat.SendMessage(chat.NewTextMessage(controller.parent.User.GetUser(), message))
if err != nil {
log.Error().Err(err).Msg("error sending textmessage to server")
}
}

func (controller *ChatController) SendFileMessage(path string) {
controller.parent.Status.Info(fmt.Sprintf("Uploading file \"%s\" ...", path), 3*time.Second)
fileresponse, err := controller.parent.client.File.UploadFile(path)
if err != nil {
controller.parent.Status.Error(fmt.Sprintf("Error uploading file \"%s\"", path), 3*time.Second)
log.Error().Err(err).Str("file", path).Msg("error uploading file to server")
return
}

message := chat.NewFileMessage(controller.parent.User.GetUser(), fileresponse)
err = controller.parent.client.Chat.SendMessage(message)
if err != nil {
log.Error().Err(err).Interface("message", message).Msg("error sending filemessage to server")
}
}

func (controller *ChatController) Subscribe(subscriber chan chat.Message) {
defer controller.mutex.Unlock()
controller.mutex.Lock()
Expand Down
8 changes: 8 additions & 0 deletions pkg/controller/settingscontroller.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ func (controller *SettingsController) SetUsername(username string) {
controller.Save()
}

func (controller *SettingsController) SetDownloadDirectory(downloaddirectory string) {
controller.mutex.Lock()
controller.settings.DownloadDirectory = downloaddirectory
controller.mutex.Unlock()
controller.notifySubcriber()
controller.Save()
}

func (controller *SettingsController) Settings() setting.Settings {
defer controller.mutex.RUnlock()
controller.mutex.RLock()
Expand Down
7 changes: 4 additions & 3 deletions pkg/setting/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ const (
)

type Settings struct {
ServerURL string `yaml:"serverurl"`
GameDirectory string `yaml:"gamedirectory"`
Username string `yaml:"username"`
ServerURL string `yaml:"serverurl"`
GameDirectory string `yaml:"gamedirectory"`
Username string `yaml:"username"`
DownloadDirectory string `yaml:"downloaddirectory"`
}

func LoadSettings() (s *Settings, err error) {
Expand Down
39 changes: 35 additions & 4 deletions pkg/widget/chatbrowser.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package widget
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/storage"
fynetheme "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"github.com/rs/zerolog/log"
Expand All @@ -13,20 +15,24 @@ import (
type ChatBrowser struct {
widget.BaseWidget
controller *controller.Controller
window fyne.Window
messageboard *MessageBoard
scroll *ScrollWithState
messageentry *widget.Entry
sendbutton *widget.Button
filebutton *widget.Button
newmessage chan struct{}
}

func NewChatBrowser(controller *controller.Controller) (chatbrowser *ChatBrowser) {
func NewChatBrowser(controller *controller.Controller, window fyne.Window) (chatbrowser *ChatBrowser) {
chatbrowser = &ChatBrowser{
controller: controller,
window: window,
messageboard: NewMessageBoard(controller),
messageentry: widget.NewMultiLineEntry(),
newmessage: make(chan struct{}, 50),
sendbutton: widget.NewButtonWithIcon("", fynetheme.MailSendIcon(), nil),
filebutton: widget.NewButtonWithIcon("", fynetheme.MailAttachmentIcon(), nil),
}
chatbrowser.ExtendBaseWidget(chatbrowser)

Expand All @@ -37,18 +43,40 @@ func NewChatBrowser(controller *controller.Controller) (chatbrowser *ChatBrowser
chatbrowser.messageentry.SetPlaceHolder("Type your message here!")
chatbrowser.messageentry.OnSubmitted = func(s string) {
controller.Chat.SendTextMessage(s)
chatbrowser.messageentry.SetText("")
}

chatbrowser.sendbutton.OnTapped = func() {
controller.Chat.SendTextMessage(chatbrowser.messageentry.Text)
chatbrowser.messageentry.SetText("")
}

chatbrowser.filebutton.OnTapped = chatbrowser.fileUploadCallback

chatbrowser.messageboard.Subscribe(chatbrowser.newmessage)
chatbrowser.run()

return chatbrowser
}

func (widget *ChatBrowser) fileUploadCallback() {
folderdialog := dialog.NewFileOpen(func(uri fyne.URIReadCloser, err error) {
if uri == nil || err != nil {
return
}
widget.controller.Chat.SendFileMessage(uri.URI().Path())
}, widget.window)

dialogStartURI, err := storage.ListerForURI(storage.NewFileURI(widget.controller.Settings.Settings().GameDirectory))
if err == nil {
folderdialog.SetLocation(dialogStartURI)
}

//This will make the folderopen dialog to be "fullscreen" inside the app
folderdialog.Resize(fyne.NewSize(10000, 10000))
folderdialog.Show()
}

func (widget *ChatBrowser) run() {
widget.controller.WaitGroup().Add(1)
go widget.messageUpdater()
Expand Down Expand Up @@ -89,6 +117,7 @@ func (renderer *chatBrowserRenderer) Objects() []fyne.CanvasObject {
objects := []fyne.CanvasObject{
renderer.widget.scroll,
renderer.widget.sendbutton,
renderer.widget.filebutton,
renderer.widget.messageentry,
}
return objects
Expand All @@ -101,14 +130,15 @@ func (renderer *chatBrowserRenderer) Layout(size fyne.Size) {
renderer.widget.scroll.Resize(fyne.NewSize(size.Width-2*theme.InnerPadding(), size.Height-3*theme.InnerPadding()-bottomHeight))
renderer.widget.sendbutton.Resize(fyne.NewSize(bottomHeight, bottomHeight))
renderer.widget.sendbutton.Move(fyne.NewPos(size.Width-theme.InnerPadding()-renderer.widget.sendbutton.Size().Width, renderer.widget.scroll.Position().Y+renderer.widget.scroll.Size().Height+theme.InnerPadding()))
renderer.widget.filebutton.Resize(fyne.NewSize(bottomHeight, bottomHeight))
renderer.widget.filebutton.Move(fyne.NewPos(renderer.widget.sendbutton.Position().X-theme.InnerPadding()-renderer.widget.filebutton.Size().Width, renderer.widget.scroll.Position().Y+renderer.widget.scroll.Size().Height+theme.InnerPadding()))
renderer.widget.messageentry.Move(fyne.NewPos(renderer.widget.scroll.Position().X, renderer.widget.scroll.Position().Y+renderer.widget.scroll.Size().Height+theme.InnerPadding()))
renderer.widget.messageentry.Resize(fyne.NewSize(size.Width-3*theme.InnerPadding()-renderer.widget.sendbutton.Size().Width, bottomHeight))

renderer.widget.messageentry.Resize(fyne.NewSize(renderer.widget.filebutton.Position().X-2*theme.InnerPadding(), bottomHeight))
}

func (renderer *chatBrowserRenderer) MinSize() fyne.Size {
bottomHeight := fyne.Max(renderer.widget.messageentry.MinSize().Height, renderer.widget.sendbutton.MinSize().Height)
minWidth := fyne.Max(3*theme.InnerPadding()+renderer.widget.messageentry.MinSize().Width+renderer.widget.sendbutton.MinSize().Width, 2*theme.InnerPadding()+renderer.widget.scroll.MinSize().Width)
minWidth := fyne.Max(4*theme.InnerPadding()+renderer.widget.messageentry.MinSize().Width+renderer.widget.sendbutton.MinSize().Width+renderer.widget.filebutton.MinSize().Width, 2*theme.InnerPadding()+renderer.widget.scroll.MinSize().Width)
minHeight := 3*theme.InnerPadding() + bottomHeight
return fyne.NewSize(minWidth, minHeight)
}
Expand All @@ -118,6 +148,7 @@ func (renderer *chatBrowserRenderer) Refresh() {
renderer.widget.scroll.Refresh()
renderer.widget.messageentry.Refresh()
renderer.widget.sendbutton.Refresh()
renderer.widget.filebutton.Refresh()
//Without first messagetiles will be never be shown until "resize" event happens
//then all messagetile are shown correctly
canvas.Refresh(renderer.widget)
Expand Down
2 changes: 1 addition & 1 deletion pkg/widget/lanty.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func NewLanty(controller *controller.Controller, window fyne.Window) *Lanty {
downloadbrowser := NewDownloadBrowser(controller)
userbrowser := NewUserBrowser(controller)
settingsbrowser := NewSettingsBrowser(controller, window)
chatbrowser := NewChatBrowser(controller)
chatbrowser := NewChatBrowser(controller, window)

lanty := &Lanty{
controller: controller,
Expand Down
14 changes: 14 additions & 0 deletions pkg/widget/messageboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"slices"
"strings"
"sync"
"time"

"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
Expand All @@ -15,6 +16,7 @@ import (
"github.com/seternate/go-lanty-client/pkg/theme"
"github.com/seternate/go-lanty/pkg/chat"
"github.com/seternate/go-lanty/pkg/util"
"golang.design/x/clipboard"
)

type MessageBoard struct {
Expand Down Expand Up @@ -82,6 +84,15 @@ func (widget *MessageBoard) addMessageTile(message chat.Message) {
messagetile.SetBackgroundColor(fynetheme.PrimaryColor())
messagetile.HideUser()
}
if message.GetType() == chat.TYPE_FILE {
messagetile.SetIcon(fynetheme.FileIcon())
messagetile.OnTapped = widget.controller.Chat.DownloadFile
} else if message.GetType() == chat.TYPE_TEXT {
messagetile.OnTapped = func(m chat.Message) {
clipboard.Write(clipboard.FmtText, []byte(m.GetMessage()))
widget.controller.Status.Info("Message copied", 3*time.Second)
}
}
widget.messagetiles.PushFront(messagetile)
widget.Refresh()
widget.notifySubcriber()
Expand Down Expand Up @@ -134,6 +145,9 @@ func (renderer *messageBoardRenderer) Layout(size fyne.Size) {
}
//Calculate Tile Width
maxtextwidth := fyne.MeasureText(longestline, fynetheme.TextSize(), fyne.TextStyle{}).Width + 4*fynetheme.LineSpacing()
if messagetile.HasIcon() {
maxtextwidth = maxtextwidth + 18
}
messagetilewidth := fyne.Max(maxtextwidth, messagetile.MinSize().Width)
if messagetilewidth > size.Width*0.6 {
messagetilewidth = size.Width * 0.6
Expand Down
Loading

0 comments on commit 9e03d49

Please sign in to comment.