package main import ( "errors" encrypt "fckeuspy-go/lib" "image/color" "os" "time" "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" ) type uiParts struct { outKey *widget.Entry msg *widget.Entry peer *widget.Entry cipherOut *widget.Entry payload *widget.Entry plainOut *widget.Entry toastLabel *widget.Label } func buildEntries() *uiParts { p := &uiParts{ outKey: widget.NewMultiLineEntry(), msg: widget.NewMultiLineEntry(), peer: widget.NewMultiLineEntry(), cipherOut: widget.NewMultiLineEntry(), payload: widget.NewMultiLineEntry(), plainOut: widget.NewMultiLineEntry(), toastLabel: widget.NewLabel(""), } p.outKey.SetPlaceHolder("Veřejný klíč / certifikát…") p.msg.SetPlaceHolder("Sem napiš zprávu…") p.peer.SetPlaceHolder("-----BEGIN PUBLIC KEY----- … nebo CERTIFICATE …") p.cipherOut.SetPlaceHolder(`{"ek":"…","n":"…","ct":"…"}`) p.payload.SetPlaceHolder(`{"ek":"…","n":"…","ct":"…"}`) p.plainOut.SetPlaceHolder("Dešifrovaná zpráva…") // Zvýšení výšky (více řádků viditelně) p.outKey.SetMinRowsVisible(10) p.peer.SetMinRowsVisible(6) p.msg.SetMinRowsVisible(8) p.cipherOut.SetMinRowsVisible(8) p.payload.SetMinRowsVisible(8) p.plainOut.SetMinRowsVisible(8) p.toastLabel.Hide() return p } func (p *uiParts) showToast(msg string) { fyne.Do(func() { p.toastLabel.SetText(msg) p.toastLabel.Show() }) time.AfterFunc(1500*time.Millisecond, func() { fyne.Do(func() { if p.toastLabel.Text == msg { // avoid race if overwritten p.toastLabel.SetText("") p.toastLabel.Hide() } }) }) } // custom fixed dark theme type simpleTheme struct{} func (simpleTheme) Color(n fyne.ThemeColorName, v fyne.ThemeVariant) color.Color { base := theme.DefaultTheme().Color(n, v) if n == theme.ColorNameBackground || n == theme.ColorNameButton { return color.NRGBA{R: 30, G: 34, B: 39, A: 255} } return base } func (simpleTheme) Font(st fyne.TextStyle) fyne.Resource { return theme.DefaultTheme().Font(st) } func (simpleTheme) Icon(n fyne.ThemeIconName) fyne.Resource { return theme.DefaultTheme().Icon(n) } func (simpleTheme) Size(n fyne.ThemeSizeName) float32 { return theme.DefaultTheme().Size(n) } var forceDark = true // Build key section func buildIdentityTab(parts *uiParts, svc ServiceFacade, vaultPath string) fyne.CanvasObject { btnCopyPub := widget.NewButton("Copy public.pem", func() { copyClip(svc.PublicPEM(), parts) }) btnCopyCrt := widget.NewButton("Copy identity.crt", func() { copyClip(svc.PublicCert(), parts) }) btnShowPub := widget.NewButton("Show pub", func() { parts.outKey.SetText(svc.PublicPEM()) }) btnShowCrt := widget.NewButton("Show crt", func() { parts.outKey.SetText(svc.PublicCert()) }) btnClear := widget.NewButton("Clear", func() { parts.outKey.SetText("") }) btnPaste := widget.NewButton("Paste", func() { parts.outKey.SetText(fyne.CurrentApp().Clipboard().Content()) }) deleteBtn := widget.NewButton("Smazat identitu", func() { // dialog pro heslo pwEntry := widget.NewPasswordEntry() pwEntry.SetPlaceHolder("Heslo pro potvrzení…") content := widget.NewForm(widget.NewFormItem("Heslo", pwEntry)) dialog.ShowCustomConfirm("Potvrdit smazání", "Smazat", "Zrušit", content, func(ok bool) { if !ok { return } pw := pwEntry.Text if pw == "" { dialog.NewError(errors.New("heslo je prázdné"), fyne.CurrentApp().Driver().AllWindows()[0]).Show() return } if _, err := encrypt.OpenEncryptedStore(vaultPath, pw); err != nil { dialog.NewError(errors.New("neplatné heslo"), fyne.CurrentApp().Driver().AllWindows()[0]).Show() return } if err := os.Remove(vaultPath); err != nil { dialog.NewError(err, fyne.CurrentApp().Driver().AllWindows()[0]).Show() return } // Quit app po odstranění (uživatel musí znovu spustit a vytvořit nový vault) fyne.CurrentApp().Quit() }, fyne.CurrentApp().Driver().AllWindows()[0]) }) tileIdentity := buttonTile(btnCopyPub, btnCopyCrt, btnPaste, btnShowPub, btnShowCrt, btnClear, deleteBtn) group := container.NewVBox( widget.NewLabelWithStyle("Moje identita", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), tileIdentity, parts.outKey, ) return container.NewVScroll(group) } // Helper functions separated to avoid circular dependency with encrypt.Service // We'll adapt by passing a small facade interface if needed. type ServiceFacade interface { Encrypt(msg, peer string) (string, error) Decrypt(json string) (string, error) PublicPEM() string PublicCert() string } func copyClip(s string, parts *uiParts) { fyne.CurrentApp().Clipboard().SetContent(s) parts.showToast("Zkopírováno") } // (Removed legacy buildEncryptSection and buildDecryptSection) // assembleResponsive builds split view that collapses for narrow widths // Tab: Encrypt func buildEncryptTab(parts *uiParts, svc ServiceFacade) fyne.CanvasObject { parts.cipherOut.Disable() // read only output peerBtns := buttonTile( widget.NewButton("Paste", func() { parts.peer.SetText(fyne.CurrentApp().Clipboard().Content()) }), widget.NewButton("Clear", func() { parts.peer.SetText("") }), ) encAction := func() { m := parts.msg.Text p := parts.peer.Text if m == "" || p == "" { parts.showToast("Chybí data") return } go func(msg, peer string) { res, err := svc.Encrypt(msg, peer) if err != nil { fyne.Do(func() { parts.cipherOut.SetText(""); parts.showToast("Chyba") }) return } fyne.Do(func() { parts.cipherOut.SetText(res); parts.showToast("OK") }) }(m, p) } msgBtns := buttonTile( widget.NewButton("Clear+Paste", func() { parts.msg.SetText(""); parts.msg.SetText(fyne.CurrentApp().Clipboard().Content()) }), widget.NewButton("Encrypt", encAction), widget.NewButton("Copy encrypted", func() { copyClip(parts.cipherOut.Text, parts) }), ) group := container.NewVBox( widget.NewLabelWithStyle("Šifrování", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), widget.NewLabel("Veřejný klíč příjemce"), peerBtns, parts.peer, widget.NewLabel("Zpráva"), msgBtns, parts.msg, widget.NewLabel("Výsledek"), parts.cipherOut, ) return container.NewVScroll(group) } // Tab: Decrypt func buildDecryptTab(parts *uiParts, svc ServiceFacade) fyne.CanvasObject { parts.plainOut.Disable() decryptAction := func() { pl := parts.payload.Text if pl == "" { parts.showToast("Chybí payload") return } go func(js string) { res, err := svc.Decrypt(js) if err != nil { fyne.Do(func() { parts.plainOut.SetText(""); parts.showToast("Chyba") }) return } fyne.Do(func() { parts.plainOut.SetText(res); parts.showToast("OK") }) }(pl) } payloadBtns := buttonTile( widget.NewButton("Paste+Decrypt", func() { clip := fyne.CurrentApp().Clipboard().Content() parts.payload.SetText(clip) decryptAction() }), // widget.NewButton("Decrypt", decryptAction), widget.NewButton("Clear", func() { parts.payload.SetText(""); parts.plainOut.SetText("") }), ) plainBtns := buttonTile(widget.NewButton("Copy", func() { copyClip(parts.plainOut.Text, parts) })) group := container.NewVBox( widget.NewLabelWithStyle("Dešifrování", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), widget.NewLabel("Payload"), payloadBtns, parts.payload, widget.NewLabel("Výsledek"), plainBtns, parts.plainOut, ) return container.NewVScroll(group) } func buildTabbedUI(parts *uiParts, svc ServiceFacade, vaultPath string) fyne.CanvasObject { idTab := container.NewTabItem("Identita", buildIdentityTab(parts, svc, vaultPath)) encTab := container.NewTabItem("Šifrování", buildEncryptTab(parts, svc)) decTab := container.NewTabItem("Dešifrování", buildDecryptTab(parts, svc)) tabs := container.NewAppTabs(idTab, encTab, decTab) tabs.SetTabLocation(container.TabLocationTop) // apply fixed dark theme once fyne.CurrentApp().Settings().SetTheme(simpleTheme{}) prefs := fyne.CurrentApp().Preferences() if prefs != nil { idx := prefs.IntWithFallback("lastTab", 0) if idx >= 0 && idx < len(tabs.Items) { tabs.SelectIndex(idx) } // only persist lastTab now } tabs.OnSelected = func(ti *container.TabItem) { if prefs != nil { prefs.SetInt("lastTab", tabs.SelectedIndex()) } } return container.NewBorder(nil, parts.toastLabel, nil, nil, tabs) } // buttonTile renders buttons above a related entry with a colored background spanning full width func buttonTile(btns ...fyne.CanvasObject) fyne.CanvasObject { if len(btns) == 0 { return widget.NewLabel("") } cols := len(btns) if cols > 3 { cols = 3 } // wrap into multiple rows if many grid := container.NewGridWithColumns(cols, btns...) bgColor := color.NRGBA{R: 240, G: 240, B: 245, A: 255} if forceDark { bgColor = color.NRGBA{R: 50, G: 54, B: 60, A: 255} } rect := canvas.NewRectangle(bgColor) rect.SetMinSize(fyne.NewSize(100, 38)) padded := container.NewPadded(grid) return container.NewStack(rect, padded) }