UI: tombol GitHub + Donasi, tombol Open per shortcut, aksen gradien

- Footer: Check for Updates, GitHub (github.com/oxpoe/lazyrun), Donate (donasi.oxpoe.qzz.io)
- Baris shortcut: tombol play untuk menjalankan (pakai exec_args hasil install)
- metadata shortcut menyimpan exec_args; ada fallback ke binary/URL untuk data lama
- Chip jumlah shortcut di header daftar + garis gradien brand di bawah header
- README: link repo & donasi absolut
This commit is contained in:
oxpoe committed 2026-10-13 08:00:12 +07:00
1 parent f00662627a
commit b12efefd6a
10 files changed
+111 -4

No files matched your search

+2
View File
@@ -71,6 +71,7 @@ func InstallExecutable(o ExecOptions) (store.Shortcut, error) {
Slug: sl,
Name: name,
Type: store.TypeExecutable,
ExecArgs: entry.ExecArgs,
BinaryPath: binDst,
IconPath: iconPath,
DesktopPath: deskDst,
@@ -156,6 +157,7 @@ func InstallWebApp(o WebOptions) (store.Shortcut, error) {
Type: store.TypeWebApp,
URL: pageURL,
Browser: strings.Join(o.Browser.Exec, " "),
ExecArgs: entry.ExecArgs,
IconPath: iconPath,
DesktopPath: deskDst,
Targets: store.Targets{Menu: o.Menu, Desktop: o.Desktop},
+16
View File
@@ -86,6 +86,19 @@ func TestInstallExecutable(t *testing.T) {
}
}
func TestInstallExecutableStoresExecArgs(t *testing.T) {
home := testEnv(t)
src := fakeBinary(t, home, "args")
sc, err := InstallExecutable(ExecOptions{BinaryPath: src, Name: "Args", Menu: true})
if err != nil {
t.Fatal(err)
}
if len(sc.ExecArgs) != 1 || sc.ExecArgs[0] != sc.BinaryPath {
t.Errorf("exec_args untuk tombol Open salah: %v", sc.ExecArgs)
}
}
func TestInstallExecutableDesktopOnly(t *testing.T) {
home := testEnv(t)
src := fakeBinary(t, home, "tool")
@@ -188,6 +201,9 @@ func TestInstallWebAppFirefoxUsesNewWindow(t *testing.T) {
if !strings.Contains(string(content), "Exec=/usr/bin/firefox --new-window https://example.com\n") {
t.Errorf("Exec Firefox salah:\n%s", content)
}
if len(sc.ExecArgs) != 3 || sc.ExecArgs[2] != "https://example.com" {
t.Errorf("exec_args PWA salah: %v", sc.ExecArgs)
}
}
func TestInstallWebAppFlatpakBrowser(t *testing.T) {
+1
View File
@@ -27,6 +27,7 @@ type Shortcut struct {
Type string `json:"type"`
URL string `json:"url,omitempty"`
Browser string `json:"browser,omitempty"`
ExecArgs []string `json:"exec_args,omitempty"`
BinaryPath string `json:"binary_path,omitempty"`
IconPath string `json:"icon_path,omitempty"`
DesktopPath string `json:"desktop_path"`
+51
View File
@@ -0,0 +1,51 @@
package ui
import (
"net/url"
"os/exec"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/dialog"
"lazyrun/internal/store"
)
// Link aplikasi: repo GitHub LazyRun dan halaman donasi.
const (
repoURL = "https://github.com/oxpoe/lazyrun"
donateURL = "https://donasi.oxpoe.qzz.io/"
)
// openURL membuka link di browser default.
func (ui *app) openURL(raw string) {
u, err := url.Parse(raw)
if err == nil {
err = fyne.CurrentApp().OpenURL(u)
}
if err != nil {
dialog.ShowError(err, ui.win)
}
}
// launch menjalankan shortcut: pakai argv hasil install, fallback ke binary/URL.
func (ui *app) launch(sc store.Shortcut) {
args := sc.ExecArgs
if len(args) == 0 {
switch {
case sc.Type == store.TypeWebApp && sc.URL != "":
args = []string{"xdg-open", sc.URL}
case sc.BinaryPath != "":
args = []string{sc.BinaryPath}
}
}
if len(args) == 0 {
return
}
cmd := exec.Command(args[0], args[1:]...)
if err := cmd.Start(); err != nil {
dialog.ShowError(err, ui.win)
return
}
go cmd.Wait() // supaya proses anak tidak jadi zombie
}
+14 -2
View File
@@ -5,8 +5,10 @@ import (
"fmt"
"os/exec"
"path/filepath"
"strconv"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/theme"
@@ -24,6 +26,7 @@ type app struct {
store *store.Store
browsers []browser.Browser
rows *fyne.Container
count *canvas.Text // angka jumlah shortcut di header daftar
version string
}
@@ -46,6 +49,8 @@ func build(a fyne.App, version string) (*app, error) {
ui.browsers = browser.Detect()
ui.rows = container.NewVBox()
countChip, count := chipLabel("0", colorNameChipMenu)
ui.count = count
ui.win = a.NewWindow(T("app_title"))
ui.win.Resize(fyne.NewSize(840, 600))
@@ -64,13 +69,17 @@ func build(a fyne.App, version string) (*app, error) {
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) })
footer := container.NewBorder(
widget.NewSeparator(), nil,
update,
container.NewHBox(update, github, donate),
caption("v"+version),
)
top := container.NewVBox(header, widget.NewSeparator(), sectionLabel(T("installed")))
top := container.NewVBox(header, accentBar(),
container.NewHBox(sectionLabel(T("installed")), countChip))
body := container.NewBorder(
top,
nil, nil, nil,
@@ -92,6 +101,8 @@ func (ui *app) reload() {
ui.store = st
}
ui.rows.RemoveAll()
ui.count.Text = strconv.Itoa(len(ui.store.Items))
ui.count.Refresh()
if len(ui.store.Items) == 0 {
ui.rows.Add(emptyState())
}
@@ -118,6 +129,7 @@ func (ui *app) row(sc store.Shortcut) fyne.CanvasObject {
}
actions := container.NewHBox(chips...)
actions.Add(iconButton(theme.MediaPlayIcon(), func() { ui.launch(sc) }))
actions.Add(iconButton(theme.FolderOpenIcon(), func() {
if err := exec.Command("xdg-open", filepath.Dir(sc.DesktopPath)).Start(); err != nil {
dialog.ShowError(err, ui.win)
+2
View File
@@ -55,6 +55,8 @@ var tr = map[string][2]string{
"need_target": {"Pick at least one location: menu or desktop.", "Pilih minimal satu lokasi: menu atau desktop."},
"no_browser": {"No supported browser found. Install Chrome, Chromium, Brave, Edge, Vivaldi, or Firefox first.", "Browser yang didukung tidak ditemukan. Install Chrome, Chromium, Brave, Edge, Vivaldi, atau Firefox dulu."},
"github": {"GitHub", "GitHub"},
"donate": {"Donate", "Donasi"},
"check_updates": {"Check for Updates", "Cek Update"},
"not_appimage": {"This build is not an AppImage (version %s). Update from where you got it.", "Build ini bukan AppImage (versi %s). Update dari sumber kamu mendapatkannya."},
"no_appimageupdate": {"AppImageUpdate is not installed. Install it to update LazyRun automatically.", "AppImageUpdate tidak terpasang. Install dulu supaya LazyRun bisa update otomatis."},
+14 -1
View File
@@ -62,11 +62,24 @@ func card(content fyne.CanvasObject) fyne.CanvasObject {
// chip adalah label kecil dengan latar berwarna.
func chip(text string, bg fyne.ThemeColorName) fyne.CanvasObject {
object, _ := chipLabel(text, bg)
return object
}
// chipLabel mengembalikan chip sekaligus teksnya supaya bisa diperbarui.
func chipLabel(text string, bg fyne.ThemeColorName) (fyne.CanvasObject, *canvas.Text) {
rect := canvas.NewRectangle(theme.Color(bg))
rect.CornerRadius = 7
label := textLine(text, 11, true, theme.ColorNameForeground)
label.Alignment = fyne.TextAlignCenter
return container.NewStack(rect, container.NewPadded(label))
return container.NewStack(rect, container.NewPadded(label)), label
}
// accentBar adalah garis gradien brand di bawah header.
func accentBar() fyne.CanvasObject {
bar := canvas.NewLinearGradient(brandIndigo, brandCyan, 90)
bar.SetMinSize(fyne.NewSize(0, 3))
return bar
}
// iconButton adalah tombol ikon tanpa label, gaya datar.