diff --git a/backend/config.go b/backend/config.go index fe89a64..4e30009 100644 --- a/backend/config.go +++ b/backend/config.go @@ -107,14 +107,14 @@ func DefaultConfig(appVersionTag string) *Config { CloseToSystemTray: false, }, AlbumPage: AlbumPageConfig{ - TracklistColumns: []string{"Artist", "Time", "Plays", "Favorite"}, + TracklistColumns: []string{"Artist", "Time", "Plays", "Favorite", "Rating"}, }, AlbumsPage: AlbumsPageConfig{ SortOrder: string(AlbumSortRecentlyAdded), }, ArtistPage: ArtistPageConfig{ InitialView: "Discography", - TracklistColumns: []string{"Album", "Time", "Plays", "Favorite"}, + TracklistColumns: []string{"Album", "Time", "Plays", "Favorite", "Rating"}, }, FavoritesPage: FavoritesPageConfig{ TracklistColumns: []string{"Artist", "Album", "Time", "Plays"}, diff --git a/backend/playbackmanager.go b/backend/playbackmanager.go index d5d1463..c4a703a 100644 --- a/backend/playbackmanager.go +++ b/backend/playbackmanager.go @@ -242,6 +242,14 @@ func (p *PlaybackManager) OnTrackFavoriteStatusChanged(id string, fav bool) { } } +// Any time the user changes the rating of a track elsewhere in the app, +// this should be called to ensure the in-memory track model is updated. +func (p *PlaybackManager) OnTrackRatingChanged(id string, rating int) { + if tr := sharedutil.FindTrackByID(id, p.playQueue); tr != nil { + tr.UserRating = rating + } +} + // trackIdxs must be sorted func (p *PlaybackManager) RemoveTracksFromQueue(trackIdxs []int) { newQueue := make([]*subsonic.Child, 0, len(p.playQueue)-len(trackIdxs)) diff --git a/res/bundled.go b/res/bundled.go index 9a66eab..d02d826 100644 --- a/res/bundled.go +++ b/res/bundled.go @@ -100,26 +100,6 @@ var ResPodcastInvertPng = &fyne.StaticResource{ StaticContent: []byte( "\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x80\x00\x00\x00\x80\b\x04\x00\x00\x00i7\xa9@\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\v\xfca\x05\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x02bKGD\x00\xff\x87\x8f̿\x00\x00\x00\tpHYs\x00\x00\v\x13\x00\x00\v\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\atIME\a\xe7\x01\x0e\x01\x13\bt\xdc|\x8e\x00\x00\x12sIDATx\xda\xed]{p\x95յ\xff\x9d/9\xe4E^\x84\b4\x06\x02\x11\x10\x88\x15\bHj\x91\x0e\nEl\x01Q\xb02J\xed-\xe8\x8c-S\xe5v\x9c\xb9-\xb6\xd3\x19tz\xc1\x8eҙK\xa60\x01A\xa0\xafq\xec\x15+P\x91\xa7A\x10\x01\xa5zA\xa1@\n\x86\x18\xc2#\ty\x90\xe7\xef\xfe\x11\xe29{}\xdf\xde\xdfw\xce\xf9N\xa03]\xdf_\xfb\xec\xb5_k\xaf\xbd\xf6\xdak\xaf\xb5O\x80\xe8\x01\b \x15}\x91\x8fB\x14` \xf2\x91\x8bLd \x19\x89\b\x02hC;\xae\xa1\x1eu\xa8\xc19\x9cE\x05N\xe1\x1c.\xa2\t=й@\\\xdb\b \x13C0\ncP\x84\x81\xc8E:\x12\x11p)C\xb4\xe3*jp\x16\x9f\xe2#\xfc\x1fN\xa3.\x9e\x84\x88\x17\x01\x12\x91\x87\xf1\x98\x88\t\x18\x86,XQ\xd6҉Z\x9c\xc0\a(LJ\xa8D\xfb\xbf\x06\x01,\f\xc0D\u070f\x89\x18\x84\xa0Ou\xb6\xe1\x9f(\xc76\x94\xa3\n\x9d73\x01R0\x16\x0fb:\x86\xf96t\x95\f'\xb0\x15\xff\x8b#h\xbe\x19\t\x90\x85{\xf18&#+\x0eC\x0f\x87Z\xec\xc2F\xecD\xed\xcdD\x80l<\x80\x85(Ar\x9c\a\xdf\r\xd7p\x00e\u0602+7\x03\x01\xd2\xf0\x00~\x84\xbb\xd1\xcb\x03n\a\x1aP\x87\x8b\xb8\x84Z\\A\x03\xda\xd0\x06 \x88 z#\x1bY\xc8A_d\xa27\x12<\xd4Պ\xf7Q\x8a-h\xbc\x91\x04H\xc47\xf0,\xa6#ňE\xd4\xe3,\x8e\xe1\x18>G\x05.\xa0\x1e\x8dhC\xa7\xb2\xb9\x05`!\x884d\xe0\x16\x14`8Fb$\x06\"\xc3e\xd3l\xc6V\xac\xc0\xfeX\xf6\x87X\b\x90\x8fE\xf8!\xfa\x1a0Z\xf1O\x1c\xc6^\x1c\xc1\x19\\\x8e\xa8\x9b\x89\xe8\x83\xc1\x18\x8bI(\xc6 #w]\xc4Z\xfc\x0f\xceE=\nF\xf7\xf5\xe2\xc3<\xc4Nꠝ'Y\xc69\xccg0\xca\x16\xba\xbe \xf39\x87e<\xc9vm[\x9d<ć\xd9+\xba\x16\xa2\xeb\xd6\xd7\xf82\xeb\xb5\x1d\xaa\xe3\x16.d\x01\x13b\x1az\xf8\x97\xc0\x02.\xe4\x16\xd6i۬\xe7\xcb\xfcZ\xcf\x10 \xc0{\xf8\x9ev\xee\xbf\xe4\x1aNf\x9aoC\x0f\xff\xd28\x99k\xf8\xa5\x96\x0f\xde\xe3=\fě\x00I\\\xc0s\x9a.T\xb3\x94\xc51\xb2\xbc\xdb\x17d1KY\xad\xe9\xc19.`R<\t\x90\xcd\xe5lа\xe0k\x9c\x10\xe7\xc1\x87\x880\x81\xafi\x96`\x03\x973;^\x04\xc8\xe7&GQ\xd4\xceݜ\xc1\xe4\x1e\x19|\xf7\x97\xcc\x19ܭ\xe9\xcd&\xe6ǃ\x00#\xb8ݑ\xe6繄\xb9=:\xf8\xee/\x97KxޱO\xdb9\xc2o\x02\xdc\xc9\xf7\x1d\x1a\xea\xe0\x0e~\x93\xd6\r\x19>\bZ\xfc&w\xb0ág\xef\xf3N?\tP\xccC\x8e\xeb~\x19\xfbݰ\xc1w\x7f\xfd\xb8\xccQ\x1e\x1cb\xb1_\x04\xb8\xd3q\xf8g\xf8X\xd4B/\xc0\x04\x06\x99\xc2\f\xe60\x87\x19La\x90\t\x91oa\u05ff \x1f\xe3\x19G\x12x\xe0\x02wUx\x04\xd6\xe0\x1b\xb6_\x0f`1\x0eD\xa8tZ\xc8@\x1e\xf2P\x80\xc1\x18\x88L\xa4 \x05\xc9\x00\xae\xa1\x19ͨ\xc3Y\x9cA\x05*Q\x89\xfa\x88\xcd\x1e%x\x05%\xb6_\xf7c\x01\x8eǦ\n\xe7;\x88\xbeN\xbe\xc5\xdb\"\x9a\xa34\x16q>W\xf2\x00\xbfd3M\xd0\xcc/y\x80+9\x9fE\x11\xaaS\xb7\xf1-\a\xf5l\xbbێ`\xae4\x9b\x9b\x1c\x04\xdf\x06\xf6\x8f\x80=o\xe7\"nc\xb5A\x97w\x82vVs\x1b\x17\xf1\xf6\b\x96Y\x7fnp\x10\x88\x9b\xccz\x81\xa9\xc2$.\xb7U\xd8\xce2\xe6x\xecP:gr\x13\xcf9Jio\xd0\xc1s\xdcęL\xf7\xd8b\x0e\xcbl\x84\xee\xe0r\x93vh\xaan\x81M\xeb\xeb`\x99G=+\x8bs\xb9\x8d\x8dQ\x0f=\x1c\x1a\xb9\x8ds\x99\xe5\xa9\xddl\x96\xd9\b\xde\xc0\x05\xd1\x10`\x92\x83ο\x81}=t\"\x99\xb3\xb8\xc3e\xadG\n\xcd\xdc\xc1Y\x9e\xb4;\xdc`+}\x8e\x93\"%@\x1e\xf7ڪ\xd9\xeca\xed\aX\xc45\x86\xa3r,P\xcf5,\xf2\xb0Y\xf6\xe7f[ٽ\xccs\xc6v\xde\x06\x83\xf8o\xfc\xa7mKy\x02']\xb6\x94t\xfc\a\x9e\xc5`\x97m\xa7\x19u\xa8F\r\x1aЈ&\x00\xa9HCo\xe4\xa2\x1f2\x91\xe2b\x04;\x83\x15x\x15W]\xfa1\x14\xebm[\xf7\xcb\xf8/\xb4y\xdd\x06\x1f\xb2\x99\x1eN\xb3ĕ\xf2\x85\xdc\xc8\x16\xc3\f\xd6\xf2\b\xcb\xf8\x14's8\xfb2\x99\x89\xb4\x18`\x80\x16\x13\x99̾\x1c\xce\xc9|\x8ae<\xc2ZC--\xdc\xc8B\u05fe\x94\xf0\xb4(WLJ\xbc.\x81|\x1e\xb41\xdf\xe3.\rZ\x9c\xc6\xc3\xdaN7p\x1f\x97\xb0\x849\x1e\xce\r\x16sX\xc2%ܧ9x\x93\xe4aNs\xad\xe9q\xdbB<\xe8\xa4\x13\xd8\v&p\x99(\xd8\xc9e.\xbbq\x90Oj\x8d\x14U\\\xc5)̊P\xd1\r0\x8bS\xb8\x8aU\x9aZ\xab\xf9\xa4k\x9f\x96\xd9\x14\xa3ev3\x9d\xbd\xe0$ֈb\xef\xba\x1cy\x92\xf9\x9c\xc6ZW\xc9\x15\x1c\x1b\x83\x99$ȱ\\\xc1JǺ\xeb\xf8\x9cˮЏ\xef\x8a25\xf6\xdd@\x16J\xe3\xeb\xa2\xd0yN46ӛ/\xb2ɡ\x83\x8d\xfc=\x8b}0\x8c&\xb0\x98\xbfw\xd4(\x9a\xf8\"{\x1b\xcbN\xb4\xd9\v^\x97\n\xb6,\xf2\x88\x18L\a\x97\x18\x997\x99/\xb2աs\x1f\xf1Q\xa6\xc6<\xf8\xee/\x95\x8f\xf2#\x87VZ\xf9\xa2\x91\v\x02\\\"Ԣ&>b\"@6w\x89&v\xf3\x16#\x8b>\xe7\xa0\xf0\xb4p=\x87\xf86\xf8\xeeo\b\xd7;\xec1\xcd|θ\xc4n\xe1nQb\x97\xaa˪菋&\xea9\xd3P\xb9\xc5'\x1d\xd6\xfe\x05.va\xcch\xbf\xde\\\xcc\v\x0e\xb2\xe0I\xe3\x8e0S\xec\x06-\xea\x8ef\x9e\xff\u05cc\f6\xcdA\xf2Wpv\x1cMd\x16g\xb3\xc2\xd6f5\xa7\x19\xca$\xf35\x13\x0f\x84\xa3>$ع\x9a\x13\f\x15\xdf\xe6`':\xce\xfb\xe26\xf8\xee\xef>\x1e\xb7\xb5{\xc8h\x9f\x98 &\xaa9\\%\n\xa1\xa5\xf0\rQm)\x13\xb5\x95\xa6;\x1c9\x8e\xf1\xee\xb8\x0f\x1f\x04\xef\xe61[\xdb\x1b\fG\xe6D\x96\n\xec7\x98b'\xc0D^V\x90\xaa8\xceЉgl\xb2\xbf\"\xc2\xd9\x0f0\x81ILc\x1a\x93\"\xb6\a\xdeg[\b\xad|ƀ?N(T\x97C[{\xe2W\x87\x82Y\xc8V\x0e\t[\xf0\xb1\xf6\xb0q\a~\"\xbc\x80j\xb0\x18;\xe0\x05\x12\x90\x83a(B\xe1u\xbb \xae\xdb\x03O\xe1S\x9c\xc0%tx\xa8c\a\x16c\x15r\xc3~\t\xe2'؉O4\xf8\x1fc\v~\x18\x96\xce\xc6,\x94\xab\x87\xa1<~\xaaШ\x96\x93\xb5\xf4L\xe2\x1aA\xff\x16.\xf64\x87}\xf8\x00KyTs\\\xae\xe7Q\x96\xf2\x01\xf6\xf1\xc4?\x8bm\x9b\xe2\x1a\x83\xe5g\xb28`}\xda}<\xeeF\xf8\x9e`\xe9-\x06\x93䃶\x01\xac\xf7\xb0\xf1\xe5r!\xcb\x1duF\x15\x9aX΅\x1e\xee\x9azs\xbd\x8d\x80\x0fj\xb1ӸE,\x99\xef\x85\x13 \x81\xaf*\xd9\x1d\\\xa8\xad*˦a\x1fq=\x9e&s\x16\xcb\xd9\xe6:\xf8nhc\xb9\a\xfbO!\x8f\x88r\xef\x1a\fg\v\x85N\xf8j\x97\x9aޕ9\x88'\x94̓,\xd0V\xf4\b\xaf)\xb8\x8d|ԥ\xa3\xb7r%\xafz\x1e|7\\\xe5J\xde\xeaR\xf3\xa3\xe2\x8cpM*\xbaa_\x01O*\xb8'8(D\x809b\x01\x94i\x0f1\xe9\xdc*:\xba\xc9E翋{\r\xae4&\xe8\xe4^\xdee\xac;\xd5f\xb6ߪ\xdd\x0e\x13X\xa6`\xb6rN\x88\x00+\x94\xac\x96\xae,\xc7o\xa6\xa0y\xa5q\xb3\fp*?\x8bj\xf0\xdd\xf0\x19\xa7\x1a\xc5\xeb8qXn4(\xefs\x84\xd8\\\xd1M\x80,\xee\x17\xcc1PSE\x90\x1bE\aW\x18\x15ߩ\x0e\x8ak\xa4P\xc1\xa9\x86\x16,1y\xe4F\xed\xe1h\xa0X\xe8\xfb\x99\xd5E\x801\xbc\xa8d\xfcA\xab\x01\xdeγ\nf\x95\xf1\x06vB\x8c\xb3\x1f\xe2\x02\x93J^,\x94\x9c\xb3\xbc]\x83\x99\xc8?(\x98\x179\x86\xb0\x00\x14\t\x15\xe8=\xadG\xdf\x14\xe4+\xe9\xcd8\xaaUVn\xc5K\x18\xeeI5r\x83\xe1x\t\xb7js\x8fb\xb3\x92\xce\xc7\x14\rf;\xdeS\xd2\xd9(\x02,\x00c\x14\x7f\xfeZ\x1c\xd6T\x90\x86\xef*\xe9F\xbc\xae%U\x12~\x86{|\x19>\x00܃\x9f!I;\xacׅ\xbb\xecw\x91\xa6\xc1=\xac\xb8X[\x18\x03XHC\x91\x82t\x16g4Ň`\xb4\x92>\x8aC\xda.O\xc7|߆\x0f\x00\xf31]\x9bwH\xf0\xe1h\f\xd1`\x9e\xc1Y%]\x844\v}1P\xf9\xf1\x18.k\x8a\x8fV\xb4o\x18\xbc\xb5s\xf1S\xa4\xfbJ\x80t\xfcT\xb4\x1e\x82+\xd8\"Z\x1f\xad\xc1\xbc\x8ccJz \xfaZ\xc8\x17\x15\x1fײu\x89\xb2T\xea\xb0S\xdb\xddY\x0e\xce\n\xb1B\tfi\xf3v\xa2.,ei[o\x17\xee\x12\xb9ȷP\xa8\xccU\a>\xd7\x14\xceB\xb1\x92>\xad\xbd(\xeb\x83'\xc2N\x99~A\"\x9e@\x1fM\xdeI\x9cV\xd2\xc5ڰ\x8dϕ\xd3f:\n-\x14(\a\xdb\x06Th\x8a\xe6\xa1@I\x1f\xd1.\x95\x12A*\xbf\xa0X;\xb3\x97qDI\x17 O\x83Y\x81\x86\xb0T\x10\x05\x96\x90\x00u\xb8\xa0%@\xa6\x92>\xa8\xf1\xe3\t\xe0;.\xf1\x03\xd1B\n\xbe\xa3\xb9:\xed\xc4A%\x9d\xa9%\xc0\x05e\xb1\x00\x03-\xb1\xb3_D\xbd\xa6h\x81\x12\x10Ӥ]\x00\xb9\x98\x18\x97\xe1\x03\xc0D\xad <\x89\xa6\xb0T\xb2\xe0\xd6\x10\xd4㢒ηD\x95\x97\xb4!(\xea\xa5w\x1d\xcek\xf0\x86\xb9\\\x8f\xc7\x02\x831L\x93s^̬\xae\x0f\x8d\xb8\xa4\xa4s-\xc1ص\x8ew\xe8@\x82X*բ\xa2\x10\x14\xf9\xbc\x01\x86C\xba\xd0YBp\t\xd5Jz\xa0&\xee\xa8MD\x9beZ\xc8P~\xb8\xa2YْP5\x8a0\t\x87¸\r\xdfT{\x03jԁi\xa2U;\x85\xee\x92a\x89P\xb7\x068{N&\n\xc1֨\xd1\x16$\xa7\xf8\r\xba\x99m\x17K7E\xb3\x11SL\\\xb2%\x10\x9d\x17\x00\x10\x14\x04h\xd0pJ\xa2\xe0\x14\xbf!S3\xb0N1\xb0\x14m\xec\xaa:\xc2DK \xea\t\xa0rJ\x93GN\xf1\x1b\xf43ۤ\xa4\x93=\x12 h!\xba\xb89F\x91\xe3\a\xf8\xdd.-\xb4*?\xe8\x0e\x9dm\xb8\xa6\xa4S5*I\xabV8\xfa\x03\r\xa2\xbf\xdd\x10@\xaa\x92\xbe\xa6\xe5eu\x84\xad\x16Z<\x12@\x8d\xd8N\xd5JY\x9d\"\xe5\x0f\xd4kw)\x95\x00\xcd\x1e\t\xd0\"9 Y3\xb3\xedb\x8d\xa5k\xd6b\a*\xe3J\x80J\xcd\xd5Y\xa2\xd0>\x9a4\xbbT@ȲVKl\x1f}4\xdbL\xa7д\xfa\xa1\xb7\xa6\x8b\xc7\xe3(\x05\xa8\xf5\xfe\xef\x8d~JZ\xc7)\t\xe2D\xd9h\t\x8d.G\x13\xa7\xdb!\xa2ss\x85\x1d1\x04\x9f\xb9\xfaqF\x0fW\xf1\x99&'[\xa8\xf4g5\x9c\xd2\v9J\xfa\x92\x85*QQ*\x9cᔒ\xca\xc4\x00-\x9eΤ\x16;\x9c\x11\xbd\b\xc1\x00\xa1\x7f\x9c\xd6ॊ\x89\xab\x92\x04\xe8#T\xe3\x10T(b0\r\xa34x\xd5\xd8\x1d7\x02\xec\x16\x1a\x7f\bF)\x86\xd0kZ\xabF\x86X\x02U\x96\x10Z\xd9ڙ\xad\x14R`\x9c֭\xf9\x1d?\xdf\xf8\b\x83f\xbc\xa3\xc9\t`\x9c\x92\xaeӊ\xe2\x01\x82\x03*-\x9cP6\xc24\xedI\xbaJP\xf5\xebZ)\xb0_\x98'\xfc\x82\x83د\xc9\xc9\xc6וt\x85\xf6\xb0^\xa0pJ\vNX8%\f\x8aC5Ek\xf1\xa1\x92\x1e\x8a\x11\x1a\xcc+X\x17\x877\x7fڱNk\x85\x1e!z\xfd\xa1\xf6\x89\x95\xa1°{\xcaB\xa5XWEZ-z\xbf\"Y3q\x9f\xb6\xb3oG\x1cR\xe7\x0e\a\xf0\xb66\xef>E\x04vh9%(\xec\tը\xb4pY\\\x16\x8c\x10\x1bE\b\x8e\n{\xe1\x14\xedɯ\x06/\t\x89\x11+\xd4\xe1%q\xe2\x0fA\xa6\xb8\f\xbb\xa0\xf5n\xca\x11\\{\x16\x97-\\\x13\xaeEyZsR\x85\xa8x4\xc6k;\xbc\x15\xeb}%\xc0zl\xd5\xe6\x8d\x17\x17!\x1fk\xf7\x80\xc1\xc2X\xfa\t\xaeY\x00\x0e++6SH\xd4\x104ᯊ\x96\x97\x8eyZ\xfb\x7f\x1b~\x83=\xbe\r\x7f\x0f~\xa3\xd5\xed\x131OQ\x83\x89\xbf\n\xb5=\x04\xe3\x14\x9em\xc7a\x00\x04G\b\xa7\xf2?ko؇\x89\xfb\xfe\xf3\x1cm\xbc\xb8>N?\xe0\xb8\xf1\x12~\xb4\xe8}\x05\x87i\xbd\x1b\xfe,z?\xa2\xebz\xfc\va\xe2.\xd6Z\xd5Oc\x97\x92\x1e\x80\xf9\x86\x97\xe2\x0e\xe3Y\x1f\xb4\xc23xV{_\rX\x98/4\x97]Z-0O\\\u061c\xc4\x17\xb8\xee\"\xa3\x06ɴr\xae\x96\xde3\x84\x8bL\x95\x8b\x17Ͻ1r\xc1q\xdek\xac\xff.\xe1\x1e\xd1\xc8\x19Zܹ\xc2\x13jY\xc8G\xe8~ῷN\xeb#\x92.\xfc\xed\xc8\xf5.ϖL\xe0\x9e\xa8\x9d\xa4\xf6\x18}C\xc0$\x9b\xaf\xe0\x16\xad\x93T\"\xd7)\x98M\xbc?D\x80\xfe\xfc\xbb\x92y\xda\xe0\xf9\xf7\xb0 V\xbd\xc1\xa5\xaa\xeb\x1b\xc0\xdf\x1a\xc3霡\x85\xbf\xe5\x00\x97\x9a\xe7\b\x97\xcd&>\xac\xc5-\x14\x81t\x7f\xef\n\x03\xedZ\xc1\xd5\xc2yd\x10\xeeծ\xba\xedث\xa4\xd3\xf1\xbc\xcb]@}T\xafA\xb6\xa3\xd2źT\x88\xe7\x85\x19d/\xb6k\xb1\xef\xc5 %\xfd\xdeu\x05𫵭\xc6\n\xbccp?\x7f\xd0\xe6\xf6\xb8:\xe4~n\xfb\x86\xb8\x84S\x9ax`\xa3!\xf4&\x85\xab\x05\xfeU\x83\xabl:\xdfQp\x9b\xbbeE7B_~ *\xd3;\xa7%ۜ\xa5\x9b\xf9\x8c\xc6]n\x14\xf7D5\xf8n\xd8\xc3Q\x8e\xf5Z|\xc6\x16\xad\xb4\xc6\xe0\\;UL\xda\a\xdda\xe0!\x94_تӇK\x8c\x12n\xa7d\x8d#\xf5\xc7\xd8bP#\x87\x83\x1c\xe3ȅ2\xba\xf1\xa4\x86T]\x02PN\xd9/\xba\xf3BHcE`\x899`f\x91-`\xe2/6\xfa\x0ffy\xcc\xc3'\xc9r\x0e\xb6\xf1\xe0_\x04N+\x17\x19z;\xc16\xb6\xb1v\x02\xf4\xb2y\x81\xae2\xf0@&\xff\xa8\xe0\xb6\xd9\x1e)\xe8\xc3?\xf92|\x92\xfc\x93-\x86`\x81\xf0=\xff#3\r\xf3\xbfJԷ1\xf4\xf8^8\xe2\xb7ŦRm\x8c\x18\x1f\xaa\x04M\x1d\x10\xf1\x85\x16\x9f\x8f\xe1\xe9\f\t\x1d|^Ș[x ,\xff\x10\x87\x1azZ\"濞\xdf\x0e\xe5\x86#\xa6\xd9\x1e\x1e\xd8h\x90\xee\xe0\x94\xaf\xb4\xf0\x0e>-\xf2&jÞ\xa3\x83*[\x00\xef\xd3_\x11\xf8<\xa7\x18z\x99b\xe3\xec\xcd\xe1\xc1 *\xf2CBɹ\xcaن\xaa\x03|\xfaz\x88\xfb1\x11\x98\x9e\xee\xf0\x86C\xac\xb0Yl\xcc\xf9\xd7c\xc7\x1a\xf8\xb4џ|\xb6\x90\xffM\xea;\x02p\xe9\xf8^c\xe8l/\xfe\x9c\xcd$W\x8a.\xcc\xf6\x10\x1a\x13)4\x89\xc9\bp%\xc9f\xfe\xdc\xf8\x98\xe6-\xb6\xa7@\x04!e\x81i\"\xb8Ⱦ\xfa\xa4<^\xca:q\x00I㛾\x0f\x9f$\xdf\x14qL3Xǥ\xc6\xc0\x1a\xbb$\xaa\x95Q\xa6\xb2H\x9282\x90U\xfa\x17X\xaes\xcdB\xf1\xb8ʷ\fo\x7f\xc6\x02u\xfc\x96\xd2N\x7f.tyah\x92M\x12\xad\x93G7{\xa1\xf1\xfcB\x14z\xc7%\x86\xcb\x12\v\xe0\x85\xb8\f\x9f$_\x10\x8b\xc0\x1c\xa5\x9c+\xd4_\xf2\v\x8e\x97XN\xa2M\xc6\xdcwrY\x04\x8f\xd6f\x8b\xf8\x13?a\x7f\x04\xcfe\xf6\xb2=\xa1\xe1\xf8\x16\x82\xb3\xe0\x901\xf7\xf5|\xccs\xc3\xe3D\b\xae\x9fp\xd9\x18\xa1\xa4~\x8f٢\x1b\x1d\xdfBp.<\xdd6\x88\x7fxxF\xa7\xbba\xff\x14 \t\x1d\x9e'\xa2\x84\xff\xb0\x11o\xba\x13\xa6\xb3Eo;~\a\xf5\x96\xbf\x10\xafh\xef\x8cT\x18\x19\xf5\xffI\xb8\x83\x85\x91\x9e\xf0\x86\xe2\x15a\xa3 ~\xa7\xb1\x15h\x05\xc86\x1b\xfd\xdf\xf4\xf0\x80\xa6\xe5\x10V\xef'l\xf0\xf0\xcc\xc3r\xbf\xfe\xee\xce'\xa8\xc5r\xccþȗctƋj\xfc\x12\xdf׆O\xf74t\xe2 \xbe\x8f_j=ɍ\x10m\x9c\x7f\x1b\xde\xc2a<\x85\x1f!pCw\x81\x00jP\x8a\xd5Z\xe7\xe8\xb8\x11\x00\x00\xcec)\xb6k\xdf\xeb\xe8\x19؆R\x1c\xf0\xf4\x04_\x1c\b\x00t\xe0}쿁\xdb`'6u\xa9s7\x8a\x00\x88\xb5\xf9\x1b\xdfz\xfc,\xb8\xff\"\xf0o\x02\xf8\\_\x87\x88D\xf5\x1bZb\x11x=A\x80&\xedæ\xfe\xc0'ZW\xf8\x9b\x84\x00\xc4Z\xd7\xffv\x8a\x1e\x8ec\xad\xefB׳=\xc0\xfb7\x89;\xe2\xe2!\xb2\xc3\xc5S!\xaa\xef\xff\x018\xa9\xbc̆f\xeb:\x00\x00\x00%tEXtdate:create\x002023-01-13T01:53:03+00:00:v\xe0\x02\x00\x00\x00%tEXtdate:modify\x002023-01-13T01:50:11+00:00\xfb)\xf2\n\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\x9b\xee<\x1a\x00\x00\x00\x00IEND\xaeB`\x82"), } -var ResStarFilledPng = &fyne.StaticResource{ - StaticName: "star-filled.png", - StaticContent: []byte( - "\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x80\x00\x00\x00\x80\b\x04\x00\x00\x00i7\xa9@\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\v\xfca\x05\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x02bKGD\x00\xff\x87\x8f̿\x00\x00\x00\tpHYs\x00\x00\r\xd7\x00\x00\r\xd7\x01B(\x9bx\x00\x00\x00\atIME\a\xe7\x01\r\x012-\xa1\xf2\x12\xc4\x00\x00\a\x9bIDATx\xda\xed\x9d[L\x15G\x1c\xc6\x7f{\x0e\x02\n\x05\x11Z.\n\x1c\xb9\xaa\x84xKSkS\x8d\x896i\xb4\xc6\x18\xe3\x85`\xa2\x89\xf1\x12\xa3\x91'\xd3ć\xa6\xb1\xb17cm\xda7\x9f\xf5ŇFc\xa2\xa9\x89\x89\xa6\xb5\xa8D-J\xbd\xa1\a\x10Q\x91\x12\xa8\a\x0fz`\xfa Z\x91sٝݝY\xb1\xdf\xff\xc5pֳ\xdf\xf7\xcd\x7f\x99ٙ\xf9\x0f\xf0?\xden\x18Z\xef\x9eʻ@'a\xdd6\xe8\xc1,\x0e\xd1L3\x87\x98\xa5\x9b\x8a\x0eL\xe1\x12b(.1E7\x1d\xd5\xf0\xb3\xff\xa5|\x81`?~ݔ\xd4b&\xed\xc3\fhg\xa6nJ*\xe1\xe3\x87a\xf2\x05\x82\x1f\xf0馥\x0eӹ;\u0080\xbbL\xd7MK\x15|\xec\x1d!_ \xd8\xfb\xb6\xe4@5\xadQ\rh\xa5Z=\x19\xf5\x9e\x1b\xac\xa50\xea'\x85\xac\xd5<0S\x82*\x82Q\xdb_ \bR\xa5\x9a\x8e\xea\f0XKq\xccO\x8bG\x7f\x0eL\xe5N\xcc\xf6\x17\b\xee0U-!\xb5\x19`PK \xee\x15\x01jGs\x0eL\xa19n\xfb\v\x04\xcdj\xdf\v\xd4f@\r%\t\xaf)\xa1F)'\x85\xa8\xe0f\xc2\xf6\x17\bnR\xa1\x8e\x94\xca\f\xa8\xa1\xcc\xd4ue\xa33\aʸa\xaa\xfd\x05\x82\x1b&\xadr\x00\xea2`\r妯-g\x8d2^\x8aP\xca5\xd3\xed/\x10\\\xa3T\r1U\x19\xb0\x8aJK\xd7W\xb2J\x113%\x98L\x93\xa5\xf6\x17\b\x9a\x98\xac\x82\x9a\x9a\fX%1\xc0\x9d:zr\xa0\x98+\x96\xdb_ \xb8\x12\xe7\xb5\xc91\xa8Ȁ\x95L\x93\xfa\x7f\xd3X\xa9\x80\x9d\xeb(\xa2Q\xaa\xfd\x05\x82F\x8aܦ\xe7~\x06\xac\xb01\xc9Q\xc5\n\xd7\xf9\xb9\x8cI\\\x96n\x7f\x81\xe02\x93tK\xb0\x87\x1d\f\xda2`\x90\x1d\xba%\xd8\xc1D.ڒ/\x10\\d\xa2n\x19\xf2\xd8ƀm\x03\x06ئ[\x86,\xf2i\xb0-_ h _\xb7\x149lu\xa0\xfd\x9f\xe7\xc0V\xddRd\x90\xc7yG\xe4\v\x04\xe7\xc9\xd3-\xc7:6\x13q̀\b\x9bu˱\x8a\\\xea\x1d\x93/\x10ԓ\xab[\x925lt\xb0\xfd\x9f\xe7\xc0Fݒ\xcc\xc2\xc78*\xf9\xc3Q\xf9\x02\xc1\x1fT2\xce\xf9\xa1\xbb\x13\xab0>R\xc9 \x87|\x8a\b\x10\xa0\x90B\x02\x8eS\x1d$H\x1bm\x04\t\xd2J\a\x8f\xe8%̠\x1e\x03\fR\xc8 \x9b|\n\t\x10\xa0\x88|\xb2\xc9 Eɲ\x96\xa0\x9f^\xba蠕 A\xda蠋^\xfa\x11\xee\x19`\x90\xcc;L \xef\xa5\xe4\x02r\xc8$U\xfbJ\x9e L\x0f\x8f\xb8\xf7Ҏ\xfb\xfc\xcd?<5g\x87\x11\xe7\x931\xa4\x93E\x1e\x93\b\x10\xa0\x98\x02\xde%\x93\xb1\x9e\xde\xc82\xc8\x13z\xe8\xe4\x1e-\x04\tr\x97\xfbt\xf3\x98g\xb1\xec0\x86\xfd;\x894\xb2\xc8e\xe2\x90䉼G&i\x9e\x96\x1cߎ\x10=<\xa4}Ȏv\x1e\xd0M\x88\xc8\x7fv\x18\x80\x9f\xc9Lc\xf2\x90\xe4\\\xb2H\x1b\x95\xdb\x16\a\b\xd1̓!;\xee\xd0\xc4\x1d\x06 \x8d\x9d\x04y\xeax\xb7\xe5\xf5xJ\x90\x9d\xa4\xf9\xa9\xe3\vrFe\x8bLJ\x9f\xf1|D\x9f\xc1-U\x8bP\x9eD\xb3A\x98\x14\xdd,4\xa2\xdfG\x8bn\x0eZ\xd1\xe2\xc7\xcf\xc7$\xeb\xe6\xa1\t!\xbe\xf1\xf3'>搤\x9b\x8b\x06\x84\xf9\x8e\x1f\xfdD\xa8g\x909\x8c\xd1\xcdG1\x9e\xf05\xdf\xf2\xc4\x0fD\xa8\xe7\x19\x1f\xbeU\x16\xf4\xf1\x15\xdf\x13f\xa8\xff\x8fp\x8e0sߚ\xdf\x05!\xbed\x1f\xfd\xc3\x7f\x98L\x1d\xbd\xda\xc7g*\xa2\x97\xba\xe8M=\x86m\xf4h\xa7\xe7v\xf4\xb0-\xf6Þ\xc4\x16\xba\xb5St3\xba\xd9\x12\xbf\xc7Kb\x03]\xdai\xba\x15]lH\xdc\xe1\xfbYG\xa7v\xaanD'\xeb̽\xf4\xf9\xa8\xe5\xa1v\xbaN\xc7Cj\xcdO\xec\xf8XM\x87v\xcaNF\a\xab\xad\xcdk\x19\xacx\xad\xb6\xf3M\x8evVX\x9f\xbc5XF\x9bv\xeaND\x1b\xcb\xe4\xe6\xae\r\x96ĩ\xf0zS\"\xc8\x12;S\xf7\x9fr[\xbb\x04;q\x9bO\xe5\xc5?\xc7\"\x93u\x1e^\x8c\x9b,\xb2+\x1f`\x01\u05f5K\x91\x89\xeb,pB>\xc0<\x89\xdd\u07ba\xa3\x89yN\xc9\a\x98kcë\x8ehd\xae\x93\xf2\x01>x\xe5\xcc\x0f\xaf\xc7%>pZ>\xc0l\x87\xb6\xbd\xb9\x1d\r\xccvC>\xc0\f\xcei\x97\x97(\xce1\xc3-\xf9\x00\xd5\xfc\xae]b\xbc\xf8\xdd\xfdC\x18\xa6qF\xbb\xccXqF\xb24\xc3\"\xa6pJ\xbb\xd4hqJ]\xd9u9'\xb5\xcb}=NZ(\xcct\x00%\x1c\xd7.\xf9\xd58n\xa2.\xdda\x048\xa6]\xf6\x8b8\x96\xe0X\x06\x97\xb0\x94~\xed\xd2\x05\x82~\x96ʋ\xb0\xb3\xfd)\xdf#+I\xc9v\xea\t\xec\x18\xa0\xf0\x98\x03\xf7\x98\xc8\x1b\x90\xac\xf6\xb7n\\\x94\xcb碼\x01\x99\xee\x175\x9aF\x11\x99\xea\r\xc8\xf5P%O\xbe|5\x81\xbc\x016\\w\x1c6\xb2QހR\x0f\xed.K\x91\xdf\xea'o\x80\xb5\x13!܆4\x1bY\x03R=\xb6\xbd\xb2\x94T\xb5\x06\x8c\xf7P\x1f\x00P\xc4x\xb5\x06\xe4y\xac\x8a+W\xb6\xb2Pրb2tk\x1e\x86\f\xd9\xe36d\r(\xf7ئ\xba1\xb2\xe3R9\x03\f\x0f\xbd\a\xbc@\x85\xdc\x12\xa8\x9c\x01c=\xd6\a\x00\x942V\x9d\x01Y1΅ՉB\xb2\xd4\x19P@\x8en\xbd#\x90C\x81:\x03\x02\x1e\xeb\x03\x002\xe4\xa6\xc5\xe4\f(\xf7`\x8d\x91_\xae\x1f\x901\xc0\xe7\xc1>\x00\xa0BF\x8d\x8c\x01iꧠM\xa1\x8445\x06L\xf0\xe8\xe1F\x93\x98\xa0ƀ\x02\xb2uk\x8d\x8al\x99~@ƀ\x12\xd2\x1d#\x1d\"\xe4\xd8w\xa5\xcb<\x9a2\x06\x94;TL\xdd\xc7QV\xb2\x92\xa3\xf49\xf2}>5\xf3\xd4~\x0e:\xb0\x9a\x13\xe6$ˇ2)\x9d\xe5\x9c$\xec\xc0\xb7\x1eT\xd1=gr\xd6&\xcdg\xfcF\xedk\x13\x18\xe3\xa9\xe57\x9e\xd9\xfc\xe6\xb3*&jKc\xfc\x81\fs\x11\xa1\x81M1\x06\xd29l\xa2\xc1\xd6\xe93\xad*^\xd2\xe6\xf3X\x92\xde W\xa9K\xb0\x9a\x90O\x1dW\xa5\x8f\xe0{\xcc|\xf7\rX/I\xef\x16\xbb(6\xf1\xcenP\xcc.nIZ\xbc\xde}\x03\xf6H\xa5\xe6\x1eK\x03U\x1f\x15\xec\x91z\xd4\xf6\xb8-?\x89\xc3\x16)\xddg?\xd5\x12\x1d\xa7\x8fj\xf6s\xdf\xe2\xdd\x0e\xbb]\x05=\x81\v\x16\xe8tq\x80\xf7mPJ\xe2}\x0eX\xaab\xbb 3\x1c\xb6\x82JӅ4=\x1ct\xa40?\x99\x8f9h\xba\xa0\xb3\xdd\xed\x15\xab\x85\xf4\x99\xa0\x11\xe2\x17>\x91]\xab\x89\x82T>\xe1\x17B&\xee\xdc\xc7Bw\rؔ\x90B\x98\x13,\x93y1M\x804\x96q\xc2\xc4xq\x93\xbb\x06\xec\x8d{\U000e7726\xc6\xc5\xd1X&5\x9cNp\xe4\xcf^7\xe5's$\xe6\x8d#\x9cg\x83\x82\x17\xe5l6p>\xcex\xf1\x88\x9b[\xb7\xb2c\x9c\x13<@#\xdb\x15\x9e\xfb\x99\xc7v\x1ac\x1c\xd9z\xd1\xcdFȋR:3\xc8\r>\xa7H\xf1\xa9r\x06E|\u038d(\xa3\xd2&7\x1b\"u\xc4#\xd0\xc2n\xca4\x1d\xa9gP\xc6nZF<\x02\xce\xf5>Q\xb0\xf8\x95\xb2\xea{\xec\xa3J\xf3Is>\xaa\xd8ǽ\x97\x9c\x1e\xb2\xd8\xdd\x1b\xfa\xf9\x8c\x13\xb4p\x8d\x9f\x99\xed\x91\xc3w\x92\x98\xcdO\xfcE\v\xbf\xb2\xd4ꔈL\xf2\xa6\x93C?\x9dDt+\x1ffB\x0e)t\xf1X7\x91\xff\xf1\xa6\xe1_\xd34\x88\xc4*\xae\x97L\x00\x00\x00%tEXtdate:create\x002023-01-13T01:21:22+00:00\xf3\xea)u\x00\x00\x00%tEXtdate:modify\x002023-01-13T01:14:29+00:00\xff{hv\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\x9b\xee<\x1a\x00\x00\x00\x00IEND\xaeB`\x82"), -} -var ResStarFilledInvertPng = &fyne.StaticResource{ - StaticName: "star-filled-invert.png", - StaticContent: []byte( - "\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x80\x00\x00\x00\x80\b\x04\x00\x00\x00i7\xa9@\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\v\xfca\x05\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x02bKGD\x00\xff\x87\x8f̿\x00\x00\x00\tpHYs\x00\x00\r\xd7\x00\x00\r\xd7\x01B(\x9bx\x00\x00\x00\atIME\a\xe7\x01\x0e\x01\x13\x15\x17\xda\x10W\x00\x00\brIDATx\xda\xe5\x9dml\x14\xc7\x19\xc7\x7f{g\xec\x03\x13\x1b\xbfP\xfbll\x1f6\xb6\x89]\x93\x17+RU5\x8a*\xa5H\x15jA\x94\x98\x00\xe6\xc5\x01jH\xd2DDj\xab\xb4\xa8\xa5\x15\x8d\xfa\x12\x05\xa1\x02\x01\x1a\x89JU\xf9\x10\xa5\x12M\x84\x1a\x9aH\x8e\x9a&\xb4\"4Di\xa0B\xae\xb8\x83`\x1c\xc0\xa1&>c㗧\x1f\xc0\x06\xdb绝\xb9ݝ;\xfb\xbf\x9fnov\xe6y~;{\xb33\xf3̜%Lo\xf9L\x1b`Z\x19FK\x0f0\x17\xb8B\x9f9\x13Lր\a9\xc4;\xbc\xc3!\x1e4h\x85\x98:\x16\xca)\x19\xd1)Yh\xca\x0eS5\xc0\xcfV\xee\x1b\xfdt\x1f[\xf1\x9b1\xc4\x14\x80E\xac\x18\xf3y\x05\x8b\xa6\x13\x00\x1f\xeb)\x19s\xa6\x84\xf5fl1\x03\xa0a\xdc\xfd\aXA\xc3t\x01\xe0c\x1d\xa5\x13Ζ\xb2΄5&\x00\xd4\xf3X\xcc\xf3\x8fQ?\x1d\x00X\xac\xa5,\xe67e\xacŚ\xfa\x00\xeah\x9a\xf4\xbb&\xea\xa6:\x00\x8b\xb5TL\xfam\x85\xf7u\xc0k\x00\vY\x19\xf7\xfb\x95,\x9c\xca\x00,\x9a\t\xc5M\x11\xa2\xd9\xdb:\xe0-\x80Z\x1eO\x98\xe6qj\xa7.\x80\xd5T&LS\xc9\xea\xa9\n\xa0\x86U\xb6ҭ\xa2fj\x02X\xcd\x02[\xe9\x16xY\a\xbc\x03\xa0\xe2\x96]Ti\x05`\x15ն\xd3V\xdb|X\xd2\b@\x15k\x94ү\xa1jj\x01X\xa9ظ\xd5&xaJ3\x00\xf3iV\xbe\xa6\x99\xf9S\a\xc0J\xeeU\xbe\xe6^o\xea\x80\x17\x00*4\xee?@s\x9cnSZ\x01\xd0\xed\xe4\xc6\xeb8\xa7\x11\x80r\xd6ivo,\xd6Q\x9e\xfe\x00V$1\xd0U\x1fc\xf04\xcd\x00\xccc}\x12\xdd[\x8b\xf5\xccKo\x00\xc9\x0ev7\xb8]\a\xdc\x05P\x9a\xd4\xfd\x87[u\xa04\xa9\x1c\x8c\x02X\xee\xc0\x84\xd7\"\x96\xa7+\x80 \x1b\x1c\xc8\xdf\xc7\x06\x82\xe9\t`9\xf7;\x92\xcf\xfdn\xd6\x01\xf7\x00\x14;r\xffoٸ\x81\xe2\xf4\x03\xb0\x8c\a\x1c\xcb\xeb\x01\x96\xa5\x1b\x80\"Z\x1c\fy\xf0\xd3BQz\x01XJ\xa3\xa3\xf95\xb24]\x00\xf8\x98E-O8\x1c\xf2\xe2\xe7\tj\x99弽\x96\x03\x81\x92>\x02\xe4PH\x90rB\x84(\xa3\x8c\x90\xe3\xa6\x0e\x13\xe6\x02\x17\b\x13\xe6<\x97\xb8\xcau\xfa\x186\x03\xc0\"\x8b\x1c\n\bRF\x88\x10\xe5\x04) \x87,O\xa6\xb5\x84~\xae\xd3\xc5%\xce\x13&\xcc\x05.\xd1\xc5u\xfa\xd1p\xc6.\x00\x8bL\xee!\x9f\xe2Q\x97K($\x97\x80\xf73\xfa\x13p\xf4\xd1\xcdU:Fqt\xf29_p\xd3\x1e\x8e\xc9\x01X\xcc`6y\x143\x8f\x10!*(a.\xb9\xccL\xe9\xf0\xdaan\xd0\xcd\x15:\x88\x10&̧tr\x8d\x1e\x06&\xc3q7\x00\x8b\f\xb2ɣ\x88\xd2\xdb.\x97\xf2%r\xc9Ni\x97\xe3\xe3\x88\xd2\xcde.\xde\xc6q\x91ϸF\x94\xc1;8,\x01?\xf3\xa9c\xfem\x97\x8b\xc8#\xdbTآ\xab\x1a\"\xca5>\xbb\x8d\xe3\x1c\xa79ǐ%\xd9<\xcdVJ\x98a\xda>\x8f5@\a/\xb3ǒ\x1f\xb0\x83\x99\xa6\xad1\xa4\x1b찤ݫI\xa8\x94\xd4\x7f-\xe9#˴\x15\x06\xd5\xef#b\xda\x06\xa3\x8a\xf8\xd8GԴ\x15\xc6\x14e\x9f\x8f\x83\xbchrɊA\xf5\xf1\"\a\xfd;\x06\xf9'\xc3|e\xda5\x837\xf8%\xbf\xe6ƭ7\xc1\x00\xcf\xf1cf\x99\xb6\xc9C\xf5\xf2\v^\xa2\xefΫp\x16\xcf\xf2\x13\xb2M\xdb呢\xfc\x9c\xdd\xf4\xc3\xdd}\x81L\x9e\xe2g\xdcc\xda6\x0f\xf4\x05?e/7o}\xb8\xbb34\x83-\xec$Ǵ}.\xeb:\xdb\xd9\xcf\xc0\xc8DZ\xdd\xe1\f6\xf3\x02sL\xdb\xe8\xa2\xfeǏ\xf8\x1d\x83wN\x8c\x1f\x0f\xc8`\x03\xbf\"ߴ\x9d.\xe9s~\xc8\xef\xefv?ր\x88\x9f\xb5\xfc\x86BӶ\xba\xa0\xab|\x9f?04\xf6d\xac\x11!\x1f\xaby\x89\xb9\xa6\xeduXWx\x8e\xc3\x13\aQc\x0f\x89\xf9hb\x97{\xd3Q\x06\xd4\xc96^\x8d5\x86<٘\xa0\xc5w\xd8=nqc\xfa\xaa\x83g\xf9S\xecQ\xc1x\x83\xa2K\xf9\xad\xdb\x01*\x9e\xe8S\xbeǟ\xed\f\x8aND\xb0\x84=^\xc4깪\bOst\xf2!\xf2D\xf3\x02\xdfd\xaf7!\xab.\xe9\x1cO\xf1\x97x\t\x12O\x8c|\x83}\xdeE\xef;\xacv\x9e\xe4\xad\xf8I\xec\xcc\f}\x9d\xfd^.bqLg\xd9B[\xa2Dv\xa6<\xda\xd8\xcc\x19\xd3\xde(\xeb\f\x9b\x13\xbbo\x7fn\xf0\xab\x1c\xe0˦}Rпi\xe5};\t\xedNz\xbd\xcf&>2\xed\x95m}\xc4&{\xee\xabM\x8f7r\xd0\xe8~/v\xf5/\xbe\xcbI\xbb\x89U\xa6=O\xb2\x91\x13\xa6\xbdK\xa8\x13l\xb4\xef\xbej\x88\xcc)6rܴ\x87qu\x9c\x8d\x9cR\xbaBy\xe7\x9d:yWRU\xefJ\x9d\xaa?z[ \xb5\x99\xf64\xa6\xdat\xb6c\xd2\xdb\x7f\xa9Z\xde6\xed\xed\x04\xbd-\xd5:\xbe\xe8nAU)o\x9a\xf6x\x8cޔJ=O\xf4w\xe1\n\xc9Q\xd3^\x8fꨄ\xbc\xdfK,́\x91\xb1uú\xc9\x01º\x17'\x13\xfe\x14$Ӵ\xef\x00d&\xb3\x9e \x19\x00\xa9\xd3CL\xc2\x12}\x00\x99\n\xcb\xe1\xddV\xb5~]\xd4\a\x90\xeb\xfe\xa2F\xdb*'\xd7{\x00En\xae\xe4QTP\x7f5\x81>\x80$\xa8;\xae$j\xa3>\x80\xaa\x14\x8a.\xcb\xd2\x0f\xf5\xd3\a\xe0\xe9vG\xeeY\xa3\v \x90b\xe1\x95U\x04\xbc\x050'\x85\xda\x00\x80rݨ\x06]\x00\xc5n\xad\xe2\xd2T\x91\xeeT\xae.\x80\x8a\x14\v\xa5\xc9ѝ\xc2\xd3\x05P\x9dbq\x853t\xdfK\xf5\x00X)\xd4\x0f\x18Q\x8d\xde\xea%=\x003S\xac\r\x00\xa8\xd2[\xf5\xa0\a o\x92}aM\xaa\x8c<\xef\x00\x94\xa4`\x10U\xa1^<\x8b\x1e\x80P\x8a\xb5\x01\x009\tv+u\x14@u\n\xae*\xf3\xeb\xb5\x03:\x00|)\xd8\x06\x00\xd4\xe8x\xa3\x03 \xdb\xc6֨&T\xa9\x13\xed\xae\x03 ?Ec\xc7\xe6\xe9\x84\xf8\xea\x00(\xa1\xc0\xb4\xaf1U\xa0\xd3\x0e\xe8\x00\xa8d\xb6cFG\x1d\\\xb25[\xe7\xd1\xd4\x01P\xed\xd0b\xea^ޠ\x89&ޠב\xfc|:\xed\x80\xfa\x1f-\xf9\x1di\x03\xfa\xf9;\xfb\xf8+=\xc0\xdfX̓|́!\xb6\x1a\xfc\xe3\xa3\xc1\x13Jy6-W\x8e'9\x937 \xefI\xb3\xcc\x19\x93\xeb\x1ci\x96\xf7d ɜ\x8fK\xae\xfb\x93\xa3Ur>\t\x13\a头Ja̜\v\xa5UN\xca`\x12\xb9\x9f\x97*\xf7\x01<\"=\x9a\xe6\r\xcb'\xb2M\x82qs\x0f\xca6\xf9D\x865K\xe8\x91G\xdc\aТi^\xbbl\x97\n\xb1\x12\xe6oI\x85l\x97vM\xc4-\xaa\xfe\xa8\xff\b\xea\f<\\\xe0\x8f\x1c\xa2\xdd֦7B\x84\x17x\x95\x16\xd6(w\xbau\x06j\x14\x89e\xc8k\x8aw\xa5SvK\x83\xf8\x94k\x9aO\x1ad\xb7t*\x96\xf6\x9ad\xb8\xfb\b\xe4\xcb\a\n\xe6t\xc9+\xf2\x90\xaaIcp?$\xafH\x97B\x89\x1fH\xbe\xbb\x00j\xe5\xa2MS\xba\xe5\xb0<,\x99\xdaΏ\x1c\x99\xf2\xb0\x1c\x96n\x9b\xa5^\x94Zw\x01<*\xbd6̈\xca\x11Y,\x81\xa4\x9d\x1f9\x02\xb2X\x8eH\xd4Fɽ\xf2\xa8\xbb\x00Z\x13\x9a\xd0'\xc7d\x99d;\xe6\xfcȑ-\xcb\xe4\x98\xf4%,\xbfU-_\xd5V \xfe\xaf\xec\x00\xff`?G\xe9V\xfe-N\xac(Ghc\t[\x12\xecu\xa0\xda\x0e(>\x8f\xafOJ~PN\xc8&)p\xfcΏ?\nd\x93\x9c\x88\xf3\xbe\xf8\xba\xda\xef\x8ej\xe1\x1f\xc6,tH>\x96g\xa4\xd8u\xe7G\x8ebyF>\x96\xa1\x98\xb6|\xa8v\x13T\v>=\xa1\xc0a9+\xcfK\xb9\x8dw<'\x0fK\xca\xe5y9\x1b\xe3\xad\xf4\xb4ڍP+60\xe1\x11\x88\xc8NY\xe0\xb1\xf3w ,\x90\x9d\x12\x99\xf0\b(\xb5>\xaa\x85.\x91ˣEu\xc8.\xa9\xd7x\xc7s\xf2\xf0I\xbd쒎Q\x9b.\xcb\x12\xb5\x1cT\v\xf4˷\xe4\x98D\xe4?\xb2W\x1a\x93x\xc7s\xf2ȐF\xd9#g$\"oɷův\xb5Ζ\x9a\xb3)\xa4\x9f+c7\"0\xac\f\nɢ\x8b\x1e\xd5\v\x9d\xd8T5\xad\x95\xae{E:\xa6\xff\x03\xdayd\xff\xa2\xe3\x82\x1a\x00\x00\x00%tEXtdate:create\x002023-01-13T01:53:51+00:00\xe5\t\xffO\x00\x00\x00%tEXtdate:modify\x002023-01-13T01:50:45+00:00G\x86\xd8}\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\x9b\xee<\x1a\x00\x00\x00\x00IEND\xaeB`\x82"), -} -var ResStarOutlinePng = &fyne.StaticResource{ - StaticName: "star-outline.png", - StaticContent: []byte( - "\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x80\x00\x00\x00\x80\b\x04\x00\x00\x00i7\xa9@\x00\x00\x00\x04gAMA\x00\x00\xb1\x8f\v\xfca\x05\x00\x00\x00 cHRM\x00\x00z&\x00\x00\x80\x84\x00\x00\xfa\x00\x00\x00\x80\xe8\x00\x00u0\x00\x00\xea`\x00\x00:\x98\x00\x00\x17p\x9c\xbaQ<\x00\x00\x00\x02bKGD\x00\xff\x87\x8f̿\x00\x00\x00\tpHYs\x00\x00\r\xd7\x00\x00\r\xd7\x01B(\x9bx\x00\x00\x00\atIME\a\xe7\x01\r\x013\x05\x8d\\\x8b\x7f\x00\x00\v\x91IDATx\xda\xed\x9d{l\x15\xc5\x1e\xc7?\xe7\xb4\xf4A\xa1\x05\xdaڞ>\x0f\xaf\x82 Q!ĨQc\xa2&\x82\xf7J\x8coˍF\x14ԀB\x04\x91\xab\xa6A\xaf$נ\x90p\x13\xf1\x91\xab1\x82&j\x94\\\t\xe4bL\xf0-\"\b\xdaK[۞R\xda\xd2\xd2R\n\x14\xfa<{\xff\xe0\xf41\xb3{\xf6\x9c\xddsv\xb6E\xbf\xfbO\xf7\xec\xec\xcc\xef\xf7\x9d\xd9\xf9\xfdf\xe67S\xf8\x13\x7flx\\-=\x85l\xe0\x04]n\xd3\xe0\x0e沝j\xaa\xd9\xce\\\xb7Eq\x0339\x88\x16\xba\x0e2\xd3mqT#\x81̓\xeakhl&\xc1m\x91\xd4\xe2J\x1a\x04\x02\x1a\xb8\xd2m\x91T\xc2\xcb&A}\r\x8dMx\xdd\x16K\x1d.瘎\x80c\\\xee\xb6X\xaa\xe0e\xa3N}\r\x8d\x8d\x7f\x9460\x87\xa3\x86\x04\x1ce\x8eza\xd4s\xeea1\x85\x86O\nY\xec\xb2c\xa6\x04\xb3\t\x18ֿ\x86F\x80٪\xc5Q\xdd\x02<,\xa68\xec\xd3⋿\r\\Jm\xd8\xfa\xd7Ш\xe5R\xb5\x02\xa9m\x01\x1eJ\xf1\v\xbf\x1c\xe0\x80p\xef\xa7\xf4bn\x033\xa9\x16껇\xc5,\xa6G\xf8\xad\xfab\x1e\x17\xac\x97\x1a\xfc\xd7d\x92\xc9\xd7ү\xeb\xdd\x16\xd3)\x94P%(\xda\xcb\xc3\x00T\xb2\x9d\xe7\x04\x9a\xa70\x05\x00\x8dnN\xd3F\x13G\t\x10\xa0\x9e&\xda8M7\x9a\x1dU\xa2M\x97\xc4x&\x91;\xa8r\x1eYd\x90\x12E\x0e5,\xe4\x88\r\x9ag\xf2yHe3ht\xd1A+\x8d\x83t\x1c\xe7$g艎\x8e\xf0-\xc0\xc3\x18\xc61\x91\\\n\xf0㧘<\xb2\xc9 \xd5\xf2w\xf8\x01\x156ԇ\n>\x88\xc2w\xf0\x90J*\xb9\\\x06@\x90\xf3tp\x82F\xea\b\x10\xe0\x18\xc7i\xe7,\xbd\xe1\xe8\xf0\b\x7f'\x92\xc6Dr\xc8\x0f\xa9\x9c\xcf%d\x90\x16S\xd7S\xcbm\x94\xdb|w\x16\xff\x89ij4H'\x1d\xb4\xd0\x10\xa2\xa3\x81f\xda\xe9\xa4o\x88\x0e\x0f\x90\xc0df19\xa4r\x0e\x13I\x8b[\xd8b/\xcf\xf3O;\xdffH\xba5\xbcȘ8\xc9\xd2O'\xed4\x87訥\x9cZ\xfa!\x8dg\bHK\xd4\xf1\xb8\x82tR\xc1Z\xc6\xc5$\xf48\xd6RA\xa74D\x8e\xc7\xd5C\x80gH\xf3\xb0\x862R\xe3°F\x17\xa7i\x1d읏RC#\xfd1\xe6\x9a@\x1eSB\x06\xb6\b\x1fY\xa4G\xd5\xf5F\x83\xf3\x94y\xf8=\x86E(\x8d\x9e\x90A\x1a\xb2\xcf\x17\fR\xcc\xf6\xd9\x00\xdeA\xe3;\xe0o\\0\xbeI1\xd0Q㡋dK\xaf\xf4p\x86\x934q\x8c\x00\x01\xeah\xa2\x95\x8ex\xb8$\x96\xe9H!\x83,|\x14\xe3\xc7O\x01>&1\x9e$K\xb9t{\xa8\x88\x18\x8e\xd0\xcbYN\xd2L=u\x04\xa8\xa3\x91\x13tp>\xe6\xc6\x1dO$\x90J\x06\xd9\xe4Q\x8c\x9fb\n\xc9a\x12\xe3\"v\xa0\x95\x1e\x9e\xe4\x1f\xa4\xe9\x1e\xf4q\x8c\xe3\xa1\xfe\xb2\x8e\x06Z8\xc59\x82\xb6\xfbs\x95\xf0\xe0e,\x13\xb8\x84\xfc\x10\x1d\xf9\xe4R`\xe0\xf3t\xf2wH\xa5\x8c\xf3\xba>\xf2\x1c\xabH'\xf1\"\x88\xd8\xf2\x90H:\xab8\xa7\xd3\xf1\xfc@\xf7\x9f\xca\v\x06\x8f\x1b\xb9\xe7\"\t^\xf6r\x0f\x8d\x06U\xfc\u0090\xf5Ka\x1d\x9d\xba$͔^\x04\x14x)\xa5Y\xa7['\xebH\x19\x9e,\x995\x9c\xd5%;\xc1\x83\xa3|+K\x02\x0frB\xa7\xd7Y\xd6\xe8m_\x12+9\xadK\xda\xc6\x12\x9bC摀D\x96Ц\xd3\xe94+\x8d\x8d\xe5\x18\x96ӡK\xdeβQJA\"\xcbh\xd7\xe9\xd3\xc1\xf2\xf0\xc61\x91\xc7\f^9e\xf6ʈ\xc5\x18\x96sʠ:\x1f3\xafN\x8b\x8df\xc4\"\x86\x0f:\\\xb7\xb1ڢ\xcb\xec&\x92Y\x1dK\x97\ue954\x96Ȇc\xc4\xc2ب\xb7X1\xea^\xee\xa5\xc9\xdcu\x18\xb10v뚸ךO\xe3\xe1Nio\xe7\x05\xe7q=c\xdd\xd6\xd0\x14cYo\xe0\xd87p\xa7u\xa7\xde\xc3\"\xeauYu\xb3\xc1`\xe84R\x90\xc6\x06\xbau2׳\xc8ޘ\xc6\xc3m\x06;\xbc\xbay\x85\xf1nkj\x88\xf1\xbcb\xa0~\x80\xdbb\x19\xd2\xddJ\x8d\xc1l\xda&\xd2\xdd\xd6V\x87t6\x19\xccl\xd6pk\xac\x19\xdf,\xed\xf3\xd0\xd0\xe8e\v\x13\xdc\xd6X\xc0\x04\xb6H{\x0e44\xaa\xb89\x1e\x99\xdfH\x85.\xeb>^g\xa2\xdbZ\x0fb\"\xafK\xab\xd1\x1a\x1a\x15\xdc\x18\xaf\x02\xae\x97\x82V44\xfay\x9bL\xb75\a \x93\xb7\xa5`\x1c\r\x8dr\xae\x8fg!\xd7H\x01\xaf\x17(x7\xb4\xeb\xc7Md\xf3\xae\x81\xfa\x87\xb9&\xde\x05]%\x85\xb1jh\x04y\xdf\xe5ȍ\x1c\xde7X49\xc8UN\x146O\x17\xf6\xa6\x11\xe4C\x17\x83\xd8||h\xa0\xfe~\xe69U\xe0\x15\xfc\xa8+N\xe3c\x97b9\xf3\xf9\xd8@\x9a\x1f\xb9\xc2\xc9B\xe7\xe8\xf6\xf8ih|\xe6BX{\x11\x9f\x19H\xf2\xad\xf3\x870\xcc\xe2+\x83\x82?\x976E;\r?\x9f\x1bH\xf1\x15\xb3T\x14>\x93/\r\n\xff\x84I\xcaԟ\xc4'\x06\x12|\xa9n\xdb\xf5t\xf6\x18x\x87\x8b\x95\x11\xf07\x03\xafo\x0fӕ\x95\x0fLa\x97N\x84M\xcaJןB\xb4+\x8ah\"C\xd8]\xf8\xa8a\x19;\xa5\xdfz\x95\x11 \x97\xb4\x93eԨ%\x00\x02l\xa5G\xf8\xa5Z\x19\x01bI=l%`7\xabX\x96\xbe|\xc2Lq\x97R\x02\x86\x9f@\x98\x14\x8b+\x16\v\x01b\\A;\xf5\xca\b\xa8\xa7\xddD\x12E\x04$I\xbdn\x13-\xca\bh\xa1I\xb8\x9fn\x7f\xd5\xc2>\x01\x19\x92\xf7\x17\xe0\x8c2\x02\xceH\xdf|\x11\x19\xea\tȑ\xbe\xbc*\xa5V\xa0J\xb8\xf7\xd9\x1f\x93\xda'@f\xddz8t,\x10K˰?\x16\xb1O\xc0Ta\xa9\xacӮ\x1d\xb6\x89\x1a:\x87\xdd%\xdb\x0f\xf5\xb3O\xc0\f\xe1\xee\xa4B\x1b\x00P\xcfI\x13i\x14\x10\x90\"q\xdeH\x9bR\x02\xdah\x14\xee\xa7\xda]\xb7\xb4K\xc0\x04髫Uh\x03\x00\xceP+\xdc\x17ٝ\xa6\xb7K@\xae\xd4\xefV*\x0e\x9b엺\xc1\x1c\xbb;\v\xed\x12P,\xac\r\x05%\xb3\xa4\x02UBpn\xba\xdd\xe36\xec\x120]\b\x9aQm\x03@\xb6\x03c\xec\xce\x06\xd8#\xc0#y߭4('\xa0\x81V\xe1\xbe\xc4\xde\x12\xa8=\x02R%\x1b\xd0 \x19%\x158)\x91>\xd5^\xf0\x86=\x02&J\xe7\xc2V\v\xcd1z$\x91G\x9éL\xa74\xfc.\xb4\xb7Vi/\x020\x8f,\xe1\xbe\xca\xc6n\x81d\xae\xe3!\xe6\x03\xfb\xf87_\xd1m\xf1}\xb9\xe3\xcd\"O݇x\x97\xb0\x16\xdb\xc7\xdd\x16\xdf\x1fõ\xbc7,\x8a\xef\x14\xefq\xad\xe5XĻ%)\xeeR\xa5\xbe\xbc\xbf\xbf\x9d\xf9\x16\xdeM`.[\r\x02\xf1N\xb0\x95\xb9\x96\"\x93\xe7KA\x9dʎ_\xf3\xf2\x8ePpe\xd4G\xddx\x99ͫ\x06\xa1\xeb\x03W#\xaf2;\xea~\xa9@\xdaa\xfe\x8e\xaa\xd8\xf6\xf1\xec\x15\n\xfe\"\xaa\xc81\x0f\xd3x\xd1\xe4T\xe9\x81+\xc0\x8bL\x8bʤ\x8d\xe5\v\xe1ͽ\xaab\x97\x8a\xa5\xb8\xa17\"\x8a롐g\xa9\x8cr\xf7_\x90J\x9e\xa50\x8a\\\xdf\x10ޫQu\xf8\xda\xd5RL\xf9\xd3\x11\xd2粂C\x06A\ffW?\x87X\x11ѿ\x7fZx\xa7\x83\xab\xd5\x10\xf0\x80\xa0L\x0f\x8bL\xd2f\xb2\x84}\x06\xf1;\x03\xd7Y\x83\x98ޡ~}\x1fKL\xc3p\x16\tqa\xfd<\xa0\x86\x00\xf1l\x87ְ\xff\x1d$\x83\xfb\xd8k\xb2)\xb7\x93\x1d,`\x01;\f\"{\x87\xe8\xdd\xcb}a\xa7<\xaf\xa4UH]\xa6B\xfd\x04\xb6\t\x85\x96\x1b6\xd44ng7]a\x15\xebb\x0fw\x84\xf6\x15\x8f\xe3\x0e\xf6\x98\xa6\xdd\xcd\xed\x86\xf1\xa9\xb9R\xf0\xd66\x15\x1b|2\xf8N(t\x97n.&\x85[\xf8ԤV{\xf9\x86Ri\x02c\x02\xa5|c\xb0\xe6;\xd4Z>\xe5\x16\x83\x92\xc4%\xda\xef\xecO\x8fG\x8f\xa9\xd2?\xc8\xd8\"\xfb\xde{ι\xbd\xe7x\xcbڿ\xf6\xb9{\xaf\xbdַ\xf7\xd9k?\xd6:W#\xaenr9-\x80\xd3\x14\xe9h뱘\n\xe0\x7f{\x10\xb3\xc6;\x00\x1a\x96\"\xd7篹\xf6\x8f\x01\xbb\x01\x98\x89\xc5~\x7f_\x8c\x99\xe3\x19\x00\r%p\vO\x8e⨐w\xa3\xc4\xe61`\xf3\xfa\xff4GS\x1f\x97r)\xfb\x84g\xa7\xed\xdd\x17\xd8;\x02\x1e\xc1t!\x7f\b\xfb\xb1\x1f\x87\x84g\xd3\xf1\x88\x9d\"\xd9\t@!\x1e\x16\xf2\x03(C+ZQ\x86\x01\xe1\xf9\xc3(\x1c\x9f\x00<\x82|!\x7f\x18{\x01\x00{qXx\x9eo\xe7\x18\xb0\x0f\x00Y\xad\x01\x94\xe1<\x00\xe0\xbcn\f\xc8P\x8d\v\x00\x1eF\x81\x90?\xe2\xed\x7f\x00؋#\xc2o\x05\xd2\xcb2\x0e\x00\xc8\xc3\x12!?\x88mh\x1e\xc95c\x1b\x06\x85ߗ o|\x01\xb0\x183\x84\xfcQ\xec\x11\xf2{\xa4\xf5\xc0\x8c\x00\v\xa60\x03`\x1aJ\x84\xfc\x10\xca\xd0$K\x9fŻ\xd2\x18X\x8e\xac\xf0\x06@>\xec\xfe\t\xbb\xfc\x96߅\x9f\x84|Q\xa8\xc7@h\x01\xc8\xd4\xf5\xff{\xa8\xf5[\xa3\x16\xef\xe9\xc6@&BH\xa1\x05`\xa1t\xe1U\x8e\x8f\x03\xd6\xf9\x18\xe5B\xfez,\fW\x00ұB\xe2\xff>j\x02֪\xc1\xfb\x92\x84+\x90\x1eB)Cx\xdc\xf4\x18\a\x85îrN3To\x1a˅z\x83|,\x1c\x8f\xc4\xd2\x14\xfd\x7f\xc6P\xcd3\x8a1\x90\x16~#\xa0\x94\x03B?\x9ed\x9e\xe1\xbay<)\xd4\x1d`i\xb8\x8d\x80T\xac\x94\\\x1ev\xe0\xb4\xe1ڧ\xb1C\xc8G`%R\xc3k\x04\xac\x96\xfa\xbf\x92\xf9\xa6\xea\xe7\xb3R\x1a\x03\xab\xc3e\x04\xb8\x10\x8f\x19xT\xea\xff\x9d8e\x8a\xcb)\xec\x94\xc6\xc0\xa3\x98\x81\xf8\xb1\x97W\x1b\x03GI\x17b\x91\x88\x14\xa4#\an\xb8\x91\x8dl\xb8\x05QOc\x1e*Mr-\xc4~\xe1Xl\b\x1eԡ\x0e\x1exP\x8bF\xb4\xa0\x03=\xd2\x06\xda\x02Y\xf3\x13\xd4\x10\x83D$#\x1d\xd9pÍ\x1c\xa4#\x19\x89\x88\xf1q\xad\xf5\x81i\xf5\x81J|\x80\xe7\x04\x98\xa7{\xafU\x88^t\xa0\x15\x8d\xa8\x85\a\x1eԡ\x11\xad\xe8@/,\xf4\xa6\xd1\x11\xa0!\x1a\x131\x05i#*g \x05I\x885p\x93W\x8d\xf98i\x01\xe6\x99\xd8'\xdd$\xa9\x88\xe8A;Z\xd00\x02G\x13.\xa0\x13}\xc6\xe0\xf0=\x024Da\x02&#\rYpÍ\\d`*\x92\x10g\xfa=\xfc\x10\x15\x16\xd4\a*\xf0!6\x06,\xa5!\x0eqH\xc3u\x00\x80!\\B;Σ\x015\xf0\xc0\x83\xb3hB\x1b\xba\xd0\xef\v\x8e\xd1#@C$\x120\x19\xa9\xc8\xf4\xaa\x9c\x89k\x90\x84\x84\xa0\xa6\x9e3\xb8WZ\xdd\x1b\xa7Y\xf8wPG\xa3C\xe8F;Ρ\xde\vG=\x9aцn\f\\\x81C#\x10\x81i\x98\x85i^\x95S1\x19\tc\xe6\xb6؏\xbf\xe0oV\xde\xcd\xcb\xd2a\x03^D\xd4\x18\xc92\x88n\xb4\xa1\xd9\v\xc7\x19\x94\xe3\f\x06\xc1\x04>M\x8ftE=\x164\xc4nV\xf0\x19N\b\xcaNO\xe03\xac`7\x87\xc6\\\xbe>z\xf84\x134n\xc0&č\t\xc2D\x0f:\xd022;ע\x1a\rҍ\x8fy\x8a@\x06\xa6{\rl\x0eґ\x82DCS\xaf\x11\xba\x84M\x1aO\x05q\tE\xf4y\r\xd2\x15\xfb|\xd9 \x05m\x9f\x15\xe4\x1a1\xbe\xc3\xeb\x8d\xcb\xc67:\b8\xaa5\xf6 \xc6T\x95>t\xe2\x02\x1aq\x16\x1exP\x83F\xb4\xa0},\x96$\xa6\xe1\x88E\x12R\x90\x8e\\\xb8\xe1F\x16\xd21\x05\x13\x11m\x8aK\xafƊ\x80\xee\b\xfd\xe8\xc2\x054\xa3\x0e5\xf0\xa0\x06\r8\x8fv\\\nzp\x8f%E \x0eI\x98\x8a\f\xe4\u008d\\d#\x15S0!\xe0\x04Z\xa9\xf1O\xf8+\x12t?\f\xe0,\x9a\xbc\xf3e\r\xeaq\x0e\xbf\xe0\"\x86,\xcf\xe7v\x92\x06\x17\xe21\t\xd7 \xd3\vG&Ґ\xa5X\xf3t\xe3\xcf`\x1c7\xf1\x92n\x8e\xbc\xc8'\x98\xc8Hj!;/\xb0+i\x8cd\"\x9f\xe0E\x9d\x8e\x97\xb8\x89q \x18\xc7\xe7\x15?7p1]\x8e\x8b?\x16\xc9\xc5\xc5lPt\xf1\xf3\x8c\xa3\xf7< \x96\x1b٭+\xd2̒q\x00\x81\x8b%l\xd6\xe9\xd6͍\x8c%8r \x12\xc3\r\xec\xd2\x15;\xcf\x15\x8cp\\\x85`R\x04W\xf0\xbcN\xaf.n`\xcc\xe5\x12W\x8aFs=;tE[\xb9\x8a\x91\x8e\xaba5Er\x15[u:up=\xa3\x87ˌ.\x1e\xc5\xc7ٮ+\xde\xc6\xd20\x85 \x92\xa5l\xd3\xe9\xd3\xce\xc7\x19u\xa5\x94\\e\xad\xa2\xca/b\x950IQ|\x9c\xbf(\xbas\xad\u061d\x16\x06MX$\xc3/\xb4\xbe\xaa\xafi\xe3\xa9\xe1i#\fR\f\x9f2:\xa5\xab\xaa\xbbX\xc2s\xbe\rǯ>\xa9\x8d\xfa9\xb5QW\xb3p\xf1!6\xeaXx\x97\x0e\xbf\xf2\xa4^\xd65\xf2!\xf5\x9a\xc6\x17\x1b\x8d\x8bX\xafcs\x89/0\xdeq\x15\xfd\xa5x\xbe\xa0X\xd8\xd7s\x91\xafE\xbdoV\x1a\x17\xb0NǪ\x97\x9b\x99บ\xbeR\x027\xb3W's\x1d\x17\xf8\xde\xd3\xf8c\xa7\xf1^z\x14\x10\xbc\u0089\x8e\xab\xaaJ\x13\xf9\x8aB}\x0f\xef\xf5\xb7\xa5\v\xc4\xf4\x1eV\xebX\xf6\xf1u&:\xae\xae\x9c\x12\xf9\xba\xe2d\xb3\x9a\xf7\xf8\xaf\x17\x98\xf1]\xacұ\xed\xe7\x16Nr\\\xe5\xd1i\x12\xb7\xb0_'g\x15\xef\nT\xd3\b\xf3;X\xa1c=\xc0\xad\x9c\xec\xb8\xda\xc3i2\xb7J\xb7\xd1$Y\xc1;\x02\xd75\xd6\xc0m\x92\xd3\nI\x0e\xf2\x1d&;\xae:\b&\xf3\x1d\xc9\x19\x87$\xcby\x9b\x91\xdaF\x1b\xb9\x85'\x14\x10l\xe7T\xc7՟\xca\xed\n\xf5O\xf0\x16c\xf5\x8d7tӨo~\f\xd3\x10w0\xd5Q\xf5S\xb9Cqi\xf2#o2\xca\xc1Lc\xc5<\xa2\x80\xe0#\xa6;\xa6~:?R\xa8\x7f\x84\xc5\xc6y\x98k\xf0\x06\x1e\xa2\x9ev3\xd3\x11\xf53\xb9[!\xcd!\xde`\x86\x8b\xd9F\x8b\xf8\x9d\xa2\xd1Ϙc\xbb\xfa9\xfcL!\xc9w,2\xc7\xc7|ó\xf8\x8d\xa2\xe1}t۪\xbe\x9b\xfb\x14R|\xc3Yf9Yi|&\xbfT4\xfe\t\xa7ئ\xfe\x14~\xa2\x90\xe0K+a\xd7\xd6\x04(\xe0A]\xf3\xfd\\j\x1b\x00\xcb\x14\xab\xbe\x83,\xb0\xc2˚\xefG\x15V\xe3\x80\xf4,\x12Ŷ]~ݨ\xbb\xe6:\x80ը\xb2\xc2ʪ\xf3K5J\xb1_z\xd6o\x1b\x00rK\xfbQ\x8ajk\xac\xac{\xffx\xf0\x06\xfa\x84'\xc6]a\x83%\xb1\xa5>\xbc\x01\x8fUV\xc1\xb8?\xa5\vw\xf1=\xb6\x020\xfa\v\x84\xd1\xc1\xc4\x13\x04\x03\x80\xe8WІ:\xdb\x00\xa8C\x9b\x1fIl\x02 Z\n\x87o\xc49\xdb\x008'E\x9e\x16\x98\xf4\v\x19\x13\x00\x92\xa4\xa0F\x0f:m\x03\xa0Sz\xe7s\x90d?\x00\xa9қWe\xab\x15\x10M^\xba\xf5h\x02\xeb\x00Ȩ\x9bw\x87\x0e\x86\xc4֒\xac\x87\xd8Z\a O\xf0.\xeb\xb6j\x87-R5\xbaG\xe5b\xac\xbb\xfaY\a@\xfc\"\xc4\x05\x1bm\x00\x00\xd4\xe1\x82\x1fil\x00 V¼\x01\xad\xb6\x02\xd0*E\xa0\xe7!\xd6^\x00&Io\xdd\x19\x1bm\x00\x00tJ!x9\x98d/\x00iҼ[i\xb3\xdb\xe4\xa04\r\xa6Z\x8d,\xb4\n@.\x12G冬\xedĂ\xa2*\xc197\xd1\xea\xe76\xac\x02P 8\xa1\xdam\x03\x00\xd9\x0eDI\xeb\xd2\x10\x03\xa0I\xab\xef\x16\xd4\xdb\x0e@=Z\x84|\xa15\x9fqk\x00\xc4I6\xa0^2Jv\xd0\x05\t\xf4\xfe\x85\xad8dr;U\x85\xc1Q\xc1]\x89pK\xdf%\f!\x00\x05BTY\xa7\xc1\xef\x02\\\xa6\b\xcc\xc1\x1a,\x14\xc6P\x12J\xf0\a|\x827p\xcc\xc4z\xe2\f:G-\x7f\",\xda\x01\vG\xc9.n\x13\x0e\xa4+\x99e\xb8\xe6l\xbe\xaap]\x1f\xa6\x06\xbe\xcaن=Գ\xa4\b\xf3mV|ۭ\x000\x91_\v\r\x7fa\xc8sLc>_T\xf8\x1c\xc9\xe4\xe1\x8b\xcc7\x14\xa8\x11\xcf/\x84\x9a_[\xf1]\xb2\x02@\xae\xe47\xf4f@q5f\xf3YV\x1a\x8c\xfe\x1bb%\x9fe\xb6\x01\xaeo\n\xf5\xaa\x99k\x0f\x007K>\xe5O\x06(\x9f\xc6u<\xaepb\xf0G\x83<\xceuL\v\xc0\xf9I\xa1N;o\xb6\a\x80%\x822}\\\xe0\xa7l2W\xf1\xb0\xc2\x7fg\x98\xba\x14>\xbd\xc34\xc0\xc3\\\xe5\xd7\rg\x81\xe0\x176\xc8%浱b\x05\n\x84\xe5S\x87\xcfo\x03%a\x1e\xd6\xe2w>C\xd7.\xe2\vl\x05P\x8a\xdf#^i/~\x839X\x86\x7fb?ڕ\x1cjЁ䑜˒\x1d0\x8dY\x04w\n\xfdT\xae\x1c\xa8\t\xbc\x9f\a\xd8\xe3\xb3w{x\x90\v\xbdq\xc5\x13\xb8\x90\a\xfd\x96=\xc0\xfb\x95\xfe\xa9i\x92\xf3\xd6N\xf3\x01>\xe6\x01H\xe2\xf7B\xa3\x9f\xeb|\xc8cy7\xf7(\xfc\xb5\x87\xa9\x9f߲D\xf23\x9c\xc4\x12~\xab\xb8\xf3\x1d\xa6n\xee\xe1݊\x96>\x17J}Ϥ\xd0\x03\x90\xc7Z\xa1\xd1-¯Ѽ\x95;\x15\x817W\xde\xeb#\\\xc3\x14%\xe7\x14\xae\xe1\x11?\xf3E;w\xf2V)pc\x8bP\xa2\xd6ķ\x8a,\x03p\xbb4m\xad\x1b\xf9%\x92s\xf9\x96\"\xded\x98\x86\xf83\xd7\ap\xa9J\xe7z\xfe\xec\xc7\\\xb6\xf2-\xce\x1d\x15\xf5\xb1N\xf8\xb5\x8b\xb7\x87\x1e\x80\x95\x82x\xbd\x9cG\x10t\xb1\x88\x7fg\x13}\xd3)>\xc7\\\x03\v\x1c\x8d\xb9|\x8e\xa7\xfcpj\xe2\xdfY\xe4]\xf5\xcd\x13ܣ\x87\xb82\xf4\x00l\x16\x84i\xe6u\x04\v\xf9\xb2\xf4b\x88T\xcb\xcd,4\xb1Pu\xb1\x90\x9b\x03p|\x99\x85\x04\xaf\x93\x82\"7\x87\x1a\x80H\xee\x12\x1a<\xc6bn4\xdc_f\x92\x911\xb5\x91\xc5<&<\xdbe6\xc4ϬXS\xf8\x834\x02N\xfaY\xe3\xb5\xf2m\xe1\x8d5\x0f\xf7\\\xbe\xedgV\x19\xe4Ii\x04\xfc`\xd6UˬH3\x14\x814jR\xcd\xd9VR \xbb\"R=g\x84\x16\x80;\x15\x01IzR[m\xeb)\xd0\xca\xe2\n]䝡\x05`M@\x11zx\x80\vB\x10W\x94\xc0\x05~זô\xc6\x1c_\xb3{\x01\xff\xce(\xfd\xf8/\xb6b\x9f\x8f\x95{pԍ=\xf8\x12\xf3Q\xeagw\x11X\xc2 \xf7\x02\xd1\xdc\xeb\x13\xf9\xc0{\xb7\xb1I\x81\xf6\x97{\xcd\xcd;f\x1b?\xaalt\x90'\f\xec\xde\xc7.\xa5q\x1dO\xf8\xb0>G\xcdu\x82ن\xf5\xa13\x97\xcforl\xfeڈ\xc6\x1c\x1fgL\xe5\xe6:\xc2\\\xb3\xb1\xbaW\xa0\x86/\x19<\xc1\v\x05\b\xf9|\x895\xbaW\xc0\x94\xf51\xdb\xe8\xfcQa\xd5\r|\xcd\xc4\x19nh\x92\x8b\xb3\xf9ڨs\xe6s\x9co\x8e\x83\xd9\x06#\xf8G\xfe\x875\xfc\x1f\xb7\xb0\xf8W\xf2e\x89H\x16\xf3\x1f<\xc9\x1a\x1e\xe0}f\x8fD\xac|St\x02\x92ы\x16\xe9ϑ\x9c\xa5HLE\fZ\xd0e\xb6\xe2X|T5\xac\xe9\xaa\xff\xf7\xf9\xff\x03L\x81\x90\xb0\xd65\xa1>\x00\x00\x00%tEXtdate:create\x002023-01-13T01:53:58+00:00p\x91\xba\x9c\x00\x00\x00%tEXtdate:modify\x002023-01-13T01:51:05+00:00,\x0e\xbd\xb9\x00\x00\x00\x19tEXtSoftware\x00www.inkscape.org\x9b\xee<\x1a\x00\x00\x00\x00IEND\xaeB`\x82"), -} var ResTheatermasksPng = &fyne.StaticResource{ StaticName: "theatermasks.png", StaticContent: []byte( @@ -140,6 +120,16 @@ var ResShuffleInvertSvg = &fyne.StaticResource{ StaticContent: []byte( "\n\n\n\r\n\r\n\r \n\r"), } +var ResStarOutlineSvg = &fyne.StaticResource{ + StaticName: "star-outline.svg", + StaticContent: []byte( + "\r\n\r\n\r\n"), +} +var ResStarFilledSvg = &fyne.StaticResource{ + StaticName: "star-filled.svg", + StaticContent: []byte( + "\r\n\r\n\r\n"), +} var ResLICENSE = &fyne.StaticResource{ StaticName: "LICENSE", StaticContent: []byte( diff --git a/res/bundled_gen.sh b/res/bundled_gen.sh index 18fbba2..f21f5fe 100755 --- a/res/bundled_gen.sh +++ b/res/bundled_gen.sh @@ -19,14 +19,12 @@ fyne bundle -append -prefix Res icons/freepik/playlist.png >> bundled.go fyne bundle -append -prefix Res icons/freepik/playlist-invert.png >> bundled.go fyne bundle -append -prefix Res icons/freepik/podcast.png >> bundled.go fyne bundle -append -prefix Res icons/freepik/podcast-invert.png >> bundled.go -fyne bundle -append -prefix Res icons/freepik/star-filled.png >> bundled.go -fyne bundle -append -prefix Res icons/freepik/star-filled-invert.png >> bundled.go -fyne bundle -append -prefix Res icons/freepik/star-outline.png >> bundled.go -fyne bundle -append -prefix Res icons/freepik/star-outline-invert.png >> bundled.go fyne bundle -append -prefix Res icons/freepik/theatermasks.png >> bundled.go fyne bundle -append -prefix Res icons/freepik/theatermasks-invert.png >> bundled.go fyne bundle -append -prefix Res icons/publicdomain/shuffle.svg >> bundled.go fyne bundle -append -prefix Res icons/publicdomain/shuffle-invert.svg >> bundled.go +fyne bundle -append -prefix Res icons/publicdomain/star-outline.svg >> bundled.go +fyne bundle -append -prefix Res icons/publicdomain/star-filled.svg >> bundled.go fyne bundle -append -prefix Res ../LICENSE >> bundled.go fyne bundle -append -prefix Res licenses/BSDLICENSE >> bundled.go diff --git a/res/icons/freepik/star-filled-invert.png b/res/icons/freepik/star-filled-invert.png deleted file mode 100644 index 825e2e1..0000000 Binary files a/res/icons/freepik/star-filled-invert.png and /dev/null differ diff --git a/res/icons/freepik/star-filled.png b/res/icons/freepik/star-filled.png deleted file mode 100644 index 140d612..0000000 Binary files a/res/icons/freepik/star-filled.png and /dev/null differ diff --git a/res/icons/freepik/star-outline-invert.png b/res/icons/freepik/star-outline-invert.png deleted file mode 100644 index 63bab7b..0000000 Binary files a/res/icons/freepik/star-outline-invert.png and /dev/null differ diff --git a/res/icons/freepik/star-outline.png b/res/icons/freepik/star-outline.png deleted file mode 100644 index 7506f69..0000000 Binary files a/res/icons/freepik/star-outline.png and /dev/null differ diff --git a/res/icons/publicdomain/star-filled.svg b/res/icons/publicdomain/star-filled.svg new file mode 100644 index 0000000..9dd73d2 --- /dev/null +++ b/res/icons/publicdomain/star-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/res/icons/publicdomain/star-outline.svg b/res/icons/publicdomain/star-outline.svg new file mode 100644 index 0000000..3de6c5f --- /dev/null +++ b/res/icons/publicdomain/star-outline.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/ui/controller/controller.go b/ui/controller/controller.go index 4caf8fa..f191a1d 100644 --- a/ui/controller/controller.go +++ b/ui/controller/controller.go @@ -3,12 +3,14 @@ package controller import ( "image" "log" + "math" "strconv" "supersonic/backend" "supersonic/player" "supersonic/ui/dialogs" "supersonic/ui/util" "supersonic/ui/widgets" + "sync" "time" "fyne.io/fyne/v2" @@ -111,6 +113,7 @@ func (m *Controller) ConnectTracklistActions(tracklist *widgets.Tracklist) { m.App.PlaybackManager.OnTrackFavoriteStatusChanged(id, fav) } } + tracklist.OnSetRating = m.setTrackRatings tracklist.OnShowAlbumPage = func(albumID string) { m.NavigateTo(AlbumRoute(albumID)) } @@ -386,3 +389,43 @@ func (c *Controller) doModalClosed() { c.runOnModalClosed = nil } } + +func (c *Controller) setTrackRatings(trackIDs []string, rating int) { + // Subsonic doesn't allow bulk setting ratings. + // To not overwhelm the server with requests, set rating for + // only 5 tracks at a time concurrently + batchSize := 5 + batchSetRating := func(offs int, wg *sync.WaitGroup) { + for i := 0; i < batchSize && offs+i < len(trackIDs); i++ { + if wg != nil { + wg.Add(1) + } + go func(idx int) { + c.App.ServerManager.Server.SetRating(trackIDs[idx], rating) + if wg != nil { + wg.Done() + } + }(offs + i) + } + } + + if len(trackIDs) <= 5 { + // one batch only - no need to use wait group + batchSetRating(0, nil) + } else { + go func() { + numBatches := int(math.Ceil(float64(len(trackIDs)) / float64(batchSize))) + for i := 0; i < numBatches; i++ { + var wg sync.WaitGroup + batchSetRating(i*batchSize, &wg) + wg.Wait() + } + }() + } + + // Notify PlaybackManager of rating change to update + // the in-memory track models + for _, id := range trackIDs { + c.App.PlaybackManager.OnTrackRatingChanged(id, rating) + } +} diff --git a/ui/widgets/starrating.go b/ui/widgets/starrating.go new file mode 100644 index 0000000..11cebbd --- /dev/null +++ b/ui/widgets/starrating.go @@ -0,0 +1,125 @@ +package widgets + +import ( + "math" + "supersonic/res" + "supersonic/ui/layouts" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/driver/desktop" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" +) + +var ( + themedResStarFilled = theme.NewThemedResource(res.ResStarFilledSvg) + themedResStarOutline = theme.NewThemedResource(res.ResStarOutlineSvg) +) + +type StarRating struct { + widget.BaseWidget + + Rating int + StarSize float32 + + OnRatingChanged func(int) + + holdRating bool // don't render mouseHoverRating + mouseHoverRating int // zero if not hovered + container *fyne.Container +} + +func NewStarRating() *StarRating { + s := &StarRating{} + s.ExtendBaseWidget(s) + return s +} + +func (s *StarRating) createContainer() { + s.container = container.New(&layouts.HboxCustomPadding{ + DisableThemePad: true, + }) + var im *canvas.Image + for i := 0; i < 5; i++ { + if s.Rating > i { + im = canvas.NewImageFromResource(themedResStarFilled) + } else { + im = canvas.NewImageFromResource(themedResStarOutline) + } + im.SetMinSize(fyne.NewSize(s.StarSize, s.StarSize)) + s.container.Add(im) + } +} + +var _ desktop.Hoverable = (*StarRating)(nil) + +func (s *StarRating) MouseIn(e *desktop.MouseEvent) { + s.MouseMoved(e) +} + +func (s *StarRating) MouseMoved(e *desktop.MouseEvent) { + hoverRating := int(math.Ceil(5 * float64(e.Position.X/s.Size().Width))) + if s.mouseHoverRating != hoverRating { + s.holdRating = false + s.mouseHoverRating = hoverRating + s.Refresh() + } +} + +func (s *StarRating) MouseOut() { + s.mouseHoverRating = 0 + s.holdRating = false + s.Refresh() +} + +var _ fyne.Tappable = (*StarRating)(nil) + +func (s *StarRating) Tapped(*fyne.PointEvent) { + if s.mouseHoverRating <= 0 { + return //shouldn't happen + } + if s.Rating == s.mouseHoverRating { + s.Rating = 0 + } else { + s.Rating = s.mouseHoverRating + } + s.holdRating = true + s.Refresh() + if s.OnRatingChanged != nil { + s.OnRatingChanged(s.Rating) + } +} + +func (s *StarRating) Refresh() { + // widget has not had renderer created yet + if s.container == nil { + return + } + rating := s.Rating + if !s.holdRating && s.mouseHoverRating > 0 { + rating = s.mouseHoverRating + } + for i := 0; i < 5; i++ { + im := s.container.Objects[i].(*canvas.Image) + im.SetMinSize(fyne.NewSize(s.StarSize, s.StarSize)) + if rating > i { + im.Resource = themedResStarFilled + } else { + im.Resource = themedResStarOutline + } + } + s.BaseWidget.Refresh() +} + +func (s *StarRating) MinSize() fyne.Size { + return fyne.NewSize(s.StarSize*5, s.StarSize) +} + +func (s *StarRating) CreateRenderer() fyne.WidgetRenderer { + if s.container == nil { + s.createContainer() + } + return widget.NewSimpleRenderer(container.NewCenter(s.container)) +} diff --git a/ui/widgets/tracklist.go b/ui/widgets/tracklist.go index bbc2621..a11604b 100644 --- a/ui/widgets/tracklist.go +++ b/ui/widgets/tracklist.go @@ -26,6 +26,7 @@ const ( ColumnTime = "Time" ColumnYear = "Year" ColumnFavorite = "Favorite" + ColumnRating = "Rating" ColumnPlays = "Plays" ColumnBitrate = "Bitrate" ColumnSize = "Size" @@ -62,6 +63,7 @@ type Tracklist struct { OnAddToQueue func(trackIDs []*subsonic.Child) OnAddToPlaylist func(trackIDs []string) OnSetFavorite func(trackIDs []string, fav bool) + OnSetRating func(trackIDs []string, rating int) OnShowArtistPage func(artistID string) OnShowAlbumPage func(albumID string) @@ -83,12 +85,12 @@ type Tracklist struct { } func NewTracklist(tracks []*subsonic.Child) *Tracklist { - t := &Tracklist{Tracks: tracks, visibleColumns: make([]bool, 11)} + t := &Tracklist{Tracks: tracks, visibleColumns: make([]bool, 12)} t.ExtendBaseWidget(t) t.selectionMgr = util.NewListSelectionManager(t.lenTracks) - // #, Title, Artist, Album, Time, Year, Favorite, Plays, Bitrate, Size, Path - t.colLayout = layouts.NewColumnsLayout([]float32{40, -1, -1, -1, 60, 60, 47, 65, 75, 70, -1}) + // #, Title, Artist, Album, Time, Year, Favorite, Rating, Plays, Bitrate, Size, Path + t.colLayout = layouts.NewColumnsLayout([]float32{40, -1, -1, -1, 50, 50, 45, 95, 55, 65, 70, -1}) t.buildHeader() t.hdr.OnColumnVisibilityChanged = t.setColumnVisible t.hdr.OnColumnVisibilityMenuShown = func(pop *widget.PopUp) { @@ -132,6 +134,7 @@ func (t *Tracklist) buildHeader() { {Text: "Time", AlignTrailing: true, CanToggleVisible: true}, {Text: "Year", AlignTrailing: true, CanToggleVisible: true}, {Text: "Fav.", AlignTrailing: false, CanToggleVisible: true}, + {Text: "Rating", AlignTrailing: false, CanToggleVisible: true}, {Text: "Plays", AlignTrailing: true, CanToggleVisible: true}, {Text: "Bitrate", AlignTrailing: true, CanToggleVisible: true}, {Text: "Size", AlignTrailing: true, CanToggleVisible: true}, @@ -303,6 +306,18 @@ func (t *Tracklist) onSetFavorite(trackID string, fav bool) { } } +func (t *Tracklist) onSetRating(trackID string, rating int) { + // update our own track model + t.tracksMutex.RLock() + tr := sharedutil.FindTrackByID(trackID, t.Tracks) + t.tracksMutex.RUnlock() + tr.UserRating = rating + // notify listener + if t.OnSetRating != nil { + t.OnSetRating([]string{trackID}, rating) + } +} + func (t *Tracklist) onArtistTapped(artistID string) { if t.OnShowArtistPage != nil { t.OnShowArtistPage(artistID) @@ -360,14 +375,16 @@ func ColNumber(colName string) int { return 5 case ColumnFavorite: return 6 - case ColumnPlays: + case ColumnRating: return 7 - case ColumnBitrate: + case ColumnPlays: return 8 - case ColumnSize: + case ColumnBitrate: return 9 - case ColumnPath: + case ColumnSize: return 10 + case ColumnPath: + return 11 default: log.Printf("error: Tracklist: invalid column name %s", colName) return -100 @@ -388,12 +405,14 @@ func colName(i int) string { case 6: return ColumnFavorite case 7: - return ColumnPlays + return ColumnRating case 8: - return ColumnBitrate + return ColumnPlays case 9: - return ColumnSize + return ColumnBitrate case 10: + return ColumnSize + case 11: return ColumnPath default: return "" @@ -421,6 +440,7 @@ type TrackRow struct { dur *widget.RichText year *widget.RichText favorite *fyne.Container + rating *StarRating bitrate *widget.RichText plays *widget.RichText size *widget.RichText @@ -449,6 +469,9 @@ func NewTrackRow(tracklist *Tracklist, playingIcon fyne.CanvasObject) *TrackRow favorite := NewTappbaleIcon(res.ResHeartOutlineInvertPng) favorite.OnTapped = t.toggleFavorited t.favorite = container.NewCenter(favorite) + t.rating = NewStarRating() + t.rating.StarSize = 16 + t.rating.OnRatingChanged = t.setTrackRating t.plays = widget.NewRichTextWithText("") t.plays.Segments[0].(*widget.TextSegment).Style.Alignment = fyne.TextAlignTrailing t.bitrate = widget.NewRichTextWithText("") @@ -459,7 +482,7 @@ func NewTrackRow(tracklist *Tracklist, playingIcon fyne.CanvasObject) *TrackRow t.path.Wrapping = fyne.TextTruncate t.Content = container.New(tracklist.colLayout, - t.num, t.name, t.artist, t.album, t.dur, t.year, t.favorite, t.plays, t.bitrate, t.size, t.path) + t.num, t.name, t.artist, t.album, t.dur, t.year, t.favorite, t.rating, t.plays, t.bitrate, t.size, t.path) return t } @@ -546,12 +569,15 @@ func (t *TrackRow) Update(tr *subsonic.Child, rowNum int) { t.favorite.Objects[0].(*TappableIcon).Resource = res.ResHeartFilledInvertPng } + t.rating.Rating = tr.UserRating + // Show only columns configured to be visible t.artist.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnArtist)] t.album.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnAlbum)] t.dur.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnTime)] t.year.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnYear)] t.favorite.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnFavorite)] + t.rating.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnRating)] t.plays.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnPlays)] t.bitrate.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnBitrate)] t.size.Hidden = !t.tracklist.visibleColumns[ColNumber(ColumnSize)] @@ -574,6 +600,10 @@ func (t *TrackRow) toggleFavorited() { } } +func (t *TrackRow) setTrackRating(rating int) { + t.tracklist.onSetRating(t.trackID, rating) +} + func (t *TrackRow) TappedSecondary(e *fyne.PointEvent) { if t.OnTappedSecondary != nil { t.OnTappedSecondary(e, t.trackIdx)