Perbaiki ikon internet yang kadang hilang / bikin crash
- Semua SVG diraster ke PNG lebih dulu (install, sync, thumbnail). SVG dari internet sebagian memakai fitur yang tidak didukung renderer sehingga ikon tidak muncul atau proses menggambar berhenti. - fill="currentColor" diganti warna brand (penyebab tersering ikon Material/ Remix gagal diraster), rasterisasi dibungkus recover - Thumbnail pencarian: placeholder + cache unduhan + batas 3 unduhan bersamaan - Kandidat ikon yang gagal dilewati, install tetap lanjut ke ikon default - Race pada data ikon pilihan diperbaiki (mutex); tes -race hijau - Baris status modal pencarian dipindah ke barisnya sendiri (tidak bertabrakan dengan tombol Back/Search di layar berskala besar)
This commit is contained in:
1 parent
0885d69015
commit
6e6f9afea9
9 files changed
+312
-72
No files matched your search
+71
-15
@@ -3,6 +3,7 @@ package ui
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
@@ -13,6 +14,41 @@ import (
|
||||
"lazyrun/internal/icon"
|
||||
)
|
||||
|
||||
// iconCache menyimpan ikon yang sudah diunduh supaya pencarian ulang tidak
|
||||
// mengunduh berkali-kali: API ikon publik membatasi jumlah permintaan beruntun.
|
||||
type iconCache struct {
|
||||
mu sync.Mutex
|
||||
items map[string][]byte
|
||||
}
|
||||
|
||||
func newIconCache() *iconCache { return &iconCache{items: map[string][]byte{}} }
|
||||
|
||||
func (c *iconCache) load(url string) ([]byte, bool) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
data, ok := c.items[url]
|
||||
return data, ok
|
||||
}
|
||||
|
||||
func (c *iconCache) store(url string, data []byte) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.items[url] = data
|
||||
}
|
||||
|
||||
// fetchIcon mengunduh ikon (atau mengambil dari cache).
|
||||
func (ui *app) fetchIcon(url string) ([]byte, error) {
|
||||
if data, ok := ui.icons.load(url); ok {
|
||||
return data, nil
|
||||
}
|
||||
data, _, err := icon.FromURL(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ui.icons.store(url, data)
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// showIconSearch menampilkan pencarian ikon online (Iconify) dan mengirim hasil pilihan.
|
||||
func (ui *app) showIconSearch(query string, onPick func(icon.Result, []byte)) {
|
||||
entry := widget.NewEntry()
|
||||
@@ -20,9 +56,9 @@ func (ui *app) showIconSearch(query string, onPick func(icon.Result, []byte)) {
|
||||
entry.SetPlaceHolder("whatsapp")
|
||||
|
||||
status := muted(T("search_hint"))
|
||||
slots := make(chan struct{}, 4)
|
||||
grid := container.NewGridWithColumns(6)
|
||||
results := container.NewVScroll(container.NewPadded(grid))
|
||||
slots := make(chan struct{}, 3)
|
||||
|
||||
var d *dialog.CustomDialog
|
||||
search := func() {
|
||||
@@ -48,45 +84,65 @@ 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(
|
||||
spacer(),
|
||||
widget.NewButtonWithIcon(T("back"), theme.NavigateBackIcon(), func() { d.Hide() }),
|
||||
widget.NewButtonWithIcon(T("search"), theme.SearchIcon(), search),
|
||||
))
|
||||
content := container.NewBorder(
|
||||
container.NewVBox(closeRow(func() { d.Hide() }), entry),
|
||||
container.NewBorder(nil, nil, status,
|
||||
container.NewHBox(
|
||||
widget.NewButtonWithIcon(T("back"), theme.NavigateBackIcon(), func() { d.Hide() }),
|
||||
widget.NewButtonWithIcon(T("search"), theme.SearchIcon(), search),
|
||||
)),
|
||||
nil, nil,
|
||||
bottom, nil, nil,
|
||||
results,
|
||||
)
|
||||
|
||||
d = dialog.NewCustom(T("icon_search_title"), "", content, ui.win)
|
||||
showDialog(d, ui.win, 560, 470)
|
||||
showDialog(d, ui.win, 560, 470) // sekaligus menampilkan dialog
|
||||
search()
|
||||
}
|
||||
|
||||
// iconChoice adalah satu ikon hasil pencarian; gambarnya diunduh di latar belakang.
|
||||
// iconChoice adalah satu ikon hasil pencarian. Gambarnya diunduh di latar belakang
|
||||
// dan digambar sebagai PNG: SVG dari internet kadang tidak bisa digambar sehingga
|
||||
// thumbnail tidak muncul atau proses menggambar berhenti.
|
||||
func (ui *app) iconChoice(result icon.Result, onPick func(icon.Result, []byte), d *dialog.CustomDialog, slot chan struct{}) fyne.CanvasObject {
|
||||
var data []byte
|
||||
var mu sync.Mutex
|
||||
var raw []byte
|
||||
|
||||
button := widget.NewButton("", func() {
|
||||
onPick(result, data)
|
||||
mu.Lock()
|
||||
picked := raw
|
||||
mu.Unlock()
|
||||
onPick(result, picked)
|
||||
if d != nil {
|
||||
d.Hide()
|
||||
}
|
||||
})
|
||||
button.Importance = widget.LowImportance
|
||||
button.Icon = theme.NewThemedResource(theme.FileImageIcon()) // placeholder selama diunduh
|
||||
|
||||
go func() {
|
||||
slot <- struct{}{} // batasi unduhan bersamaan supaya tidak kena rate limit
|
||||
slot <- struct{}{}
|
||||
defer func() { <-slot }()
|
||||
|
||||
downloaded, _, err := icon.FromURL(result.URL)
|
||||
downloaded, err := ui.fetchIcon(result.URL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data = downloaded
|
||||
thumb, err := icon.Rasterize(downloaded, 64)
|
||||
if err != nil {
|
||||
if icon.Ext(downloaded) == ".svg" {
|
||||
return
|
||||
}
|
||||
thumb = downloaded // PNG/JPEG bisa langsung dipakai
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
raw = downloaded
|
||||
mu.Unlock()
|
||||
|
||||
fyne.Do(func() {
|
||||
button.Icon = fyne.NewStaticResource(result.Name, downloaded)
|
||||
button.Icon = fyne.NewStaticResource(result.Name+".png", thumb)
|
||||
button.Refresh()
|
||||
})
|
||||
}()
|
||||
|
||||
@@ -29,6 +29,7 @@ type app struct {
|
||||
browsers []browser.Browser
|
||||
rows *fyne.Container
|
||||
cards map[string]*hoverCard
|
||||
icons *iconCache
|
||||
count *canvas.Text // angka jumlah shortcut di header daftar
|
||||
version string
|
||||
}
|
||||
@@ -52,6 +53,7 @@ func build(a fyne.App, version string) (*app, error) {
|
||||
a.SetIcon(fyne.NewStaticResource("lazyrun.png", assets.Icon))
|
||||
|
||||
ui.browsers = browser.Detect()
|
||||
ui.icons = newIconCache()
|
||||
ui.rows = container.NewVBox()
|
||||
countChip, count := chipLabel("0", colorNameChipMenu)
|
||||
ui.count = count
|
||||
|
||||
Reference in new issue
Block a user