diff --git a/api/api.go b/api/api.go new file mode 100644 index 0000000..5b8c714 --- /dev/null +++ b/api/api.go @@ -0,0 +1,76 @@ +package api + +import ( + "fmt" + "net" +) + +type Pizza struct { + Name string `json:"name"` + Price int `json:"price"` + Quantity int `json:"quantity"` +} + +/** +* Send and Receive messages from the API + */ + +func SendPrintCommand(socket *net.TCPConn, text string) (*net.TCPConn, error) { + json_command := PrintCommand{Command: "print", Text: text} + socket, err := sendJSONToServer(socket, json_command) + + if err != nil { + fmt.Println("Error while connecting to the printer server") + return nil, err + } + + return socket, nil +} + +func SendPizzaCommand(socket *net.TCPConn, pizzas []Pizza) (*net.TCPConn, error) { + json_command := PizzaCommand{Command: "pizza", Pizzas: pizzas} + socket, err := sendJSONToServer(nil, json_command) + + if err != nil { + fmt.Println("Error while connecting to the printer server") + return nil, err + } + + return socket, nil +} + +func SendGetPizzasCommand(socket *net.TCPConn) (*net.TCPConn, error) { + json_command := GetPizzasCommand{Command: "get-pizzas"} + socket, err := sendJSONToServer(nil, json_command) + + if err != nil { + fmt.Println("Error while connecting to the printer server") + return nil, err + } + + return socket, nil +} + +func ReceivePizzasCommand(socket *net.TCPConn) (*net.TCPConn, ReceivePizzas, error) { + pizzas := ReceivePizzas{} + socket, err := receiveJSONFromServer(socket, &pizzas) + + if err != nil { + fmt.Println("Error while connecting to the printer server") + return nil, pizzas, err + } + + return socket, pizzas, nil +} + +func ReceivePriceCommand(socket *net.TCPConn) (*net.TCPConn, ReceivePrice, error) { + price := ReceivePrice{} + socket, err := receiveJSONFromServer(socket, &price) + + if err != nil { + fmt.Println("Error while connecting to the printer server") + return nil, price, err + } + + return socket, price, nil +} diff --git a/api/json_structs.go b/api/json_structs.go new file mode 100644 index 0000000..4cc37ea --- /dev/null +++ b/api/json_structs.go @@ -0,0 +1,37 @@ +package api + +/** +* Commands to give to the API + */ +type JSONCommand interface { + PrintCommand | PizzaCommand | GetPizzasCommand +} + +type PrintCommand struct { + Command string `json:"command"` + Text string `json:"text"` +} + +type PizzaCommand struct { + Command string `json:"command"` + Pizzas []Pizza `json:"pizzas"` +} + +type GetPizzasCommand struct { + Command string `json:"command"` +} + +/** +* What to receive from the API + */ +type JSONReceived interface { + ReceivePizzas | ReceivePrice +} + +type ReceivePizzas struct { + Pizzas []Pizza `json:"pizzas"` +} + +type ReceivePrice struct { + TotalPrice int `json:"total_price"` +} diff --git a/api/protocol.go b/api/protocol.go new file mode 100644 index 0000000..cb1a22e --- /dev/null +++ b/api/protocol.go @@ -0,0 +1,78 @@ +package api + +import ( + "encoding/binary" + "encoding/json" + "fmt" + "net" +) + +var ( + printer_server_IP = net.ParseIP("127.0.0.1") + printer_server_port = 4444 +) + +/** +* All messages from and to the API must have the first 4 bytes set to represent +* the length of the message, followed by the message itself + */ +func sendJSONToServer[T JSONCommand](socket *net.TCPConn, json_command T) (*net.TCPConn, error) { + json_data, err := json.Marshal(json_command) + + if err != nil { + return nil, err + } + + // in case a socket doesn't already exist create a new one + if socket == nil { + socket, err = net.DialTCP("tcp4", nil, &net.TCPAddr{printer_server_IP, printer_server_port, ""}) + } + + if err != nil { + return nil, err + } + + // the first 4 bytes must be the length of the message + to_send := make([]byte, 4) + binary.LittleEndian.PutUint32(to_send, uint32(len(json_data))) + + to_send = append(to_send, json_data...) + socket.Write([]byte(to_send)) + + return socket, nil +} + +func receiveJSONFromServer[T JSONReceived](socket *net.TCPConn, json_received *T) (*net.TCPConn, error) { + var err error + + // in case a socket doesn't already exist create a new one + if socket == nil { + fmt.Println("nil") + socket, err = net.DialTCP("tcp4", nil, &net.TCPAddr{printer_server_IP, printer_server_port, ""}) + } + + if err != nil { + return nil, err + } + + // the first 4 bytes are the length of the message + b := make([]byte, 4) + _, err = socket.Read(b[0:]) + + if err != nil { + return nil, err + } + + body_length := binary.LittleEndian.Uint32(b) + + body := make([]byte, body_length) + _, err = socket.Read(body[0:]) + + err = json.Unmarshal(body, json_received) + + if err != nil { + return nil, err + } + + return socket, nil +} diff --git a/main.go b/main.go index 246d93c..8170b01 100644 --- a/main.go +++ b/main.go @@ -3,16 +3,18 @@ package main import ( "fmt" "os" + "strconv" "strings" + "Scontrini-TUI-Client/api" + "github.com/gdamore/tcell/v2" "github.com/rivo/tview" ) const ( - USB_SERIAL_PORT = "/dev/ttyUSB0" - MAIN_WIDTH = 240 - MAIN_HEIGHT = 55 + MAIN_WIDTH = 240 + MAIN_HEIGHT = 55 ) var ( @@ -31,23 +33,18 @@ func main() { } } -func writeToPrinter(text string) { - // move outside - serial_port, err := os.OpenFile(USB_SERIAL_PORT, os.O_WRONLY, 0755) - if err != nil { - panic(err) - } - - fmt.Fprintln(serial_port, text) -} - /** -* If text gets piped from terminal, print it +* If text gets piped from terminal, send it to the API */ func printFromStdIn() { text := "" - fmt.Scanf("%s", text) - writeToPrinter(text) + fmt.Scanf("%s", &text) + + _, err := api.SendPrintCommand(nil, text) + + if err != nil { + os.Exit(1) + } } func startTui() { @@ -66,10 +63,10 @@ func startTui() { } /** -* Return a page with a TextView used to print +* Return a page with a TextView used to print text */ func createPrintPage(width, height int) *tview.Grid { - page := tview.NewGrid() + print_page := tview.NewGrid() textview := tview.NewTextView(). SetText("[green]SCRIVI QUELLO CHE VUOI STAMPARE[white]"). @@ -82,7 +79,11 @@ func createPrintPage(width, height int) *tview.Grid { print_button := tview.NewButton("Stampa"). SetSelectedFunc(func() { - writeToPrinter(textarea.GetText()) + _, err := api.SendPrintCommand(nil, textarea.GetText()) + + if err != nil { + os.Exit(1) // TODO: add error page + } }) pizza_button := tview.NewButton("Pizza"). @@ -118,14 +119,14 @@ func createPrintPage(width, height int) *tview.Grid { return event }) - page.SetColumns(1/5, 1/5, 1/5, 1/5, 1/5). + print_page.SetColumns(1/5, 1/5, 1/5, 1/5, 1/5). SetRows(1/5, 1/5, 1/5, 1/5, 1/5). AddItem(textview, 1, 1, 1, 3, 0, 0, false). AddItem(textarea, 2, 1, 1, 3, 0, 0, true). AddItem(print_button, 3, 1, 1, 1, 0, 0, false). AddItem(pizza_button, 3, 3, 1, 1, 0, 0, false) - return page + return print_page } /** @@ -140,17 +141,41 @@ func createPizzaPage(width, height int) *tview.Grid { SetDynamicColors(true) setCommonBoxAttributes(textview.Box, "Scontrini") + // get a list of all the pizzas + socket, err := api.SendGetPizzasCommand(nil) + + if err != nil { + os.Exit(1) // TODO: error page + } + + _, pizzas_json, err := api.ReceivePizzasCommand(socket) + row_index := 0 table := tview.NewTable(). - SetBorders(true). InsertRow(row_index). InsertColumn(0). - InsertColumn(1) - setCommonBoxAttributes(table.Box, "") + InsertColumn(1). + InsertColumn(2) - table.SetCellSimple(0, 0, "Quantità") - table.SetCellSimple(0, 1, "Pizza") + table.SetCellSimple(0, 0, "Pizza") + table.SetCellSimple(0, 1, "Prezzo") + table.SetCellSimple(0, 2, "Quantità") + + // make the rows selectable + table.SetSelectable(true, false) + + // populate the table + for _, v := range pizzas_json.Pizzas { + row_index += 1 + + table.InsertRow(row_index) + table.SetCellSimple(row_index, 0, v.Name) + table.SetCellSimple(row_index, 1, strconv.Itoa(v.Price)) + table.SetCellSimple(row_index, 2, "0") + } + + setCommonBoxAttributes(table.Box, "") print_button := tview.NewButton("Stampa"). SetSelectedFunc(func() { @@ -159,7 +184,7 @@ func createPizzaPage(width, height int) *tview.Grid { pizza_button := tview.NewButton("Pizza"). SetSelectedFunc(func() { - printPizza(table) + sendOrder(table) }) // define keys and behaviour for selected events @@ -169,10 +194,26 @@ func createPizzaPage(width, height int) *tview.Grid { return nil } - if event.Key() == tcell.KeyEnter { - row_index += 1 - table.InsertRow(row_index) - App.Draw() + // increase/decrease the quantity column of the selected row + if event.Rune() == '+' || event.Rune() == '-' { + selected_row, _ := table.GetSelection() + + if selected_row == 0 { + return event + } + + current_quantity, err := strconv.Atoi(table.GetCell(selected_row, 2).Text) + if err != nil { + os.Exit(1) + } + + if event.Rune() == '+' { + table.SetCellSimple(selected_row, 2, strconv.Itoa(current_quantity+1)) + } else { + if current_quantity > 0 { + table.SetCellSimple(selected_row, 2, strconv.Itoa(current_quantity-1)) + } + } return nil } @@ -208,10 +249,122 @@ func createPizzaPage(width, height int) *tview.Grid { } /** -* Format the pizza order table and print it +* Return a page with a Table used to show the pizza order */ -func printPizza(table *tview.Table) { - // TODO +func createOrderPage(width, height int, pizzas []api.Pizza, price int) *tview.Grid { + order_page := tview.NewGrid() + + textview := tview.NewTextView(). + SetText("[green]IL TUO ORDINE\n\nPrezzo Totale: [red]" + strconv.Itoa(price) + "€[white]"). + SetTextAlign(tview.AlignCenter). + SetDynamicColors(true) + setCommonBoxAttributes(textview.Box, "Scontrini") + + row_index := 0 + + table := tview.NewTable(). + InsertRow(row_index). + InsertColumn(0). + InsertColumn(1) + + table.SetCellSimple(0, 0, "Pizza") + table.SetCellSimple(0, 1, "Quantità") + + // populate the table + for _, v := range pizzas { + row_index += 1 + + table.InsertRow(row_index) + table.SetCellSimple(row_index, 0, v.Name) + table.SetCellSimple(row_index, 1, strconv.Itoa(v.Quantity)) + } + + setCommonBoxAttributes(table.Box, "") + + print_button := tview.NewButton("Stampa"). + SetSelectedFunc(func() { + Pages.RemovePage("order") + Pages.SwitchToPage("print") + }) + + pizza_button := tview.NewButton("Pizza"). + SetSelectedFunc(func() { + Pages.RemovePage("order") + Pages.SwitchToPage("pizza") + }) + + // define keys and behaviour for selected events + table.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if event.Key() == tcell.KeyTAB { + App.SetFocus(print_button) + return nil + } + + return event + }) + + print_button.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if event.Key() == tcell.KeyTAB { + App.SetFocus(pizza_button) + return nil + } + + return event + }) + + pizza_button.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if event.Key() == tcell.KeyTAB { + App.SetFocus(table) + return nil + } + + return event + }) + + order_page.SetColumns(1/5, 1/5, 1/5, 1/5, 1/5). + SetRows(1/5, 1/5, 1/5, 1/5, 1/5). + AddItem(textview, 1, 1, 1, 3, 0, 0, false). + AddItem(table, 2, 2, 1, 1, 0, 0, true). + AddItem(print_button, 3, 1, 1, 1, 0, 0, false). + AddItem(pizza_button, 3, 3, 1, 1, 0, 0, false) + + return order_page +} + +/** +* Format the pizza order from the table and send it to the API + */ +func sendOrder(table *tview.Table) { + pizzas := make([]api.Pizza, 0) + + // populate the slice with pizzas from the table + for i := 1; i < table.GetRowCount(); i++ { + name := table.GetCell(i, 0).Text + quantity, err := strconv.Atoi(table.GetCell(i, 2).Text) + + if err != nil { + os.Exit(1) + } + + if quantity > 0 { + pizzas = append(pizzas, api.Pizza{name, 0, quantity}) + } + } + + socket, err := api.SendPizzaCommand(nil, pizzas) + + if err != nil { + os.Exit(1) // TODO: add error page + } + + _, price, err := api.ReceivePriceCommand(socket) + + if err != nil { + os.Exit(1) // TODO: add error page + } + + order_page := createOrderPage(MAIN_WIDTH, MAIN_HEIGHT, pizzas, price.TotalPrice) + Pages.AddAndSwitchToPage("order", order_page, true) } /**