313 lines
7.3 KiB
Go
313 lines
7.3 KiB
Go
package main
|
|
|
|
import (
|
|
"image/color"
|
|
"log"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"fyne.io/fyne/v2"
|
|
"fyne.io/fyne/v2/canvas"
|
|
"fyne.io/fyne/v2/container"
|
|
"fyne.io/fyne/v2/dialog"
|
|
"fyne.io/fyne/v2/layout"
|
|
"fyne.io/fyne/v2/storage"
|
|
"fyne.io/fyne/v2/widget"
|
|
)
|
|
|
|
type FlashKind int
|
|
|
|
const (
|
|
FlashError FlashKind = iota
|
|
FlashSuccess
|
|
)
|
|
|
|
var (
|
|
presFileFilter []string = []string{".pptx", ".odp"}
|
|
videoFileFilter []string = []string{".mp4", ".mkv", ".mov", ".webm"}
|
|
|
|
flashTimers = map[*widget.Button]*time.Timer{}
|
|
)
|
|
|
|
func flashColor(btn *widget.Button, kind FlashKind) {
|
|
if t, ok := flashTimers[btn]; ok {
|
|
t.Stop() // cancel previous flash
|
|
}
|
|
|
|
duration := 1 * time.Second // default flash duration
|
|
// Determine button style based on kind
|
|
var importance widget.Importance
|
|
switch kind {
|
|
case FlashError:
|
|
importance = widget.DangerImportance
|
|
case FlashSuccess:
|
|
importance = widget.SuccessImportance
|
|
duration = 3 * time.Second
|
|
}
|
|
|
|
// Apply the flash immediately on UI thread
|
|
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
|
|
btn.Importance = importance
|
|
btn.Refresh()
|
|
}, false)
|
|
|
|
// Schedule reset after duration (non-blocking)
|
|
flashTimers[btn] = time.AfterFunc(duration, func() {
|
|
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
|
|
btn.Importance = widget.LowImportance
|
|
btn.Refresh()
|
|
}, false)
|
|
})
|
|
}
|
|
|
|
func drawSeparator(trailNewLine bool) *fyne.Container {
|
|
emptyLabel := widget.NewLabel("") // Give it some space at the bottom with an empty line
|
|
separatorLine := canvas.NewLine(color.Gray{Y: 128})
|
|
|
|
var separContainer *fyne.Container
|
|
if trailNewLine {
|
|
separContainer = container.NewVBox(
|
|
emptyLabel,
|
|
separatorLine,
|
|
emptyLabel,
|
|
)
|
|
} else {
|
|
separContainer = container.NewVBox(
|
|
emptyLabel,
|
|
separatorLine,
|
|
)
|
|
}
|
|
|
|
return separContainer
|
|
}
|
|
|
|
func drawModeRow(targetMode *int) *fyne.Container {
|
|
actionText := widget.NewLabel("Select Mode")
|
|
// targetMode is defined as:
|
|
// 0 undefined / not used
|
|
// 1 presentation (PowerPoint, Impress)
|
|
// 2 video (mp4, mkv)
|
|
var presModeBtn *widget.Button
|
|
var videoModeBtn *widget.Button
|
|
presModeBtn = widget.NewButton("Presentation", func() {
|
|
*targetMode = 1
|
|
presModeBtn.Importance = widget.HighImportance
|
|
videoModeBtn.Importance = widget.LowImportance
|
|
log.Println("Set mode to presentation", *targetMode)
|
|
refreshButtons(presModeBtn, videoModeBtn)
|
|
})
|
|
presWide := container.NewGridWrap(
|
|
buttonSize,
|
|
presModeBtn,
|
|
)
|
|
|
|
videoModeBtn = widget.NewButton("Video", func() {
|
|
*targetMode = 2
|
|
presModeBtn.Importance = widget.LowImportance
|
|
videoModeBtn.Importance = widget.HighImportance
|
|
log.Println("Set mode to video", *targetMode)
|
|
refreshButtons(presModeBtn, videoModeBtn)
|
|
})
|
|
videoWide := container.NewGridWrap(
|
|
buttonSize,
|
|
videoModeBtn,
|
|
)
|
|
|
|
modeRow := container.NewHBox(
|
|
presWide,
|
|
widget.NewLabel(""),
|
|
videoWide,
|
|
)
|
|
|
|
modeCol := container.NewVBox(
|
|
actionText,
|
|
modeRow,
|
|
)
|
|
|
|
return modeCol
|
|
}
|
|
|
|
func drawTargetSection(raspiNames []string, raspiTarget *string, uploadBtn, reloadBtn *widget.Button, cfg RaspiConfig) *fyne.Container {
|
|
actionText := widget.NewLabel("Select Target")
|
|
|
|
var previousTarget string
|
|
var verifyBtn *widget.Button
|
|
|
|
// Left side for selection of target
|
|
piSelection := widget.NewRadioGroup(raspiNames, func(selected string) {
|
|
if selected == previousTarget {
|
|
return // nothing changed
|
|
}
|
|
previousTarget = selected
|
|
*raspiTarget = selected
|
|
|
|
refreshButtons(verifyBtn, uploadBtn, reloadBtn)
|
|
})
|
|
|
|
selecCol := container.NewVBox(
|
|
actionText,
|
|
piSelection,
|
|
)
|
|
|
|
var credOK bool
|
|
verifyBtn = widget.NewButton("Check Connection", func() {
|
|
verifyBtn.Importance = widget.HighImportance
|
|
refreshButtons(verifyBtn)
|
|
|
|
log.Println("Verifying credentials...")
|
|
uploadBtn.Disable()
|
|
reloadBtn.Disable()
|
|
refreshButtons(verifyBtn, uploadBtn, reloadBtn)
|
|
|
|
go func() {
|
|
credOK = verifyCred(*raspiTarget, cfg)
|
|
|
|
if credOK {
|
|
// Must update UI on main thread
|
|
fyne.CurrentApp().Driver().DoFromGoroutine(func() {
|
|
flashColor(verifyBtn, FlashSuccess) // flashcolor should handle RunOnMain internally
|
|
uploadBtn.Enable()
|
|
reloadBtn.Enable()
|
|
}, false)
|
|
|
|
} else {
|
|
flashColor(verifyBtn, FlashError) // flashcolor should handle RunOnMain internally
|
|
}
|
|
}()
|
|
})
|
|
verifyWide := container.NewGridWrap(
|
|
buttonSize,
|
|
verifyBtn,
|
|
)
|
|
|
|
wholeCol := container.NewVBox(
|
|
selecCol,
|
|
widget.NewLabel(""),
|
|
verifyWide,
|
|
)
|
|
|
|
return wholeCol
|
|
}
|
|
|
|
func drawFileSelection(localPath *string, targetMode *int, parentWindow fyne.Window) *fyne.Container {
|
|
actionText := widget.NewLabel("Select File")
|
|
pathLabel := widget.NewLabel("No File Selected Yet...")
|
|
|
|
var uploadBtn *widget.Button
|
|
uploadBtn = widget.NewButton("Click to Select File", func() {
|
|
// Stupid dialog error on 'headless' servers...
|
|
log.Println("Ignore the next error. ~Refer: https://github.com/fyne-io/fyne/issues/4110")
|
|
|
|
// Use NewFileOpen to get the dialog object
|
|
uploadSelecDiag := dialog.NewFileOpen(func(r fyne.URIReadCloser, err error) {
|
|
if r != nil {
|
|
defer r.Close()
|
|
|
|
pathLabel.SetText(r.URI().Name())
|
|
*localPath = r.URI().Path()
|
|
|
|
uploadBtn.Importance = widget.HighImportance
|
|
refreshButtons(uploadBtn)
|
|
log.Println("Local path of selected file:", *localPath)
|
|
}
|
|
}, parentWindow)
|
|
|
|
var fileFilter storage.FileFilter
|
|
switch *targetMode {
|
|
case 1:
|
|
fileFilter = storage.NewExtensionFileFilter(presFileFilter)
|
|
case 2:
|
|
fileFilter = storage.NewExtensionFileFilter(videoFileFilter)
|
|
}
|
|
|
|
uploadSelecDiag.SetFilter(fileFilter)
|
|
|
|
// Resize the dialog to the whole window, if it wants to do that -
|
|
uploadSelecDiag.Resize(windowSize)
|
|
uploadSelecDiag.Show()
|
|
})
|
|
uploadWide := container.NewGridWrap(
|
|
buttonSize,
|
|
uploadBtn,
|
|
)
|
|
|
|
fileSelecRow := container.NewHBox(
|
|
uploadWide,
|
|
pathLabel,
|
|
)
|
|
|
|
fileSelecCol := container.NewVBox(
|
|
actionText,
|
|
fileSelecRow,
|
|
)
|
|
|
|
return fileSelecCol
|
|
}
|
|
|
|
// targetMode *int
|
|
func drawFooter(app fyne.App, raspiTarget, localUploadPath *string, targetMode *int, cfg RaspiConfig) (*fyne.Container, *widget.Button, *widget.Button) {
|
|
fExt := filepath.Ext(*localUploadPath)
|
|
remotePresPath := "/opt/akartontv/media" + fExt
|
|
|
|
// Configuration of the bottom of the application
|
|
var uploadBtn *widget.Button
|
|
uploadBtn = widget.NewButton("Upload File", func() {
|
|
uploadBtn.Importance = widget.HighImportance
|
|
refreshButtons(uploadBtn)
|
|
|
|
go func() {
|
|
ok := sftpUploadFile(*raspiTarget, *localUploadPath, remotePresPath, cfg)
|
|
|
|
if ok {
|
|
flashColor(uploadBtn, FlashSuccess)
|
|
} else {
|
|
flashColor(uploadBtn, FlashError)
|
|
}
|
|
}()
|
|
})
|
|
uploadWide := container.NewGridWrap(
|
|
dButtonSize,
|
|
uploadBtn,
|
|
)
|
|
|
|
var reloadBtn *widget.Button
|
|
reloadBtn = widget.NewButton("Restart Program", func() {
|
|
reloadBtn.Importance = widget.HighImportance
|
|
refreshButtons(uploadBtn)
|
|
|
|
go func() {
|
|
ok := restartShow(*raspiTarget, *targetMode, cfg)
|
|
|
|
if ok {
|
|
flashColor(reloadBtn, FlashSuccess)
|
|
} else {
|
|
flashColor(reloadBtn, FlashError)
|
|
}
|
|
}()
|
|
})
|
|
reloadWide := container.NewGridWrap(
|
|
dButtonSize,
|
|
reloadBtn,
|
|
)
|
|
|
|
cancelBtn := widget.NewButton("Exit", func() {
|
|
log.Println("Quitting")
|
|
app.Quit()
|
|
})
|
|
cancelWide := container.NewGridWrap(
|
|
buttonSize,
|
|
cancelBtn,
|
|
)
|
|
|
|
bottom := container.NewHBox(
|
|
uploadWide, // left
|
|
reloadWide, // also next to the one above
|
|
layout.NewSpacer(), // flexible space
|
|
cancelWide, // right
|
|
)
|
|
|
|
uploadBtn.Disable()
|
|
reloadBtn.Disable()
|
|
return bottom, uploadBtn, reloadBtn
|
|
}
|