Merge pull request #427 from dweymouth/translations

Prepare for translations
This commit is contained in:
Drew Weymouth
2024-07-21 16:46:16 -07:00
committed by GitHub
41 changed files with 676 additions and 378 deletions
@@ -9,17 +9,11 @@ import (
"github.com/dweymouth/supersonic/sharedutil"
)
const (
ArtistSortAlbumCount string = "Album Count"
ArtistSortNameAZ string = "Name (A-Z)"
ArtistSortRandom string = "Random"
)
func (j *jellyfinMediaProvider) ArtistSortOrders() []string {
return []string{
ArtistSortAlbumCount,
ArtistSortNameAZ,
ArtistSortRandom,
mediaprovider.ArtistSortAlbumCount,
mediaprovider.ArtistSortNameAZ,
mediaprovider.ArtistSortRandom,
}
}
@@ -29,10 +23,10 @@ func (j *jellyfinMediaProvider) IterateArtists(sortOrder string, filter mediapro
var sortFn func([]*jellyfin.Artist) []*jellyfin.Artist
if sortOrder == "" {
sortOrder = ArtistSortNameAZ // default
sortOrder = mediaprovider.ArtistSortNameAZ // default
}
switch sortOrder {
case ArtistSortAlbumCount:
case mediaprovider.ArtistSortAlbumCount:
// Pagination needs to be disabled, to retrieve all results in a single request, and correctly sort them.
disablePagination = true
sortFn = func(artists []*jellyfin.Artist) []*jellyfin.Artist {
@@ -41,10 +35,10 @@ func (j *jellyfinMediaProvider) IterateArtists(sortOrder string, filter mediapro
})
return artists
}
case ArtistSortNameAZ:
case mediaprovider.ArtistSortNameAZ:
jfSort.Field = jellyfin.SortByName
jfSort.Mode = jellyfin.SortAsc
case ArtistSortRandom:
case mediaprovider.ArtistSortRandom:
jfSort.Field = jellyfin.SortByRandom
}
+13 -22
View File
@@ -9,44 +9,35 @@ import (
"github.com/dweymouth/supersonic/sharedutil"
)
const (
AlbumSortRecentlyAdded string = "Recently Added"
AlbumSortRandom string = "Random"
AlbumSortTitleAZ string = "Title (A-Z)"
AlbumSortArtistAZ string = "Artist (A-Z)"
AlbumSortYearAscending string = "Year (ascending)"
AlbumSortYearDescending string = "Year (descending)"
)
func (j *jellyfinMediaProvider) AlbumSortOrders() []string {
return []string{
AlbumSortRecentlyAdded,
AlbumSortRandom,
AlbumSortTitleAZ,
AlbumSortArtistAZ,
AlbumSortYearAscending,
AlbumSortYearDescending,
mediaprovider.AlbumSortRecentlyAdded,
mediaprovider.AlbumSortRandom,
mediaprovider.AlbumSortTitleAZ,
mediaprovider.AlbumSortArtistAZ,
mediaprovider.AlbumSortYearAscending,
mediaprovider.AlbumSortYearDescending,
}
}
func (j *jellyfinMediaProvider) IterateAlbums(sortOrder string, filter mediaprovider.AlbumFilter) mediaprovider.AlbumIterator {
var jfSort jellyfin.Sort
switch sortOrder {
case AlbumSortRecentlyAdded:
case mediaprovider.AlbumSortRecentlyAdded:
jfSort.Field = jellyfin.SortByDateCreated
jfSort.Mode = jellyfin.SortDesc
case AlbumSortRandom:
case mediaprovider.AlbumSortRandom:
jfSort.Field = jellyfin.SortByRandom
case AlbumSortArtistAZ:
case mediaprovider.AlbumSortArtistAZ:
jfSort.Field = jellyfin.SortByArtist
jfSort.Mode = jellyfin.SortAsc
case AlbumSortTitleAZ:
case mediaprovider.AlbumSortTitleAZ:
jfSort.Field = jellyfin.SortByName
jfSort.Mode = jellyfin.SortAsc
case AlbumSortYearAscending:
case mediaprovider.AlbumSortYearAscending:
jfSort.Field = jellyfin.SortByYear
jfSort.Mode = jellyfin.SortAsc
case AlbumSortYearDescending:
case mediaprovider.AlbumSortYearDescending:
jfSort.Field = jellyfin.SortByYear
jfSort.Mode = jellyfin.SortDesc
}
@@ -64,7 +55,7 @@ func (j *jellyfinMediaProvider) IterateAlbums(sortOrder string, filter mediaprov
return sharedutil.MapSlice(al, toAlbum), nil
}
if sortOrder == AlbumSortRandom {
if sortOrder == mediaprovider.AlbumSortRandom {
determFetcher := func(offs, limit int) ([]*mediaprovider.Album, error) {
al, err := j.client.GetAlbums(jellyfin.QueryOpts{
Sort: jellyfin.Sort{Field: "SortName", Mode: jellyfin.SortAsc},
+19
View File
@@ -9,6 +9,25 @@ import (
"github.com/deluan/sanitize"
)
const (
// set of all supported album sorts across all media providers
// these strings may be translated
AlbumSortRecentlyAdded string = "Recently Added"
AlbumSortRecentlyPlayed string = "Recently Played"
AlbumSortFrequentlyPlayed string = "Frequently Played"
AlbumSortRandom string = "Random"
AlbumSortTitleAZ string = "Title (A-Z)"
AlbumSortArtistAZ string = "Artist (A-Z)"
AlbumSortYearAscending string = "Year (ascending)"
AlbumSortYearDescending string = "Year (descending)"
// set of all supported artist sorts across all media providers
// these strings may be translated
ArtistSortAlbumCount string = "Album Count"
ArtistSortNameAZ string = "Name (A-Z)"
ArtistSortRandom string = "Random"
)
type MediaIterator[M any] interface {
Next() *M
}
+17 -28
View File
@@ -11,27 +11,16 @@ import (
"github.com/dweymouth/supersonic/sharedutil"
)
const (
AlbumSortRecentlyAdded string = "Recently Added"
AlbumSortRecentlyPlayed string = "Recently Played"
AlbumSortFrequentlyPlayed string = "Frequently Played"
AlbumSortRandom string = "Random"
AlbumSortTitleAZ string = "Title (A-Z)"
AlbumSortArtistAZ string = "Artist (A-Z)"
AlbumSortYearAscending string = "Year (ascending)"
AlbumSortYearDescending string = "Year (descending)"
)
func (s *subsonicMediaProvider) AlbumSortOrders() []string {
return []string{
AlbumSortRecentlyAdded,
AlbumSortRecentlyPlayed,
AlbumSortFrequentlyPlayed,
AlbumSortRandom,
AlbumSortTitleAZ,
AlbumSortArtistAZ,
AlbumSortYearAscending,
AlbumSortYearDescending,
mediaprovider.AlbumSortRecentlyAdded,
mediaprovider.AlbumSortRecentlyPlayed,
mediaprovider.AlbumSortFrequentlyPlayed,
mediaprovider.AlbumSortRandom,
mediaprovider.AlbumSortTitleAZ,
mediaprovider.AlbumSortArtistAZ,
mediaprovider.AlbumSortYearAscending,
mediaprovider.AlbumSortYearDescending,
}
}
@@ -86,28 +75,28 @@ func (s *subsonicMediaProvider) IterateAlbums(sortOrder string, filter mediaprov
return s.baseIterFromSimpleSortOrder("starred", modifiedFilter)
}
if sortOrder == "" {
sortOrder = AlbumSortRecentlyAdded // default
sortOrder = mediaprovider.AlbumSortRecentlyAdded // default
}
switch sortOrder {
case AlbumSortRecentlyAdded:
case mediaprovider.AlbumSortRecentlyAdded:
return s.baseIterFromSimpleSortOrder("newest", filter)
case AlbumSortRecentlyPlayed:
case mediaprovider.AlbumSortRecentlyPlayed:
return s.baseIterFromSimpleSortOrder("recent", filter)
case AlbumSortFrequentlyPlayed:
case mediaprovider.AlbumSortFrequentlyPlayed:
return s.baseIterFromSimpleSortOrder("frequent", filter)
case AlbumSortRandom:
case mediaprovider.AlbumSortRandom:
return s.newRandomIter(filter, s.prefetchCoverCB)
case AlbumSortTitleAZ:
case mediaprovider.AlbumSortTitleAZ:
return s.baseIterFromSimpleSortOrder("alphabeticalByName", filter)
case AlbumSortArtistAZ:
case mediaprovider.AlbumSortArtistAZ:
return s.baseIterFromSimpleSortOrder("alphabeticalByArtist", filter)
case AlbumSortYearAscending:
case mediaprovider.AlbumSortYearAscending:
fetchFn := func(offset, limit int) ([]*subsonic.AlbumID3, error) {
return s.client.GetAlbumList2("byYear",
map[string]string{"fromYear": "0", "toYear": "3000", "offset": strconv.Itoa(offset), "limit": strconv.Itoa(limit)})
}
return helpers.NewAlbumIterator(makeFetchFn(fetchFn), filter, s.prefetchCoverCB)
case AlbumSortYearDescending:
case mediaprovider.AlbumSortYearDescending:
fetchFn := func(offset, limit int) ([]*subsonic.AlbumID3, error) {
return s.client.GetAlbumList2("byYear",
map[string]string{"fromYear": "3000", "toYear": "0", "offset": strconv.Itoa(offset), "limit": strconv.Itoa(limit)})
@@ -14,17 +14,11 @@ import (
"github.com/dweymouth/supersonic/sharedutil"
)
const (
ArtistSortAlbumCount string = "Album Count"
ArtistSortNameAZ string = "Name (A-Z)"
ArtistSortRandom string = "Random"
)
func (s *subsonicMediaProvider) ArtistSortOrders() []string {
return []string{
ArtistSortAlbumCount,
ArtistSortNameAZ,
ArtistSortRandom,
mediaprovider.ArtistSortAlbumCount,
mediaprovider.ArtistSortNameAZ,
mediaprovider.ArtistSortRandom,
}
}
@@ -37,10 +31,10 @@ func filterArtistMatches(f mediaprovider.ArtistFilter, artist *subsonic.ArtistID
func (s *subsonicMediaProvider) IterateArtists(sortOrder string, filter mediaprovider.ArtistFilter) mediaprovider.ArtistIterator {
if sortOrder == "" {
sortOrder = ArtistSortNameAZ // default
sortOrder = mediaprovider.ArtistSortNameAZ // default
}
switch sortOrder {
case ArtistSortAlbumCount:
case mediaprovider.ArtistSortAlbumCount:
return s.baseArtistIterFromSimpleSortOrder(
func(artists []*subsonic.ArtistID3) []*subsonic.ArtistID3 {
slices.SortStableFunc(artists, func(a, b *subsonic.ArtistID3) int {
@@ -50,7 +44,7 @@ func (s *subsonicMediaProvider) IterateArtists(sortOrder string, filter mediapro
},
filter,
)
case ArtistSortNameAZ:
case mediaprovider.ArtistSortNameAZ:
return s.baseArtistIterFromSimpleSortOrder(
func(artists []*subsonic.ArtistID3) []*subsonic.ArtistID3 {
c := collate.New(language.English, collate.Loose)
@@ -61,7 +55,7 @@ func (s *subsonicMediaProvider) IterateArtists(sortOrder string, filter mediapro
},
filter,
)
case ArtistSortRandom:
case mediaprovider.ArtistSortRandom:
return s.baseArtistIterFromSimpleSortOrder(
func(artists []*subsonic.ArtistID3) []*subsonic.ArtistID3 {
newArtists := make([]*subsonic.ArtistID3, len(artists))
@@ -12,7 +12,7 @@ func (s *subsonicMediaProvider) IterateTracks(searchQuery string) mediaprovider.
return &allTracksIterator{
s: s,
albumIter: s.IterateAlbums(
AlbumSortArtistAZ,
mediaprovider.AlbumSortArtistAZ,
mediaprovider.NewAlbumFilter(mediaprovider.AlbumFilterOptions{}),
),
}
+1 -1
View File
@@ -53,4 +53,4 @@ require (
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace fyne.io/fyne/v2 v2.5.0 => github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240701152141-03bb69ca60ab
replace fyne.io/fyne/v2 v2.5.0 => github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240721175043-f7b0391c76a6
+2 -2
View File
@@ -79,8 +79,8 @@ github.com/dweymouth/fyne-advanced-list v0.0.0-20240623145729-9c6b8f99bcfe h1:ow
github.com/dweymouth/fyne-advanced-list v0.0.0-20240623145729-9c6b8f99bcfe/go.mod h1:sbOhla4VcfFb4OjXiUFTLXMPTnhRUlVrDMhB8HtWR4o=
github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64 h1:RUIrnGY034rDMlcOui/daurwX5b+52KdUKhH9aXaDSg=
github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64/go.mod h1:3YrjFDHMlhCsSZ/OvmJCxWm9QHSgOVWZBxnraZz9Z7c=
github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240701152141-03bb69ca60ab h1:ShhYp3PTlbBGMo2Q2E2bmK9IlXuh0e1675VawRz6ps4=
github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240701152141-03bb69ca60ab/go.mod h1:9D4oT3NWeG+MLi/lP7ItZZyujHC/qqMJpoGTAYX5Uqc=
github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240721175043-f7b0391c76a6 h1:4NuEVS2zgD14ptLoMU21j6Fd23Bjoject4LjfGnCPv0=
github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240721175043-f7b0391c76a6/go.mod h1:9D4oT3NWeG+MLi/lP7ItZZyujHC/qqMJpoGTAYX5Uqc=
github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645 h1:KzqSaQwG3HsTZQlEtkp0BeUy9vmYZ0rq0B15qIPSiBs=
github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645/go.mod h1:fcUagHBaQnt06GmBAllNE0J4O/7064zXRWdqnTTtVjI=
github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee h1:ZGyJ6wp7CAfT31BugypcF/TPKEy2RrGR9JFq1JOjOpY=
+2
View File
@@ -14,6 +14,7 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/lang"
)
func main() {
@@ -43,6 +44,7 @@ func main() {
os.Setenv("FYNE_SCALE", "1.1")
}
lang.AddTranslationsFS(res.Translations, "translations")
fyneApp := app.New()
fyneApp.SetIcon(res.ResAppicon256Png)
+6
View File
@@ -0,0 +1,6 @@
package res
import "embed"
//go:embed translations
var Translations embed.FS
+190
View File
@@ -0,0 +1,190 @@
{
"Add Server": "Add Server",
"Add to playlist": "Add to playlist",
"Add to queue": "Add to queue",
"Album": "Album",
"album": "album",
"Album count": "Album count",
"Album Count": "Album Count",
"Album filters": "Album filters",
"Album gain": "Album gain",
"Album info not available": "Album info not available",
"Album peak": "Album peak",
"Albums": "Albums",
"albums": "albums",
"All": "All",
"All Tracks": "All Tracks",
"Alt. URL": "Alt. URL",
"Are you sure you want to delete the server": "Are you sure you want to delete the server",
"Artist": "Artist",
"Artist (A-Z)": "Artist (A-Z)",
"Artists": "Artists",
"Artist biography not available.": "Artist biography not available.",
"Audio device": "Audio device",
"Audio Drama": "Audio Drama",
"Audiobook": "Audiobook",
"Authentication failed": "Authentication failed",
"Auto": "Auto",
"Autoselect device": "Autoselect device",
"Bit rate": "Bit rate",
"BPM": "BPM",
"Broadcast": "Broadcast",
"Cancel": "Cancel",
"Close": "Close",
"Close to system tray": "Close to system tray",
"Comment": "Comment",
"Compilation": "Compilation",
"Composer": "Composer",
"Configure your music server to add radio stations": "Configure your music server to add radio stations",
"Confirm Delete Playlist": "Confirm Delete Playlist",
"Confirm Delete Server": "Confirm Delete Server",
"Connect to Server": "Connect to Server",
"Connecting": "Connecting",
"Connecting to": "Connecting to",
"Content type": "Content type",
"Could not reach server": "Could not reach server",
"Create new playlist": "Create new playlist",
"day": "day",
"days": "days",
"Delete Playlist": "Delete Playlist",
"Demo": "Demo",
"Description": "Description",
"Disable server transcoding": "Disable server transcoding",
"Disc number": "Disc number",
"Discography": "Discography",
"discs": "discs",
"DJ-Mix": "DJ-Mix",
"Download": "Download",
"Download completed": "Download completed",
"Duration": "Duration",
"Edit": "Edit",
"Edit Playlist": "Edit Playlist",
"Edit server": "Edit server",
"Enable system tray": "Enable system tray",
"Enabled": "Enabled",
"Enter": "Enter",
"EP": "EP",
"Equalizer": "Equalizer",
"Exclusive mode": "Exclusive mode",
"Favorites": "Favorites",
"Field Recording": "Field Recording",
"File path": "File path",
"File size": "File size",
"Filter genres": "Filter genres",
"Frequently Played": "Frequently Played",
"General": "General",
"Genre": "Genre",
"Genres": "Genres",
"Github page": "Github page",
"Home Page": "Home Page",
"hr": "hr",
"hrs": "hrs",
"Internet Radio Stations": "Internet Radio Stations",
"Interview": "Interview",
"Is favorite": "Is favorite",
"Is not favorite": "Is not favorite",
"Last played": "Last played",
"Live": "Live",
"Locally": "Locally",
"Login to Server": "Login to Server",
"Lyrics": "Lyrics",
"Lyrics not available": "Lyrics not available",
"min": "min",
"minutes of track have been played": "minutes of track have been played",
"Mixtape": "Mixtape",
"My Server": "My Server",
"Name": "Name",
"Name (A-Z)": "Name (A-Z)",
"Nickname": "Nickname",
"No radio stations available": "No radio stations available",
"None": "None",
"none": "none",
"none selected": "none selected",
"OK": "OK",
"optional": "optional",
"or when": "or when",
"Password": "Password",
"Paused": "Paused",
"percent of track is played": "percent of track is played",
"Play": "Play",
"Play Artist Radio": "Play Artist Radio",
"Play count": "Play count",
"Play Discography": "Play Discography",
"Play next": "Play next",
"Play Queue": "Play Queue",
"Play random": "Play random",
"Playback": "Playback",
"Playing": "Playing",
"Playlist": "Playlist",
"Playlists": "Playlists",
"playlist by": "playlist by",
"Plays": "Plays",
"Prevent clipping": "Prevent clipping",
"Private": "Private",
"Public": "Public",
"Random": "Random",
"Rating": "Rating",
"reissued": "reissued",
"Recently Added": "Recently Added",
"Recently Played": "Recently Played",
"Related": "Related",
"Remix": "Remix",
"Remove from playlist": "Remove from playlist",
"ReplayGain mode": "ReplayGain mode",
"ReplayGain preamp": "ReplayGain preamp",
"Restart required": "Restart required",
"Save play queue on exit": "Save play queue on exit",
"Saved at": "Saved at",
"Scrobble when": "Scrobble when",
"Search Everywhere": "Search Everywhere",
"Search page": "Search page",
"Search playlists or new playlist name": "Search playlists or new playlist name",
"sec": "sec",
"selected": "selected",
"Send playback statistics to server": "Send playback statistics to server",
"Server": "Server",
"Server Type": "Server Type",
"Server unreachable": "Server unreachable",
"Set rating": "Set rating",
"Share": "Share",
"Share content": "Share content",
"Show info": "Show info",
"Show notification on track change": "Show notification on track change",
"Show year in album grid cards": "Show year in album grid cards",
"Shuffle": "Shuffle",
"Shuffle albums": "Shuffle albums",
"Shuffle tracks": "Shuffle tracks",
"Similar artists": "Similar artists",
"Single": "Single",
"Skip duplicate tracks": "Skip duplicate tracks",
"Soundtrack": "Soundtrack",
"Spoken Word": "Spoken Word",
"Startup page": "Startup page",
"Stopped": "Stopped",
"Support the project": "Support the project",
"Testing connection": "Testing connection",
"Theme": "Theme",
"Title": "Title",
"Title (A-Z)": "Title (A-Z)",
"Top Tracks": "Top Tracks",
"Total time": "Total time",
"Track": "Track",
"track": "track",
"Track count": "Track count",
"Track gain": "Track gain",
"Track Info": "Track Info",
"Track number": "Track number",
"Track peak": "Track peak",
"tracks": "tracks",
"UI Scaling": "UI Scaling",
"URL": "URL",
"Use legacy authentication": "Use legacy authentication",
"Username": "Username",
"version": "version",
"wrong URL": "wrong URL",
"wrong username/password": "wrong username/password",
"Year": "Year",
"Year (ascending)": "Year (ascending)",
"Year (descending)": "Year (descending)",
"Year from": "Year from"
}
+14 -13
View File
@@ -15,6 +15,7 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
@@ -211,7 +212,7 @@ func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader {
SizeName: theme.SizeNameHeadingText,
}
a.releaseTypeLabel = widget.NewRichText(
&widget.TextSegment{Text: "Album", Style: util.BoldRichTextStyle},
&widget.TextSegment{Text: lang.L("Album"), Style: util.BoldRichTextStyle},
&widget.TextSegment{Text: " by", Style: widget.RichTextStyle{Inline: true}},
)
a.artistLabel = widgets.NewMultiHyperlink()
@@ -224,10 +225,10 @@ func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader {
a.page.contr.NavigateTo(controller.GenreRoute(genre))
}
a.miscLabel = widget.NewLabel("")
playButton := widget.NewButtonWithIcon("Play", theme.MediaPlayIcon(), func() {
playButton := widget.NewButtonWithIcon(lang.L("Play"), theme.MediaPlayIcon(), func() {
go a.page.pm.PlayAlbum(a.page.albumID, 0, false)
})
shuffleBtn := widget.NewButtonWithIcon("Shuffle", myTheme.ShuffleIcon, func() {
shuffleBtn := widget.NewButtonWithIcon(lang.L("Shuffle"), myTheme.ShuffleIcon, func() {
a.page.pm.LoadTracks(a.page.tracklist.GetTracks(), backend.Replace, true)
a.page.pm.PlayFromBeginning()
})
@@ -235,28 +236,28 @@ func NewAlbumPageHeader(page *AlbumPage) *AlbumPageHeader {
menuBtn := widget.NewButtonWithIcon("", theme.MoreHorizontalIcon(), nil)
menuBtn.OnTapped = func() {
if pop == nil {
playNext := fyne.NewMenuItem("Play next", func() {
playNext := fyne.NewMenuItem(lang.L("Play next"), func() {
go a.page.pm.LoadAlbum(a.albumID, backend.InsertNext, false /*shuffle*/)
})
playNext.Icon = myTheme.PlayNextIcon
queue := fyne.NewMenuItem("Add to queue", func() {
queue := fyne.NewMenuItem(lang.L("Add to queue"), func() {
go a.page.pm.LoadAlbum(a.albumID, backend.Append, false /*shuffle*/)
})
queue.Icon = theme.ContentAddIcon()
playlist := fyne.NewMenuItem("Add to playlist...", func() {
playlist := fyne.NewMenuItem(lang.L("Add to playlist")+"...", func() {
a.page.contr.DoAddTracksToPlaylistWorkflow(
sharedutil.TracksToIDs(a.page.tracks))
})
playlist.Icon = myTheme.PlaylistIcon
download := fyne.NewMenuItem("Download...", func() {
download := fyne.NewMenuItem(lang.L("Download")+"...", func() {
a.page.contr.ShowDownloadDialog(a.page.tracks, a.titleLabel.String())
})
download.Icon = theme.DownloadIcon()
info := fyne.NewMenuItem("Show Info...", func() {
info := fyne.NewMenuItem(lang.L("Show info")+"...", func() {
a.page.contr.ShowAlbumInfoDialog(a.albumID, a.titleLabel.String(), a.cover.Image())
})
info.Icon = theme.InfoIcon()
a.shareMenuItem = fyne.NewMenuItem("Share...", func() {
a.shareMenuItem = fyne.NewMenuItem(lang.L("Share")+"...", func() {
a.page.contr.ShowShareDialog(a.albumID)
})
a.shareMenuItem.Icon = myTheme.ShareIcon
@@ -360,16 +361,16 @@ func formatMiscLabelStr(a *mediaprovider.AlbumWithTracks) string {
var discs string
if len(a.Tracks) > 0 {
if discCount := a.Tracks[len(a.Tracks)-1].DiscNumber; discCount > 1 {
discs = fmt.Sprintf("%d discs · ", discCount)
discs = fmt.Sprintf("%d %s · ", discCount, lang.L("discs"))
}
}
tracks := "tracks"
tracks := lang.L("tracks")
if a.TrackCount == 1 {
tracks = "track"
tracks = lang.L("track")
}
yearStr := strconv.Itoa(a.Year)
if a.ReissueYear > a.Year {
yearStr += fmt.Sprintf(" (reissued %d)", a.ReissueYear)
yearStr += fmt.Sprintf(" (%s %d)", lang.L("reissued"), a.ReissueYear)
}
return fmt.Sprintf("%s · %d %s · %s%s", yearStr, a.TrackCount, tracks, discs, util.SecondsToTimeString(float64(a.Duration)))
}
+14 -9
View File
@@ -4,9 +4,11 @@ import (
"slices"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/widget"
"github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/sharedutil"
"github.com/dweymouth/supersonic/ui/controller"
myTheme "github.com/dweymouth/supersonic/ui/theme"
"github.com/dweymouth/supersonic/ui/util"
@@ -27,7 +29,7 @@ func NewAlbumsPage(cfg *backend.AlbumsPageConfig, pool *util.WidgetPool, contr *
return NewGridViewPage(adapter, pool, mp, im)
}
func (a *albumsPageAdapter) Title() string { return "Albums" }
func (a *albumsPageAdapter) Title() string { return lang.L("Albums") }
func (a *albumsPageAdapter) Filter() mediaprovider.AlbumFilter {
if a.filter == nil {
@@ -49,22 +51,25 @@ func (a *albumsPageAdapter) PlaceholderResource() fyne.Resource { return myTheme
func (a *albumsPageAdapter) Route() controller.Route { return controller.AlbumsRoute() }
func (a *albumsPageAdapter) SortOrders() ([]string, string) {
func (a *albumsPageAdapter) SortOrders() ([]string, int) {
orders := a.mp.AlbumSortOrders()
sortOrder := a.cfg.SortOrder
if !slices.Contains(orders, sortOrder) {
sortOrder = string(orders[0])
sortOrder := slices.Index(orders, a.cfg.SortOrder)
if sortOrder < 0 {
sortOrder = 0
}
return orders, sortOrder
translatedOrders := sharedutil.MapSlice(orders, func(s string) string { return lang.L(s) })
return translatedOrders, sortOrder
}
func (a *albumsPageAdapter) SaveSortOrder(order string) {
a.cfg.SortOrder = order
func (a *albumsPageAdapter) SaveSortOrder(orderIdx int) {
a.cfg.SortOrder = a.mp.AlbumSortOrders()[orderIdx]
}
func (a *albumsPageAdapter) ActionButton() *widget.Button { return nil }
func (a *albumsPageAdapter) Iter(sortOrder string, filter mediaprovider.AlbumFilter) widgets.GridViewIterator {
func (a *albumsPageAdapter) Iter(sortOrderIdx int, filter mediaprovider.AlbumFilter) widgets.GridViewIterator {
sortOrder := a.mp.AlbumSortOrders()[sortOrderIdx]
return widgets.NewGridViewAlbumIterator(a.mp.IterateAlbums(sortOrder, filter))
}
+18 -12
View File
@@ -15,6 +15,7 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
@@ -50,9 +51,14 @@ type ArtistPage struct {
container *fyne.Container
}
const (
viewTopTracks = "Top Tracks"
viewDiscography = "Discography"
)
func NewArtistPage(artistID string, cfg *backend.ArtistPageConfig, pool *util.WidgetPool, pm *backend.PlaybackManager, mp mediaprovider.MediaProvider, im *backend.ImageManager, contr *controller.Controller) *ArtistPage {
activeView := 0
if cfg.InitialView == "Top Tracks" {
if cfg.InitialView == viewTopTracks {
activeView = 1
}
return newArtistPage(artistID, cfg, pool, pm, mp, im, contr, activeView, widgets.TracklistSort{})
@@ -81,7 +87,7 @@ func newArtistPage(artistID string, cfg *backend.ArtistPageConfig, pool *util.Wi
if img, ok := im.GetCachedArtistImage(artistID); ok {
a.header.artistImage.SetImage(img, true /*tappable*/)
}
viewToggle := widgets.NewToggleText(0, []string{"Discography", "Top Tracks"})
viewToggle := widgets.NewToggleText(0, []string{lang.L("Discography"), lang.L("Top Tracks")})
viewToggle.SetActivatedLabel(a.activeView)
viewToggle.OnChanged = a.onViewChange
//line := canvas.NewLine(theme.TextColor())
@@ -270,9 +276,9 @@ func (a *ArtistPage) onViewChange(num int) {
}
a.activeView = num
if num == 1 {
a.cfg.InitialView = "Top Tracks"
a.cfg.InitialView = viewTopTracks
} else {
a.cfg.InitialView = "Discography"
a.cfg.InitialView = viewDiscography
}
}
@@ -285,7 +291,7 @@ func (s *artistPageState) Restore() Page {
return newArtistPage(s.artistID, s.cfg, s.pool, s.pm, s.mp, s.im, s.contr, s.activeView, s.trackSort)
}
const artistBioNotAvailableStr = "Artist biography not available."
const artistBioNotAvailableKey = "Artist biography not available."
type ArtistPageHeader struct {
widget.BaseWidget
@@ -310,7 +316,7 @@ func NewArtistPageHeader(page *ArtistPage) *ArtistPageHeader {
a := &ArtistPageHeader{
artistPage: page,
titleDisp: widget.NewRichTextWithText(""),
biographyDisp: widgets.NewMaxRowsLabel(5, artistBioNotAvailableStr),
biographyDisp: widgets.NewMaxRowsLabel(5, lang.L(artistBioNotAvailableKey)),
similarArtists: container.NewHBox(),
}
a.titleDisp.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyle{
@@ -323,10 +329,10 @@ func NewArtistPageHeader(page *ArtistPage) *ArtistPageHeader {
}
}
a.favoriteBtn = widgets.NewFavoriteButton(func() { go a.toggleFavorited() })
a.playBtn = widget.NewButtonWithIcon("Play Discography", theme.MediaPlayIcon(), func() {
a.playBtn = widget.NewButtonWithIcon(lang.L("Play Discography"), theme.MediaPlayIcon(), func() {
go a.artistPage.pm.PlayArtistDiscography(a.artistID, false /*shuffle*/)
})
a.playRadioBtn = widget.NewButtonWithIcon("Play Artist Radio", myTheme.ShuffleIcon, func() {
a.playRadioBtn = widget.NewButtonWithIcon(lang.L("Play Artist Radio"), myTheme.ShuffleIcon, func() {
// must not pass playArtistRadio func directly to NewButton
// because the artistPage bound to this header can change when reused
a.artistPage.playArtistRadio()
@@ -336,11 +342,11 @@ func NewArtistPageHeader(page *ArtistPage) *ArtistPageHeader {
a.menuBtn = widget.NewButtonWithIcon("", theme.MoreHorizontalIcon(), nil)
a.menuBtn.OnTapped = func() {
if pop == nil {
shuffleTracks := fyne.NewMenuItem("Shuffle tracks", func() {
shuffleTracks := fyne.NewMenuItem(lang.L("Shuffle tracks"), func() {
go a.artistPage.pm.PlayArtistDiscography(a.artistID, true /*shuffle*/)
})
shuffleTracks.Icon = myTheme.TracksIcon
shuffleAlbums := fyne.NewMenuItem("Shuffle albums", func() {
shuffleAlbums := fyne.NewMenuItem(lang.L("Shuffle albums"), func() {
go a.artistPage.pm.ShuffleArtistAlbums(a.artistID)
})
shuffleAlbums.Icon = myTheme.AlbumIcon
@@ -374,7 +380,7 @@ func (a *ArtistPageHeader) Clear() {
a.artistID = ""
a.favoriteBtn.IsFavorited = false
a.titleDisp.Segments[0].(*widget.TextSegment).Text = ""
a.biographyDisp.Text = artistBioNotAvailableStr
a.biographyDisp.Text = lang.L(artistBioNotAvailableKey)
for _, obj := range a.similarArtists.Objects {
obj.Hide()
}
@@ -410,7 +416,7 @@ func (a *ArtistPageHeader) UpdateInfo(info *mediaprovider.ArtistInfo) {
}
if len(a.similarArtists.Objects) == 0 {
a.similarArtists.Add(widget.NewLabel("Similar Artists:"))
a.similarArtists.Add(widget.NewLabel(lang.L("Similar artists") + ":"))
}
for _, obj := range a.similarArtists.Objects {
obj.Hide()
+14 -9
View File
@@ -4,9 +4,11 @@ import (
"slices"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/widget"
"github.com/dweymouth/supersonic/backend"
"github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/sharedutil"
"github.com/dweymouth/supersonic/ui/controller"
myTheme "github.com/dweymouth/supersonic/ui/theme"
"github.com/dweymouth/supersonic/ui/util"
@@ -26,7 +28,7 @@ func NewArtistsPage(cfg *backend.ArtistsPageConfig, pool *util.WidgetPool, contr
return NewGridViewPage(adapter, pool, mp, im)
}
func (a *artistsPageAdapter) Title() string { return "Artists" }
func (a *artistsPageAdapter) Title() string { return lang.L("Artists") }
func (a *artistsPageAdapter) Filter() mediaprovider.ArtistFilter {
if a.filter == nil {
@@ -45,22 +47,25 @@ func (a *artistsPageAdapter) PlaceholderResource() fyne.Resource { return myThem
func (a *artistsPageAdapter) Route() controller.Route { return controller.ArtistsRoute() }
func (a *artistsPageAdapter) SortOrders() ([]string, string) {
func (a *artistsPageAdapter) SortOrders() ([]string, int) {
orders := a.mp.ArtistSortOrders()
sortOrder := a.cfg.SortOrder
if !slices.Contains(orders, sortOrder) {
sortOrder = string(orders[0])
sortOrder := slices.Index(orders, a.cfg.SortOrder)
if sortOrder < 0 {
sortOrder = 0
}
return orders, sortOrder
translatedOrders := sharedutil.MapSlice(orders, func(s string) string { return lang.L(s) })
return translatedOrders, sortOrder
}
func (a *artistsPageAdapter) SaveSortOrder(order string) {
a.cfg.SortOrder = order
func (a *artistsPageAdapter) SaveSortOrder(orderIdx int) {
a.cfg.SortOrder = a.mp.ArtistSortOrders()[orderIdx]
}
func (a *artistsPageAdapter) ActionButton() *widget.Button { return nil }
func (a *artistsPageAdapter) Iter(sortOrder string, filter mediaprovider.ArtistFilter) widgets.GridViewIterator {
func (a *artistsPageAdapter) Iter(sortOrderIdx int, filter mediaprovider.ArtistFilter) widgets.GridViewIterator {
sortOrder := a.mp.ArtistSortOrders()[sortOrderIdx]
return widgets.NewGridViewArtistIterator(a.mp.IterateArtists(sortOrder, filter))
}
+5 -4
View File
@@ -14,6 +14,7 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
@@ -85,7 +86,7 @@ func NewFavoritesPage(cfg *backend.FavoritesPageConfig, pool *util.WidgetPool, c
}
func (a *FavoritesPage) createHeader(activeBtnIdx int) {
a.titleDisp = widget.NewRichTextWithText("Favorites")
a.titleDisp = widget.NewRichTextWithText(lang.L("Favorites"))
a.titleDisp.Segments[0].(*widget.TextSegment).Style = widget.RichTextStyle{
SizeName: theme.SizeNameHeadingText,
}
@@ -94,7 +95,7 @@ func (a *FavoritesPage) createHeader(activeBtnIdx int) {
widget.NewButtonWithIcon("", myTheme.ArtistIcon, a.onShowFavoriteArtists),
widget.NewButtonWithIcon("", myTheme.TracksIcon, a.onShowFavoriteSongs))
a.searcher = widgets.NewSearchEntry()
a.searcher.PlaceHolder = "Search page"
a.searcher.PlaceHolder = lang.L("Search page")
a.searcher.OnSearched = a.OnSearched
a.searcher.Entry.Text = a.searchText
a.filterBtn = widgets.NewAlbumFilterButton(a.filter, a.mp.GetGenres)
@@ -364,9 +365,9 @@ func (a *FavoritesPage) onShowFavoriteArtists() {
func buildArtistGridViewModel(artists []*mediaprovider.Artist) []widgets.GridViewItemModel {
model := make([]widgets.GridViewItemModel, 0)
for _, ar := range artists {
albums := "albums"
albums := lang.L("albums")
if ar.AlbumCount == 1 {
albums = "album"
albums = lang.L("album")
}
model = append(model, widgets.GridViewItemModel{
ID: ar.ID,
+4 -3
View File
@@ -9,6 +9,7 @@ import (
"github.com/dweymouth/supersonic/ui/widgets"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/widget"
)
@@ -58,11 +59,11 @@ func (g *genrePageAdapter) Route() controller.Route {
func (g *genrePageAdapter) ActionButton() *widget.Button {
fn := func() { go g.pm.PlayRandomSongs(g.genre) }
return widget.NewButtonWithIcon("Play random", myTheme.ShuffleIcon, fn)
return widget.NewButtonWithIcon(lang.L("Play random"), myTheme.ShuffleIcon, fn)
}
func (a *genrePageAdapter) Iter(sortOrder string, filter mediaprovider.AlbumFilter) widgets.GridViewIterator {
return widgets.NewGridViewAlbumIterator(a.mp.IterateAlbums(sortOrder, filter))
func (a *genrePageAdapter) Iter(sortOrderIdx int, filter mediaprovider.AlbumFilter) widgets.GridViewIterator {
return widgets.NewGridViewAlbumIterator(a.mp.IterateAlbums("", filter))
}
func (a *genrePageAdapter) SearchIter(query string, filter mediaprovider.AlbumFilter) widgets.GridViewIterator {
+12 -6
View File
@@ -14,6 +14,7 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
@@ -42,14 +43,14 @@ func newGenresPage(contr *controller.Controller, mp mediaprovider.MediaProvider,
a := &GenresPage{
contr: contr,
mp: mp,
titleDisp: widget.NewRichTextWithText("Genres"),
titleDisp: widget.NewRichTextWithText(lang.L("Genres")),
}
a.ExtendBaseWidget(a)
a.titleDisp.Segments[0].(*widget.TextSegment).Style.SizeName = theme.SizeNameHeadingText
a.list = NewGenreList(sorting)
a.list.OnNavTo = func(id string) { a.contr.NavigateTo(controller.GenreRoute(id)) }
a.searcher = widgets.NewSearchEntry()
a.searcher.PlaceHolder = "Search page"
a.searcher.PlaceHolder = lang.L("Search page")
a.searcher.OnSearched = a.onSearched
a.searcher.Entry.Text = searchText
a.buildContainer()
@@ -188,15 +189,20 @@ func NewGenreListRow(layout *layouts.ColumnsLayout) *GenreListRow {
}
func NewGenreList(sorting widgets.ListHeaderSort) *GenreList {
albumCount := lang.L("Album count")
trackCount := lang.L("Track count")
albumW := widget.NewLabel(albumCount).MinSize().Width + 15 /*room for sort icon */
trackW := widget.NewLabel(trackCount).MinSize().Width + 15 /*room for sort icon */
a := &GenreList{
sorting: sorting,
columnsLayout: layouts.NewColumnsLayout([]float32{-1, 125, 125}),
columnsLayout: layouts.NewColumnsLayout([]float32{-1, albumW, trackW}),
}
a.ExtendBaseWidget(a)
a.hdr = widgets.NewListHeader([]widgets.ListColumn{
{Text: "Name", Alignment: fyne.TextAlignLeading, CanToggleVisible: false},
{Text: "Album Count", Alignment: fyne.TextAlignTrailing, CanToggleVisible: false},
{Text: "Track Count", Alignment: fyne.TextAlignTrailing, CanToggleVisible: false}},
{Text: lang.L("Name"), Alignment: fyne.TextAlignLeading, CanToggleVisible: false},
{Text: albumCount, Alignment: fyne.TextAlignTrailing, CanToggleVisible: false},
{Text: trackCount, Alignment: fyne.TextAlignTrailing, CanToggleVisible: false}},
a.columnsLayout)
a.hdr.SetSorting(sorting)
a.hdr.OnColumnSortChanged = a.onSorted
+23 -17
View File
@@ -9,6 +9,7 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
@@ -61,7 +62,7 @@ type GridViewPageAdapter[M, F any] interface {
// Returns the iterator for the given sortOrder and filter.
// (Non-media pages can ignore the filter argument)
Iter(sortOrder string, filter mediaprovider.MediaFilter[M, F]) widgets.GridViewIterator
Iter(sortOrderIdx int, filter mediaprovider.MediaFilter[M, F]) widgets.GridViewIterator
// Returns the iterator for the given search query and filter.
SearchIter(query string, filter mediaprovider.MediaFilter[M, F]) widgets.GridViewIterator
@@ -76,11 +77,12 @@ type GridViewPageAdapter[M, F any] interface {
}
type SortableGridViewPageAdapter interface {
// Returns the list of sort orders and the initially selected sort order
SortOrders() ([]string, string)
// Returns the list of sort orders
// and the index of the initially selected sort order
SortOrders() ([]string, int)
// Saves the given sort order setting.
SaveSortOrder(string)
// Saves the given sort order setting, the selected index is passed.
SaveSortOrder(int)
}
type sortOrderSelect struct {
@@ -120,7 +122,7 @@ func NewGridViewPage[M, F any](
gp.createTitleAndSort()
_, canShare := mp.(mediaprovider.SupportsSharing)
iter := adapter.Iter(gp.getSortOrder(), gp.getFilter())
iter := adapter.Iter(gp.getSortOrderIdx(), gp.getFilter())
if g := pool.Obtain(util.WidgetTypeGridView); g != nil {
gp.grid = g.(*widgets.GridView)
gp.grid.Placeholder = adapter.PlaceholderResource()
@@ -143,13 +145,13 @@ func (g *GridViewPage[M, F]) createTitleAndSort() {
if s, ok := g.adapter.(SortableGridViewPageAdapter); ok {
sorts, selected := s.SortOrders()
g.sortOrder = NewSortOrderSelect(sorts, g.onSortOrderChanged)
g.sortOrder.Selected = selected
g.sortOrder.SetSelectedIndex(selected)
}
}
func (g *GridViewPage[M, F]) createSearchAndFilter() {
g.searcher = widgets.NewSearchEntry()
g.searcher.PlaceHolder = "Search page"
g.searcher.PlaceHolder = lang.L("Search page")
g.searcher.Text = g.searchText
g.searcher.OnSearched = g.OnSearched
g.filterBtn = g.adapter.FilterButton()
@@ -179,7 +181,7 @@ func (g *GridViewPage[M, F]) Reload() {
if g.searchText != "" {
g.doSearch(g.searchText)
} else {
g.grid.Reset(g.adapter.Iter(g.getSortOrder(), g.getFilter()))
g.grid.Reset(g.adapter.Iter(g.getSortOrderIdx(), g.getFilter()))
}
}
@@ -222,20 +224,24 @@ func (g *GridViewPage[M, F]) doSearch(query string) {
g.grid.Reset(g.adapter.SearchIter(query, g.getFilter()))
}
func (g *GridViewPage[M, F]) onSortOrderChanged(order string) {
g.adapter.(SortableGridViewPageAdapter).SaveSortOrder(g.getSortOrder())
g.grid.Reset(g.adapter.Iter(g.getSortOrder(), g.getFilter()))
func (g *GridViewPage[M, F]) onSortOrderChanged(_ string) {
if g.grid == nil {
return // callback from initializing
}
g.adapter.(SortableGridViewPageAdapter).SaveSortOrder(g.getSortOrderIdx())
g.grid.Reset(g.adapter.Iter(g.getSortOrderIdx(), g.getFilter()))
}
func (g *GridViewPage[M, F]) getFilter() mediaprovider.MediaFilter[M, F] {
return g.filter
}
func (g *GridViewPage[M, F]) getSortOrder() string {
func (g *GridViewPage[M, F]) getSortOrderIdx() int {
if g.sortOrder != nil {
return g.sortOrder.Selected
return g.sortOrder.SelectedIndex()
}
return ""
return 0
}
func (g *GridViewPage[M, F]) CreateRenderer() fyne.WidgetRenderer {
@@ -249,7 +255,7 @@ type savedGridViewPage[M, F any] struct {
searchText string
filter mediaprovider.MediaFilter[M, F]
pool *util.WidgetPool
sortOrder string
sortOrderIdx int
gridState *widgets.GridViewState
searchGridState *widgets.GridViewState
}
@@ -262,7 +268,7 @@ func (g *GridViewPage[M, F]) Save() SavedPage {
im: g.im,
searchText: g.searchText,
filter: g.filter,
sortOrder: g.getSortOrder(),
sortOrderIdx: g.getSortOrderIdx(),
gridState: g.gridState,
searchGridState: g.searchGridState,
}
+12 -10
View File
@@ -27,6 +27,7 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
@@ -181,7 +182,7 @@ func NewNowPlayingPage(
a.pm.LoadItems(items, backend.InsertNext, false)
}
a.lyricsViewer = widgets.NewLyricsViewer()
a.statusLabel = widget.NewLabel("Stopped")
a.statusLabel = widget.NewLabel(lang.L("Stopped"))
a.Reload()
return a
@@ -203,12 +204,12 @@ func (a *NowPlayingPage) CreateRenderer() fyne.WidgetRenderer {
a.lyricsLoading = widgets.NewLoadingDots()
a.relatedLoading = widgets.NewLoadingDots()
a.tabs = container.NewAppTabs(
container.NewTabItem("Play Queue",
container.NewTabItem(lang.L("Play Queue"),
container.NewBorder(layout.NewSpacer(), nil, nil, nil, a.queueList)),
container.NewTabItem("Lyrics", container.NewStack(
container.NewTabItem(lang.L("Lyrics"), container.NewStack(
a.lyricsViewer,
container.NewCenter(a.lyricsLoading))),
container.NewTabItem("Related", container.NewStack(
container.NewTabItem(lang.L("Related"), container.NewStack(
a.relatedList,
container.NewCenter(a.relatedLoading))),
)
@@ -537,12 +538,13 @@ func (a *NowPlayingPage) formatStatusLine() {
curPlayer := a.pm.CurrentPlayer()
playerStats := curPlayer.GetStatus()
lastStatus := a.statusLabel.Text
state := "Stopped"
stopped := lang.L("Stopped")
state := stopped
switch playerStats.State {
case player.Paused:
state = "Paused"
state = lang.L("Paused")
case player.Playing:
state = "Playing"
state = lang.L("Playing")
}
dur := 0.0
@@ -551,7 +553,7 @@ func (a *NowPlayingPage) formatStatusLine() {
}
statusSuffix := ""
trackNum := 0
if state != "Stopped" {
if state != stopped {
trackNum = a.pm.NowPlayingIndex() + 1
statusSuffix = fmt.Sprintf(" %s/%s",
util.SecondsToMMSS(playerStats.TimePos),
@@ -561,14 +563,14 @@ func (a *NowPlayingPage) formatStatusLine() {
len(a.queue), statusSuffix)
mediaInfo := ""
if state != "Stopped" {
if state != stopped {
mediaInfo = a.formatMediaInfoStr(curPlayer)
}
if mediaInfo != "" {
mediaInfo = " · " + mediaInfo
}
a.statusLabel.Text = fmt.Sprintf("%s%s | Total time: %s", status, mediaInfo, util.SecondsToTimeString(a.totalTime))
a.statusLabel.Text = fmt.Sprintf("%s%s | %s: %s", status, mediaInfo, lang.L("Total time"), util.SecondsToTimeString(a.totalTime))
if lastStatus != a.statusLabel.Text {
a.statusLabel.Refresh()
}
+17 -13
View File
@@ -14,6 +14,7 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
@@ -89,7 +90,7 @@ func newPlaylistPage(
a.tracklist.OnReorderTracks = a.doSetNewTrackOrder
_, canRate := a.sm.Server.(mediaprovider.SupportsRating)
_, canShare := a.sm.Server.(mediaprovider.SupportsSharing)
remove := fyne.NewMenuItem("Remove from playlist", a.onRemoveSelectedFromPlaylist)
remove := fyne.NewMenuItem(lang.L("Remove from playlist"), a.onRemoveSelectedFromPlaylist)
remove.Icon = theme.ContentClearIcon()
a.tracklist.Options = widgets.TracklistOptions{
Reorderable: true,
@@ -252,17 +253,17 @@ func NewPlaylistPageHeader(page *PlaylistPage) *PlaylistPageHeader {
a.ownerLabel = util.NewTruncatingLabel()
a.createdAtLabel = widget.NewLabel("")
a.trackTimeLabel = widget.NewLabel("")
a.editButton = widget.NewButtonWithIcon("Edit", theme.DocumentCreateIcon(), func() {
a.editButton = widget.NewButtonWithIcon(lang.L("Edit"), theme.DocumentCreateIcon(), func() {
if a.playlistInfo != nil {
a.page.contr.DoEditPlaylistWorkflow(&a.playlistInfo.Playlist)
}
})
a.editButton.Hidden = true
playButton := widget.NewButtonWithIcon("Play", theme.MediaPlayIcon(), func() {
playButton := widget.NewButtonWithIcon(lang.L("Play"), theme.MediaPlayIcon(), func() {
a.page.pm.LoadTracks(a.page.tracks, backend.Replace, false)
a.page.pm.PlayFromBeginning()
})
shuffleBtn := widget.NewButtonWithIcon("Shuffle", myTheme.ShuffleIcon, func() {
shuffleBtn := widget.NewButtonWithIcon(lang.L("Shuffle"), myTheme.ShuffleIcon, func() {
a.page.pm.LoadTracks(a.page.tracks, backend.Replace, true)
a.page.pm.PlayFromBeginning()
})
@@ -270,20 +271,20 @@ func NewPlaylistPageHeader(page *PlaylistPage) *PlaylistPageHeader {
menuBtn := widget.NewButtonWithIcon("", theme.MoreHorizontalIcon(), nil)
menuBtn.OnTapped = func() {
if pop == nil {
playNext := fyne.NewMenuItem("Play next", func() {
playNext := fyne.NewMenuItem(lang.L("Play next"), func() {
go a.page.pm.LoadPlaylist(a.page.playlistID, backend.InsertNext, false)
})
playNext.Icon = myTheme.PlayNextIcon
queue := fyne.NewMenuItem("Add to queue", func() {
queue := fyne.NewMenuItem(lang.L("Add to queue"), func() {
go a.page.pm.LoadPlaylist(a.page.playlistID, backend.Append, false)
})
queue.Icon = theme.ContentAddIcon()
playlist := fyne.NewMenuItem("Add to playlist...", func() {
playlist := fyne.NewMenuItem(lang.L("Add to playlist")+"...", func() {
a.page.contr.DoAddTracksToPlaylistWorkflow(
sharedutil.TracksToIDs(a.page.tracks))
})
playlist.Icon = myTheme.PlaylistIcon
download := fyne.NewMenuItem("Download...", func() {
download := fyne.NewMenuItem(lang.L("Download")+"...", func() {
a.page.contr.ShowDownloadDialog(a.page.tracks, a.titleLabel.String())
})
download.Icon = theme.DownloadIcon()
@@ -342,17 +343,20 @@ func (a *PlaylistPageHeader) Update(playlist *mediaprovider.PlaylistWithTracks)
}
func (a *PlaylistPageHeader) formatPlaylistOwnerStr(p *mediaprovider.PlaylistWithTracks) string {
pubPriv := "Public"
pubPriv := lang.L("Public")
if !p.Public {
pubPriv = "Private"
pubPriv = lang.L("Private")
}
return fmt.Sprintf("%s playlist by %s", pubPriv, p.Owner)
playlistBy := lang.L("playlist by")
return fmt.Sprintf("%s %s %s", pubPriv, playlistBy, p.Owner)
}
func (a *PlaylistPageHeader) formatPlaylistTrackTimeStr(p *mediaprovider.PlaylistWithTracks) string {
tracks := "tracks"
var tracks string
if p.TrackCount == 1 {
tracks = "track"
tracks = lang.L("track")
} else {
tracks = lang.L("tracks")
}
return fmt.Sprintf("%d %s, %s", p.TrackCount, tracks, util.SecondsToTimeString(float64(p.Duration)))
}
+18 -12
View File
@@ -19,6 +19,7 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
@@ -71,14 +72,14 @@ func newPlaylistsPage(
mp: mp,
contr: contr,
listSort: listSort,
titleDisp: widget.NewRichTextWithText("Playlists"),
titleDisp: widget.NewRichTextWithText(lang.L("Playlists")),
initialListScrollPos: listScrollPos,
initialGridScrollPos: gridScrollPos,
}
a.ExtendBaseWidget(a)
a.titleDisp.Segments[0].(*widget.TextSegment).Style.SizeName = theme.SizeNameHeadingText
a.searcher = widgets.NewSearchEntry()
a.searcher.PlaceHolder = "Search page"
a.searcher.PlaceHolder = lang.L("Search page")
a.searcher.OnSearched = a.onSearched
a.searcher.Entry.Text = searchText
a.viewToggle = widgets.NewToggleButtonGroup(0,
@@ -193,9 +194,11 @@ func (a *PlaylistsPage) showGridView() {
func createPlaylistGridViewModel(playlists []*mediaprovider.Playlist) []widgets.GridViewItemModel {
return sharedutil.MapSlice(playlists, func(pl *mediaprovider.Playlist) widgets.GridViewItemModel {
tracks := "tracks"
var tracks string
if pl.TrackCount == 1 {
tracks = "track"
tracks = lang.L("track")
} else {
tracks = lang.L("tracks")
}
return widgets.GridViewItemModel{
Name: pl.Name,
@@ -334,10 +337,9 @@ type PlaylistList struct {
func NewPlaylistList(initialSort widgets.ListHeaderSort) *PlaylistList {
a := &PlaylistList{
sorting: initialSort,
columnsLayout: layouts.NewColumnsLayout([]float32{-1, -1, 200, 125}),
sorting: initialSort,
}
a.buildHeader()
a.buildHeaderAndLayout()
a.list = widgets.NewFocusList(
func() int {
return len(a.playlists)
@@ -369,12 +371,16 @@ func NewPlaylistList(initialSort widgets.ListHeaderSort) *PlaylistList {
return a
}
func (p *PlaylistList) buildHeader() {
func (p *PlaylistList) buildHeaderAndLayout() {
trackCount := lang.L("Track count")
trackCountWidth := fyne.Max(125, widget.NewLabel(trackCount).MinSize().Width+15)
p.columnsLayout = layouts.NewColumnsLayout([]float32{-1, -1, 200, trackCountWidth})
p.header = widgets.NewListHeader([]widgets.ListColumn{
{Text: "Name", Alignment: fyne.TextAlignLeading, CanToggleVisible: false},
{Text: "Description", Alignment: fyne.TextAlignLeading, CanToggleVisible: false},
{Text: "Owner", Alignment: fyne.TextAlignLeading, CanToggleVisible: false},
{Text: "Track Count", Alignment: fyne.TextAlignTrailing, CanToggleVisible: false}}, p.columnsLayout)
{Text: lang.L("Name"), Alignment: fyne.TextAlignLeading, CanToggleVisible: false},
{Text: lang.L("Description"), Alignment: fyne.TextAlignLeading, CanToggleVisible: false},
{Text: lang.L("Owner"), Alignment: fyne.TextAlignLeading, CanToggleVisible: false},
{Text: trackCount, Alignment: fyne.TextAlignTrailing, CanToggleVisible: false}}, p.columnsLayout)
p.header.SetSorting(p.sorting)
p.header.OnColumnSortChanged = p.onSorted
}
+10 -9
View File
@@ -15,6 +15,7 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
@@ -48,7 +49,7 @@ func newRadiosPage(contr *controller.Controller, rp mediaprovider.RadioProvider,
contr: contr,
rp: rp,
pm: pm,
titleDisp: widget.NewRichTextWithText("Internet Radio Stations"),
titleDisp: widget.NewRichTextWithText(lang.L("Internet Radio Stations")),
}
a.ExtendBaseWidget(a)
a.titleDisp.Segments[0].(*widget.TextSegment).Style.SizeName = theme.SizeNameHeadingText
@@ -56,13 +57,13 @@ func newRadiosPage(contr *controller.Controller, rp mediaprovider.RadioProvider,
a.list.OnPlay = a.onPlay
a.list.OnQueue = a.onQueue
a.searcher = widgets.NewSearchEntry()
a.searcher.PlaceHolder = "Search page"
a.searcher.PlaceHolder = lang.L("Search page")
a.searcher.OnSearched = a.onSearched
a.searcher.Entry.Text = searchText
a.noRadiosMsg = container.NewCenter(widgets.NewInfoMessage(
"No radio stations available",
"Configure your music server to add radio stations",
lang.L("No radio stations available"),
lang.L("Configure your music server to add radio stations"),
))
a.noRadiosMsg.Hide()
@@ -248,8 +249,8 @@ func NewRadioList(nowPlayingIDPtr *string) *RadioList {
a.playingIcon = container.NewCenter(widget.NewIcon(playIcon))
a.ExtendBaseWidget(a)
a.hdr = widgets.NewListHeader([]widgets.ListColumn{
{Text: "Name", Alignment: fyne.TextAlignLeading, CanToggleVisible: false},
{Text: "Home Page", Alignment: fyne.TextAlignLeading, CanToggleVisible: false}},
{Text: lang.L("Name"), Alignment: fyne.TextAlignLeading, CanToggleVisible: false},
{Text: lang.L("Home Page"), Alignment: fyne.TextAlignLeading, CanToggleVisible: false}},
a.columnsLayout)
a.hdr.DisableSorting = true
a.list = widgets.NewFocusList(
@@ -314,21 +315,21 @@ func NewRadioList(nowPlayingIDPtr *string) *RadioList {
func (a *RadioList) showMenu(pos fyne.Position) {
if a.menu == nil {
play := fyne.NewMenuItem("Play", func() {
play := fyne.NewMenuItem(lang.L("Play"), func() {
if a.OnPlay != nil {
a.OnPlay(a.selected.Item)
}
})
play.Icon = theme.MediaPlayIcon()
playNext := fyne.NewMenuItem("Play next", func() {
playNext := fyne.NewMenuItem(lang.L("Play next"), func() {
if a.OnQueue != nil {
a.OnQueue(a.selected.Item, true)
}
})
playNext.Icon = myTheme.PlayNextIcon
append := fyne.NewMenuItem("Add to queue", func() {
append := fyne.NewMenuItem(lang.L("Add to queue"), func() {
if a.OnQueue != nil {
a.OnQueue(a.selected.Item, false)
}
+4 -3
View File
@@ -11,6 +11,7 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/widget"
)
@@ -65,11 +66,11 @@ func NewTracksPage(contr *controller.Controller, conf *backend.TracksPageConfig,
}
contr.ConnectTracklistActions(t.tracklist)
t.title = widget.NewRichTextWithText("All Tracks")
t.title = widget.NewRichTextWithText(lang.L("All Tracks"))
t.title.Segments[0].(*widget.TextSegment).Style.SizeName = widget.RichTextStyleHeading.SizeName
t.playRandom = widget.NewButtonWithIcon("Play random", theme.ShuffleIcon, t.playRandomSongs)
t.playRandom = widget.NewButtonWithIcon(lang.L("Play random"), theme.ShuffleIcon, t.playRandomSongs)
t.searcher = widgets.NewSearchEntry()
t.searcher.PlaceHolder = "Search page"
t.searcher.PlaceHolder = lang.L("Search page")
t.searcher.OnSearched = t.OnSearched
t.createContainer()
t.Reload()
+19 -18
View File
@@ -25,6 +25,7 @@ import (
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
@@ -246,7 +247,7 @@ func (m *Controller) GetArtistTracks(artistID string) []*mediaprovider.Track {
}
func (m *Controller) PromptForFirstServer() {
d := dialogs.NewAddEditServerDialog("Connect to Server", false, nil, m.MainWindow.Canvas().Focus)
d := dialogs.NewAddEditServerDialog(lang.L("Connect to Server"), false, nil, m.MainWindow.Canvas().Focus)
pop := widget.NewModalPopUp(d, m.MainWindow.Canvas())
d.OnSubmit = func() {
d.DisableSubmit()
@@ -333,7 +334,7 @@ func (m *Controller) DoEditPlaylistWorkflow(playlist *mediaprovider.Playlist) {
}
dlg.OnDeletePlaylist = func() {
pop.Hide()
dialog.ShowCustomConfirm("Confirm Delete Playlist", "OK", "Cancel", layout.NewSpacer(), /*custom content*/
dialog.ShowCustomConfirm(lang.L("Confirm Delete Playlist"), lang.L("OK"), lang.L("Cancel"), layout.NewSpacer(), /*custom content*/
func(ok bool) {
if !ok {
pop.Show()
@@ -380,8 +381,8 @@ func (c *Controller) DoConnectToServerWorkflow(server *backend.ServerConfig) {
canceled := false
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
dlg := dialog.NewCustom("Connecting", "Cancel",
widget.NewLabel(fmt.Sprintf("Connecting to %s", server.Nickname)), c.MainWindow)
dlg := dialog.NewCustom(lang.L("Connecting"), lang.L("Cancel"),
widget.NewLabel(fmt.Sprintf(lang.L("Connecting to")+" %s", server.Nickname)), c.MainWindow)
dlg.SetOnClosed(func() {
canceled = true
cancel()
@@ -414,16 +415,16 @@ func (m *Controller) PromptForLoginAndConnect() {
pop := widget.NewModalPopUp(d, m.MainWindow.Canvas())
d.OnSubmit = func(server *backend.ServerConfig, password string) {
d.DisableSubmit()
d.SetInfoText("Testing connection...")
d.SetInfoText(lang.L("Testing connection") + "...")
go func() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err := m.App.ServerManager.TestConnectionAndAuth(ctx, server.ServerConnection, password)
if err == backend.ErrUnreachable {
d.SetErrorText("Server unreachable")
d.SetErrorText(lang.L("Server unreachable"))
} else if err != nil {
d.SetErrorText("Authentication failed")
d.SetErrorText(lang.L("Authentication failed"))
} else {
pop.Hide()
m.trySetPasswordAndConnectToServer(server, password)
@@ -434,7 +435,7 @@ func (m *Controller) PromptForLoginAndConnect() {
}
d.OnEditServer = func(server *backend.ServerConfig) {
pop.Hide()
editD := dialogs.NewAddEditServerDialog("Edit server", true, server, m.MainWindow.Canvas().Focus)
editD := dialogs.NewAddEditServerDialog(lang.L("Edit server"), true, server, m.MainWindow.Canvas().Focus)
editPop := widget.NewModalPopUp(editD, m.MainWindow.Canvas())
editD.OnSubmit = func() {
d.DisableSubmit()
@@ -461,7 +462,7 @@ func (m *Controller) PromptForLoginAndConnect() {
}
d.OnNewServer = func() {
pop.Hide()
newD := dialogs.NewAddEditServerDialog("Add server", true, nil, m.MainWindow.Canvas().Focus)
newD := dialogs.NewAddEditServerDialog(lang.L("Add Server"), true, nil, m.MainWindow.Canvas().Focus)
newPop := widget.NewModalPopUp(newD, m.MainWindow.Canvas())
newD.OnSubmit = func() {
d.DisableSubmit()
@@ -491,8 +492,8 @@ func (m *Controller) PromptForLoginAndConnect() {
}
d.OnDeleteServer = func(server *backend.ServerConfig) {
pop.Hide()
dialog.ShowConfirm("Confirm delete server",
fmt.Sprintf("Are you sure you want to delete the server %q?", server.Nickname),
dialog.ShowConfirm(lang.L("Confirm Delete Server"),
fmt.Sprintf(lang.L("Are you sure you want to delete the server")+" %q?", server.Nickname),
func(ok bool) {
if ok {
m.App.ServerManager.DeleteServer(server.ID)
@@ -527,7 +528,7 @@ func (c *Controller) ShowSettingsDialog(themeUpdateCallbk func(), themeFiles map
devs, err := c.App.LocalPlayer.ListAudioDevices()
if err != nil {
log.Printf("error listing audio devices: %v", err)
devs = []mpv.AudioDevice{{Name: "auto", Description: "Autoselect device"}}
devs = []mpv.AudioDevice{{Name: "auto", Description: lang.L("Autoselect device")}}
}
curPlayer := c.App.PlaybackManager.CurrentPlayer()
@@ -634,7 +635,7 @@ func (c *Controller) tryConnectToServer(ctx context.Context, server *backend.Ser
}
func (c *Controller) testConnectionAndUpdateDialogText(dlg *dialogs.AddEditServerDialog) bool {
dlg.SetInfoText("Testing connection...")
dlg.SetInfoText(lang.L("Testing connection") + "...")
conn := backend.ServerConnection{
ServerType: dlg.ServerType,
Hostname: dlg.Host,
@@ -646,10 +647,10 @@ func (c *Controller) testConnectionAndUpdateDialogText(dlg *dialogs.AddEditServe
defer cancel()
err := c.App.ServerManager.TestConnectionAndAuth(ctx, conn, dlg.Password)
if err == backend.ErrUnreachable {
dlg.SetErrorText("Could not reach server (wrong hostname?)")
dlg.SetErrorText(lang.L("Could not reach server") + fmt.Sprintf(" (%s?)", lang.L("wrong URL")))
return false
} else if err != nil {
dlg.SetErrorText("Authentication failed (wrong username/password)")
dlg.SetErrorText(lang.L("Authentication failed") + fmt.Sprintf(" (%s)", lang.L("wrong username/password")))
return false
}
return true
@@ -697,7 +698,7 @@ func (c *Controller) ShowShareDialog(id string) {
}
hyperlink := widget.NewHyperlink(shareUrl.String(), shareUrl)
dlg := dialog.NewCustom("Share content", "OK",
dlg := dialog.NewCustom(lang.L("Share content"), lang.L("OK"),
container.NewHBox(
hyperlink,
widget.NewButtonWithIcon("", theme.ContentCopyIcon(), func() {
@@ -787,7 +788,7 @@ func (c *Controller) downloadTrack(track *mediaprovider.Track, filePath string)
}
log.Printf("Saved song %s to: %s\n", track.Title, filePath)
c.sendNotification(fmt.Sprintf("Download completed: %s", track.Title), fmt.Sprintf("Saved at: %s", filePath))
c.sendNotification(fmt.Sprintf(lang.L("Download completed")+": %s", track.Title), fmt.Sprintf(lang.L("Saved at")+": %s", filePath))
}
func (c *Controller) downloadTracks(tracks []*mediaprovider.Track, filePath, downloadName string) {
@@ -825,7 +826,7 @@ func (c *Controller) downloadTracks(tracks []*mediaprovider.Track, filePath, dow
log.Printf("Saved song %s to: %s\n", track.Title, filePath)
}
c.sendNotification(fmt.Sprintf("Download completed: %s", downloadName), fmt.Sprintf("Saved at: %s", filePath))
c.sendNotification(fmt.Sprintf(lang.L("Download completed")+": %s", downloadName), fmt.Sprintf("Saved at: %s", filePath))
}
func (c *Controller) sendNotification(title, content string) {
+5 -4
View File
@@ -9,6 +9,7 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
@@ -37,7 +38,7 @@ func NewAboutDialog(version string) *AboutDialog {
widget.NewSeparator(),
container.NewHBox(
layout.NewSpacer(),
widget.NewButton("Close", func() {
widget.NewButton(lang.L("Close"), func() {
if a.OnDismiss != nil {
a.OnDismiss()
}
@@ -59,7 +60,7 @@ func (a *AboutDialog) buildMainTabContainer(version string) *fyne.Container {
title.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = true
title.Segments[0].(*widget.TextSegment).Style.SizeName = theme.SizeNameSubHeadingText
title.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignCenter
versionLbl := newCenterAlignLabel(fmt.Sprintf("version %s", version))
versionLbl := newCenterAlignLabel(fmt.Sprintf("%s %s", lang.L("version"), version))
copyright := newCenterAlignLabel(res.Copyright)
license := widget.NewRichTextWithText("GNU General Public License version 3 (GPL v3)")
ts := license.Segments[0].(*widget.TextSegment)
@@ -69,9 +70,9 @@ func (a *AboutDialog) buildMainTabContainer(version string) *fyne.Container {
kofiUrl, _ := url.Parse(res.KofiURL)
githubKofi := container.NewCenter(
container.New(layout.NewCustomPaddedHBoxLayout(-10),
widget.NewHyperlink("Github page", ghUrl),
widget.NewHyperlink(lang.L("Github page"), ghUrl),
widget.NewLabel("·"),
widget.NewHyperlink("Support the project", kofiUrl)),
widget.NewHyperlink(lang.L("Support the project"), kofiUrl)),
)
return container.New(&layout.CustomPaddedLayout{TopPadding: 10, BottomPadding: 10},
+14 -11
View File
@@ -1,11 +1,14 @@
package dialogs
import (
"fmt"
"github.com/dweymouth/supersonic/backend"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
@@ -46,7 +49,7 @@ func NewAddEditServerDialog(title string, cancelable bool, prefillServer *backen
titleLabel := widget.NewLabel(title)
titleLabel.TextStyle.Bold = true
legacyAuthCheck := widget.NewCheckWithData("Use legacy authentication", binding.BindBool(&a.LegacyAuth))
legacyAuthCheck := widget.NewCheckWithData(lang.L("Use legacy authentication"), binding.BindBool(&a.LegacyAuth))
serverTypeChoice := widget.NewRadioGroup([]string{"Subsonic", "Jellyfin"}, func(s string) {
a.ServerType = backend.ServerType(s)
if s == string(backend.ServerTypeSubsonic) {
@@ -67,15 +70,15 @@ func NewAddEditServerDialog(title string, cancelable bool, prefillServer *backen
userField := widget.NewEntryWithData(binding.BindString(&a.Username))
userField.OnSubmitted = func(_ string) { focusHandler(a.passField) }
altHostField := widget.NewEntryWithData(binding.BindString(&a.AltHost))
altHostField.SetPlaceHolder("(optional) https://my-external-domain.net/music")
altHostField.SetPlaceHolder(fmt.Sprintf("(%s)", lang.L("optional")) + " https://my-external-domain.net/music")
altHostField.OnSubmitted = func(_ string) { focusHandler(userField) }
hostField := widget.NewEntryWithData(binding.BindString(&a.Host))
hostField.SetPlaceHolder("http://localhost:4533")
hostField.OnSubmitted = func(_ string) { focusHandler(altHostField) }
nickField := widget.NewEntryWithData(binding.BindString(&a.Nickname))
nickField.SetPlaceHolder("My Server")
nickField.SetPlaceHolder(lang.L("My Server"))
nickField.OnSubmitted = func(_ string) { focusHandler(hostField) }
a.submitBtn = widget.NewButton("Enter", a.doSubmit)
a.submitBtn = widget.NewButton(lang.L("Enter"), a.doSubmit)
a.submitBtn.Importance = widget.HighImportance
a.promptText = widget.NewRichTextWithText("")
a.promptText.Hidden = true
@@ -85,7 +88,7 @@ func NewAddEditServerDialog(title string, cancelable bool, prefillServer *backen
bottomRow = container.NewHBox(
a.promptText,
layout.NewSpacer(),
widget.NewButton("Cancel", a.onCancel),
widget.NewButton(lang.L("Cancel"), a.onCancel),
a.submitBtn)
} else {
bottomRow = container.NewHBox(
@@ -97,17 +100,17 @@ func NewAddEditServerDialog(title string, cancelable bool, prefillServer *backen
a.container = container.NewVBox(
container.NewHBox(layout.NewSpacer(), titleLabel, layout.NewSpacer()),
container.New(layout.NewFormLayout(),
widget.NewLabel("Type"),
widget.NewLabel(lang.L("Server Type")),
serverTypeChoice,
widget.NewLabel("Nickname"),
widget.NewLabel(lang.L("Nickname")),
nickField,
widget.NewLabel("URL"),
widget.NewLabel(lang.L("URL")),
hostField,
widget.NewLabel("Alt. URL"),
widget.NewLabel(lang.L("Alt. URL")),
altHostField,
widget.NewLabel("Username"),
widget.NewLabel(lang.L("Username")),
userField,
widget.NewLabel("Password"),
widget.NewLabel(lang.L("Password")),
a.passField,
),
container.NewHBox(layout.NewSpacer(), legacyAuthCheck),
+3 -2
View File
@@ -11,6 +11,7 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
@@ -36,7 +37,7 @@ func NewAlbumInfoDialog(albumInfo *mediaprovider.AlbumInfo, albumName string, al
widget.NewSeparator(),
container.NewHBox(
layout.NewSpacer(),
widget.NewButton("Close", func() {
widget.NewButton(lang.L("Close"), func() {
if a.OnDismiss != nil {
a.OnDismiss()
}
@@ -61,7 +62,7 @@ func (a *AlbumInfoDialog) buildMainContainer(albumInfo *mediaprovider.AlbumInfo,
title.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignCenter
title.Truncation = fyne.TextTruncateEllipsis
infoContent := widget.NewLabel("Album info not available")
infoContent := widget.NewLabel(lang.L("Album info not available"))
if albumInfo.Notes != "" {
infoContent = a.infoLabel(albumInfo.Notes)
+8 -7
View File
@@ -4,6 +4,7 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/widget"
"github.com/dweymouth/supersonic/backend/mediaprovider"
@@ -31,33 +32,33 @@ func NewEditPlaylistDialog(playlist *mediaprovider.Playlist, showPublicCheck boo
}
e.ExtendBaseWidget(e)
isPublicCheck := widget.NewCheckWithData("Public", binding.BindBool(&e.IsPublic))
isPublicCheck := widget.NewCheckWithData(lang.L("Public"), binding.BindBool(&e.IsPublic))
isPublicCheck.Hidden = !showPublicCheck
nameEntry := widget.NewEntryWithData(binding.BindString(&e.Name))
descriptionEntry := widget.NewEntryWithData(binding.BindString(&e.Description))
deleteBtn := widget.NewButton("Delete Playlist", func() {
deleteBtn := widget.NewButton(lang.L("Delete Playlist"), func() {
if e.OnDeletePlaylist != nil {
e.OnDeletePlaylist()
}
})
submitBtn := widget.NewButton("OK", func() {
submitBtn := widget.NewButton(lang.L("OK"), func() {
if e.OnUpdateMetadata != nil {
e.OnUpdateMetadata()
}
})
submitBtn.Importance = widget.HighImportance
cancelBtn := widget.NewButton("Cancel", func() {
cancelBtn := widget.NewButton(lang.L("Cancel"), func() {
if e.OnCanceled != nil {
e.OnCanceled()
}
})
e.container = container.NewVBox(
container.NewHBox(layout.NewSpacer(), widget.NewLabel("Edit Playlist"), layout.NewSpacer()),
container.NewHBox(layout.NewSpacer(), widget.NewLabel(lang.L("Edit Playlist")), layout.NewSpacer()),
container.New(layout.NewFormLayout(),
widget.NewLabel("Name"),
widget.NewLabel(lang.L("Name")),
nameEntry,
widget.NewLabel("Description"),
widget.NewLabel(lang.L("Description")),
descriptionEntry,
),
container.NewHBox(isPublicCheck, layout.NewSpacer(), deleteBtn),
+5 -4
View File
@@ -7,6 +7,7 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
@@ -37,7 +38,7 @@ var _ fyne.Widget = (*LoginDialog)(nil)
func NewLoginDialog(servers []*backend.ServerConfig, pwFetch PasswordFetchFunc) *LoginDialog {
l := &LoginDialog{servers: servers}
l.ExtendBaseWidget(l)
titleLabel := widget.NewLabel("Login to Server")
titleLabel := widget.NewLabel(lang.L("Login to Server"))
titleLabel.TextStyle.Bold = true
l.passField = widget.NewPasswordEntry()
l.passField.OnSubmitted = func(_ string) { l.onSubmit() }
@@ -57,7 +58,7 @@ func NewLoginDialog(servers []*backend.ServerConfig, pwFetch PasswordFetchFunc)
editBtn := widget.NewButtonWithIcon("", theme.DocumentCreateIcon(), l.onEditServer)
newBtn := widget.NewButtonWithIcon("", theme.ContentAddIcon(), l.onNewServer)
deleteBtn := widget.NewButtonWithIcon("", theme.DeleteIcon(), func() { l.onDeleteServer(l.serverSelect.SelectedIndex()) })
l.submitBtn = widget.NewButton("OK", l.onSubmit)
l.submitBtn = widget.NewButton(lang.L("OK"), l.onSubmit)
l.submitBtn.Importance = widget.HighImportance
l.promptText = widget.NewRichTextWithText("")
l.promptText.Segments[0].(*widget.TextSegment).Style.ColorName = theme.ColorNameError
@@ -66,9 +67,9 @@ func NewLoginDialog(servers []*backend.ServerConfig, pwFetch PasswordFetchFunc)
l.container = container.NewVBox(
container.NewHBox(layout.NewSpacer(), titleLabel, layout.NewSpacer()),
container.New(layout.NewFormLayout(),
widget.NewLabel("Server"),
widget.NewLabel(lang.L("Server")),
container.NewBorder(nil, nil, nil, container.NewHBox(editBtn, newBtn, deleteBtn), l.serverSelect),
widget.NewLabel("Password"),
widget.NewLabel(lang.L("Password")),
l.passField),
widget.NewSeparator(),
container.NewHBox(l.promptText, layout.NewSpacer(), l.submitBtn),
+2 -1
View File
@@ -4,6 +4,7 @@ import (
"log"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/lang"
"github.com/dweymouth/supersonic/backend/mediaprovider"
"github.com/dweymouth/supersonic/ui/util"
)
@@ -15,7 +16,7 @@ type QuickSearch struct {
func NewQuickSearch(mp mediaprovider.MediaProvider, im util.ImageFetcher) *QuickSearch {
q := &QuickSearch{mp: mp}
q.SearchDialog = NewSearchDialog(im, "Quick Search", "Close", q.onSearched)
q.SearchDialog = NewSearchDialog(im, lang.L("Search Everywhere"), lang.L("Close"), q.onSearched)
return q
}
+4 -3
View File
@@ -8,6 +8,7 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
@@ -270,11 +271,11 @@ func (s *searchResult) Update(result *mediaprovider.SearchResult) {
s.imageLoader.Load(result.CoverID)
s.title.SetText(result.Name)
maybePluralize := func(s string, size int) string {
maybePluralize := func(key string, size int) string {
if size != 1 {
return s + "s"
return lang.L(key + "s")
}
return s
return lang.L(key)
}
var secondaryText string
switch result.Type {
+6 -5
View File
@@ -8,6 +8,7 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/widget"
"github.com/deluan/sanitize"
"github.com/dweymouth/supersonic/backend/mediaprovider"
@@ -31,12 +32,12 @@ func NewSelectPlaylistDialog(mp mediaprovider.MediaProvider, im util.ImageFetche
}
sd := NewSearchDialog(
im,
"Add to playlist",
"Cancel",
lang.L("Add to playlist"),
lang.L("Cancel"),
sp.onSearched,
)
sd.ActionItem = widget.NewCheckWithData("Skip duplicate tracks", binding.BindBool(&sp.SkipDuplicates))
sd.PlaceholderText = "Search playlists or new playlist name"
sd.ActionItem = widget.NewCheckWithData(lang.L("Skip duplicate tracks"), binding.BindBool(&sp.SkipDuplicates))
sd.PlaceholderText = lang.L("Search playlists or new playlist name")
sp.SearchDialog = sd
return sp
}
@@ -82,7 +83,7 @@ func (sp *SelectPlaylist) onSearched(query string) []*mediaprovider.SearchResult
)
})
results = append(results, &mediaprovider.SearchResult{
Name: fmt.Sprintf("Create new playlist: %s", query),
Name: fmt.Sprintf("%s: %s", lang.L("Create new playlist"), query),
Type: mediaprovider.ContentTypePlaylist,
})
}
+32 -29
View File
@@ -19,6 +19,7 @@ import (
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/storage"
"fyne.io/fyne/v2/theme"
@@ -138,12 +139,12 @@ func (s *SettingsDialog) createGeneralTab(canSaveQueueToServer bool) *container.
if startupPage.Selected == "" {
startupPage.SetSelectedIndex(0)
}
closeToTray := widget.NewCheckWithData("Close to system tray",
closeToTray := widget.NewCheckWithData(lang.L("Close to system tray"),
binding.BindBool(&s.config.Application.CloseToSystemTray))
if !s.config.Application.EnableSystemTray {
closeToTray.Disable()
}
systemTrayEnable := widget.NewCheck("Enable system tray", func(val bool) {
systemTrayEnable := widget.NewCheck(lang.L("Enable system tray"), func(val bool) {
s.config.Application.EnableSystemTray = val
// TODO: see https://github.com/fyne-io/fyne/issues/3788
// Once Fyne supports removing/hiding an existing system tray menu,
@@ -159,18 +160,20 @@ func (s *SettingsDialog) createGeneralTab(canSaveQueueToServer bool) *container.
systemTrayEnable.Checked = s.config.Application.EnableSystemTray
// save play queue settings
saveToServer := widget.NewRadioGroup([]string{"Locally", "To server"}, func(choice string) {
s.config.Application.SaveQueueToServer = choice == "To server"
locally := lang.L("Locally")
toServer := lang.L("To server")
saveToServer := widget.NewRadioGroup([]string{locally, toServer}, func(choice string) {
s.config.Application.SaveQueueToServer = choice == toServer
})
saveToServer.Horizontal = true
if !s.config.Application.SavePlayQueue {
saveToServer.Disable()
}
saveToServer.Selected = "Locally"
saveToServer.Selected = locally
if s.config.Application.SaveQueueToServer {
saveToServer.Selected = "To server"
saveToServer.Selected = toServer
}
saveQueue := widget.NewCheck("Save play queue on exit", func(save bool) {
saveQueue := widget.NewCheck(lang.L("Save play queue on exit"), func(save bool) {
s.config.Application.SavePlayQueue = save
if save && canSaveQueueToServer {
saveToServer.Enable()
@@ -184,9 +187,9 @@ func (s *SettingsDialog) createGeneralTab(canSaveQueueToServer bool) *container.
saveQueueHBox.Add(saveToServer)
}
trackNotif := widget.NewCheckWithData("Show notification on track change",
trackNotif := widget.NewCheckWithData(lang.L("Show notification on track change"),
binding.BindBool(&s.config.Application.ShowTrackChangeNotification))
albumGridYears := widget.NewCheck("Show year in album grid cards", func(b bool) {
albumGridYears := widget.NewCheck(lang.L("Show year in album grid cards"), func(b bool) {
s.config.AlbumsPage.ShowYears = b
s.config.FavoritesPage.ShowAlbumYears = b
if s.OnPageNeedsRefresh != nil {
@@ -232,7 +235,7 @@ func (s *SettingsDialog) createGeneralTab(canSaveQueueToServer bool) *container.
if lastScrobbleText == "" {
lastScrobbleText = "4" // default scrobble minutes
}
durationEnabled := widget.NewCheck("or when", func(checked bool) {
durationEnabled := widget.NewCheck(lang.L("or when"), func(checked bool) {
if !checked {
s.config.Scrobbling.ThresholdTimeSeconds = -1
lastScrobbleText = durationEntry.Text
@@ -256,7 +259,7 @@ func (s *SettingsDialog) createGeneralTab(canSaveQueueToServer bool) *container.
durationEnabled.Disable()
}
scrobbleEnabled := widget.NewCheck("Send playback statistics to server", func(checked bool) {
scrobbleEnabled := widget.NewCheck(lang.L("Send playback statistics to server"), func(checked bool) {
s.config.Scrobbling.Enabled = checked
if !checked {
percentEntry.Disable()
@@ -274,13 +277,13 @@ func (s *SettingsDialog) createGeneralTab(canSaveQueueToServer bool) *container.
})
scrobbleEnabled.Checked = s.config.Scrobbling.Enabled
return container.NewTabItem("General", container.NewVBox(
container.NewBorder(nil, nil, widget.NewLabel("Theme"), /*left*/
return container.NewTabItem(lang.L("General"), container.NewVBox(
container.NewBorder(nil, nil, widget.NewLabel(lang.L("Theme")), /*left*/
container.NewHBox(widget.NewLabel("Mode"), themeModeSelect, util.NewHSpace(5)), // right
themeFileSelect, // center
),
container.NewHBox(
widget.NewLabel("Startup page"), container.NewGridWithColumns(2, startupPage),
widget.NewLabel(lang.L("Startup page")), container.NewGridWithColumns(2, startupPage),
),
container.NewHBox(systemTrayEnable, closeToTray),
saveQueueHBox,
@@ -291,20 +294,20 @@ func (s *SettingsDialog) createGeneralTab(canSaveQueueToServer bool) *container.
widget.NewRichText(&widget.TextSegment{Text: "Scrobbling", Style: util.BoldRichTextStyle}),
scrobbleEnabled,
container.NewHBox(
widget.NewLabel("Scrobble when"),
widget.NewLabel(lang.L("Scrobble when")),
percentEntry,
widget.NewLabel("percent of track is played"),
widget.NewLabel(lang.L("percent of track is played")),
),
container.NewHBox(
durationEnabled,
durationEntry,
widget.NewLabel("minutes of track have been played"),
widget.NewLabel(lang.L("minutes of track have been played")),
),
))
}
func (s *SettingsDialog) createPlaybackTab(isLocalPlayer, isReplayGainPlayer bool) *container.TabItem {
disableTranscode := widget.NewCheckWithData("Disable server transcoding", binding.BindBool(&s.config.Transcoding.ForceRawFile))
disableTranscode := widget.NewCheckWithData(lang.L("Disable server transcoding"), binding.BindBool(&s.config.Transcoding.ForceRawFile))
deviceList := make([]string, len(s.audioDevices))
var selIndex int
for i, dev := range s.audioDevices {
@@ -323,7 +326,7 @@ func (s *SettingsDialog) createPlaybackTab(isLocalPlayer, isReplayGainPlayer boo
}
}
replayGainSelect := widget.NewSelect([]string{"None", "Album", "Track", "Auto"}, nil)
replayGainSelect := widget.NewSelect([]string{lang.L("None"), lang.L("Album"), lang.L("Track"), lang.L("Auto")}, nil)
replayGainSelect.OnChanged = func(_ string) {
switch replayGainSelect.SelectedIndex() {
case 0:
@@ -376,7 +379,7 @@ func (s *SettingsDialog) createPlaybackTab(isLocalPlayer, isReplayGainPlayer boo
})
preventClipping.Checked = s.config.ReplayGain.PreventClipping
audioExclusive := widget.NewCheck("Audio exclusive mode", func(checked bool) {
audioExclusive := widget.NewCheck(lang.L("Exclusive mode"), func(checked bool) {
s.config.LocalPlayback.AudioExclusive = checked
s.onAudioExclusiveSettingsChanged()
})
@@ -392,26 +395,26 @@ func (s *SettingsDialog) createPlaybackTab(isLocalPlayer, isReplayGainPlayer boo
preampGain.Disable()
}
return container.NewTabItem("Playback", container.NewVBox(
return container.NewTabItem(lang.L("Playback"), container.NewVBox(
disableTranscode,
container.New(&layout.CustomPaddedLayout{TopPadding: 5},
container.New(layout.NewFormLayout(),
widget.NewLabel("Audio device"), container.NewBorder(nil, nil, nil, util.NewHSpace(70), deviceSelect),
widget.NewLabel(lang.L("Audio device")), container.NewBorder(nil, nil, nil, util.NewHSpace(70), deviceSelect),
layout.NewSpacer(), audioExclusive,
)),
s.newSectionSeparator(),
widget.NewRichText(&widget.TextSegment{Text: "ReplayGain", Style: util.BoldRichTextStyle}),
container.New(layout.NewFormLayout(),
widget.NewLabel("ReplayGain mode"), container.NewGridWithColumns(2, replayGainSelect),
widget.NewLabel("ReplayGain preamp"), container.NewHBox(preampGain, widget.NewLabel("dB")),
widget.NewLabel("Prevent clipping"), preventClipping,
widget.NewLabel(lang.L("ReplayGain mode")), container.NewGridWithColumns(2, replayGainSelect),
widget.NewLabel(lang.L("ReplayGain preamp")), container.NewHBox(preampGain, widget.NewLabel("dB")),
widget.NewLabel(lang.L("Prevent clipping")), preventClipping,
),
))
}
func (s *SettingsDialog) createEqualizerTab(eqBands []string) *container.TabItem {
enabled := widget.NewCheck("Enabled", func(b bool) {
enabled := widget.NewCheck(lang.L("Enabled"), func(b bool) {
s.config.LocalPlayback.EqualizerEnabled = b
if s.OnEqualizerSettingsChanged != nil {
s.OnEqualizerSettingsChanged()
@@ -435,7 +438,7 @@ func (s *SettingsDialog) createEqualizerTab(eqBands []string) *container.TabItem
debouncer()
}
cont := container.NewBorder(enabled, nil, nil, nil, geq)
return container.NewTabItem("Equalizer", cont)
return container.NewTabItem(lang.L("Equalizer"), cont)
}
func (s *SettingsDialog) createExperimentalTab(window fyne.Window) *container.TabItem {
@@ -486,7 +489,7 @@ func (s *SettingsDialog) createExperimentalTab(window fyne.Window) *container.Ta
return container.NewTabItem("Experimental", container.NewVBox(
warningLabel,
s.newSectionSeparator(),
widget.NewRichText(&widget.TextSegment{Text: "UI Scaling", Style: util.BoldRichTextStyle}),
widget.NewRichText(&widget.TextSegment{Text: lang.L("UI Scaling"), Style: util.BoldRichTextStyle}),
uiScaleRadio,
s.newSectionSeparator(),
widget.NewRichText(&widget.TextSegment{Text: "Application Font", Style: util.BoldRichTextStyle}),
@@ -524,7 +527,7 @@ func (s *SettingsDialog) setRestartRequired() {
if ts.Text != "" {
return
}
ts.Text = "Restart required"
ts.Text = lang.L("Restart required")
ts.Style.ColorName = theme.ColorNameError
s.promptText.Refresh()
}
+23 -22
View File
@@ -7,6 +7,7 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
@@ -36,7 +37,7 @@ func NewTrackInfoDialog(track *mediaprovider.Track) *TrackInfoDialog {
func (t *TrackInfoDialog) CreateRenderer() fyne.WidgetRenderer {
c := container.New(layout.NewFormLayout())
addFormRow(c, "Title", t.track.Title)
addFormRow(c, lang.L("Title"), t.track.Title)
c.Add(newFormText("Album", true))
album := widget.NewHyperlink(t.track.Album, nil)
@@ -47,7 +48,7 @@ func (t *TrackInfoDialog) CreateRenderer() fyne.WidgetRenderer {
}
c.Add(album)
c.Add(newFormText("Artists", true))
c.Add(newFormText(lang.L("Artists"), true))
artists := widgets.NewMultiHyperlink()
artists.BuildSegments(t.track.ArtistNames, t.track.ArtistIDs)
artists.OnTapped = func(id string) {
@@ -58,7 +59,7 @@ func (t *TrackInfoDialog) CreateRenderer() fyne.WidgetRenderer {
c.Add(artists)
if len(t.track.ComposerNames) > 0 {
c.Add(newFormText("Composers", true))
c.Add(newFormText(lang.L("Composers"), true))
composers := widgets.NewMultiHyperlink()
composers.BuildSegments(t.track.ComposerNames, t.track.ComposerIDs)
artists.OnTapped = func(id string) {
@@ -70,7 +71,7 @@ func (t *TrackInfoDialog) CreateRenderer() fyne.WidgetRenderer {
}
if len(t.track.Genres) > 0 {
c.Add(newFormText("Genres", true))
c.Add(newFormText(lang.L("Genres"), true))
genres := widgets.NewMultiHyperlink()
genres.BuildSegments(t.track.Genres, t.track.Genres)
genres.OnTapped = func(g string) {
@@ -81,7 +82,7 @@ func (t *TrackInfoDialog) CreateRenderer() fyne.WidgetRenderer {
c.Add(genres)
}
addFormRow(c, "Duration", util.SecondsToTimeString(float64(t.track.Duration)))
addFormRow(c, lang.L("Duration"), util.SecondsToTimeString(float64(t.track.Duration)))
copyBtn := widgets.NewIconButton(theme.ContentCopyIcon(), func() {
if t.OnCopyFilePath != nil {
@@ -91,39 +92,39 @@ func (t *TrackInfoDialog) CreateRenderer() fyne.WidgetRenderer {
copyBtn.IconSize = widgets.IconButtonSizeSmaller
btnCtr := container.New(layout.NewCustomPaddedLayout(8, 0, 10, 0),
container.NewVBox(copyBtn, layout.NewSpacer()))
c.Add(container.NewHBox(btnCtr, newFormText("File path", true)))
c.Add(container.NewHBox(btnCtr, newFormText(lang.L("File path"), true)))
c.Add(newFormText(t.track.FilePath, false))
addFormRow(c, "Comment", t.track.Comment)
addFormRow(c, "Year", strconv.Itoa(t.track.Year))
addFormRow(c, "Track number", strconv.Itoa(t.track.TrackNumber))
addFormRow(c, "Disc number", strconv.Itoa(t.track.DiscNumber))
addFormRow(c, lang.L("Comment"), t.track.Comment)
addFormRow(c, lang.L("Year"), strconv.Itoa(t.track.Year))
addFormRow(c, lang.L("Track number"), strconv.Itoa(t.track.TrackNumber))
addFormRow(c, lang.L("Disc number"), strconv.Itoa(t.track.DiscNumber))
if t.track.BPM > 0 {
addFormRow(c, "BPM", strconv.Itoa(t.track.BPM))
addFormRow(c, lang.L("BPM"), strconv.Itoa(t.track.BPM))
}
addFormRow(c, "Content type", t.track.ContentType)
addFormRow(c, "Bit rate", fmt.Sprintf("%d kbps", t.track.BitRate))
addFormRow(c, "File size", util.BytesToSizeString(t.track.Size))
addFormRow(c, "Play count", strconv.Itoa(t.track.PlayCount))
addFormRow(c, lang.L("Content type"), t.track.ContentType)
addFormRow(c, lang.L("Bit rate"), fmt.Sprintf("%d kbps", t.track.BitRate))
addFormRow(c, lang.L("File size"), util.BytesToSizeString(t.track.Size))
addFormRow(c, lang.L("Play count"), strconv.Itoa(t.track.PlayCount))
if !t.track.LastPlayed.IsZero() {
addFormRow(c, "Last played", t.track.LastPlayed.Format(time.RFC1123))
addFormRow(c, lang.L("Last played"), t.track.LastPlayed.Format(time.RFC1123))
}
if t.track.ReplayGain.TrackPeak > 0 {
addFormRow(c, "Track gain", fmt.Sprintf("%0.2f dB", t.track.ReplayGain.TrackGain))
addFormRow(c, "Track peak", fmt.Sprintf("%0.6f", t.track.ReplayGain.TrackPeak))
addFormRow(c, lang.L("Track gain"), fmt.Sprintf("%0.2f dB", t.track.ReplayGain.TrackGain))
addFormRow(c, lang.L("Track peak"), fmt.Sprintf("%0.6f", t.track.ReplayGain.TrackPeak))
}
if t.track.ReplayGain.AlbumPeak > 0 {
addFormRow(c, "Album gain", fmt.Sprintf("%0.2f dB", t.track.ReplayGain.AlbumGain))
addFormRow(c, "Album peak", fmt.Sprintf("%0.6f", t.track.ReplayGain.AlbumPeak))
addFormRow(c, lang.L("Album gain"), fmt.Sprintf("%0.2f dB", t.track.ReplayGain.AlbumGain))
addFormRow(c, lang.L("Album peak"), fmt.Sprintf("%0.6f", t.track.ReplayGain.AlbumPeak))
}
title := widget.NewRichTextWithText("Track Info")
title := widget.NewRichTextWithText(lang.L("Track Info"))
title.Segments[0].(*widget.TextSegment).Style.TextStyle.Bold = true
dismissBtn := widget.NewButton("Close", func() {
dismissBtn := widget.NewButton(lang.L("Close"), func() {
if t.OnDismiss != nil {
t.OnDismiss()
}
+25 -24
View File
@@ -12,6 +12,7 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
@@ -56,24 +57,24 @@ func SecondsToTimeString(s float64) string {
var str string
if days > 0 {
daysStr := "days"
daysStr := lang.L("days")
if days == 1 {
daysStr = "day"
daysStr = lang.L("day")
}
str = fmt.Sprintf("%d %s ", days, daysStr)
}
if hr > 0 {
hrStr := "hrs"
hrStr := lang.L("hrs")
if hr == 1 {
hrStr = "hr"
hrStr = lang.L("hr")
}
str += fmt.Sprintf("%d %s ", hr, hrStr)
}
if min > 0 {
str += fmt.Sprintf("%d min ", min)
str += fmt.Sprintf("%d %s ", min, lang.L("min"))
}
if sec > 0 {
str += fmt.Sprintf("%d sec ", sec)
str += fmt.Sprintf("%d %s ", sec, lang.L("sec"))
}
return str[:len(str)-1]
}
@@ -158,45 +159,45 @@ func PlaintextFromHTMLString(s string) string {
}
func DisplayReleaseType(releaseTypes mediaprovider.ReleaseTypes) string {
baseType := "Album"
baseType := lang.L("Album")
switch {
case releaseTypes&mediaprovider.ReleaseTypeAudiobook > 0:
baseType = "Audiobook"
baseType = lang.L("Audiobook")
case releaseTypes&mediaprovider.ReleaseTypeAudioDrama > 0:
baseType = "Audio Drama"
baseType = lang.L("Audio Drama")
case releaseTypes&mediaprovider.ReleaseTypeBroadcast > 0:
baseType = "Broadcast"
baseType = lang.L("Broadcast")
case releaseTypes&mediaprovider.ReleaseTypeDJMix > 0:
baseType = "DJ-Mix"
baseType = lang.L("DJ-Mix")
case releaseTypes&mediaprovider.ReleaseTypeEP > 0:
baseType = "EP"
baseType = lang.L("EP")
case releaseTypes&mediaprovider.ReleaseTypeFieldRecording > 0:
baseType = "Field Recording"
baseType = lang.L("Field Recording")
case releaseTypes&mediaprovider.ReleaseTypeInterview > 0:
baseType = "Interview"
baseType = lang.L("Interview")
case releaseTypes&mediaprovider.ReleaseTypeMixtape > 0:
baseType = "Mixtape"
baseType = lang.L("Mixtape")
case releaseTypes&mediaprovider.ReleaseTypeSingle > 0:
baseType = "Single"
baseType = lang.L("Single")
case releaseTypes&mediaprovider.ReleaseTypeSoundtrack > 0:
baseType = "Soundtrack"
baseType = lang.L("Soundtrack")
}
var modifiers []string
if releaseTypes&mediaprovider.ReleaseTypeLive > 0 {
modifiers = append(modifiers, "Live")
modifiers = append(modifiers, lang.L("Live"))
}
if releaseTypes&mediaprovider.ReleaseTypeDemo > 0 {
modifiers = append(modifiers, "Demo")
modifiers = append(modifiers, lang.L("Demo"))
}
if releaseTypes&mediaprovider.ReleaseTypeRemix > 0 {
modifiers = append(modifiers, "Remix")
modifiers = append(modifiers, lang.L("Remix"))
}
if releaseTypes&mediaprovider.ReleaseTypeSpokenWord > 0 {
modifiers = append(modifiers, "Spoken Word")
modifiers = append(modifiers, lang.L("Spoken Word"))
}
if releaseTypes&mediaprovider.ReleaseTypeCompilation > 0 {
modifiers = append(modifiers, "Compilation")
modifiers = append(modifiers, lang.L("Compilation"))
}
modifiers = append(modifiers, baseType)
@@ -205,7 +206,7 @@ func DisplayReleaseType(releaseTypes mediaprovider.ReleaseTypes) string {
func NewRatingSubmenu(onSetRating func(int)) *fyne.MenuItem {
newRatingMenuItem := func(rating int) *fyne.MenuItem {
label := "(none)"
label := fmt.Sprintf("(%s)", lang.L("none"))
if rating > 0 {
label = strconv.Itoa(rating)
}
@@ -213,7 +214,7 @@ func NewRatingSubmenu(onSetRating func(int)) *fyne.MenuItem {
onSetRating(rating)
})
}
ratingMenu := fyne.NewMenuItem("Set rating", nil)
ratingMenu := fyne.NewMenuItem(lang.L("Set rating"), nil)
ratingMenu.Icon = theme.NewThemedResource(res.ResStarOutlineSvg)
ratingMenu.ChildMenu = fyne.NewMenu("", []*fyne.MenuItem{
newRatingMenuItem(0),
+12 -11
View File
@@ -11,6 +11,7 @@ import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
@@ -153,7 +154,7 @@ func NewAlbumFilterPopup(filter *AlbumFilterButton) *AlbumFilterPopup {
}
// setup is favorite/not favorite filters
a.isFavorite = widget.NewCheck("Is favorite", func(fav bool) {
a.isFavorite = widget.NewCheck(lang.L("Is favorite"), func(fav bool) {
filterOptions := a.filterBtn.filter.Options()
if fav {
a.isNotFavorite.SetChecked(false)
@@ -163,7 +164,7 @@ func NewAlbumFilterPopup(filter *AlbumFilterButton) *AlbumFilterPopup {
debounceOnChanged()
})
a.isFavorite.Hidden = a.filterBtn.FavoriteDisabled
a.isNotFavorite = widget.NewCheck("Is not favorite", func(fav bool) {
a.isNotFavorite = widget.NewCheck(lang.L("Is not favorite"), func(fav bool) {
filterOptions := a.filterBtn.filter.Options()
if fav {
a.isFavorite.SetChecked(false)
@@ -184,11 +185,11 @@ func NewAlbumFilterPopup(filter *AlbumFilterButton) *AlbumFilterPopup {
a.genreFilter.Hidden = a.filterBtn.GenreDisabled
// setup container
title := widget.NewLabel("Album filters")
title := widget.NewLabel(lang.L("Album filters"))
title.TextStyle.Bold = true
a.container = container.NewVBox(
container.NewHBox(layout.NewSpacer(), title, layout.NewSpacer()),
container.NewHBox(widget.NewLabel("Year from"), minYear, widget.NewLabel("to"), maxYear),
container.NewHBox(widget.NewLabel(lang.L("Year from")), minYear, widget.NewLabel("to"), maxYear),
container.NewHBox(a.isFavorite, a.isNotFavorite),
a.genreFilter,
)
@@ -281,7 +282,7 @@ func NewGenreFilterSubsection(onChanged func([]string), initialSelectedGenres []
},
)
g.filterText = widget.NewEntry()
g.filterText.SetPlaceHolder("Filter genres")
g.filterText.SetPlaceHolder(lang.L("Filter genres"))
i := NewTappableIcon(theme.ContentClearIcon())
i.NoPointerCursor = true
i.OnTapped = func() { g.filterText.SetText("") }
@@ -290,18 +291,18 @@ func NewGenreFilterSubsection(onChanged func([]string), initialSelectedGenres []
g.filterText.OnChanged = func(_ string) {
debouncer()
}
g.allBtn = widget.NewButton("All", func() { g.selectAllOrNoneInView(false) })
g.noneBtn = widget.NewButton("None", func() { g.selectAllOrNoneInView(true) })
g.allBtn = widget.NewButton(lang.L("All"), func() { g.selectAllOrNoneInView(false) })
g.noneBtn = widget.NewButton(lang.L("None"), func() { g.selectAllOrNoneInView(true) })
g.genresTitleLine = widget.NewRichText(
&widget.TextSegment{
Text: "Genres ",
Text: lang.L("Genres") + " ",
Style: widget.RichTextStyle{
Inline: true,
TextStyle: fyne.TextStyle{Bold: true},
},
},
&widget.TextSegment{Text: "(none selected)"},
&widget.TextSegment{Text: fmt.Sprintf("(%s)", lang.L("none selected"))},
)
g.updateNumSelectedText()
@@ -378,11 +379,11 @@ func (g *GenreFilterSubsection) invokeOnChanged() {
}
func (g *GenreFilterSubsection) updateNumSelectedText() {
numText := "none"
numText := lang.L("none")
if l := len(g.selectedGenres); l > 0 {
numText = strconv.Itoa(l)
}
t := fmt.Sprintf("(%s selected)", numText)
t := fmt.Sprintf("(%s %s)", numText, lang.L("selected"))
g.genresTitleLine.Segments[1].(*widget.TextSegment).Text = t
g.genresTitleLine.Refresh()
}
+2 -1
View File
@@ -3,6 +3,7 @@ package widgets
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/widget"
fynelyrics "github.com/dweymouth/fyne-lyrics"
"github.com/dweymouth/supersonic/backend/mediaprovider"
@@ -29,7 +30,7 @@ type LyricsViewer struct {
func NewLyricsViewer() *LyricsViewer {
l := &LyricsViewer{
noLyricsMsg: container.NewCenter(NewInfoMessage(
"Lyrics not available", "")),
lang.L("Lyrics not available"), "")),
isEmpty: true,
}
l.ExtendBaseWidget(l)
+2 -1
View File
@@ -4,6 +4,7 @@ import (
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"github.com/dweymouth/supersonic/ui/util"
@@ -27,7 +28,7 @@ func NewSearchEntry() *SearchEntry {
// For use only by extending widgets
func (sf *SearchEntry) Init() {
sf.PlaceHolder = "Search"
sf.PlaceHolder = lang.L("Search")
sf.ActionItem = NewClearTextButton(func() {
sf.SetText("")
})
+1
View File
@@ -107,6 +107,7 @@ type Tracklist struct {
}
func NewTracklist(tracks []*mediaprovider.Track, im *backend.ImageManager, useCompactRows bool) *Tracklist {
initTracklistColumns()
playIcon := theme.NewThemedResource(theme.MediaPlayIcon())
playIcon.ColorName = theme.ColorNamePrimary
+83 -35
View File
@@ -4,10 +4,12 @@ import (
"fmt"
"image"
"strconv"
"sync"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/lang"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
@@ -42,44 +44,90 @@ const (
)
var (
ExpandedTracklistRowColumns = []TracklistColumn{
{Name: ColumnNum, Col: ListColumn{Text: "#", Alignment: fyne.TextAlignTrailing, CanToggleVisible: false}},
{Name: ColumnTitleArtist, Col: ListColumn{Text: "Title / Artist", Alignment: fyne.TextAlignLeading, CanToggleVisible: false}},
{Name: ColumnAlbum, Col: ListColumn{Text: "Album", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}},
{Name: ColumnComposer, Col: ListColumn{Text: "Composer", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}},
{Name: ColumnTime, Col: ListColumn{Text: "Time", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}},
{Name: ColumnYear, Col: ListColumn{Text: "Year", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}},
{Name: ColumnFavorite, Col: ListColumn{Text: " Fav.", Alignment: fyne.TextAlignCenter, CanToggleVisible: true}},
{Name: ColumnRating, Col: ListColumn{Text: "Rating", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}},
{Name: ColumnPlays, Col: ListColumn{Text: "Plays", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}},
{Name: ColumnComment, Col: ListColumn{Text: "Comment", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}},
{Name: ColumnBitrate, Col: ListColumn{Text: "Bitrate", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}},
{Name: ColumnSize, Col: ListColumn{Text: "Size", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}},
{Name: ColumnPath, Col: ListColumn{Text: "File Path", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}},
}
ExpandedTracklistRowColumns []TracklistColumn
// #, Title/Artist, Album, Composer, Time, Year, Favorite, Rating, Plays, Comment, Bitrate, Size, Path
ExpandedTracklistRowColumnWidths = []float32{40, -1, -1, -1, 60, 60, 55, 100, 65, -1, 75, 75, -1}
ExpandedTracklistRowColumnWidths []float32
CompactTracklistRowColumns = []TracklistColumn{
{Name: ColumnNum, Col: ListColumn{Text: "#", Alignment: fyne.TextAlignTrailing, CanToggleVisible: false}},
{Name: ColumnTitle, Col: ListColumn{Text: "Title", Alignment: fyne.TextAlignLeading, CanToggleVisible: false}},
{Name: ColumnArtist, Col: ListColumn{Text: "Artist", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}},
{Name: ColumnAlbum, Col: ListColumn{Text: "Album", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}},
{Name: ColumnComposer, Col: ListColumn{Text: "Composer", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}},
{Name: ColumnTime, Col: ListColumn{Text: "Time", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}},
{Name: ColumnYear, Col: ListColumn{Text: "Year", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}},
{Name: ColumnFavorite, Col: ListColumn{Text: " Fav.", Alignment: fyne.TextAlignCenter, CanToggleVisible: true}},
{Name: ColumnRating, Col: ListColumn{Text: "Rating", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}},
{Name: ColumnPlays, Col: ListColumn{Text: "Plays", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}},
{Name: ColumnComment, Col: ListColumn{Text: "Comment", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}},
{Name: ColumnBitrate, Col: ListColumn{Text: "Bitrate", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}},
{Name: ColumnSize, Col: ListColumn{Text: "Size", Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}},
{Name: ColumnPath, Col: ListColumn{Text: "File Path", Alignment: fyne.TextAlignLeading, CanToggleVisible: true}},
}
CompactTracklistRowColumns []TracklistColumn
// #, Title, Artist, Album, Composer, Time, Year, Favorite, Rating, Plays, Comment, Bitrate, Size, Path
CompactTracklistRowColumnWidths = []float32{40, -1, -1, -1, -1, 60, 60, 55, 100, 65, -1, 75, 75, -1}
CompactTracklistRowColumnWidths []float32
initTracklistColumns = sync.OnceFunc(func() {
title := lang.L("Title")
artist := lang.L("Artist")
album := lang.L("Album")
composer := lang.L("Composer")
time := lang.L("Time")
year := lang.L("Year")
fav := lang.L("Fav.")
rating := lang.L("Rating")
plays := lang.L("Plays")
comment := lang.L("Comment")
bitrate := lang.L("Bit rate")
size := lang.L("Size")
filepath := lang.L("File path")
CompactTracklistRowColumns = []TracklistColumn{
{Name: ColumnNum, Col: ListColumn{Text: "#", Alignment: fyne.TextAlignTrailing, CanToggleVisible: false}},
{Name: ColumnTitle, Col: ListColumn{Text: title, Alignment: fyne.TextAlignLeading, CanToggleVisible: false}},
{Name: ColumnArtist, Col: ListColumn{Text: artist, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}},
{Name: ColumnAlbum, Col: ListColumn{Text: album, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}},
{Name: ColumnComposer, Col: ListColumn{Text: composer, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}},
{Name: ColumnTime, Col: ListColumn{Text: time, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}},
{Name: ColumnYear, Col: ListColumn{Text: year, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}},
{Name: ColumnFavorite, Col: ListColumn{Text: " " + fav, Alignment: fyne.TextAlignCenter, CanToggleVisible: true}},
{Name: ColumnRating, Col: ListColumn{Text: rating, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}},
{Name: ColumnPlays, Col: ListColumn{Text: plays, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}},
{Name: ColumnComment, Col: ListColumn{Text: comment, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}},
{Name: ColumnBitrate, Col: ListColumn{Text: bitrate, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}},
{Name: ColumnSize, Col: ListColumn{Text: size, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}},
{Name: ColumnPath, Col: ListColumn{Text: filepath, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}},
}
ExpandedTracklistRowColumns = []TracklistColumn{
{Name: ColumnNum, Col: ListColumn{Text: "#", Alignment: fyne.TextAlignTrailing, CanToggleVisible: false}},
{Name: ColumnTitleArtist, Col: ListColumn{Text: title + " / " + artist, Alignment: fyne.TextAlignLeading, CanToggleVisible: false}},
{Name: ColumnAlbum, Col: ListColumn{Text: album, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}},
{Name: ColumnComposer, Col: ListColumn{Text: composer, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}},
{Name: ColumnTime, Col: ListColumn{Text: time, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}},
{Name: ColumnYear, Col: ListColumn{Text: year, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}},
{Name: ColumnFavorite, Col: ListColumn{Text: fav, Alignment: fyne.TextAlignCenter, CanToggleVisible: true}},
{Name: ColumnRating, Col: ListColumn{Text: rating, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}},
{Name: ColumnPlays, Col: ListColumn{Text: plays, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}},
{Name: ColumnComment, Col: ListColumn{Text: comment, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}},
{Name: ColumnBitrate, Col: ListColumn{Text: bitrate, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}},
{Name: ColumnSize, Col: ListColumn{Text: size, Alignment: fyne.TextAlignTrailing, CanToggleVisible: true}},
{Name: ColumnPath, Col: ListColumn{Text: filepath, Alignment: fyne.TextAlignLeading, CanToggleVisible: true}},
}
var sortIconWidth float32 = 15
numColWidth := widget.NewLabel("9999").MinSize().Width
timeColWidth := fyne.Max(
widget.NewLabel(time).MinSize().Width+sortIconWidth,
widget.NewLabel("99:99").MinSize().Width)
yearColWidth := fyne.Max(
widget.NewLabel(year).MinSize().Width+sortIconWidth,
widget.NewLabel("2000").MinSize().Width)
favColWidth := fyne.Max(55,
widget.NewLabel(fav).MinSize().Width+sortIconWidth)
ratingColWidth := fyne.Max(100,
widget.NewLabel(rating).MinSize().Width+sortIconWidth)
playsColWidth := fyne.Max(
widget.NewLabel("9999").MinSize().Width,
widget.NewLabel(plays).MinSize().Width+sortIconWidth)
bitrateColWidth := fyne.Max(
widget.NewLabel("1000").MinSize().Width,
widget.NewLabel(bitrate).MinSize().Width+sortIconWidth)
sizeColWidth := fyne.Max(
widget.NewLabel("99.9 MB").MinSize().Width,
widget.NewLabel(size).MinSize().Width+sortIconWidth)
// #, Title, Artist, Album, Composer, Time, Year, Favorite, Rating, Plays, Comment, Bitrate, Size, Path
CompactTracklistRowColumnWidths = []float32{numColWidth, -1, -1, -1, -1, timeColWidth, yearColWidth, favColWidth, ratingColWidth, playsColWidth, -1, bitrateColWidth, sizeColWidth, -1}
// #, Title/Artist, Album, Composer, Time, Year, Favorite, Rating, Plays, Comment, Bitrate, Size, Path
ExpandedTracklistRowColumnWidths = []float32{numColWidth, -1, -1, -1, timeColWidth, yearColWidth, favColWidth, ratingColWidth, playsColWidth, -1, bitrateColWidth, sizeColWidth, -1}
})
)
type tracklistRowBase struct {