Tooltip hint, loading bar, edit shortcut, export/import .lazy
- Keterangan tombol saat hover: baris hint di footer window dan di dalam dialog (bukan popup: overlay Fyne menelan klik pertama, tombol jadi perlu 2x klik) - Dialog proses sekarang pakai progress bar berjalan (bukan spinner diam) - Tombol edit per shortcut: ganti binary/URL, ikon (file/online), nama, kategori, dan lokasi; berkas .desktop + salinan Desktop ditulis ulang, berkas lama dibuang, jenis aplikasi dideteksi ulang, binary lama dihapus kalau nama berubah - Export/Import daftar shortcut ke berkas SQLite .lazy (driver murni Go modernc), skema + meta format versi, impor tidak menimpa shortcut yang sudah ada - Tes: detect kind, edit (rename/ganti binary/ikon/target), db roundtrip, merge impor, tooltip (hint muncul/hilang + klik tetap sampai ke tombol), semua paket hijau - README: bagian backup + hint tombol
This commit is contained in:
1 parent
c1aed1e286
commit
deaa30689c
19 files changed
+1362
-61
No files matched your search
@@ -0,0 +1,277 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/storage"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
|
||||
"lazyrun/internal/browser"
|
||||
"lazyrun/internal/db"
|
||||
"lazyrun/internal/icon"
|
||||
"lazyrun/internal/shortcut"
|
||||
"lazyrun/internal/store"
|
||||
)
|
||||
|
||||
var lazyFilter = storage.NewExtensionFileFilter([]string{".lazy"})
|
||||
|
||||
// exportShortcuts menyimpan daftar shortcut ke satu berkas SQLite (.lazy),
|
||||
// mis. "db.lazy", supaya bisa dipindah ke komputer lain.
|
||||
func (ui *app) exportShortcuts() {
|
||||
save := dialog.NewFileSave(func(w fyne.URIWriteCloser, err error) {
|
||||
if err != nil {
|
||||
dialog.ShowError(err, ui.win)
|
||||
return
|
||||
}
|
||||
if w == nil {
|
||||
return
|
||||
}
|
||||
path := w.URI().Path()
|
||||
w.Close() // berkas kosong dari dialog akan diganti basis data
|
||||
if !strings.HasSuffix(strings.ToLower(path), ".lazy") {
|
||||
path += ".lazy"
|
||||
}
|
||||
|
||||
if err := db.Export(path, ui.version, ui.store.Items); err != nil {
|
||||
dialog.ShowError(err, ui.win)
|
||||
return
|
||||
}
|
||||
dialog.ShowInformation(T("export"), fmt.Sprintf(T("export_done"), len(ui.store.Items), path), ui.win)
|
||||
}, ui.win)
|
||||
save.SetFileName(db.DefaultName)
|
||||
save.SetFilter(lazyFilter)
|
||||
save.Show()
|
||||
}
|
||||
|
||||
// importShortcuts menambahkan shortcut dari berkas .lazy tanpa menimpa yang sudah ada.
|
||||
func (ui *app) importShortcuts() {
|
||||
open := dialog.NewFileOpen(func(r fyne.URIReadCloser, err error) {
|
||||
if err != nil {
|
||||
dialog.ShowError(err, ui.win)
|
||||
return
|
||||
}
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
path := r.URI().Path()
|
||||
r.Close()
|
||||
|
||||
items, err := db.Import(path)
|
||||
if err != nil {
|
||||
dialog.ShowError(err, ui.win)
|
||||
return
|
||||
}
|
||||
if len(items) == 0 {
|
||||
dialog.ShowInformation(T("import"), T("import_empty"), ui.win)
|
||||
return
|
||||
}
|
||||
|
||||
added, skipped := mergeImported(ui.store, items)
|
||||
if err := ui.store.Save(); err != nil {
|
||||
dialog.ShowError(err, ui.win)
|
||||
return
|
||||
}
|
||||
ui.reload()
|
||||
dialog.ShowInformation(T("import"), fmt.Sprintf(T("import_done"), added, skipped), ui.win)
|
||||
}, ui.win)
|
||||
open.SetFilter(lazyFilter)
|
||||
open.Show()
|
||||
}
|
||||
|
||||
// showEditDialog mengubah shortcut yang sudah ada: binary/URL, ikon, nama, lokasi.
|
||||
func (ui *app) showEditDialog(sc store.Shortcut) {
|
||||
isWeb := sc.Type == store.TypeWebApp
|
||||
|
||||
var iconPath, iconURL, binPath string
|
||||
var iconData []byte
|
||||
|
||||
nameEntry := widget.NewEntry()
|
||||
nameEntry.SetText(sc.Name)
|
||||
iconLabel := muted(iconLabelOf(sc))
|
||||
menu := widget.NewCheck(T("check_menu"), nil)
|
||||
menu.SetChecked(sc.Targets.Menu)
|
||||
desktopChk := widget.NewCheck(T("check_desktop"), nil)
|
||||
desktopChk.SetChecked(sc.Targets.Desktop)
|
||||
|
||||
browseIcon := newButton(T("browse"), theme.FolderOpenIcon(), T("tip_browse"), widget.MediumImportance, func() {
|
||||
pickFile(ui.win, imageFilter, func(path string) {
|
||||
iconPath, iconURL, iconData = path, "", nil
|
||||
iconLabel.SetText(filepath.Base(path))
|
||||
})
|
||||
})
|
||||
onlineIcon := newButton(T("online"), theme.SearchIcon(), T("tip_online"), widget.MediumImportance, func() {
|
||||
ui.showIconSearch(nameEntry.Text, func(result icon.Result, data []byte) {
|
||||
iconURL, iconPath, iconData = result.URL, "", data
|
||||
iconLabel.SetText(result.Name)
|
||||
})
|
||||
})
|
||||
|
||||
// widget khusus executable
|
||||
binLabel := muted(sc.BinaryPath)
|
||||
browseBin := newButton(T("browse"), theme.FolderOpenIcon(), T("tip_browse"), widget.MediumImportance, func() {
|
||||
pickFile(ui.win, nil, func(path string) {
|
||||
binPath = path
|
||||
binLabel.SetText(filepath.Base(path) + " · " + detectedKind(path))
|
||||
})
|
||||
})
|
||||
category := widget.NewSelect(categories, nil)
|
||||
category.SetSelected(categoryOf(sc))
|
||||
|
||||
// widget khusus web app
|
||||
urlEntry := widget.NewEntry()
|
||||
urlEntry.SetText(sc.URL)
|
||||
browserSel := widget.NewSelect(browserNames(ui.browsers), nil)
|
||||
browserSel.SetSelected(currentBrowser(ui.browsers, sc.Browser))
|
||||
|
||||
iconItem := widget.NewFormItem(T("field_icon"), container.NewBorder(nil, nil, nil,
|
||||
container.NewHBox(onlineIcon, browseIcon), iconLabel))
|
||||
|
||||
items := []*widget.FormItem{}
|
||||
if isWeb {
|
||||
items = append(items,
|
||||
widget.NewFormItem(T("field_url"), urlEntry),
|
||||
widget.NewFormItem(T("field_name"), nameEntry),
|
||||
iconItem,
|
||||
widget.NewFormItem(T("field_browser"), browserSel),
|
||||
)
|
||||
} else {
|
||||
items = append(items,
|
||||
widget.NewFormItem(T("field_binary"), container.NewBorder(nil, nil, nil, browseBin, binLabel)),
|
||||
widget.NewFormItem(T("field_name"), nameEntry),
|
||||
iconItem,
|
||||
widget.NewFormItem(T("field_category"), category),
|
||||
)
|
||||
}
|
||||
|
||||
var d *dialog.CustomDialog
|
||||
save := newButton(T("save"), theme.DocumentSaveIcon(), T("tip_save"), widget.HighImportance, func() {
|
||||
switch {
|
||||
case strings.TrimSpace(nameEntry.Text) == "":
|
||||
dialog.ShowInformation(T("err_title"), T("need_name"), ui.win)
|
||||
return
|
||||
case !menu.Checked && !desktopChk.Checked:
|
||||
dialog.ShowInformation(T("err_title"), T("need_target"), ui.win)
|
||||
return
|
||||
}
|
||||
|
||||
ui.install(func() (store.Shortcut, error) {
|
||||
if isWeb {
|
||||
b := ui.browsers[indexOf(ui.browsers, browserSel.Selected)]
|
||||
return shortcut.EditWebApp(sc, shortcut.WebOptions{
|
||||
URL: urlEntry.Text,
|
||||
Name: nameEntry.Text,
|
||||
Browser: shortcut.BrowserArg{Name: b.Name, Exec: b.Exec, Firefox: b.Kind == browser.Firefox},
|
||||
IconPath: iconPath,
|
||||
IconURL: iconURL,
|
||||
IconData: iconData,
|
||||
Menu: menu.Checked,
|
||||
Desktop: desktopChk.Checked,
|
||||
})
|
||||
}
|
||||
path := binPath
|
||||
if path == "" {
|
||||
path = sc.BinaryPath
|
||||
}
|
||||
return shortcut.EditExecutable(sc, shortcut.ExecOptions{
|
||||
BinaryPath: path,
|
||||
IconPath: iconPath,
|
||||
IconURL: iconURL,
|
||||
IconData: iconData,
|
||||
Name: nameEntry.Text,
|
||||
Category: category.Selected,
|
||||
Menu: menu.Checked,
|
||||
Desktop: desktopChk.Checked,
|
||||
})
|
||||
}, func(updated store.Shortcut, err error) {
|
||||
if err != nil {
|
||||
dialog.ShowError(err, ui.win)
|
||||
return
|
||||
}
|
||||
ui.store.Add(updated)
|
||||
if err := ui.store.Save(); err != nil {
|
||||
dialog.ShowError(err, ui.win)
|
||||
return
|
||||
}
|
||||
d.Hide()
|
||||
ui.reload()
|
||||
if card, ok := ui.cards[updated.Slug]; ok {
|
||||
card.flash(0)
|
||||
}
|
||||
dialog.ShowInformation(T("edit_title"), fmt.Sprintf(T("edit_saved"), updated.Name), ui.win)
|
||||
})
|
||||
})
|
||||
|
||||
hint := textLine(T("edit_hint"), 12, false, colorNameMuted)
|
||||
content := container.NewVScroll(container.NewVBox(
|
||||
closeRow(func() { d.Hide() }),
|
||||
widget.NewForm(items...),
|
||||
widget.NewSeparator(),
|
||||
sectionTitle(T("section_location")),
|
||||
menu,
|
||||
desktopChk,
|
||||
hint,
|
||||
container.NewHBox(logoBox(newLogo(), 20), spacer(),
|
||||
newButton(T("cancel"), nil, T("tip_cancel"), widget.MediumImportance, func() { d.Hide() }),
|
||||
save),
|
||||
))
|
||||
|
||||
d = dialog.NewCustomWithoutButtons(T("edit_title"), content, ui.win)
|
||||
d.SetOnClosed(pushHintTarget(hint))
|
||||
showDialog(d, ui.win, 560, 520)
|
||||
}
|
||||
|
||||
// mergeImported menambahkan shortcut hasil impor tanpa menimpa yang sudah ada.
|
||||
func mergeImported(target *store.Store, items []store.Shortcut) (added, skipped int) {
|
||||
for _, item := range items {
|
||||
item.Name = strings.TrimSpace(item.Name)
|
||||
if item.Name == "" || item.Slug == "" {
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
if _, exists := target.Get(item.Slug); exists {
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
target.Add(item)
|
||||
added++
|
||||
}
|
||||
return added, skipped
|
||||
}
|
||||
|
||||
// iconLabelOf menampilkan ikon yang sedang dipakai shortcut.
|
||||
func iconLabelOf(sc store.Shortcut) string {
|
||||
if sc.IconPath == "" {
|
||||
return T("icon_auto")
|
||||
}
|
||||
return filepath.Base(sc.IconPath)
|
||||
}
|
||||
|
||||
// currentBrowser mengembalikan nama browser yang cocok dengan yang tersimpan.
|
||||
func currentBrowser(list []browser.Browser, saved string) string {
|
||||
for _, b := range list {
|
||||
if strings.Join(b.Exec, " ") == saved {
|
||||
return b.Name
|
||||
}
|
||||
}
|
||||
if len(list) > 0 {
|
||||
return list[0].Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// categoryOf mengambil kategori pertama yang tersimpan.
|
||||
func categoryOf(sc store.Shortcut) string {
|
||||
first, _, _ := strings.Cut(strings.TrimSuffix(sc.Categories, ";"), ";")
|
||||
for _, c := range categories {
|
||||
if c == first {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return "Utility"
|
||||
}
|
||||
@@ -41,7 +41,7 @@ func (ui *app) showExecutableDialog() {
|
||||
desktop := widget.NewCheck(T("check_desktop"), nil)
|
||||
desktop.SetChecked(true)
|
||||
|
||||
browseBin := widget.NewButtonWithIcon(T("browse"), theme.FolderOpenIcon(), func() {
|
||||
browseBin := newButton(T("browse"), theme.FolderOpenIcon(), T("tip_browse"), widget.MediumImportance, func() {
|
||||
pickFile(ui.win, nil, func(path string) {
|
||||
binPath = path
|
||||
binLabel.SetText(filepath.Base(path) + " · " + detectedKind(path))
|
||||
@@ -50,13 +50,13 @@ func (ui *app) showExecutableDialog() {
|
||||
}
|
||||
})
|
||||
})
|
||||
browseIcon := widget.NewButtonWithIcon(T("browse"), theme.FolderOpenIcon(), func() {
|
||||
browseIcon := newButton(T("browse"), theme.FolderOpenIcon(), T("tip_browse"), widget.MediumImportance, func() {
|
||||
pickFile(ui.win, imageFilter, func(path string) {
|
||||
iconPath, iconURL, iconData = path, "", nil
|
||||
iconLabel.SetText(filepath.Base(path))
|
||||
})
|
||||
})
|
||||
onlineIcon := widget.NewButtonWithIcon(T("online"), theme.SearchIcon(), func() {
|
||||
onlineIcon := newButton(T("online"), theme.SearchIcon(), T("tip_online"), widget.MediumImportance, func() {
|
||||
ui.showIconSearch(nameEntry.Text, func(result icon.Result, data []byte) {
|
||||
iconURL, iconPath, iconData = result.URL, "", data
|
||||
iconLabel.SetText(result.Name)
|
||||
@@ -64,7 +64,7 @@ func (ui *app) showExecutableDialog() {
|
||||
})
|
||||
|
||||
var d *dialog.CustomDialog
|
||||
install := widget.NewButtonWithIcon(T("install"), theme.DownloadIcon(), func() {
|
||||
install := newButton(T("install"), theme.DownloadIcon(), T("tip_install"), widget.HighImportance, func() {
|
||||
switch {
|
||||
case binPath == "":
|
||||
dialog.ShowInformation(T("err_title"), T("need_binary"), ui.win)
|
||||
@@ -91,8 +91,6 @@ func (ui *app) showExecutableDialog() {
|
||||
func(sc store.Shortcut, err error) { ui.added(sc, err, d.Hide) },
|
||||
)
|
||||
})
|
||||
install.Importance = widget.HighImportance
|
||||
|
||||
form := widget.NewForm(
|
||||
widget.NewFormItem(T("field_binary"), container.NewBorder(nil, nil, nil, browseBin, binLabel)),
|
||||
widget.NewFormItem(T("field_icon"), container.NewBorder(nil, nil, nil,
|
||||
@@ -100,6 +98,7 @@ func (ui *app) showExecutableDialog() {
|
||||
widget.NewFormItem(T("field_name"), nameEntry),
|
||||
widget.NewFormItem(T("field_category"), category),
|
||||
)
|
||||
hint := textLine(T("exec_hint"), 12, false, colorNameMuted)
|
||||
content := container.NewVScroll(container.NewVBox(
|
||||
closeRow(func() { d.Hide() }),
|
||||
form,
|
||||
@@ -107,13 +106,14 @@ func (ui *app) showExecutableDialog() {
|
||||
sectionTitle(T("section_location")),
|
||||
menu,
|
||||
desktop,
|
||||
muted(T("exec_hint")),
|
||||
hint,
|
||||
container.NewHBox(logoBox(newLogo(), 20), spacer(),
|
||||
widget.NewButton(T("cancel"), func() { d.Hide() }),
|
||||
newButton(T("cancel"), nil, T("tip_cancel"), widget.MediumImportance, func() { d.Hide() }),
|
||||
install),
|
||||
))
|
||||
|
||||
d = dialog.NewCustomWithoutButtons(T("new_exec_title"), content, ui.win)
|
||||
d.SetOnClosed(pushHintTarget(hint)) // keterangan tombol muncul di baris hint dialog ini
|
||||
showDialog(d, ui.win, 560, 520)
|
||||
}
|
||||
|
||||
|
||||
@@ -87,10 +87,11 @@ func (ui *app) showIconSearch(query string, onPick func(icon.Result, []byte)) {
|
||||
entry.OnSubmitted = func(string) { search() }
|
||||
// status di barisnya sendiri: supaya tidak bertabrakan dengan tombol
|
||||
// di layar sempit atau ber-penskalaan besar.
|
||||
bottom := container.NewVBox(status, container.NewHBox(
|
||||
hint := textLine(T("icon_search_hint"), 12, false, colorNameMuted)
|
||||
bottom := container.NewVBox(hint, status, container.NewHBox(
|
||||
spacer(),
|
||||
widget.NewButtonWithIcon(T("back"), theme.NavigateBackIcon(), func() { d.Hide() }),
|
||||
widget.NewButtonWithIcon(T("search"), theme.SearchIcon(), search),
|
||||
newButton(T("back"), theme.NavigateBackIcon(), T("tip_back"), widget.MediumImportance, func() { d.Hide() }),
|
||||
newButton(T("search"), theme.SearchIcon(), T("tip_search"), widget.HighImportance, search),
|
||||
))
|
||||
content := container.NewBorder(
|
||||
container.NewVBox(closeRow(func() { d.Hide() }), entry),
|
||||
@@ -99,6 +100,7 @@ func (ui *app) showIconSearch(query string, onPick func(icon.Result, []byte)) {
|
||||
)
|
||||
|
||||
d = dialog.NewCustomWithoutButtons(T("icon_search_title"), content, ui.win)
|
||||
d.SetOnClosed(pushHintTarget(hint))
|
||||
showDialog(d, ui.win, 560, 470) // sekaligus menampilkan dialog
|
||||
search()
|
||||
}
|
||||
@@ -126,7 +128,7 @@ func (ui *app) iconChoice(result icon.Result, onPick func(icon.Result, []byte),
|
||||
var mu sync.Mutex
|
||||
var raw []byte
|
||||
|
||||
button := widget.NewButton("", func() {
|
||||
button := newButton("", nil, result.Name, widget.LowImportance, func() {
|
||||
mu.Lock()
|
||||
picked := raw
|
||||
mu.Unlock()
|
||||
@@ -135,8 +137,8 @@ func (ui *app) iconChoice(result icon.Result, onPick func(icon.Result, []byte),
|
||||
d.Hide()
|
||||
}
|
||||
})
|
||||
button.Importance = widget.LowImportance
|
||||
button.Icon = theme.NewThemedResource(theme.FileImageIcon()) // placeholder selama diunduh
|
||||
button.Refresh()
|
||||
|
||||
go func() {
|
||||
slot <- struct{}{}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"fyne.io/fyne/v2/test"
|
||||
|
||||
"lazyrun/internal/icon"
|
||||
"lazyrun/internal/store"
|
||||
)
|
||||
|
||||
// TestNoEmptyDismissButton memastikan modal tidak memakai dialog.NewCustom,
|
||||
@@ -62,6 +63,17 @@ func TestDialogsOpenWithoutPanic(t *testing.T) {
|
||||
t.Fatal("modal web app tidak muncul")
|
||||
}
|
||||
}
|
||||
ui.showEditDialog(store.Shortcut{Slug: "winbox", Name: "Winbox", Type: store.TypeExecutable, Kind: "windows",
|
||||
BinaryPath: "/tmp/winbox", IconPath: "", Categories: "Network;", Targets: store.Targets{Menu: true, Desktop: true}})
|
||||
if top := ui.win.Canvas().Overlays().Top(); top == nil {
|
||||
t.Fatal("modal edit executable tidak muncul")
|
||||
}
|
||||
ui.showEditDialog(store.Shortcut{Slug: "wa", Name: "WhatsApp Web", Type: store.TypeWebApp, Kind: "pwa",
|
||||
URL: "https://web.whatsapp.com", Browser: "/usr/bin/vivaldi", Targets: store.Targets{Menu: true}})
|
||||
if top := ui.win.Canvas().Overlays().Top(); top == nil {
|
||||
t.Fatal("modal edit webapp tidak muncul")
|
||||
}
|
||||
|
||||
ui.showIconSearch("whatsapp", func(icon.Result, []byte) {})
|
||||
if top := ui.win.Canvas().Overlays().Top(); top == nil {
|
||||
t.Fatal("modal pencarian ikon tidak muncul")
|
||||
@@ -108,3 +120,27 @@ func TestStringsCoverUsedKeys(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeImported(t *testing.T) {
|
||||
target := &store.Store{}
|
||||
target.Add(store.Shortcut{Slug: "winbox", Name: "Winbox"})
|
||||
|
||||
added, skipped := mergeImported(target, []store.Shortcut{
|
||||
{Slug: "winbox", Name: "Winbox lama"}, // sudah ada -> dilewati
|
||||
{Slug: "figma", Name: "Figma"}, // baru
|
||||
{Slug: "", Name: "Tanpa slug"}, // rusak -> dilewati
|
||||
{Slug: "kosong", Name: " "}, // tanpa nama -> dilewati
|
||||
{Slug: "deploy", Name: " deploy.sh "}, // nama dirapikan
|
||||
})
|
||||
|
||||
if added != 2 || skipped != 3 {
|
||||
t.Errorf("added=%d skipped=%d, mau 2 dan 3", added, skipped)
|
||||
}
|
||||
if len(target.Items) != 3 {
|
||||
t.Errorf("jumlah item = %d", len(target.Items))
|
||||
}
|
||||
dep, ok := target.Get("deploy")
|
||||
if !ok || target.Items[dep].Name != "deploy.sh" {
|
||||
t.Errorf("nama item tidak dirapikan: %+v", target.Items)
|
||||
}
|
||||
}
|
||||
@@ -54,13 +54,13 @@ func (ui *app) showWebAppDialog() {
|
||||
}
|
||||
}
|
||||
|
||||
browseIcon := widget.NewButtonWithIcon(T("browse"), theme.FolderOpenIcon(), func() {
|
||||
browseIcon := newButton(T("browse"), theme.FolderOpenIcon(), T("tip_browse"), widget.MediumImportance, func() {
|
||||
pickFile(ui.win, imageFilter, func(path string) {
|
||||
iconPath, iconURL, iconData = path, "", nil
|
||||
iconLabel.SetText(filepath.Base(path))
|
||||
})
|
||||
})
|
||||
onlineIcon := widget.NewButtonWithIcon(T("online"), theme.SearchIcon(), func() {
|
||||
onlineIcon := newButton(T("online"), theme.SearchIcon(), T("tip_online"), widget.MediumImportance, func() {
|
||||
ui.showIconSearch(nameEntry.Text, func(result icon.Result, data []byte) {
|
||||
iconURL, iconPath, iconData = result.URL, "", data
|
||||
iconLabel.SetText(result.Name)
|
||||
@@ -68,7 +68,7 @@ func (ui *app) showWebAppDialog() {
|
||||
})
|
||||
|
||||
var d *dialog.CustomDialog
|
||||
install := widget.NewButtonWithIcon(T("install"), theme.DownloadIcon(), func() {
|
||||
install := newButton(T("install"), theme.DownloadIcon(), T("tip_install"), widget.HighImportance, func() {
|
||||
switch {
|
||||
case strings.TrimSpace(urlEntry.Text) == "":
|
||||
dialog.ShowInformation(T("err_title"), T("need_url"), ui.win)
|
||||
@@ -96,8 +96,6 @@ func (ui *app) showWebAppDialog() {
|
||||
func(sc store.Shortcut, err error) { ui.added(sc, err, d.Hide) },
|
||||
)
|
||||
})
|
||||
install.Importance = widget.HighImportance
|
||||
|
||||
form := widget.NewForm(
|
||||
widget.NewFormItem(T("field_url"), urlEntry),
|
||||
widget.NewFormItem(T("field_name"), nameEntry),
|
||||
@@ -105,6 +103,7 @@ func (ui *app) showWebAppDialog() {
|
||||
widget.NewFormItem(T("field_icon"), container.NewBorder(nil, nil, nil,
|
||||
container.NewHBox(onlineIcon, browseIcon), iconLabel)),
|
||||
)
|
||||
hint := textLine(T("web_hint"), 12, false, colorNameMuted)
|
||||
content := container.NewVScroll(container.NewVBox(
|
||||
closeRow(func() { d.Hide() }),
|
||||
form,
|
||||
@@ -112,13 +111,14 @@ func (ui *app) showWebAppDialog() {
|
||||
sectionTitle(T("section_location")),
|
||||
menu,
|
||||
desktop,
|
||||
muted(T("web_hint")),
|
||||
hint,
|
||||
container.NewHBox(logoBox(newLogo(), 20), spacer(),
|
||||
widget.NewButton(T("cancel"), func() { d.Hide() }),
|
||||
newButton(T("cancel"), nil, T("tip_cancel"), widget.MediumImportance, func() { d.Hide() }),
|
||||
install),
|
||||
))
|
||||
|
||||
d = dialog.NewCustomWithoutButtons(T("new_web_title"), content, ui.win)
|
||||
d.SetOnClosed(pushHintTarget(hint))
|
||||
showDialog(d, ui.win, 560, 520)
|
||||
}
|
||||
|
||||
|
||||
+23
-19
@@ -32,6 +32,7 @@ type app struct {
|
||||
icons *iconCache
|
||||
count *canvas.Text // angka jumlah shortcut di header daftar
|
||||
versionText *canvas.Text // versi di footer; berubah kalau ada update
|
||||
hint *canvas.Text // keterangan tombol yang sedang disorot
|
||||
accent *canvas.LinearGradient
|
||||
version string
|
||||
}
|
||||
@@ -40,6 +41,7 @@ type app struct {
|
||||
func Run(a fyne.App, version string) {
|
||||
ui, loadErr := build(a, version)
|
||||
ui.win.Show()
|
||||
pushHintTarget(ui.hint) // target default: baris hint di footer
|
||||
ui.flashCards()
|
||||
ui.scheduleUpdateCheck()
|
||||
if loadErr != nil {
|
||||
@@ -67,9 +69,8 @@ func build(a fyne.App, version string) (*app, error) {
|
||||
st, err := store.Load(xdg.MetadataPath())
|
||||
ui.store = st
|
||||
|
||||
addExec := widget.NewButtonWithIcon(T("btn_executable"), theme.ContentAddIcon(), ui.showExecutableDialog)
|
||||
addExec.Importance = widget.HighImportance
|
||||
addWeb := widget.NewButtonWithIcon(T("btn_webapp"), theme.ContentAddIcon(), ui.showWebAppDialog)
|
||||
addExec := newButton(T("btn_executable"), theme.ContentAddIcon(), T("tip_executable"), widget.HighImportance, ui.showExecutableDialog)
|
||||
addWeb := newButton(T("btn_webapp"), theme.ContentAddIcon(), T("tip_webapp"), widget.MediumImportance, ui.showWebAppDialog)
|
||||
|
||||
leftHeader := container.NewHBox(logoBox(newLogo(), 40), container.NewVBox(
|
||||
heading("LazyRun"),
|
||||
@@ -78,14 +79,17 @@ func build(a fyne.App, version string) (*app, error) {
|
||||
rightHeader := container.NewHBox(addExec, addWeb)
|
||||
header := container.NewBorder(nil, nil, leftHeader, rightHeader)
|
||||
|
||||
update := widget.NewButtonWithIcon(T("check_updates"), theme.DownloadIcon(), ui.checkUpdates)
|
||||
github := widget.NewButton(T("github"), func() { ui.openURL(repoURL) })
|
||||
donate := widget.NewButtonWithIcon(T("donate"),
|
||||
fyne.NewStaticResource("heart.png", assets.Heart), func() { ui.openURL(donateURL) })
|
||||
update := newButton(T("check_updates"), theme.DownloadIcon(), T("tip_check_updates"), widget.MediumImportance, ui.checkUpdates)
|
||||
github := newButton(T("github"), nil, T("tip_github"), widget.MediumImportance, func() { ui.openURL(repoURL) })
|
||||
donate := newButton(T("donate"), fyne.NewStaticResource("heart.png", assets.Heart), T("tip_donate"), widget.MediumImportance, func() { ui.openURL(donateURL) })
|
||||
export := newIconButton(theme.DocumentSaveIcon(), T("tip_export"), ui.exportShortcuts)
|
||||
importBtn := newIconButton(theme.StorageIcon(), T("tip_import"), ui.importShortcuts)
|
||||
ui.hint = caption("")
|
||||
footer := container.NewBorder(
|
||||
widget.NewSeparator(), nil,
|
||||
container.NewHBox(update, github, donate),
|
||||
container.NewHBox(update, export, importBtn, github, donate),
|
||||
ui.versionText,
|
||||
ui.hint,
|
||||
)
|
||||
|
||||
ui.accent = accentBar()
|
||||
@@ -145,16 +149,17 @@ func (ui *app) row(sc store.Shortcut) *hoverCard {
|
||||
}
|
||||
|
||||
actions := container.NewHBox(chips...)
|
||||
actions.Add(iconButton(theme.MediaPlayIcon(), func() { ui.launch(sc) }))
|
||||
actions.Add(newIconButton(theme.MediaPlayIcon(), T("tip_run"), func() { ui.launch(sc) }))
|
||||
if syncable(sc) {
|
||||
actions.Add(iconButton(theme.ViewRefreshIcon(), func() { ui.syncIcon(sc) }))
|
||||
actions.Add(newIconButton(theme.ViewRefreshIcon(), T("tip_sync"), func() { ui.syncIcon(sc) }))
|
||||
}
|
||||
actions.Add(iconButton(theme.FolderOpenIcon(), func() {
|
||||
actions.Add(newIconButton(theme.DocumentCreateIcon(), T("tip_edit"), func() { ui.showEditDialog(sc) }))
|
||||
actions.Add(newIconButton(theme.FolderOpenIcon(), T("tip_folder"), func() {
|
||||
if err := exec.Command("xdg-open", filepath.Dir(sc.DesktopPath)).Start(); err != nil {
|
||||
dialog.ShowError(err, ui.win)
|
||||
}
|
||||
}))
|
||||
actions.Add(iconButton(theme.DeleteIcon(), func() { ui.confirmDelete(sc) }))
|
||||
actions.Add(newIconButton(theme.DeleteIcon(), T("tip_delete"), func() { ui.confirmDelete(sc) }))
|
||||
|
||||
return newHoverCard(container.NewBorder(nil, nil,
|
||||
container.NewHBox(thumb(sc.IconPath, fallbackIcon(sc)), info),
|
||||
@@ -239,12 +244,11 @@ func syncable(sc store.Shortcut) bool {
|
||||
|
||||
// syncIcon mengunduh ulang ikon shortcut dari sumber online-nya.
|
||||
func (ui *app) syncIcon(sc store.Shortcut) {
|
||||
progress := dialog.NewProgressInfinite(T("sync_icon"), T("working"), ui.win)
|
||||
progress.Show()
|
||||
hide := ui.showLoading(T("sync_icon"), T("working"))
|
||||
go func() {
|
||||
path, err := shortcut.RefreshIcon(sc)
|
||||
fyne.Do(func() {
|
||||
progress.Hide()
|
||||
hide()
|
||||
if err != nil {
|
||||
dialog.ShowError(err, ui.win)
|
||||
return
|
||||
@@ -292,14 +296,14 @@ func (ui *app) confirmDelete(sc store.Shortcut) {
|
||||
d.Show()
|
||||
}
|
||||
|
||||
// install menjalankan pekerjaan berat (salin file besar / fetch favicon) di luar UI thread.
|
||||
// install menjalankan pekerjaan berat (salin file besar / unduh ikon) di luar UI thread,
|
||||
// dengan dialog proses berisi progress bar.
|
||||
func (ui *app) install(work func() (store.Shortcut, error), done func(store.Shortcut, error)) {
|
||||
progress := dialog.NewProgressInfinite(T("install"), T("working"), ui.win)
|
||||
progress.Show()
|
||||
hide := ui.showLoading(T("install"), T("working"))
|
||||
go func() {
|
||||
sc, err := work()
|
||||
fyne.Do(func() {
|
||||
progress.Hide()
|
||||
hide()
|
||||
done(sc, err)
|
||||
})
|
||||
}()
|
||||
|
||||
@@ -25,12 +25,14 @@ var tr = map[string][2]string{
|
||||
"searching": {"Searching...", "Mencari..."},
|
||||
"search_failed": {"Search failed. Check your connection.", "Pencarian gagal. Cek koneksi kamu."},
|
||||
"search_found": {"%d icons found - click one to use it.", "%d ikon ditemukan - klik satu untuk dipakai."},
|
||||
"icon_search_hint": {"Hover a button to see what it does.", "Sorot tombol untuk melihat fungsinya."},
|
||||
"search_hint": {"Type a name and press Search.", "Tulis nama lalu tekan Cari."},
|
||||
"icon_search_title": {"Icon from the internet", "Ikon dari internet"},
|
||||
"sync_icon": {"Sync icon", "Sinkron ikon"},
|
||||
"browse": {"Browse...", "Pilih..."},
|
||||
"install": {"Install", "Install"},
|
||||
"cancel": {"Cancel", "Batal"},
|
||||
"save": {"Save", "Simpan"},
|
||||
"back": {"Back", "Kembali"},
|
||||
"delete": {"Delete", "Hapus"},
|
||||
"working": {"Working...", "Sedang diproses..."},
|
||||
@@ -78,6 +80,40 @@ var tr = map[string][2]string{
|
||||
"update_restart": {"Restart now", "Restart sekarang"},
|
||||
"up_to_date": {"LazyRun %s is the latest version.", "LazyRun %s adalah versi terbaru."},
|
||||
"dev_build": {"This is a development build (%s), update check is skipped.", "Ini build development (%s), cek update dilewati."},
|
||||
|
||||
"export": {"Export", "Ekspor"},
|
||||
"import": {"Import", "Impor"},
|
||||
"edit_title": {"Edit shortcut", "Edit shortcut"},
|
||||
"edit_saved": {"%s updated.", "%s diperbarui."},
|
||||
"edit_hint": {"Change the binary, icon, name, or where the shortcut appears.", "Ubah binary, ikon, nama, atau lokasi shortcutnya."},
|
||||
"import_done": {"%d shortcuts imported (%d skipped).", "%d shortcut diimpor (%d dilewati)."},
|
||||
"import_empty": {"No shortcuts found in this file.", "Tidak ada shortcut di berkas ini."},
|
||||
"export_done": {"Exported %d shortcuts to\n%s", "%d shortcut diekspor ke\n%s"},
|
||||
|
||||
"tip_executable": {"Create a shortcut from a binary", "Buat shortcut dari berkas binary"},
|
||||
"tip_webapp": {"Create a shortcut from a web address", "Buat shortcut dari alamat web"},
|
||||
"tip_run": {"Run this shortcut", "Jalankan shortcut ini"},
|
||||
"tip_sync": {"Download the icon again from its source", "Unduh ulang ikonnya dari sumber"},
|
||||
"tip_edit": {"Edit: binary, icon, name, location", "Edit: binary, ikon, nama, lokasi"},
|
||||
"tip_folder": {"Open the .desktop folder", "Buka folder .desktop"},
|
||||
"tip_delete": {"Remove this shortcut", "Hapus shortcut ini"},
|
||||
"tip_check_updates": {"Check for a newer release", "Periksa rilis terbaru"},
|
||||
"tip_github": {"Open the LazyRun repository", "Buka repo LazyRun"},
|
||||
"tip_donate": {"Support LazyRun development", "Dukung pengembangan LazyRun"},
|
||||
"tip_export": {"Export the shortcut list to a .lazy database", "Ekspor daftar shortcut ke basis data .lazy"},
|
||||
"tip_import": {"Import shortcuts from a .lazy database", "Impor shortcut dari basis data .lazy"},
|
||||
"tip_close": {"Close this dialog", "Tutup dialog ini"},
|
||||
"tip_back": {"Back to the previous dialog", "Kembali ke dialog sebelumnya"},
|
||||
"tip_search": {"Search icons online", "Cari ikon di internet"},
|
||||
"tip_browse": {"Pick a file", "Pilih berkas"},
|
||||
"tip_online": {"Pick an icon from the internet", "Pilih ikon dari internet"},
|
||||
"tip_install": {"Install the shortcut", "Pasang shortcutnya"},
|
||||
"tip_cancel": {"Cancel without saving", "Batal tanpa menyimpan"},
|
||||
"tip_save": {"Save the changes", "Simpan perubahan"},
|
||||
"tip_update_now": {"Download and install the update", "Unduh dan pasang update"},
|
||||
"tip_update_page": {"Open the release page in the browser", "Buka halaman rilis di browser"},
|
||||
"tip_later": {"Ask me later", "Ingatkan nanti"},
|
||||
"tip_restart": {"Restart LazyRun with the new version", "Jalankan ulang LazyRun dengan versi baru"},
|
||||
}
|
||||
|
||||
var lang = detectLang()
|
||||
|
||||
@@ -83,13 +83,6 @@ func accentBar() *canvas.LinearGradient {
|
||||
return bar
|
||||
}
|
||||
|
||||
// iconButton adalah tombol ikon tanpa label, gaya datar.
|
||||
func iconButton(icon fyne.Resource, tapped func()) *widget.Button {
|
||||
button := widget.NewButtonWithIcon("", icon, tapped)
|
||||
button.Importance = widget.LowImportance
|
||||
return button
|
||||
}
|
||||
|
||||
// thumb menampilkan ikon shortcut, atau fallback kalau file ikonnya hilang.
|
||||
func thumb(path string, fallback fyne.Resource) fyne.CanvasObject {
|
||||
var image *canvas.Image
|
||||
@@ -122,7 +115,7 @@ func logoBox(image *canvas.Image, size float32) fyne.CanvasObject {
|
||||
// closeRow menempatkan tombol tutup di kanan atas isi dialog, supaya
|
||||
// selalu ada jalan keluar yang terlihat walau tombol bawah terpotong.
|
||||
func closeRow(hide func()) fyne.CanvasObject {
|
||||
return container.NewHBox(spacer(), iconButton(theme.CancelIcon(), hide))
|
||||
return container.NewHBox(spacer(), newIconButton(theme.CancelIcon(), T("tip_close"), hide))
|
||||
}
|
||||
|
||||
// showDialog menampilkan dialog dengan ukuran yang disesuaikan ke window.
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/driver/desktop"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
// Keterangan tombol muncul di satu baris hint (bawah window atau dalam dialog),
|
||||
// bukan sebagai popup: popup Fyne selalu jadi overlay, dan selama overlay ada
|
||||
// semua klik ke konten di bawahnya tidak diteruskan (tombol jadi perlu 2x klik).
|
||||
var (
|
||||
hintMu sync.Mutex
|
||||
hintTarget *canvas.Text
|
||||
)
|
||||
|
||||
// pushHintTarget mengarahkan keterangan hover ke label tertentu (dipakai dialog).
|
||||
// Fungsi yang dikembalikan mengembalikan target sebelumnya.
|
||||
func pushHintTarget(label *canvas.Text) func() {
|
||||
hintMu.Lock()
|
||||
previous := hintTarget
|
||||
hintTarget = label
|
||||
hintMu.Unlock()
|
||||
|
||||
return func() {
|
||||
hintMu.Lock()
|
||||
hintTarget = previous
|
||||
hintMu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func showHint(text string) {
|
||||
hintMu.Lock()
|
||||
target := hintTarget
|
||||
hintMu.Unlock()
|
||||
|
||||
if target == nil || text == "" {
|
||||
return
|
||||
}
|
||||
target.Text = text
|
||||
target.Refresh()
|
||||
}
|
||||
|
||||
// clearHint menghapus hint hanya kalau isinya memang milik tombol ini.
|
||||
func clearHint(text string) {
|
||||
hintMu.Lock()
|
||||
target := hintTarget
|
||||
hintMu.Unlock()
|
||||
|
||||
if target == nil || target.Text != text {
|
||||
return
|
||||
}
|
||||
target.Text = ""
|
||||
target.Refresh()
|
||||
}
|
||||
|
||||
// tipButton adalah tombol yang menampilkan fungsinya saat disorot mouse.
|
||||
type tipButton struct {
|
||||
widget.Button
|
||||
tip string
|
||||
}
|
||||
|
||||
// newButton membuat tombol (dengan ikon kalau diberikan) plus keterangan hover.
|
||||
func newButton(text string, icon fyne.Resource, tip string, importance widget.Importance, tapped func()) *tipButton {
|
||||
button := &tipButton{tip: tip}
|
||||
button.Text = text
|
||||
button.Icon = icon
|
||||
button.Importance = importance
|
||||
button.OnTapped = tapped
|
||||
button.ExtendBaseWidget(button)
|
||||
return button
|
||||
}
|
||||
|
||||
// newIconButton adalah tombol ikon saja (tanpa teks), gaya datar.
|
||||
func newIconButton(icon fyne.Resource, tip string, tapped func()) *tipButton {
|
||||
return newButton("", icon, tip, widget.LowImportance, tapped)
|
||||
}
|
||||
|
||||
func (b *tipButton) MouseIn(event *desktop.MouseEvent) {
|
||||
b.Button.MouseIn(event)
|
||||
showHint(b.tip)
|
||||
}
|
||||
|
||||
func (b *tipButton) MouseOut() {
|
||||
b.Button.MouseOut()
|
||||
clearHint(b.tip)
|
||||
}
|
||||
|
||||
// showLoading menampilkan dialog proses dengan progress bar yang berjalan,
|
||||
// dan mengembalikan fungsi untuk menutupnya.
|
||||
func (ui *app) showLoading(title, message string) func() {
|
||||
bar := widget.NewProgressBarInfinite()
|
||||
|
||||
var d *dialog.CustomDialog
|
||||
d = dialog.NewCustomWithoutButtons(title, container.NewBorder(
|
||||
container.NewVBox(logoBox(newLogo(), 22), muted(message)),
|
||||
nil, nil, nil,
|
||||
bar,
|
||||
), ui.win)
|
||||
showDialog(d, ui.win, 420, 170)
|
||||
return d.Hide
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/test"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
func TestTipButtonShowsAndHidesHint(t *testing.T) {
|
||||
a := test.NewApp()
|
||||
a.Settings().SetTheme(brand{theme.DefaultTheme()})
|
||||
defer a.Quit()
|
||||
|
||||
hint := textLine("", 12, false, colorNameMuted)
|
||||
button := newIconButton(theme.MediaPlayIcon(), "jalankan shortcut", func() {})
|
||||
w := a.NewWindow("tip")
|
||||
w.SetContent(container.NewBorder(nil, hint, nil, nil, button))
|
||||
w.Resize(fyne.NewSize(300, 200))
|
||||
w.Show()
|
||||
restore := pushHintTarget(hint)
|
||||
defer restore()
|
||||
|
||||
if hint.Text != "" {
|
||||
t.Fatal("hint muncul tanpa hover")
|
||||
}
|
||||
|
||||
test.MoveMouse(w.Canvas(), fyne.NewPos(20, 20))
|
||||
if hint.Text != "jalankan shortcut" {
|
||||
t.Errorf("hint saat hover = %q", hint.Text)
|
||||
}
|
||||
|
||||
// keluar dari tombol: hint dibersihkan
|
||||
test.MoveMouse(w.Canvas(), fyne.NewPos(280, 180))
|
||||
if hint.Text != "" {
|
||||
t.Errorf("hint masih %q setelah kursor keluar", hint.Text)
|
||||
}
|
||||
}
|
||||
|
||||
// klik saat tooltip terbuka harus tetap kena tombolnya (dulu PopUp menelan klik).
|
||||
// klik saat hint tampil harus tetap kena tombolnya (dulu pakai popup yang
|
||||
// jadi overlay, sehingga klik pertama selalu tertelan).
|
||||
func TestTipButtonClickGoesThrough(t *testing.T) {
|
||||
a := test.NewApp()
|
||||
a.Settings().SetTheme(brand{theme.DefaultTheme()})
|
||||
defer a.Quit()
|
||||
|
||||
tapped := false
|
||||
hint := textLine("", 12, false, colorNameMuted)
|
||||
button := newIconButton(theme.MediaPlayIcon(), "jalankan shortcut", func() { tapped = true })
|
||||
w := a.NewWindow("tip")
|
||||
w.SetContent(container.NewBorder(nil, hint, nil, nil, button))
|
||||
w.Resize(fyne.NewSize(300, 200))
|
||||
w.Show()
|
||||
restore := pushHintTarget(hint)
|
||||
defer restore()
|
||||
|
||||
test.MoveMouse(w.Canvas(), fyne.NewPos(20, 20))
|
||||
test.TapCanvas(w.Canvas(), fyne.NewPos(20, 20))
|
||||
if !tapped {
|
||||
t.Error("klik pertama tidak sampai ke tombol")
|
||||
}
|
||||
if top := w.Canvas().Overlays().Top(); top != nil {
|
||||
t.Error("tidak boleh ada overlay untuk keterangan tombol")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTipButtonWithoutTipDoesNothing(t *testing.T) {
|
||||
a := test.NewApp()
|
||||
a.Settings().SetTheme(brand{theme.DefaultTheme()})
|
||||
defer a.Quit()
|
||||
|
||||
hint := textLine("", 12, false, colorNameMuted)
|
||||
button := newButton("Tanpa tip", nil, "", widget.MediumImportance, func() {})
|
||||
w := a.NewWindow("tip")
|
||||
w.SetContent(container.NewBorder(nil, hint, nil, nil, button))
|
||||
w.Resize(fyne.NewSize(300, 200))
|
||||
w.Show()
|
||||
restore := pushHintTarget(hint)
|
||||
defer restore()
|
||||
|
||||
test.MoveMouse(w.Canvas(), fyne.NewPos(20, 20))
|
||||
if hint.Text != "" {
|
||||
t.Error("tombol tanpa tip tidak boleh menulis hint")
|
||||
}
|
||||
}
|
||||
+10
-12
@@ -110,18 +110,18 @@ func (ui *app) showUpdateDialog(rel update.Release) {
|
||||
buttons := container.NewHBox(
|
||||
logoBox(newLogo(), 20),
|
||||
spacer(),
|
||||
widget.NewButton(T("update_later"), func() { d.Hide() }),
|
||||
widget.NewButton(T("update_page"), func() { ui.openURL(rel.PageURL) }),
|
||||
newButton(T("update_later"), nil, T("tip_later"), widget.MediumImportance, func() { d.Hide() }),
|
||||
newButton(T("update_page"), nil, T("tip_update_page"), widget.MediumImportance, func() { ui.openURL(rel.PageURL) }),
|
||||
)
|
||||
|
||||
if updates := ui.updaterBinary(); updates != "" {
|
||||
// AppImageUpdate yang memasang (dan memverifikasi) sendiri
|
||||
buttons.Add(widget.NewButtonWithIcon(T("update_now"), theme.DownloadIcon(), func() {
|
||||
buttons.Add(newButton(T("update_now"), theme.DownloadIcon(), T("tip_update_now"), widget.HighImportance, func() {
|
||||
d.Hide()
|
||||
ui.runUpdater(updates, rel)
|
||||
}))
|
||||
} else {
|
||||
buttons.Add(widget.NewButtonWithIcon(T("update_now"), theme.DownloadIcon(), func() {
|
||||
buttons.Add(newButton(T("update_now"), theme.DownloadIcon(), T("tip_update_now"), widget.HighImportance, func() {
|
||||
d.Hide()
|
||||
ui.downloadUpdate(rel)
|
||||
}))
|
||||
@@ -151,12 +151,11 @@ func (ui *app) downloadUpdate(rel update.Release) {
|
||||
return
|
||||
}
|
||||
|
||||
progress := dialog.NewProgressInfinite(T("check_updates"), T("update_downloading"), ui.win)
|
||||
progress.Show()
|
||||
hide := ui.showLoading(T("check_updates"), T("update_downloading"))
|
||||
go func() {
|
||||
err := update.Download(rel, appImage)
|
||||
fyne.Do(func() {
|
||||
progress.Hide()
|
||||
hide()
|
||||
if err != nil {
|
||||
dialog.ShowError(err, ui.win)
|
||||
return
|
||||
@@ -174,12 +173,11 @@ func (ui *app) runUpdater(updater string, rel update.Release) {
|
||||
return
|
||||
}
|
||||
|
||||
progress := dialog.NewProgressInfinite(T("check_updates"), T("update_downloading"), ui.win)
|
||||
progress.Show()
|
||||
hide := ui.showLoading(T("check_updates"), T("update_downloading"))
|
||||
go func() {
|
||||
err := exec.Command(updater, appImage).Run()
|
||||
fyne.Do(func() {
|
||||
progress.Hide()
|
||||
hide()
|
||||
if err != nil {
|
||||
dialog.ShowError(err, ui.win)
|
||||
return
|
||||
@@ -195,8 +193,8 @@ func (ui *app) offerRestart() {
|
||||
content := container.NewVBox(
|
||||
widget.NewLabel(T("update_installed")),
|
||||
container.NewHBox(spacer(),
|
||||
widget.NewButton(T("update_later"), func() { d.Hide() }),
|
||||
widget.NewButtonWithIcon(T("update_restart"), theme.ViewRefreshIcon(), ui.restart),
|
||||
newButton(T("update_later"), nil, T("tip_later"), widget.MediumImportance, func() { d.Hide() }),
|
||||
newButton(T("update_restart"), theme.ViewRefreshIcon(), T("tip_restart"), widget.HighImportance, ui.restart),
|
||||
),
|
||||
)
|
||||
d = dialog.NewCustomWithoutButtons(T("check_updates"), content, ui.win)
|
||||
|
||||
Reference in new issue
Block a user