diff --git a/client/client.go b/client/client.go index cf3853b..63099a4 100644 --- a/client/client.go +++ b/client/client.go @@ -34,8 +34,7 @@ type Client struct { const CRLF = "\r\n" -func NewClient(host string, port string, tlsEnabled bool) *Client { - // TODO: distinguish between secure and insecure connections +func NewClient(host string, port string, tlsEnabled bool) Client { addr := fmt.Sprintf("%s:%s", host, port) if tlsEnabled { @@ -46,7 +45,7 @@ func NewClient(host string, port string, tlsEnabled bool) *Client { log.Fatal(err) } - return &Client{ + return Client{ TlsConn: conn, TcpConn: nil, } @@ -62,7 +61,7 @@ func NewClient(host string, port string, tlsEnabled bool) *Client { log.Fatal(err) } - return &Client{ + return Client{ TlsConn: nil, TcpConn: conn, } diff --git a/ui/app/app.go b/ui/app/app.go index 181f51d..6ef1a96 100644 --- a/ui/app/app.go +++ b/ui/app/app.go @@ -35,27 +35,29 @@ const ( ) type UI struct { - currentScreen Screen - login login.State - mainScreen mainscreen.State + CurrentScreen Screen + Login login.State + MainScreen mainscreen.State } type State struct { - ui UI - client *client.Client + UI UI + Client *client.Client } -func initialUiState() UI { +func initialUiState(client *client.Client) UI { return UI{ - login: login.NewLogin(), - mainScreen: mainscreen.NewMainScreen(), + Login: login.NewLogin(), + MainScreen: mainscreen.NewMainScreen(client), } } func InitialState() State { + client := &client.Client{} + s := State{ - client: &client.Client{}, - ui: initialUiState(), + Client: client, + UI: initialUiState(client), } return s @@ -70,14 +72,7 @@ func (s State) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.KeyMsg: switch msg.String() { case "ctrl+c": - // TODO: clean up and gracefully quit by leaving the IRC channel/server and then quit bubble. - if s.client.TlsConn != nil { - s.client.TlsConn.Close() - } - if s.client.TcpConn != nil { - s.client.TcpConn.Close() - } - return s, tea.Quit + return s, s.Quit } case tea.WindowSizeMsg: @@ -85,45 +80,44 @@ func (s State) Update(msg tea.Msg) (tea.Model, tea.Cmd) { Width(msg.Width). Height(msg.Height) - s.ui.mainScreen.SetSize(msg.Width, msg.Height) + s.UI.MainScreen.SetSize(msg.Width, msg.Height) return s, nil case login.ConnectingMsg: - host := s.ui.login.Inputs[0].Value() - port := s.ui.login.Inputs[1].Value() - channel := s.ui.login.Inputs[2].Value() - nickname := s.ui.login.Inputs[3].Value() - password := s.ui.login.Inputs[4].Value() - tlsEnabled := s.ui.login.TLS + host := s.UI.Login.Inputs[0].Value() + port := s.UI.Login.Inputs[1].Value() + channel := s.UI.Login.Inputs[2].Value() + nickname := s.UI.Login.Inputs[3].Value() + password := s.UI.Login.Inputs[4].Value() + tlsEnabled := s.UI.Login.TLS - s.ui.currentScreen = MainScreen + s.UI.CurrentScreen = MainScreen + s.UI.MainScreen.CurrentChannel = channel // create new client with the provided host and port - client := client.NewClient(host, port, tlsEnabled) - client.Register(nickname, password, channel) - s.client = client - s.ui.mainScreen.Client = client + (*s.Client) = client.NewClient(host, port, tlsEnabled) + s.Client.Register(nickname, password, channel) if tlsEnabled { - r := bufio.NewReaderSize(client.TlsConn, 512) - s.ui.mainScreen.Reader = r + r := bufio.NewReaderSize(s.Client.TlsConn, 512) + s.UI.MainScreen.Reader = r } else { - r := bufio.NewReaderSize(client.TcpConn, 512) - s.ui.mainScreen.Reader = r + r := bufio.NewReaderSize(s.Client.TcpConn, 512) + s.UI.MainScreen.Reader = r } return s, mainscreen.InitialRead } // switch between which screen is currently active and update its state - switch s.ui.currentScreen { + switch s.UI.CurrentScreen { case Login: - state, cmd := s.ui.login.Update(msg) - s.ui.login = state + state, cmd := s.UI.Login.Update(msg) + s.UI.Login = state return s, cmd case MainScreen: - state, cmd := s.ui.mainScreen.Update(msg) - s.ui.mainScreen = state + state, cmd := s.UI.MainScreen.Update(msg) + s.UI.MainScreen = state return s, cmd } @@ -132,11 +126,11 @@ func (s State) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (s State) View() string { // switch between which screen is currently active and render it. - switch s.ui.currentScreen { + switch s.UI.CurrentScreen { case Login: - return s.ui.login.View() + return s.UI.Login.View() case MainScreen: - return s.ui.mainScreen.View() + return s.UI.MainScreen.View() } return "Error: this screen shouldn't ever show" diff --git a/ui/app/cmds.go b/ui/app/cmds.go new file mode 100644 index 0000000..9bddaf5 --- /dev/null +++ b/ui/app/cmds.go @@ -0,0 +1,32 @@ +// gorc project +// Copyright (C) 2021 IllusionMan1212 +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see https://www.gnu.org/licenses. + +package app + +import tea "github.com/charmbracelet/bubbletea" + +func (s *State) Quit() tea.Msg { + if s.Client.TlsConn != nil { + s.Client.SendCommand("QUIT") + s.Client.TlsConn.Close() + } + if s.Client.TcpConn != nil { + s.Client.SendCommand("QUIT") + s.Client.TcpConn.Close() + } + + return tea.Quit() +} diff --git a/ui/mainscreen/cmds.go b/ui/mainscreen/cmds.go index 953a917..3a33e89 100644 --- a/ui/mainscreen/cmds.go +++ b/ui/mainscreen/cmds.go @@ -22,16 +22,16 @@ import ( tea "github.com/charmbracelet/bubbletea" ) -type ReceivedIRCCommandMsg struct { - Msg string -} - type InitialReadMsg struct{} func InitialRead() tea.Msg { return InitialReadMsg{} } +type ReceivedIRCCommandMsg struct { + Msg string +} + func (s State) readFromServer() tea.Msg { msg, err := s.Reader.ReadString('\n') if err != nil { @@ -40,3 +40,13 @@ func (s State) readFromServer() tea.Msg { return ReceivedIRCCommandMsg{Msg: msg} } + +type SendPrivMsg struct { + Msg string +} + +func (s InputState) SendingPrivMsg(msg string) tea.Cmd { + return func() tea.Msg { + return SendPrivMsg{Msg: msg} + } +} diff --git a/ui/mainscreen/input.go b/ui/mainscreen/input.go index 4299804..843c214 100644 --- a/ui/mainscreen/input.go +++ b/ui/mainscreen/input.go @@ -20,23 +20,41 @@ import ( "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "github.com/illusionman1212/gorc/client" ) type InputState struct { - Input textinput.Model - Style lipgloss.Style + Input textinput.Model + Style lipgloss.Style + Client *client.Client } -func NewInputBox() InputState { +func NewInputBox(client *client.Client) InputState { input := textinput.NewModel() input.Placeholder = "Send a message..." input.Focus() - state := InputState{input, InputboxStyle.Copy()} + state := InputState{ + Input: input, + Style: InputboxStyle.Copy(), + Client: client, + } return state } func (s InputState) Update(msg tea.Msg) (InputState, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + key := msg.String() + switch key { + case "enter": + value := s.Input.Value() + s.Input.Reset() + + return s, s.SendingPrivMsg(value) + } + } + var cmd tea.Cmd s.Input, cmd = s.Input.Update(msg) return s, cmd diff --git a/ui/mainscreen/mainscreen.go b/ui/mainscreen/mainscreen.go index 59cb47e..87930a5 100644 --- a/ui/mainscreen/mainscreen.go +++ b/ui/mainscreen/mainscreen.go @@ -45,10 +45,14 @@ type State struct { Style lipgloss.Style FocusIndex Window + // a channel could be either a server channel + // or a username of a user + CurrentChannel string + InputBox InputState } -func NewMainScreen() State { +func NewMainScreen(client *client.Client) State { newViewport := viewport.Model{ HighPerformanceRendering: false, Wrap: true, @@ -57,11 +61,11 @@ func NewMainScreen() State { state := State{ Content: "", Reader: nil, - Client: nil, + Client: client, Viewport: newViewport, Style: MessagesStyle.Copy(), FocusIndex: InputBox, - InputBox: NewInputBox(), + InputBox: NewInputBox(client), } return state } @@ -75,18 +79,15 @@ func (s State) Update(msg tea.Msg) (State, tea.Cmd) { return s, s.readFromServer case ReceivedIRCCommandMsg: message := parser.ParseIRCMessage(msg.Msg) - fullMsg := fmt.Sprintf("%s %s %s %s", message.Tags, message.Source, message.Command, strings.Join(message.Parameters, " ")) - s.Content += fullMsg + s.HandleCommand(message) + + return s, s.readFromServer + case SendPrivMsg: + s.Content += msg.Msg + client.CRLF + s.Client.SendCommand("PRIVMSG", s.CurrentChannel, msg.Msg) s.Viewport.SetContent(s.Content) - // TODO: handle different commands - switch message.Command { - case "PING": - s.Client.SendCommand("PONG", message.Parameters[0]) - break - } - return s, s.readFromServer case tea.KeyMsg: key := msg.String() @@ -147,6 +148,20 @@ func (s *State) SetSize(width, height int) { s.Viewport.SetContent(s.Content) } +func (s *State) HandleCommand(msg parser.IRCMessage) { + fullMsg := fmt.Sprintf("%s %s %s %s", msg.Tags, msg.Source, msg.Command, strings.Join(msg.Parameters, " ")) + s.Content += fullMsg + + s.Viewport.SetContent(s.Content) + + // TODO: handle different commands + switch msg.Command { + case "PING": + s.Client.SendCommand("PONG", msg.Parameters[0]) + break + } +} + func (s State) View() string { screen := lipgloss.JoinVertical(0, s.Style.Render(s.Viewport.View()), s.InputBox.View())