summaryrefslogtreecommitdiffstats
path: root/cmd/nymdrop-reader/gui.go
blob: d8e53830ad1f0cddb75e85e2c09c2bb766bc3e95 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package main

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/app"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/widget"

	"nymdrop/internal/reader"
)

// submissionEntry is one row in the submissions list: enough to render the
// row (label) and to load the full body on demand (path) without keeping
// every decrypted body in memory at once.
type submissionEntry struct {
	label string
	path  string
}

// runGUI is the non-technical front-end for nymdrop-reader: a journalist
// running this from a USB stick should never need a terminal. It drives the
// same reader.Instance the CLI uses, through the same Hooks contract, so
// there is exactly one implementation of the receive/decrypt/save logic.
func runGUI(cfg reader.Config) {
	a := app.NewWithID("art.virebent.nymdrop-reader")
	w := a.NewWindow("NymDrop Reader")
	w.Resize(fyne.NewSize(900, 560))

	statusLabel := widget.NewLabel("Starting...")
	statusLabel.Wrapping = fyne.TextWrapWord

	addrEntry := widget.NewEntry()
	addrEntry.Disable()
	addrEntry.SetPlaceHolder("Nym inbox address will appear here once connected")

	outDirLabel := widget.NewLabel("")

	var submissions []submissionEntry

	content := widget.NewMultiLineEntry()
	content.Wrapping = fyne.TextWrapWord
	content.Disable()

	list := widget.NewList(
		func() int { return len(submissions) },
		func() fyne.CanvasObject { return widget.NewLabel("template") },
		func(id widget.ListItemID, obj fyne.CanvasObject) {
			obj.(*widget.Label).SetText(submissions[id].label)
		},
	)
	list.OnSelected = func(id widget.ListItemID) {
		if id < 0 || id >= len(submissions) {
			return
		}
		data, err := os.ReadFile(submissions[id].path)
		if err != nil {
			content.SetText(fmt.Sprintf("error reading %s: %v", submissions[id].path, err))
			return
		}
		content.SetText(string(data))
	}

	hooks := reader.Hooks{
		Log: func(format string, args ...any) {
			msg := fmt.Sprintf(format, args...)
			fyne.Do(func() { statusLabel.SetText(msg) })
		},
		SelfAddress: func(addr string) {
			fyne.Do(func() {
				addrEntry.SetText(addr)
				statusLabel.SetText("Connected -- waiting for submissions.")
			})
		},
		Submission: func(path, preview string) {
			ts := strings.TrimSuffix(filepath.Base(path), ".txt")
			oneLine := strings.ReplaceAll(preview, "\n", " ")
			if len(oneLine) > 80 {
				oneLine = oneLine[:80] + "..."
			}
			entry := submissionEntry{label: ts + "  " + oneLine, path: path}
			fyne.Do(func() {
				// Newest first: a journalist checking the box wants the
				// latest tip on top, not scrolled to the bottom.
				submissions = append([]submissionEntry{entry}, submissions...)
				list.Refresh()
				statusLabel.SetText(fmt.Sprintf("%d submission(s) received.", len(submissions)))
			})
		},
		Attachment: func(path string, size int) {
			fyne.Do(func() {
				statusLabel.SetText(fmt.Sprintf("attachment saved: %s (%d bytes)", path, size))
			})
		},
		Error: func(err error) {
			fyne.Do(func() { statusLabel.SetText("error: " + err.Error()) })
		},
	}

	in, err := reader.Start(cfg, hooks)
	if err != nil {
		statusLabel.SetText("failed to start: " + err.Error())
	} else {
		outDirLabel.SetText("Submissions saved to: " + in.OutDir())
		go in.ReceiveLoop()
	}

	top := container.NewVBox(
		widget.NewLabelWithStyle("NymDrop Reader", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
		statusLabel,
		widget.NewForm(widget.NewFormItem("Nym inbox address", addrEntry)),
		outDirLabel,
	)

	split := container.NewHSplit(
		container.NewBorder(widget.NewLabel("Submissions"), nil, nil, nil, list),
		container.NewBorder(widget.NewLabel("Content"), nil, nil, nil, content),
	)
	split.Offset = 0.35

	w.SetContent(container.NewBorder(top, nil, nil, nil, split))

	w.SetCloseIntercept(func() {
		if in != nil {
			in.Close()
		}
		w.Close()
	})

	w.ShowAndRun()
}