From 2fedb162595bc235f5b8f9a50deb484169955cf9 Mon Sep 17 00:00:00 2001 From: TheTrashCrafter <107064092+TheTrashCrafter@users.noreply.github.com> Date: Thu, 6 Jul 2023 15:47:35 +0200 Subject: [PATCH 01/84] Switch to kavin.rocks --- innertube/src/main/java/com/zionhuang/innertube/InnerTube.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/innertube/src/main/java/com/zionhuang/innertube/InnerTube.kt b/innertube/src/main/java/com/zionhuang/innertube/InnerTube.kt index e37a90e38..6d9333101 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/InnerTube.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/InnerTube.kt @@ -141,7 +141,7 @@ class InnerTube { } suspend fun pipedStreams(videoId: String) = - httpClient.get("https://watchapi.whatever.social/streams/${videoId}") { + httpClient.get("https://pipedapi.kavin.rocks/streams/${videoId}") { contentType(ContentType.Application.Json) } @@ -239,4 +239,4 @@ class InnerTube { configYTClient(client) setBody(AccountMenuBody(client.toContext(locale, visitorData))) } -} \ No newline at end of file +} From 48e74c087fe14a705292644ae41e2c965b853672 Mon Sep 17 00:00:00 2001 From: gidano Date: Fri, 7 Jul 2023 09:31:57 +0200 Subject: [PATCH 02/84] Update hu strings.xml --- app/src/main/res/values-hu/strings.xml | 78 +++++++++++++------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index fd519f52c..4c0f7563e 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -13,21 +13,21 @@ - History - Stats - Quick picks - Listen some songs to generate your quick picks - New release albums + + Statisztika + Gyors választás + Hallgasson meg néhány dalt a gyors választások elkészítéséhez + Új kiadású albumok - Today - Yesterday - This week - Last week + Ma + Tegnap + A héten + Múlt héten - Most played songs - Most played artists + A legtöbbet játszott dalok + A legtöbbet játszott előadók Keresés @@ -41,7 +41,7 @@ Listák Közösségi listák Kiemelt lejátszási listák - No results found + Nincs találat Saját könyvtárból @@ -49,7 +49,7 @@ Lájkolt dalok Letöltött dalok - The playlist is empty + Újra @@ -66,7 +66,7 @@ Könyvtárhoz ad Eltávolít a könyvtárból Letöltés - Downloading + Letöltés Letöltés eltávolítása Lista importálása Lejátszólistához ad @@ -75,9 +75,9 @@ Újrahív Megosztás Törlés - Remove from history + Előzményből eltávolít Keresés online - Sync + Szinkron. Létrehozás dátuma @@ -87,7 +87,7 @@ Dal szám Hossz Lejátszási idő - Custom order + Egyéni sorrend Média id @@ -141,17 +141,17 @@ Lista importálva - Removed \"%s\" from playlist - Playlist synced - Undo + \"%s\" eltávolítva a lejátszólistából + Lejátszólista szinkronban + Visszavon A dalszöveg nem található - Sleep timer - End of song + Alvás időzítő + A dal vége - 1 minute - %d minutes + 1 perc + %d perc Nincs elérhető adatfolyam Nincs hálózati kapcsolat @@ -172,12 +172,12 @@ Beállítások Megjelenés - Enable dynamic theme + Dinamikus téma bekapcsolás Sötét téma Be Ki Rendszer szerint - Pure black + Tiszta fekete Alapért. nyitott lap A navigációs lapok testreszabása Dalszöveg szöveg pozíció @@ -207,21 +207,21 @@ Tárhely Gyorsítótár - Image Cache - Song Cache - Max cache size - Unlimited - Clear all downloads + Kép gyorsítótár + Dal gyorsítótár + Max. gyors.tár méret + Korlátlan + Minden letöltés törlése Max. kép gyorsítótár Kép gyors.tár törlés Max. dal gyorsítótár - Clear song cache + Dal gyors.tár törlése %s használva Adatvédelem - Pause listen history - Clear listen history - Are you sure to clear all listen history? + A hallgatási előzmények szüneteltetése + Hallgatási előzmény törlése + Biztosan törli az összes hallgatási előzményt? Keresési előzmények szüneteltetése Keresési előzmények törlése Biztosan töröl minden keresési előzményt? @@ -230,11 +230,11 @@ Bizt.mentés és visszaállítás Bizt.mentés Visszaállítás - Choose a csv file from Google Takeout - Imported playlist + Válasszon egy csv-fájlt a Google Takeout-ból + Importált lejátszási lista - Imported \"%s\" with %d song - Imported \"%s\" with %d songs + \"%s\" importálva %d dallal + \"%s\" importálva %d dallal A bizt.mentés sikeresen létrehozva Nem sikerült biztonsági mentést készíteni From 20d51d1415ec071fc4df8f4cfa3b93225407d7b3 Mon Sep 17 00:00:00 2001 From: gidano Date: Fri, 7 Jul 2023 17:56:04 +0200 Subject: [PATCH 03/84] Edited HU string --- app/src/main/res/values-hu/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 4c0f7563e..f91f83948 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -13,7 +13,7 @@ - + Előzmény Statisztika Gyors választás Hallgasson meg néhány dalt a gyors választások elkészítéséhez From b92c51339ffb4b4d945a5c217fd0604e9c952be2 Mon Sep 17 00:00:00 2001 From: gidano Date: Fri, 7 Jul 2023 18:01:26 +0200 Subject: [PATCH 04/84] edited HU strings --- app/src/main/res/values-hu/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index f91f83948..d4d091273 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -49,7 +49,7 @@ Lájkolt dalok Letöltött dalok - + A lejátszólista üres Újra From 926b359ec33c76ccea71320333725d31829d6d9d Mon Sep 17 00:00:00 2001 From: atilluF <110931720+atilluF@users.noreply.github.com> Date: Sat, 8 Jul 2023 15:07:59 +0200 Subject: [PATCH 05/84] Update Italian translation --- app/src/main/res/values-it/strings.xml | 116 ++++++++++++------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index cc1935e48..9fd7b6bd0 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -13,21 +13,21 @@ - History - Stats - Quick picks - Listen some songs to generate your quick picks - New release albums + Cronologia + Statistiche + Scelte rapide + Ascolta alcuni brani per generare le tue scelte rapide + Nuovi album in uscita - Today - Yesterday - This week - Last week + Oggi + Ieri + Questa settimana + Settimana scorsa - Most played songs - Most played artists + Brani più ascoltati + Artisti più ascoltati Cerca @@ -39,9 +39,9 @@ Album Artisti Playlist - Playlist della comunità + Playlist della community Playlist in rilievo - No results found + Nessun risultato trovato Dalla tua libreria @@ -49,7 +49,7 @@ Brani piaciuti Brani scaricati - The playlist is empty + La playlist è vuota Riprova @@ -59,25 +59,25 @@ Dettagli Modifica - Apri la radio + Apri radio Riproduci Riproduci come successiva - Aggiungi alla coda - Aggiungi alla libreria - Rimuovi dalla libreria + Metti in coda + Aggiungi a libreria + Rimuovi da libreria Scarica - Downloading + In scaricamento Rimuovi scaricamento Importa playlist - Aggiungi alla playlist + Aggiungi a playlist Mostra artista Mostra album - Riottieni + Aggiorna Condividi Elimina - Remove from history + Rimuovi da cronologia Cerca online - Sync + Sincronizza Data di aggiunta @@ -87,7 +87,7 @@ Numero brani Durata Numero di riproduzioni - Custom order + Ordine personalizzato ID media @@ -105,12 +105,12 @@ Cerca testo Modifica brano - Titolo - Artista + Titolo del brano + Artisti del brano Il titolo del brano non può essere vuoto. L\'artista del brano non può essere vuoto. Salva - + Scegli una playlist Modifica playlist Crea playlist @@ -141,26 +141,26 @@ Playlist importata - Removed \"%s\" from playlist - Playlist synced - Undo + Rimosso \"%s\" da playlist + Playlist sincronizzata + Annulla Testo non trovato - Sleep timer - End of song + Timer per il sonno + Fine del brano - 1 minute - %d minutes + 1 minuto + %d minuti Stream non disponibile - Nessuna connessione a internet presente + Nessuna connessione di rete Tempo scaduto Errore sconosciuto Mi piace - Non mi piace più + Rimuovi mi piace Tutti i brani @@ -172,14 +172,14 @@ Impostazioni Aspetto - Enable dynamic theme + Abilita il tema dinamico Tema scuro Attivato Disattivato Segui sistema - Pure black + Nero Scheda principale predefinita - Personalizza schede di navigazione + Personalizza le schede di navigazione Posizione del testo dei brani Sinistra Centro @@ -191,7 +191,7 @@ Paese predefinito dei contenuti Predefinito di sistema Attiva proxy - Tipo di proxy + Tipologia del proxy URL proxy Riavvia l\'app per applicare le modifiche @@ -201,43 +201,43 @@ Alta Bassa Coda persistente - Salta silenzio + Salta il silenzio Normalizzazione dell\'audio Equalizzatore Archiviazione Cache - Image Cache - Song Cache - Max cache size - Unlimited - Clear all downloads + Cache delle immagini + Cache dei brani + Dimensione massima della cache + Illimitata + Cancella tutti i download Grandezza massima della cache delle immagini - Pulisci cache delle immagini + Pulisci la cache delle immagini Grandezza massima della cache dei brani - Clear song cache - %s usato + Pulisci la cache dei brani + %s usati Privacy - Pause listen history - Clear listen history - Are you sure to clear all listen history? + Sospendi la cronologia degli ascolti + Cancella la cronologia degli ascolti + Sei sicuro di voler cancellare la cronologia degli ascolti? Sospendi la cronologia delle ricerche Pulisci la cronologia delle ricerche Sei sicuro di voler cancellare la cronologia delle ricerche? Attiva i testi forniti da KuGou - Backup e ripristina + Backup e ripristino Backup Ripristina - Choose a csv file from Google Takeout - Imported playlist + Scegli un file csv da Google Takeout + Playlist importata - Imported \"%s\" with %d song - Imported \"%s\" with %d songs + Importato \"%s\" con %d brano + Importato \"%s\" con %d brani Backup creato con successo - Impossibile fare il backup + Impossibile eseguire il backup Impossibile eseguire il ripristino dal backup Informazioni From c10c75dd4b0115cf774823aff86e896e7f9e6b14 Mon Sep 17 00:00:00 2001 From: Oleksandr Tkachenko Date: Sat, 8 Jul 2023 17:14:38 +0300 Subject: [PATCH 06/84] Update strings.xml --- app/src/main/res/values-uk-rUA/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-uk-rUA/strings.xml b/app/src/main/res/values-uk-rUA/strings.xml index ae3f24e7c..c0ea4b003 100644 --- a/app/src/main/res/values-uk-rUA/strings.xml +++ b/app/src/main/res/values-uk-rUA/strings.xml @@ -18,7 +18,7 @@ Історія Статистика Швидкий вибір - Listen some songs to generate your quick picks + Послухайте кілька пісень, щоб створити ваш швидкий вибір Нові релізи альбомів @@ -245,8 +245,8 @@ Вибрати csv-файл із Google Takeout Імпортований плейлист - Imported \"%s\" with %d song - Imported \"%s\" with %d songs + Імпортовано «%s» з %d композицією + Імпортовано «%s» з %d композиціями Резервну копію створено успішно Не вдалося створити резервну копію From 69493c1ad58d084613bb34f8d8812f21565e627a Mon Sep 17 00:00:00 2001 From: Oleksandr Tkachenko Date: Sat, 8 Jul 2023 17:14:49 +0300 Subject: [PATCH 07/84] Update strings.xml --- app/src/main/res/values-ru-rRU/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index 95624559a..cf7597aa8 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -18,7 +18,7 @@ История Статистика Быстрый выбор - Listen some songs to generate your quick picks + Послушайте несколько песен, чтобы создать ваш быстрый выбор Новые релизы альбомов @@ -245,8 +245,8 @@ Выбрать csv-файл из Google Takeout Импортированный плейлист - Imported \"%s\" with %d song - Imported \"%s\" with %d songs + Импортировано «%s» с %d композицией + Импортировано «%s» с %d композициями Резервная копия создана успешно Не удалось создать резервную копию From 52599768d4c722fd03ed16df3ed41e9884ee4866 Mon Sep 17 00:00:00 2001 From: siggi1984 <103368536+siggi1984@users.noreply.github.com> Date: Mon, 10 Jul 2023 19:25:11 +0200 Subject: [PATCH 08/84] Update strings.xml --- app/src/main/res/values-DE/strings.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/values-DE/strings.xml b/app/src/main/res/values-DE/strings.xml index 75d7584a0..652b576d2 100644 --- a/app/src/main/res/values-DE/strings.xml +++ b/app/src/main/res/values-DE/strings.xml @@ -13,11 +13,11 @@ - History - Stats - Quick picks - Listen some songs to generate your quick picks - New release albums + Wiedergabeverlauf + Statistiken + Schnellauswahl + Hören Sie sich einige Songs an, um Ihre Schnellauswahl zu treffen + Neu veröffentlichte Alben Today From fe80a62a0826b356a97fafd644a2b08aaf6859b1 Mon Sep 17 00:00:00 2001 From: Lurux <35870988+Lurux@users.noreply.github.com> Date: Mon, 10 Jul 2023 19:30:45 +0200 Subject: [PATCH 09/84] Add "play time" sort mode to all song listings --- .../zionhuang/music/constants/PreferenceKeys.kt | 8 ++++---- .../java/com/zionhuang/music/db/DatabaseDao.kt | 15 +++++++++++++++ .../music/ui/screens/artist/ArtistSongsScreen.kt | 1 + .../ui/screens/library/LibrarySongsScreen.kt | 1 + .../ui/screens/playlist/BuiltInPlaylistScreen.kt | 2 ++ .../ui/screens/playlist/LocalPlaylistScreen.kt | 1 + .../music/viewmodels/BuiltInPlaylistViewModel.kt | 1 + .../music/viewmodels/LocalPlaylistViewModel.kt | 1 + 8 files changed, 26 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/zionhuang/music/constants/PreferenceKeys.kt b/app/src/main/java/com/zionhuang/music/constants/PreferenceKeys.kt index ac29554e1..a9c956109 100644 --- a/app/src/main/java/com/zionhuang/music/constants/PreferenceKeys.kt +++ b/app/src/main/java/com/zionhuang/music/constants/PreferenceKeys.kt @@ -52,15 +52,15 @@ val ArtistSongSortDescendingKey = booleanPreferencesKey("artistSongSortDescendin val PlaylistEditLockKey = booleanPreferencesKey("playlistEditLock") enum class SongSortType { - CREATE_DATE, NAME, ARTIST + CREATE_DATE, NAME, ARTIST, PLAY_TIME } enum class DownloadedSongSortType { - CREATE_DATE, NAME, ARTIST + CREATE_DATE, NAME, ARTIST, PLAY_TIME } enum class PlaylistSongSortType { - CUSTOM, CREATE_DATE, NAME, ARTIST + CUSTOM, CREATE_DATE, NAME, ARTIST, PLAY_TIME } enum class ArtistSortType { @@ -68,7 +68,7 @@ enum class ArtistSortType { } enum class ArtistSongSortType { - CREATE_DATE, NAME + CREATE_DATE, NAME, PLAY_TIME } enum class AlbumSortType { diff --git a/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt b/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt index e387ae520..961dc5191 100644 --- a/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt +++ b/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt @@ -30,6 +30,10 @@ interface DatabaseDao { @Query("SELECT * FROM song WHERE inLibrary IS NOT NULL ORDER BY title") fun songsByNameAsc(): Flow> + @Transaction + @Query("SELECT * FROM song WHERE inLibrary IS NOT NULL ORDER BY totalPlayTime") + fun songsByPlayTimeAsc(): Flow> + fun songs(sortType: SongSortType, descending: Boolean) = when (sortType) { SongSortType.CREATE_DATE -> songsByCreateDateAsc() @@ -39,6 +43,7 @@ interface DatabaseDao { song.artists.joinToString(separator = "") { it.name } } } + SongSortType.PLAY_TIME -> songsByPlayTimeAsc() }.map { it.reversed(descending) } @Transaction @@ -53,6 +58,10 @@ interface DatabaseDao { @Query("SELECT * FROM song WHERE liked ORDER BY title") fun likedSongsByNameAsc(): Flow> + @Transaction + @Query("SELECT * FROM song WHERE liked ORDER BY totalPlayTime") + fun likedSongsByPlayTimeAsc(): Flow> + fun likedSongs(sortType: SongSortType, descending: Boolean) = when (sortType) { SongSortType.CREATE_DATE -> likedSongsByCreateDateAsc() @@ -62,6 +71,7 @@ interface DatabaseDao { song.artists.joinToString(separator = "") { it.name } } } + SongSortType.PLAY_TIME -> likedSongsByPlayTimeAsc() }.map { it.reversed(descending) } @Query("SELECT COUNT(1) FROM song WHERE liked") @@ -83,10 +93,15 @@ interface DatabaseDao { @Query("SELECT song.* FROM song_artist_map JOIN song ON song_artist_map.songId = song.id WHERE artistId = :artistId AND inLibrary IS NOT NULL ORDER BY title") fun artistSongsByNameAsc(artistId: String): Flow> + @Transaction + @Query("SELECT song.* FROM song_artist_map JOIN song ON song_artist_map.songId = song.id WHERE artistId = :artistId AND inLibrary IS NOT NULL ORDER BY totalPlayTime") + fun artistSongsByPlayTimeAsc(artistId: String): Flow> + fun artistSongs(artistId: String, sortType: ArtistSongSortType, descending: Boolean) = when (sortType) { ArtistSongSortType.CREATE_DATE -> artistSongsByCreateDateAsc(artistId) ArtistSongSortType.NAME -> artistSongsByNameAsc(artistId) + ArtistSongSortType.PLAY_TIME -> artistSongsByPlayTimeAsc(artistId) }.map { it.reversed(descending) } @Transaction diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistSongsScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistSongsScreen.kt index 583f2aea8..c839f6301 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistSongsScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistSongsScreen.kt @@ -95,6 +95,7 @@ fun ArtistSongsScreen( when (sortType) { ArtistSongSortType.CREATE_DATE -> R.string.sort_by_create_date ArtistSongSortType.NAME -> R.string.sort_by_name + ArtistSongSortType.PLAY_TIME -> R.string.sort_by_play_time } }, trailingText = pluralStringResource(R.plurals.n_song, songs.size, songs.size) diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/library/LibrarySongsScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/library/LibrarySongsScreen.kt index 51487e3d7..90d0a44ae 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/library/LibrarySongsScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/library/LibrarySongsScreen.kt @@ -79,6 +79,7 @@ fun LibrarySongsScreen( SongSortType.CREATE_DATE -> R.string.sort_by_create_date SongSortType.NAME -> R.string.sort_by_name SongSortType.ARTIST -> R.string.sort_by_artist + SongSortType.PLAY_TIME -> R.string.sort_by_play_time } }, trailingText = pluralStringResource(R.plurals.n_song, songs.size, songs.size) diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/playlist/BuiltInPlaylistScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/playlist/BuiltInPlaylistScreen.kt index e9f9be42c..f930a7ec4 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/playlist/BuiltInPlaylistScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/playlist/BuiltInPlaylistScreen.kt @@ -114,6 +114,7 @@ fun BuiltInPlaylistScreen( SongSortType.CREATE_DATE -> R.string.sort_by_create_date SongSortType.NAME -> R.string.sort_by_name SongSortType.ARTIST -> R.string.sort_by_artist + SongSortType.PLAY_TIME -> R.string.sort_by_play_time } }, trailingText = joinByBullet( @@ -132,6 +133,7 @@ fun BuiltInPlaylistScreen( DownloadedSongSortType.CREATE_DATE -> R.string.sort_by_create_date DownloadedSongSortType.NAME -> R.string.sort_by_name DownloadedSongSortType.ARTIST -> R.string.sort_by_artist + DownloadedSongSortType.PLAY_TIME -> R.string.sort_by_play_time } }, trailingText = joinByBullet( diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/playlist/LocalPlaylistScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/playlist/LocalPlaylistScreen.kt index 051bd7aee..ff1593c10 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/playlist/LocalPlaylistScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/playlist/LocalPlaylistScreen.kt @@ -443,6 +443,7 @@ fun LocalPlaylistScreen( PlaylistSongSortType.CREATE_DATE -> R.string.sort_by_create_date PlaylistSongSortType.NAME -> R.string.sort_by_name PlaylistSongSortType.ARTIST -> R.string.sort_by_artist + PlaylistSongSortType.PLAY_TIME -> R.string.sort_by_play_time } }, trailingText = "", diff --git a/app/src/main/java/com/zionhuang/music/viewmodels/BuiltInPlaylistViewModel.kt b/app/src/main/java/com/zionhuang/music/viewmodels/BuiltInPlaylistViewModel.kt index a53f6afa8..6f813253d 100644 --- a/app/src/main/java/com/zionhuang/music/viewmodels/BuiltInPlaylistViewModel.kt +++ b/app/src/main/java/com/zionhuang/music/viewmodels/BuiltInPlaylistViewModel.kt @@ -71,6 +71,7 @@ class BuiltInPlaylistViewModel @Inject constructor( DownloadedSongSortType.ARTIST -> songs.sortedBy { song -> song.first.artists.joinToString(separator = "") { it.name } } + DownloadedSongSortType.PLAY_TIME -> songs.sortedBy { it.first.song.totalPlayTime } } .map { it.first } .reversed(descending) diff --git a/app/src/main/java/com/zionhuang/music/viewmodels/LocalPlaylistViewModel.kt b/app/src/main/java/com/zionhuang/music/viewmodels/LocalPlaylistViewModel.kt index 959dfe7d6..380a41cbf 100644 --- a/app/src/main/java/com/zionhuang/music/viewmodels/LocalPlaylistViewModel.kt +++ b/app/src/main/java/com/zionhuang/music/viewmodels/LocalPlaylistViewModel.kt @@ -44,6 +44,7 @@ class LocalPlaylistViewModel @Inject constructor( PlaylistSongSortType.ARTIST -> songs.sortedBy { song -> song.song.artists.joinToString { it.name } } + PlaylistSongSortType.PLAY_TIME -> songs.sortedBy { it.song.song.totalPlayTime } }.reversed(sortDescending && sortType != PlaylistSongSortType.CUSTOM) }.stateIn(viewModelScope, SharingStarted.Lazily, emptyList()) } From b14d0094c7a5126b6b8aecab04d9744571366894 Mon Sep 17 00:00:00 2001 From: siggi1984 <103368536+siggi1984@users.noreply.github.com> Date: Mon, 10 Jul 2023 20:35:58 +0200 Subject: [PATCH 10/84] Update strings.xml --- app/src/main/res/values-DE/strings.xml | 46 +++++++++++++------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/app/src/main/res/values-DE/strings.xml b/app/src/main/res/values-DE/strings.xml index 652b576d2..779026916 100644 --- a/app/src/main/res/values-DE/strings.xml +++ b/app/src/main/res/values-DE/strings.xml @@ -20,14 +20,14 @@ Neu veröffentlichte Alben - Today - Yesterday - This week - Last week + Heute + Gestern + Diese Woche + Letzte Woche - Most played songs - Most played artists + Meist gespielte Songs + Meist gespielte Künstler Suche @@ -41,7 +41,7 @@ Wiedergabelisten Community-Wiedergabelisten Ausgewählte Wiedergabelisten - No results found + Keine Ergebnisse gefunden Aus der Bibliothek @@ -49,7 +49,7 @@ Beliebte Titel Heruntergeladene Titel - The playlist is empty + Die Wiedergabeliste ist leer Wiederholen @@ -75,7 +75,7 @@ Neu laden Teilen Löschen - Remove from history + aus dem Wiedergabeverlauf entfernen Online-Suche Sync @@ -87,7 +87,7 @@ Anzahl der Lieder Länge Spielzeit - Custom order + Individuelle Reinfolge Medien-ID @@ -141,17 +141,17 @@ Wiedergabeliste importiert - Removed \"%s\" from playlist - Playlist synced - Undo + Entfernt \"%s\" aus der Wiedergabeliste + Wiedergabeliste synchronisiert + Rückgängig machen Liedtext nicht gefunden - Sleep timer - End of song + Schlaf-Timer + Ende des Liedes 1 minute - %d minutes + %d Minuten Kein Stream verfügbar Keine Netzverbindung @@ -172,12 +172,12 @@ Einstellungen Erscheinungsbild - Enable dynamic theme + Dynamisches Design aktivieren Dunkles Thema An Aus System folgen - Pure black + Reines Schwarz Standardmäßig geöffnete Registerkarte Anpassen der Navigationsregisterkarten Position des Liedtextes @@ -210,17 +210,17 @@ Image Cache Song Cache Max cache size - Unlimited - Clear all downloads + Unbegrenzt + Alle Downloads entfernen Maximale Größe des Bild-Caches Bild-Cache löschen Maximale Größe des Song-Cache - Clear song cache + Song-Cache löschen %s verwendet Privatsphäre - Pause listen history - Clear listen history + Pausieren des Hörverlaufs/string> + Hörverlauf löschen Are you sure to clear all listen history? Suchverlauf anhalten Suchverlauf löschen From 9622fd0f080567c3b905e7843c6b3972492ee066 Mon Sep 17 00:00:00 2001 From: siggi1984 <103368536+siggi1984@users.noreply.github.com> Date: Mon, 10 Jul 2023 20:45:10 +0200 Subject: [PATCH 11/84] Update strings.xml --- app/src/main/res/values-DE/strings.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/values-DE/strings.xml b/app/src/main/res/values-DE/strings.xml index 779026916..2621617ca 100644 --- a/app/src/main/res/values-DE/strings.xml +++ b/app/src/main/res/values-DE/strings.xml @@ -221,20 +221,20 @@ Privatsphäre Pausieren des Hörverlaufs/string> Hörverlauf löschen - Are you sure to clear all listen history? + Sind Sie sicher, dass Sie den gesamten Hörverlauf löschen wollen? Suchverlauf anhalten Suchverlauf löschen - Sind Sie sicher, dass Sie den gesamten Suchverlauf löschen? + Sind Sie sicher, dass Sie den gesamten Suchverlauf löschen wollen? KuGou-Liedtextanbieter aktivieren Sichern und Wiederherstellen Datensicherung Wiederherstellen - Choose a csv file from Google Takeout - Imported playlist + Wählen Sie eine csv-Datei von Google Takeout + Importierte Wiedergabeliste - Imported \"%s\" with %d song - Imported \"%s\" with %d songs + Importiert \"%s\" mit %d Song + Importiert \"%s\" mit %d Songs Sicherung erfolgreich erstellt Konnte keine Sicherung erstellen From e416be7edc1778d832bfa2eb26e02627e3cab1c5 Mon Sep 17 00:00:00 2001 From: Javi <45560967+javdc@users.noreply.github.com> Date: Mon, 10 Jul 2023 21:18:07 +0200 Subject: [PATCH 12/84] Update spanish translations Signed-off-by: Javi <45560967+javdc@users.noreply.github.com> --- app/src/main/res/values-es/strings.xml | 30 +++++++++---------- .../metadata/android/es/full_description.txt | 17 +++++++++++ .../metadata/android/es/short_description.txt | 1 + 3 files changed, 33 insertions(+), 15 deletions(-) create mode 100644 fastlane/metadata/android/es/full_description.txt create mode 100644 fastlane/metadata/android/es/short_description.txt diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index b493d8f27..a619f482e 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -16,7 +16,7 @@ Historial Estadísticas Selecciones rápidas - Listen some songs to generate your quick picks + Escucha algunas canciones para generar tus selecciones rápidas Nuevos lanzamientos @@ -27,7 +27,7 @@ Canciones más reproducidas - Most played artists + Artistas más reproducidos Buscar @@ -66,7 +66,7 @@ Agregar a la biblioteca Quitar de la biblioteca Descargar - Downloading + Descargando Borrar descarga Importar lista de reproducción Agregar a la lista de reproducción @@ -87,7 +87,7 @@ Número de canciones Duración Tiempo de reproducción - Custom order + Orden personalizado ID multimedia @@ -141,9 +141,9 @@ Lista de reproducción importada - Removed \"%s\" from playlist - Playlist synced - Undo + Eliminado \"%s\" de la lista de reproducción + Lista de reproducción sincronizada + Deshacer Letras no encontradas @@ -211,7 +211,7 @@ Caché de canciones Tamaño máximo de la caché Ilimitado - Clear all downloads + Borrar todas las descargas Tamaño máximo de la caché de imágenes Borrar la caché de imágenes Tamaño máximo de la caché de canciones @@ -219,9 +219,9 @@ %s usado Privacidad - Pausar historial de escucha - Clear listen history - Are you sure to clear all listen history? + Pausar historial de escuchas + Borrar historial de escuchas + ¿Estás seguro de que quieres borrar todo el historial de escuchas? Pausar historial de búsquedas Borrar historial de búsquedas ¿Estás seguro de que quieres borrar todo el historial de búsquedas? @@ -230,11 +230,11 @@ Copias de seguridad y restauración Hacer copia de seguridad Restaurar - Choose a csv file from Google Takeout - Imported playlist + Selecciona un archivo csv de Google Takeout + Lista de reproducción importada - Imported \"%s\" with %d song - Imported \"%s\" with %d songs + Importado \"%s\" con %d canción + Importado \"%s\" con %d canciones Copia de seguridad creada con éxito No se ha podido crear la copia de seguridad diff --git a/fastlane/metadata/android/es/full_description.txt b/fastlane/metadata/android/es/full_description.txt new file mode 100644 index 000000000..9b26f0a96 --- /dev/null +++ b/fastlane/metadata/android/es/full_description.txt @@ -0,0 +1,17 @@ +Un cliente de YouTube Music con Material 3 para Android + +
Características: + +- Reproduce canciones de YT/YT Music sin anuncios +- Reproducción en segundo plano +- Busca canciones, vídeos, álbumes y listas de reproducción de YouTube Music +- Gestión de biblioteca +- Guarda en caché y descarga canciones para reproducirlas sin conexión +- Letras sincronizadas +- Saltar silencio +- Normalización de audio +- Tema dinámico +- Localización +- Compatibilidad con Android Auto +- Selecciones rápidas personalizadas +- Material 3 \ No newline at end of file diff --git a/fastlane/metadata/android/es/short_description.txt b/fastlane/metadata/android/es/short_description.txt new file mode 100644 index 000000000..a8a999bb8 --- /dev/null +++ b/fastlane/metadata/android/es/short_description.txt @@ -0,0 +1 @@ +Un cliente de YouTube Music con Material 3 para Android \ No newline at end of file From f0299b0ea2537912ab416219e2d955918c6cf25c Mon Sep 17 00:00:00 2001 From: siggi1984 <103368536+siggi1984@users.noreply.github.com> Date: Mon, 10 Jul 2023 21:40:59 +0200 Subject: [PATCH 13/84] Update strings.xml --- app/src/main/res/values-DE/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-DE/strings.xml b/app/src/main/res/values-DE/strings.xml index 2621617ca..c7375eebd 100644 --- a/app/src/main/res/values-DE/strings.xml +++ b/app/src/main/res/values-DE/strings.xml @@ -219,7 +219,7 @@ %s verwendet Privatsphäre - Pausieren des Hörverlaufs/string> + Pausieren des Hörverlaufs Hörverlauf löschen Sind Sie sicher, dass Sie den gesamten Hörverlauf löschen wollen? Suchverlauf anhalten From 762848b1e49e8ef32c0df425cd19a84bf5511dba Mon Sep 17 00:00:00 2001 From: HiSubway Date: Wed, 12 Jul 2023 02:33:11 +0900 Subject: [PATCH 14/84] Update Japanese translations --- app/src/main/res/values-ja-rJP/strings.xml | 76 +++++++++++----------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index 9b0815e4e..2ed15c8a3 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -12,21 +12,21 @@ - History - Stats - Quick picks - Listen some songs to generate your quick picks - New release albums + 履歴 + 統計 + ピックアップ + 何曲か再生するとピックアップが生成できます + 新作アルバム - Today - Yesterday - This week - Last week + 今日 + 昨日 + 今週 + 先週 - Most played songs - Most played artists + 最も再生された曲 + 最も再生されたアーティスト 検索 @@ -40,7 +40,7 @@ プレイリスト コミュニティのプレイリスト 注目のプレイリスト - No results found + 見つかりませんでした ライブラリから @@ -48,7 +48,7 @@ いいねした曲 ダウンロードした曲 - The playlist is empty + プレイリストが空です 再試行 @@ -56,7 +56,7 @@ シャッフル - Details + 詳細 編集 ラジオを再生 再生 @@ -74,7 +74,7 @@ 再取得 共有 削除 - Remove from history + 履歴から削除 オンラインで検索 Sync @@ -86,7 +86,7 @@ 曲数 長さ 再生時間 - Custom order + カスタム メディアID @@ -136,16 +136,16 @@ プレイリストをインポートしました - Removed \"%s\" from playlist - Playlist synced - Undo + プレイリストから \"%s\" を削除しました + プレイリストが同期されました + 元に戻す 歌詞が見つかりません - Sleep timer - End of song + スリープタイマー + 曲の終わり - %d minutes + %d 分 ストリームが利用できません ネットワーク接続がありません @@ -166,7 +166,7 @@ 設定 外観 - Enable dynamic theme + ダイナミックテーマを有効化 ダークテーマ オン オフ @@ -201,34 +201,34 @@ ストレージ キャッシュ - Image Cache - Song Cache - Max cache size - Unlimited - Clear all downloads + 画像のキャッシュ + 曲のキャッシュ + 最大キャッシュサイズ + 無制限 + すべてのダウンロードを削除 画像の最大キャッシュサイズ 画像のキャッシュを削除 曲の最大キャッシュサイズ - Clear song cache + 曲のキャッシュを削除 %s 使用中 プライバシー - Pause listen history - Clear listen history - Are you sure to clear all listen history? - 履歴の記録を一時停止 - 履歴を削除 + 再生履歴を一時停止 + 再生履歴を削除 + すべての再生履歴を削除しますか? + 検索履歴の記録を一時停止 + 検索履歴を削除 すべての検索履歴を削除しますか? 酷狗からの歌詞取得を有効化 バックアップとリストア バックアップ リストア - Choose a csv file from Google Takeout - Imported playlist + Google TakeoutからCSVファイルを選択してください + インポートしたプレイリスト - Imported \"%s\" with %d song - Imported \"%s\" with %d songs + \"%s\" から %d 曲をインポートしました + \"%s\" から %d 曲をインポートしました バックアップの作成に成功しました バックアップを作成できませんでした From f8ce86cdf189112b74b8b3fbd6de8c241a9d8167 Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Wed, 12 Jul 2023 12:24:05 +0800 Subject: [PATCH 15/84] Fix #784 --- .../com/zionhuang/music/viewmodels/OnlineSearchViewModel.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/zionhuang/music/viewmodels/OnlineSearchViewModel.kt b/app/src/main/java/com/zionhuang/music/viewmodels/OnlineSearchViewModel.kt index 3baf5146a..b41d6b701 100644 --- a/app/src/main/java/com/zionhuang/music/viewmodels/OnlineSearchViewModel.kt +++ b/app/src/main/java/com/zionhuang/music/viewmodels/OnlineSearchViewModel.kt @@ -33,8 +33,8 @@ class OnlineSearchViewModel @Inject constructor( } } else { if (viewStateMap[filter.value] == null) { - viewStateMap[filter.value] = YouTube.search(query, filter).getOrNull()?.let { - ItemsPage(it.items, it.continuation) + viewStateMap[filter.value] = YouTube.search(query, filter).getOrNull()?.let { result -> + ItemsPage(result.items.distinctBy { it.id }, result.continuation) } } } From d8a5ecd3c2b7d3609f98b640278923d081f94e22 Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Wed, 12 Jul 2023 12:47:18 +0800 Subject: [PATCH 16/84] Fix #761 --- .../main/java/com/zionhuang/music/constants/PreferenceKeys.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/zionhuang/music/constants/PreferenceKeys.kt b/app/src/main/java/com/zionhuang/music/constants/PreferenceKeys.kt index ac29554e1..1155096f2 100644 --- a/app/src/main/java/com/zionhuang/music/constants/PreferenceKeys.kt +++ b/app/src/main/java/com/zionhuang/music/constants/PreferenceKeys.kt @@ -38,8 +38,8 @@ val SongSortTypeKey = stringPreferencesKey("songSortType") val SongSortDescendingKey = booleanPreferencesKey("songSortDescending") val DownloadedSongSortTypeKey = stringPreferencesKey("songSortType") val DownloadedSongSortDescendingKey = booleanPreferencesKey("songSortDescending") -val PlaylistSongSortTypeKey = stringPreferencesKey("songSortType") -val PlaylistSongSortDescendingKey = booleanPreferencesKey("songSortDescending") +val PlaylistSongSortTypeKey = stringPreferencesKey("playlistSongSortType") +val PlaylistSongSortDescendingKey = booleanPreferencesKey("playlistSongSortDescending") val ArtistSortTypeKey = stringPreferencesKey("artistSortType") val ArtistSortDescendingKey = booleanPreferencesKey("artistSortDescending") val AlbumSortTypeKey = stringPreferencesKey("albumSortType") From 68f546e167ee4d94115f1d1afdc2b727e370657a Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Fri, 14 Jul 2023 10:50:09 +0800 Subject: [PATCH 17/84] Fix #805 --- .../music/ui/screens/playlist/LocalPlaylistScreen.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/playlist/LocalPlaylistScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/playlist/LocalPlaylistScreen.kt index ff1593c10..d6cddc2b3 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/playlist/LocalPlaylistScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/playlist/LocalPlaylistScreen.kt @@ -25,6 +25,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarResult @@ -482,7 +483,8 @@ fun LocalPlaylistScreen( coroutineScope.launch { val snackbarResult = snackbarHostState.showSnackbar( message = context.getString(R.string.removed_song_from_playlist, currentItem.song.song.title), - actionLabel = context.getString(R.string.undo) + actionLabel = context.getString(R.string.undo), + duration = SnackbarDuration.Short ) if (snackbarResult == SnackbarResult.ActionPerformed) { database.transaction { From 992f4ec0053d384b22bf3d45e36f8c7a84e619d4 Mon Sep 17 00:00:00 2001 From: atilluF <110931720+atilluF@users.noreply.github.com> Date: Fri, 14 Jul 2023 16:11:36 +0200 Subject: [PATCH 18/84] Update Italian short_description.txt --- fastlane/metadata/android/it/short_description.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/metadata/android/it/short_description.txt b/fastlane/metadata/android/it/short_description.txt index fb186a850..ac29f8275 100644 --- a/fastlane/metadata/android/it/short_description.txt +++ b/fastlane/metadata/android/it/short_description.txt @@ -1 +1 @@ -un riproduttore musicale fatto in Material Design per YouTube Music +Un client di YouTube Music in Material 3 per Android From 7716c15d7d6b88423e5e87b18d18210afc9f09d7 Mon Sep 17 00:00:00 2001 From: atilluF <110931720+atilluF@users.noreply.github.com> Date: Fri, 14 Jul 2023 16:24:39 +0200 Subject: [PATCH 19/84] Update Italian full_description.txt --- .../metadata/android/it/full_description.txt | 38 +++++++------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/fastlane/metadata/android/it/full_description.txt b/fastlane/metadata/android/it/full_description.txt index 0b21305f8..63534faf2 100644 --- a/fastlane/metadata/android/it/full_description.txt +++ b/fastlane/metadata/android/it/full_description.txt @@ -1,27 +1,17 @@ -Con quest'app, avrai un servizio di streaming musicale gratuito. Puoi ascoltare musica da YouTube Music e crearti la tua libreria musicale personale. Inoltre, i brani possono essere scaricati per un ascolto offline. Puoi anche creare delle playlist per organizzare la tua musica. L'obiettivo di InnerTune è quello di dare la possibilità a chiunque di ascoltare musica senza costi e facilmente, in maniera pratica e priva di pubblicità. - -
Nota: - -Il progetto è attualmente in una fase instabile. Se trovi degli errori, per favore segnalali facendolo su GitHub. +Un client di YouTube Music in Material 3 per Android
Caratteristiche: -* Ascolta brani senza nessuna pubblicità -* Naviga attraverso quasi tutte le pagine di YouTube Music -* Cerca brani, video, playlist e canali da YouTube Music -* Apri link di YouTube Music -* Salva brani, album e playlist in un database locale -* Scarica musica per un ascolto offline -* Modifica il titolo dei brani -* Aggiungi collegamenti alle tue playlist preferite di YouTube Music -* Riproduttore in Material Design -* Riproduzione anche con lo schermo bloccato -* Controlli in notifica -* Passa al prossimo brano o a quello precedente -* Modalità a ripetizione o casuale -* Modifica la coda dei brani in riproduzione -* Temi personalizzati -* Tema scuro -* Localizzazione -* Proxy -* Backup e ripristina +- Ascolto dei brani da YT/YT Music senza pubblicità +- Riproduzione in background +- Ricerca dei brani, video, album e playlist da YouTube Music +- Gestione della libreria +- Cache e download dei brani per la riproduzione offline +- Testi sincronizzati +- Salto del silenzio +- Normalizzazione dell'audio +- Tema dinamico +- Varie lingue disponibili +- Supporto per Android Auto +- Scelte rapide personalizzate +- Material 3 From 9b31006f0bcf16d38c72d7fedc44057161bcdc48 Mon Sep 17 00:00:00 2001 From: Oleksandr Tkachenko Date: Fri, 14 Jul 2023 21:58:02 +0300 Subject: [PATCH 20/84] Create title.txt --- fastlane/metadata/android/uk-UA/title.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/uk-UA/title.txt diff --git a/fastlane/metadata/android/uk-UA/title.txt b/fastlane/metadata/android/uk-UA/title.txt new file mode 100644 index 000000000..0facb35fe --- /dev/null +++ b/fastlane/metadata/android/uk-UA/title.txt @@ -0,0 +1 @@ +InnerTune From f077c4e91c609bc1b07b46cad122bba1e2e5e9eb Mon Sep 17 00:00:00 2001 From: Oleksandr Tkachenko Date: Fri, 14 Jul 2023 21:58:45 +0300 Subject: [PATCH 21/84] Create title.txt --- fastlane/metadata/android/ru-RU/title.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/ru-RU/title.txt diff --git a/fastlane/metadata/android/ru-RU/title.txt b/fastlane/metadata/android/ru-RU/title.txt new file mode 100644 index 000000000..0facb35fe --- /dev/null +++ b/fastlane/metadata/android/ru-RU/title.txt @@ -0,0 +1 @@ +InnerTune From 2635e26a5512092e91c8ea9c61514e21fd100fc1 Mon Sep 17 00:00:00 2001 From: Oleksandr Tkachenko Date: Fri, 14 Jul 2023 22:03:58 +0300 Subject: [PATCH 22/84] Create short_description.txt --- fastlane/metadata/android/ru-RU/short_description.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/ru-RU/short_description.txt diff --git a/fastlane/metadata/android/ru-RU/short_description.txt b/fastlane/metadata/android/ru-RU/short_description.txt new file mode 100644 index 000000000..1e6c1efe2 --- /dev/null +++ b/fastlane/metadata/android/ru-RU/short_description.txt @@ -0,0 +1 @@ +Клиент YouTube Music для Android в стиле Material 3 From 7896188d89761e405c21a1407f7479a73ab3aeed Mon Sep 17 00:00:00 2001 From: Oleksandr Tkachenko Date: Fri, 14 Jul 2023 22:07:30 +0300 Subject: [PATCH 23/84] Create full_description.txt --- .../metadata/android/ru-RU/full_description.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 fastlane/metadata/android/ru-RU/full_description.txt diff --git a/fastlane/metadata/android/ru-RU/full_description.txt b/fastlane/metadata/android/ru-RU/full_description.txt new file mode 100644 index 000000000..2ec4ab3b1 --- /dev/null +++ b/fastlane/metadata/android/ru-RU/full_description.txt @@ -0,0 +1,17 @@ +Клиент YouTube Music для Android в стиле Material 3 + +
Особенности: + +- Воспроизведение песен с YT/YT Music без рекламы +- Фоновое воспроизведение +- Поиск песен, видео, альбомов и плейлистов в YouTube Music +- Управление библиотекой +- Кэширование и загрузка песен для офлайн-воспроизведения +- Синхронизированный текст песен +- Пропуск тишины +- Нормализация аудио +- Динамическая тема +- Локализация +- Поддержка Android Auto +- Персонализированные быстрые выборки +- Material 3 From 3372aa8419944cff9ea2877f84ea736eff643660 Mon Sep 17 00:00:00 2001 From: Oleksandr Tkachenko Date: Fri, 14 Jul 2023 22:08:40 +0300 Subject: [PATCH 24/84] Create short_description.txt --- fastlane/metadata/android/uk-UA/short_description.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/uk-UA/short_description.txt diff --git a/fastlane/metadata/android/uk-UA/short_description.txt b/fastlane/metadata/android/uk-UA/short_description.txt new file mode 100644 index 000000000..56c1e10fc --- /dev/null +++ b/fastlane/metadata/android/uk-UA/short_description.txt @@ -0,0 +1 @@ +Клієнт YouTube Music для Android у стилі Material 3 From 1ce56908805cb5f3868b0cee91d054835dfbc9cb Mon Sep 17 00:00:00 2001 From: Oleksandr Tkachenko Date: Fri, 14 Jul 2023 22:10:15 +0300 Subject: [PATCH 25/84] Create full_description.txt --- .../metadata/android/uk-UA/full_description.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 fastlane/metadata/android/uk-UA/full_description.txt diff --git a/fastlane/metadata/android/uk-UA/full_description.txt b/fastlane/metadata/android/uk-UA/full_description.txt new file mode 100644 index 000000000..8cc1dc47d --- /dev/null +++ b/fastlane/metadata/android/uk-UA/full_description.txt @@ -0,0 +1,17 @@ +Клієнт YouTube Music для Android у стилі Material 3 + +Особливості: + +- Відтворення пісень з YT/YT Music без реклами +- Фонове відтворення +- Пошук пісень, відео, альбомів та плейлистів в YouTube Music +- Керування бібліотекою +- Кешування та завантаження пісень для офлайн-відтворення +- Синхронізований текст пісень +- Пропуск тиші +- Нормалізація аудіо +- Динамічна тема +- Локалізація +- Підтримка Android Auto +- Персоналізовані швидкі вибірки +- Material 3 From d5be8bf682264fdb04a0a50f5debfeb04c0452b4 Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Sat, 15 Jul 2023 12:27:22 +0800 Subject: [PATCH 26/84] Login feature --- .../java/com/zionhuang/music/MainActivity.kt | 24 ++++ .../com/zionhuang/music/ui/component/Items.kt | 4 +- .../music/ui/screens/AccountScreen.kt | 109 ++++++++++++++++++ .../zionhuang/music/ui/screens/HomeScreen.kt | 26 ++++- .../zionhuang/music/ui/screens/LoginScreen.kt | 108 +++++++++++++++++ .../screens/playlist/OnlinePlaylistScreen.kt | 36 +++--- .../ui/screens/settings/ContentSettings.kt | 16 +++ .../music/viewmodels/AccountViewModel.kt | 25 ++++ app/src/main/res/drawable/person.xml | 9 ++ app/src/main/res/drawable/settings.xml | 2 +- app/src/main/res/values-DE/strings.xml | 5 +- app/src/main/res/values-cs/strings.xml | 1 + app/src/main/res/values-es-rUS/strings.xml | 1 + app/src/main/res/values-es/strings.xml | 5 +- app/src/main/res/values-fa-rIR/strings.xml | 1 + app/src/main/res/values-fi-rFI/strings.xml | 1 + app/src/main/res/values-fr-rFR/strings.xml | 1 + app/src/main/res/values-hu/strings.xml | 5 +- app/src/main/res/values-id/strings.xml | 1 + app/src/main/res/values-it/strings.xml | 7 +- app/src/main/res/values-ja-rJP/strings.xml | 5 +- app/src/main/res/values-ko-rKR/strings.xml | 1 + app/src/main/res/values-ml-rIN/strings.xml | 1 + app/src/main/res/values-nl/strings.xml | 1 + app/src/main/res/values-or-rIN/strings.xml | 1 + app/src/main/res/values-pa/strings.xml | 1 + app/src/main/res/values-pt-rBR/strings.xml | 1 + app/src/main/res/values-ru-rRU/strings.xml | 5 +- app/src/main/res/values-sv-rSE/strings.xml | 1 + app/src/main/res/values-tr/strings.xml | 1 + app/src/main/res/values-uk-rUA/strings.xml | 5 +- app/src/main/res/values-zh-rCN/strings.xml | 1 + app/src/main/res/values-zh-rTW/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + .../java/com/zionhuang/innertube/YouTube.kt | 26 +++-- .../models/MusicTwoRowItemRenderer.kt | 4 +- .../innertube/models/SectionListRenderer.kt | 2 +- .../com/zionhuang/innertube/models/YTItem.kt | 2 +- .../models/response/AccountMenuResponse.kt | 4 +- .../models/response/BrowseResponse.kt | 21 +++- .../innertube/pages/ArtistItemsPage.kt | 23 ++-- .../zionhuang/innertube/pages/ArtistPage.kt | 25 +++- .../innertube/pages/NewReleaseAlbumPage.kt | 8 +- .../zionhuang/innertube/pages/RelatedPage.kt | 19 ++- 44 files changed, 472 insertions(+), 75 deletions(-) create mode 100644 app/src/main/java/com/zionhuang/music/ui/screens/AccountScreen.kt create mode 100644 app/src/main/java/com/zionhuang/music/ui/screens/LoginScreen.kt create mode 100644 app/src/main/java/com/zionhuang/music/viewmodels/AccountViewModel.kt create mode 100644 app/src/main/res/drawable/person.xml diff --git a/app/src/main/java/com/zionhuang/music/MainActivity.kt b/app/src/main/java/com/zionhuang/music/MainActivity.kt index 06de681b4..359380711 100644 --- a/app/src/main/java/com/zionhuang/music/MainActivity.kt +++ b/app/src/main/java/com/zionhuang/music/MainActivity.kt @@ -530,6 +530,12 @@ class MainActivity : ComponentActivity() { composable("settings/about") { AboutScreen(navController, scrollBehavior) } + composable("login") { + LoginScreen(navController) + } + composable("account") { + AccountScreen(navController, scrollBehavior) + } } AnimatedVisibility( @@ -605,6 +611,24 @@ class MainActivity : ComponentActivity() { contentDescription = null ) } + } else if (navBackStackEntry?.destination?.route in listOf( + Screens.Home.route, + Screens.Songs.route, + Screens.Artists.route, + Screens.Albums.route, + Screens.Playlists.route + ) + ) { + IconButton( + onClick = { + navController.navigate("settings") + } + ) { + Icon( + painter = painterResource(R.drawable.settings), + contentDescription = null + ) + } } }, modifier = Modifier.align(Alignment.TopCenter) diff --git a/app/src/main/java/com/zionhuang/music/ui/component/Items.kt b/app/src/main/java/com/zionhuang/music/ui/component/Items.kt index c2a46449c..c67ff1697 100644 --- a/app/src/main/java/com/zionhuang/music/ui/component/Items.kt +++ b/app/src/main/java/com/zionhuang/music/ui/component/Items.kt @@ -551,7 +551,7 @@ fun YouTubeListItem( is SongItem -> joinByBullet(item.artists.joinToString { it.name }, makeTimeString(item.duration?.times(1000L))) is AlbumItem -> joinByBullet(item.artists?.joinToString { it.name }, item.year?.toString()) is ArtistItem -> null - is PlaylistItem -> joinByBullet(item.author.name, item.songCountText) + is PlaylistItem -> joinByBullet(item.author?.name, item.songCountText) }, badges = badges, thumbnailContent = { @@ -744,7 +744,7 @@ fun YouTubeGridItem( is SongItem -> joinByBullet(item.artists.joinToString { it.name }, makeTimeString(item.duration?.times(1000L))) is AlbumItem -> joinByBullet(item.artists?.joinToString { it.name }, item.year?.toString()) is ArtistItem -> null - is PlaylistItem -> joinByBullet(item.author.name, item.songCountText) + is PlaylistItem -> joinByBullet(item.author?.name, item.songCountText) } if (subtitle != null) { diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/AccountScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/AccountScreen.kt new file mode 100644 index 000000000..ff4efb239 --- /dev/null +++ b/app/src/main/java/com/zionhuang/music/ui/screens/AccountScreen.kt @@ -0,0 +1,109 @@ +package com.zionhuang.music.ui.screens + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavController +import com.zionhuang.music.LocalPlayerAwareWindowInsets +import com.zionhuang.music.LocalPlayerConnection +import com.zionhuang.music.R +import com.zionhuang.music.ui.component.LocalMenuState +import com.zionhuang.music.ui.component.YouTubeListItem +import com.zionhuang.music.ui.component.shimmer.ListItemPlaceHolder +import com.zionhuang.music.ui.component.shimmer.ShimmerHost +import com.zionhuang.music.ui.menu.YouTubePlaylistMenu +import com.zionhuang.music.viewmodels.AccountViewModel + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AccountScreen( + navController: NavController, + scrollBehavior: TopAppBarScrollBehavior, + viewModel: AccountViewModel = hiltViewModel(), +) { + val menuState = LocalMenuState.current + val playerConnection = LocalPlayerConnection.current ?: return + + val lazyListState = rememberLazyListState() + val coroutineScope = rememberCoroutineScope() + + val playlists by viewModel.playlists.collectAsState() + + LazyColumn( + state = lazyListState, + contentPadding = LocalPlayerAwareWindowInsets.current.asPaddingValues() + ) { + playlists.let { playlists -> + if (playlists == null) { + item(key = "loading") { + ShimmerHost { + repeat(8) { + ListItemPlaceHolder() + } + } + } + } else { + items( + items = playlists, + key = { it.id } + ) { item -> + YouTubeListItem( + item = item, + trailingContent = { + IconButton( + onClick = { + menuState.show { + YouTubePlaylistMenu( + playlist = item, + playerConnection = playerConnection, + coroutineScope = coroutineScope, + onDismiss = menuState::dismiss + ) + } + } + ) { + Icon( + painter = painterResource(R.drawable.more_vert), + contentDescription = null + ) + } + }, + modifier = Modifier + .clickable { + navController.navigate("online_playlist/${item.id}") + } + ) + } + } + } + } + + TopAppBar( + title = { Text(stringResource(R.string.account)) }, + navigationIcon = { + IconButton(onClick = navController::navigateUp) { + Icon( + painterResource(R.drawable.arrow_back), + contentDescription = null + ) + } + }, + scrollBehavior = scrollBehavior + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/HomeScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/HomeScreen.kt index 2f2065f9e..d6d09f583 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/HomeScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/HomeScreen.kt @@ -26,10 +26,12 @@ import androidx.navigation.NavController import com.google.accompanist.swiperefresh.SwipeRefresh import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import com.zionhuang.innertube.models.WatchEndpoint +import com.zionhuang.innertube.utils.parseCookieString import com.zionhuang.music.LocalDatabase import com.zionhuang.music.LocalPlayerAwareWindowInsets import com.zionhuang.music.LocalPlayerConnection import com.zionhuang.music.R +import com.zionhuang.music.constants.InnerTubeCookieKey import com.zionhuang.music.constants.ListItemHeight import com.zionhuang.music.extensions.togglePlayPause import com.zionhuang.music.models.toMediaMetadata @@ -41,6 +43,7 @@ import com.zionhuang.music.ui.component.YouTubeGridItem import com.zionhuang.music.ui.menu.SongMenu import com.zionhuang.music.ui.menu.YouTubeAlbumMenu import com.zionhuang.music.ui.utils.SnapLayoutInfoProvider +import com.zionhuang.music.utils.rememberPreference import com.zionhuang.music.viewmodels.HomeViewModel import kotlin.random.Random @@ -63,6 +66,11 @@ fun HomeScreen( val isRefreshing by viewModel.isRefreshing.collectAsState() val mostPlayedLazyGridState = rememberLazyGridState() + val innerTubeCookie by rememberPreference(InnerTubeCookieKey, "") + val isLoggedIn = remember(innerTubeCookie) { + "SAPISID" in parseCookieString(innerTubeCookie) + } + SwipeRefresh( state = rememberSwipeRefreshState(isRefreshing), onRefresh = viewModel::refresh, @@ -99,18 +107,24 @@ fun HomeScreen( onClick = { navController.navigate("history") }, modifier = Modifier.weight(1f) ) + NavigationTile( title = stringResource(R.string.stats), icon = R.drawable.trending_up, onClick = { navController.navigate("stats") }, modifier = Modifier.weight(1f) ) - NavigationTile( - title = stringResource(R.string.settings), - icon = R.drawable.settings, - onClick = { navController.navigate("settings") }, - modifier = Modifier.weight(1f) - ) + + if (isLoggedIn) { + NavigationTile( + title = stringResource(R.string.account), + icon = R.drawable.person, + onClick = { + navController.navigate("account") + }, + modifier = Modifier.weight(1f) + ) + } } Text( diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/LoginScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/LoginScreen.kt new file mode 100644 index 000000000..63e4e0c12 --- /dev/null +++ b/app/src/main/java/com/zionhuang/music/ui/screens/LoginScreen.kt @@ -0,0 +1,108 @@ +package com.zionhuang.music.ui.screens + +import android.annotation.SuppressLint +import android.webkit.CookieManager +import android.webkit.JavascriptInterface +import android.webkit.WebView +import android.webkit.WebViewClient +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.viewinterop.AndroidView +import androidx.navigation.NavController +import com.zionhuang.innertube.YouTube +import com.zionhuang.music.LocalPlayerAwareWindowInsets +import com.zionhuang.music.R +import com.zionhuang.music.constants.AccountEmailKey +import com.zionhuang.music.constants.AccountNameKey +import com.zionhuang.music.constants.InnerTubeCookieKey +import com.zionhuang.music.constants.VisitorDataKey +import com.zionhuang.music.utils.rememberPreference +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch + +@SuppressLint("SetJavaScriptEnabled") +@OptIn(ExperimentalMaterial3Api::class, DelicateCoroutinesApi::class) +@Composable +fun LoginScreen( + navController: NavController, +) { + var visitorData by rememberPreference(VisitorDataKey, "") + var innerTubeCookie by rememberPreference(InnerTubeCookieKey, "") + var accountName by rememberPreference(AccountNameKey, "") + var accountEmail by rememberPreference(AccountEmailKey, "") + + var webView: WebView? = null + + AndroidView( + modifier = Modifier + .windowInsetsPadding(LocalPlayerAwareWindowInsets.current) + .fillMaxSize(), + factory = { context -> + WebView(context).apply { + webViewClient = object : WebViewClient() { + override fun doUpdateVisitedHistory(view: WebView, url: String, isReload: Boolean) { + if (url.startsWith("https://music.youtube.com")) { + innerTubeCookie = CookieManager.getInstance().getCookie(url) + GlobalScope.launch { + YouTube.accountInfo().onSuccess { + accountName = it?.name.orEmpty() + accountEmail = it?.email.orEmpty() + }.onFailure { + it.printStackTrace() + } + } + } + } + + override fun onPageFinished(view: WebView, url: String?) { + loadUrl("javascript:Android.onRetrieveVisitorData(window.yt.config_.VISITOR_DATA)") + } + } + settings.apply { + javaScriptEnabled = true + setSupportZoom(true) + builtInZoomControls = true + } + addJavascriptInterface(object { + @JavascriptInterface + fun onRetrieveVisitorData(newVisitorData: String?) { + if (newVisitorData != null) { + visitorData = newVisitorData + } + } + }, "Android") + webView = this + loadUrl("https://accounts.google.com/ServiceLogin?ltmpl=music&service=youtube&passive=true&continue=https%3A%2F%2Fwww.youtube.com%2Fsignin%3Faction_handle_signin%3Dtrue%26next%3Dhttps%253A%252F%252Fmusic.youtube.com%252F") + } + } + ) + + TopAppBar( + title = { Text(stringResource(R.string.login)) }, + navigationIcon = { + IconButton(onClick = navController::navigateUp) { + Icon( + painterResource(R.drawable.arrow_back), + contentDescription = null + ) + } + } + ) + + BackHandler(enabled = webView?.canGoBack() == true) { + webView?.goBack() + } +} diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/playlist/OnlinePlaylistScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/playlist/OnlinePlaylistScreen.kt index 93202821d..3902578e2 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/playlist/OnlinePlaylistScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/playlist/OnlinePlaylistScreen.kt @@ -143,25 +143,27 @@ fun OnlinePlaylistScreen( fontSizeRange = FontSizeRange(16.sp, 22.sp) ) - val annotatedString = buildAnnotatedString { - withStyle( - style = MaterialTheme.typography.titleMedium.copy( - fontWeight = FontWeight.Normal, - color = MaterialTheme.colorScheme.onBackground - ).toSpanStyle() - ) { - if (playlist.author.id != null) { - pushStringAnnotation(playlist.author.id!!, playlist.author.name) - append(playlist.author.name) - pop() - } else { - append(playlist.author.name) + playlist.author?.let { artist -> + val annotatedString = buildAnnotatedString { + withStyle( + style = MaterialTheme.typography.titleMedium.copy( + fontWeight = FontWeight.Normal, + color = MaterialTheme.colorScheme.onBackground + ).toSpanStyle() + ) { + if (artist.id != null) { + pushStringAnnotation(artist.id!!, artist.name) + append(artist.name) + pop() + } else { + append(artist.name) + } } } - } - ClickableText(annotatedString) { offset -> - annotatedString.getStringAnnotations(offset, offset).firstOrNull()?.let { range -> - navController.navigate("artist/${range.tag}") + ClickableText(annotatedString) { offset -> + annotatedString.getStringAnnotations(offset, offset).firstOrNull()?.let { range -> + navController.navigate("artist/${range.tag}") + } } } diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/settings/ContentSettings.kt b/app/src/main/java/com/zionhuang/music/ui/screens/settings/ContentSettings.kt index ed49b9e1a..65a25e90f 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/settings/ContentSettings.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/settings/ContentSettings.kt @@ -6,15 +6,19 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.* import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.navigation.NavController +import com.zionhuang.innertube.utils.parseCookieString import com.zionhuang.music.LocalPlayerAwareWindowInsets import com.zionhuang.music.R import com.zionhuang.music.constants.* import com.zionhuang.music.ui.component.EditTextPreference import com.zionhuang.music.ui.component.ListPreference +import com.zionhuang.music.ui.component.PreferenceEntry import com.zionhuang.music.ui.component.PreferenceGroupTitle import com.zionhuang.music.ui.component.SwitchPreference import com.zionhuang.music.utils.rememberEnumPreference @@ -27,6 +31,12 @@ fun ContentSettings( navController: NavController, scrollBehavior: TopAppBarScrollBehavior, ) { + val accountName by rememberPreference(AccountNameKey, "") + val accountEmail by rememberPreference(AccountEmailKey, "") + val innerTubeCookie by rememberPreference(InnerTubeCookieKey, "") + val isLoggedIn = remember(innerTubeCookie) { + "SAPISID" in parseCookieString(innerTubeCookie) + } val (contentLanguage, onContentLanguageChange) = rememberPreference(key = ContentLanguageKey, defaultValue = "system") val (contentCountry, onContentCountryChange) = rememberPreference(key = ContentCountryKey, defaultValue = "system") val (proxyEnabled, onProxyEnabledChange) = rememberPreference(key = ProxyEnabledKey, defaultValue = false) @@ -39,6 +49,12 @@ fun ContentSettings( .windowInsetsPadding(LocalPlayerAwareWindowInsets.current) .verticalScroll(rememberScrollState()) ) { + PreferenceEntry( + title = if (isLoggedIn) accountName else stringResource(R.string.login), + description = if (isLoggedIn) accountEmail else null, + icon = R.drawable.person, + onClick = { navController.navigate("login") } + ) ListPreference( title = stringResource(R.string.content_language), icon = R.drawable.language, diff --git a/app/src/main/java/com/zionhuang/music/viewmodels/AccountViewModel.kt b/app/src/main/java/com/zionhuang/music/viewmodels/AccountViewModel.kt new file mode 100644 index 000000000..dd73c3124 --- /dev/null +++ b/app/src/main/java/com/zionhuang/music/viewmodels/AccountViewModel.kt @@ -0,0 +1,25 @@ +package com.zionhuang.music.viewmodels + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.zionhuang.innertube.YouTube +import com.zionhuang.innertube.models.PlaylistItem +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class AccountViewModel @Inject constructor() : ViewModel() { + val playlists = MutableStateFlow?>(null) + + init { + viewModelScope.launch { + YouTube.likedPlaylists().onSuccess { + playlists.value = it + }.onFailure { + it.printStackTrace() + } + } + } +} diff --git a/app/src/main/res/drawable/person.xml b/app/src/main/res/drawable/person.xml new file mode 100644 index 000000000..cac31b256 --- /dev/null +++ b/app/src/main/res/drawable/person.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/settings.xml b/app/src/main/res/drawable/settings.xml index 48db39640..57c7adb7c 100644 --- a/app/src/main/res/drawable/settings.xml +++ b/app/src/main/res/drawable/settings.xml @@ -5,5 +5,5 @@ android:viewportHeight="960"> + android:pathData="M370,880L354,752Q341,747 329.5,740Q318,733 307,725L188,775L78,585L181,507Q180,500 180,493.5Q180,487 180,480Q180,473 180,466.5Q180,460 181,453L78,375L188,185L307,235Q318,227 330,220Q342,213 354,208L370,80L590,80L606,208Q619,213 630.5,220Q642,227 653,235L772,185L882,375L779,453Q780,460 780,466.5Q780,473 780,480Q780,487 780,493.5Q780,500 778,507L881,585L771,775L653,725Q642,733 630,740Q618,747 606,752L590,880L370,880ZM482,620Q540,620 581,579Q622,538 622,480Q622,422 581,381Q540,340 482,340Q423,340 382.5,381Q342,422 342,480Q342,538 382.5,579Q423,620 482,620ZM482,540Q457,540 439.5,522.5Q422,505 422,480Q422,455 439.5,437.5Q457,420 482,420Q507,420 524.5,437.5Q542,455 542,480Q542,505 524.5,522.5Q507,540 482,540ZM480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480ZM440,800L519,800L533,694Q564,686 590.5,670.5Q617,655 639,633L738,674L777,606L691,541Q696,527 698,511.5Q700,496 700,480Q700,464 698,448.5Q696,433 691,419L777,354L738,286L639,328Q617,305 590.5,289.5Q564,274 533,266L520,160L441,160L427,266Q396,274 369.5,289.5Q343,305 321,327L222,286L183,354L269,418Q264,433 262,448Q260,463 260,480Q260,496 262,511Q264,526 269,541L183,606L222,674L321,632Q343,655 369.5,670.5Q396,686 427,694L440,800Z" /> diff --git a/app/src/main/res/values-DE/strings.xml b/app/src/main/res/values-DE/strings.xml index c7375eebd..6a9cb870b 100644 --- a/app/src/main/res/values-DE/strings.xml +++ b/app/src/main/res/values-DE/strings.xml @@ -15,6 +15,7 @@ Wiedergabeverlauf Statistiken + Account Schnellauswahl Hören Sie sich einige Songs an, um Ihre Schnellauswahl zu treffen Neu veröffentlichte Alben @@ -233,8 +234,8 @@ Wählen Sie eine csv-Datei von Google Takeout Importierte Wiedergabeliste - Importiert \"%s\" mit %d Song - Importiert \"%s\" mit %d Songs + Imported \"%s\" with %d song + Imported \"%s\" with %d songs Sicherung erfolgreich erstellt Konnte keine Sicherung erstellen diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 9cc60ac2d..356e76ee2 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -17,6 +17,7 @@ History Stats + Account Quick picks Listen some songs to generate your quick picks New release albums diff --git a/app/src/main/res/values-es-rUS/strings.xml b/app/src/main/res/values-es-rUS/strings.xml index e9991abd7..e5bc93d18 100644 --- a/app/src/main/res/values-es-rUS/strings.xml +++ b/app/src/main/res/values-es-rUS/strings.xml @@ -15,6 +15,7 @@ History Stats + Account Quick picks Listen some songs to generate your quick picks New release albums diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index a619f482e..ebab4eeea 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -15,6 +15,7 @@ Historial Estadísticas + Account Selecciones rápidas Escucha algunas canciones para generar tus selecciones rápidas Nuevos lanzamientos @@ -233,8 +234,8 @@ Selecciona un archivo csv de Google Takeout Lista de reproducción importada - Importado \"%s\" con %d canción - Importado \"%s\" con %d canciones + Imported \"%s\" with %d song + Imported \"%s\" with %d songs Copia de seguridad creada con éxito No se ha podido crear la copia de seguridad diff --git a/app/src/main/res/values-fa-rIR/strings.xml b/app/src/main/res/values-fa-rIR/strings.xml index 0dca69e70..768bfd308 100644 --- a/app/src/main/res/values-fa-rIR/strings.xml +++ b/app/src/main/res/values-fa-rIR/strings.xml @@ -15,6 +15,7 @@ History Stats + Account Quick picks Listen some songs to generate your quick picks New release albums diff --git a/app/src/main/res/values-fi-rFI/strings.xml b/app/src/main/res/values-fi-rFI/strings.xml index 62e6deafe..72c009f7c 100644 --- a/app/src/main/res/values-fi-rFI/strings.xml +++ b/app/src/main/res/values-fi-rFI/strings.xml @@ -15,6 +15,7 @@ History Stats + Account Quick picks Listen some songs to generate your quick picks New release albums diff --git a/app/src/main/res/values-fr-rFR/strings.xml b/app/src/main/res/values-fr-rFR/strings.xml index 7df9f1acf..19c496d8c 100644 --- a/app/src/main/res/values-fr-rFR/strings.xml +++ b/app/src/main/res/values-fr-rFR/strings.xml @@ -16,6 +16,7 @@ History Stats + Account Quick picks Listen some songs to generate your quick picks New release albums diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index d4d091273..26146e1c2 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -15,6 +15,7 @@ Előzmény Statisztika + Account Gyors választás Hallgasson meg néhány dalt a gyors választások elkészítéséhez Új kiadású albumok @@ -233,8 +234,8 @@ Válasszon egy csv-fájlt a Google Takeout-ból Importált lejátszási lista - \"%s\" importálva %d dallal - \"%s\" importálva %d dallal + Imported \"%s\" with %d song + Imported \"%s\" with %d songs A bizt.mentés sikeresen létrehozva Nem sikerült biztonsági mentést készíteni diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index dba0e8fcb..2663723a6 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -14,6 +14,7 @@ History Stats + Account Quick picks Listen some songs to generate your quick picks New release albums diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 9fd7b6bd0..9e8ebac98 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -15,6 +15,7 @@ Cronologia Statistiche + Account Scelte rapide Ascolta alcuni brani per generare le tue scelte rapide Nuovi album in uscita @@ -110,7 +111,7 @@ Il titolo del brano non può essere vuoto. L\'artista del brano non può essere vuoto. Salva - + Scegli una playlist Modifica playlist Crea playlist @@ -233,8 +234,8 @@ Scegli un file csv da Google Takeout Playlist importata - Importato \"%s\" con %d brano - Importato \"%s\" con %d brani + Imported \"%s\" with %d song + Imported \"%s\" with %d songs Backup creato con successo Impossibile eseguire il backup diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index 2ed15c8a3..0fc80e316 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -14,6 +14,7 @@ 履歴 統計 + Account ピックアップ 何曲か再生するとピックアップが生成できます 新作アルバム @@ -227,8 +228,8 @@ Google TakeoutからCSVファイルを選択してください インポートしたプレイリスト - \"%s\" から %d 曲をインポートしました - \"%s\" から %d 曲をインポートしました + Imported \"%s\" with %d song + Imported \"%s\" with %d songs バックアップの作成に成功しました バックアップを作成できませんでした diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml index 1c51a9aa2..a89011ece 100644 --- a/app/src/main/res/values-ko-rKR/strings.xml +++ b/app/src/main/res/values-ko-rKR/strings.xml @@ -14,6 +14,7 @@ History Stats + Account Quick picks Listen some songs to generate your quick picks New release albums diff --git a/app/src/main/res/values-ml-rIN/strings.xml b/app/src/main/res/values-ml-rIN/strings.xml index 9205fb986..86d7e027a 100644 --- a/app/src/main/res/values-ml-rIN/strings.xml +++ b/app/src/main/res/values-ml-rIN/strings.xml @@ -15,6 +15,7 @@ History Stats + Account Quick picks Listen some songs to generate your quick picks New release albums diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 30fe5e150..a1e52306f 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -15,6 +15,7 @@ Geschiedenis Statistieken + Account Snelle keuzes Beluister een aantal nummers om je snelle keuzes te genereren Nieuwe albums diff --git a/app/src/main/res/values-or-rIN/strings.xml b/app/src/main/res/values-or-rIN/strings.xml index e1c3fc854..cb550b428 100644 --- a/app/src/main/res/values-or-rIN/strings.xml +++ b/app/src/main/res/values-or-rIN/strings.xml @@ -15,6 +15,7 @@ History Stats + Account Quick picks Listen some songs to generate your quick picks New release albums diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index 6d657d45d..7e24dc101 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -15,6 +15,7 @@ ਅਤੀਤ ਅੰਕੜੇ + Account ਉਂਗਲਾਂ ਤੇ ਚੁਣੇ ਗੀਤ Listen some songs to generate your quick picks ਨਵੀਆਂ ਜਾਰੀ ਐਲਬਮਾਂ diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 494be8452..a83d6a7c8 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -15,6 +15,7 @@ History Stats + Account Quick picks Listen some songs to generate your quick picks New release álbuns diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index cf7597aa8..0fe7971be 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -17,6 +17,7 @@ История Статистика + Account Быстрый выбор Послушайте несколько песен, чтобы создать ваш быстрый выбор Новые релизы альбомов @@ -245,8 +246,8 @@ Выбрать csv-файл из Google Takeout Импортированный плейлист - Импортировано «%s» с %d композицией - Импортировано «%s» с %d композициями + Imported \"%s\" with %d song + Imported \"%s\" with %d songs Резервная копия создана успешно Не удалось создать резервную копию diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index 24522ccdf..2653b05a4 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -15,6 +15,7 @@ History Stats + Account Quick picks Listen some songs to generate your quick picks New release albums diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 2a0169930..8e2bfd955 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -15,6 +15,7 @@ Geçmiş İstatistikler + Account Hızlı seçimler Hızlı seçimlerinizi oluşturmak için birkaç şarkı dinleyin Yeni çıkan albümler diff --git a/app/src/main/res/values-uk-rUA/strings.xml b/app/src/main/res/values-uk-rUA/strings.xml index c0ea4b003..5b5d32a98 100644 --- a/app/src/main/res/values-uk-rUA/strings.xml +++ b/app/src/main/res/values-uk-rUA/strings.xml @@ -17,6 +17,7 @@ Історія Статистика + Account Швидкий вибір Послухайте кілька пісень, щоб створити ваш швидкий вибір Нові релізи альбомів @@ -245,8 +246,8 @@ Вибрати csv-файл із Google Takeout Імпортований плейлист - Імпортовано «%s» з %d композицією - Імпортовано «%s» з %d композиціями + Imported \"%s\" with %d song + Imported \"%s\" with %d songs Резервну копію створено успішно Не вдалося створити резервну копію diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index a8d24ed0e..3fcdbca3b 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -14,6 +14,7 @@ 历史记录 统计 + Account 歌曲快选 Listen some songs to generate your quick picks 新专辑 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index a4fd3e908..4b8f450b0 100755 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -14,6 +14,7 @@ 歷史記錄 統計 + 帳號 歌曲快選 聽一些音樂讓我們知道您的喜好 新專輯 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3ec15c363..fe5648dfc 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -14,6 +14,7 @@ History Stats + Account Quick picks Listen some songs to generate your quick picks New release albums diff --git a/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt b/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt index 627d8eec5..427ad7a65 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt @@ -5,6 +5,7 @@ import com.zionhuang.innertube.models.AlbumItem import com.zionhuang.innertube.models.Artist import com.zionhuang.innertube.models.ArtistItem import com.zionhuang.innertube.models.BrowseEndpoint +import com.zionhuang.innertube.models.GridRenderer import com.zionhuang.innertube.models.PlaylistItem import com.zionhuang.innertube.models.SearchSuggestions import com.zionhuang.innertube.models.SongItem @@ -235,21 +236,22 @@ object YouTube { suspend fun playlist(playlistId: String): Result = runCatching { val response = innerTube.browse(WEB_REMIX, "VL$playlistId").body() + val header = response.header?.musicDetailHeaderRenderer ?: response.header?.musicEditablePlaylistDetailHeaderRenderer?.header?.musicDetailHeaderRenderer!! PlaylistPage( playlist = PlaylistItem( id = playlistId, - title = response.header?.musicDetailHeaderRenderer?.title?.runs?.firstOrNull()?.text!!, - author = response.header.musicDetailHeaderRenderer.subtitle.runs?.getOrNull(2)?.let { + title = header.title.runs?.firstOrNull()?.text!!, + author = header.subtitle.runs?.getOrNull(2)?.let { Artist( name = it.text, id = it.navigationEndpoint?.browseEndpoint?.browseId ) - }!!, - songCountText = response.header.musicDetailHeaderRenderer.secondSubtitle.runs?.firstOrNull()?.text, - thumbnail = response.header.musicDetailHeaderRenderer.thumbnail.croppedSquareThumbnailRenderer?.getThumbnailUrl()!!, + }, + songCountText = header.secondSubtitle.runs?.firstOrNull()?.text, + thumbnail = header.thumbnail.croppedSquareThumbnailRenderer?.getThumbnailUrl()!!, playEndpoint = null, - shuffleEndpoint = response.header.musicDetailHeaderRenderer.menu.menuRenderer.topLevelButtons?.firstOrNull()?.buttonRenderer?.navigationEndpoint?.watchPlaylistEndpoint!!, - radioEndpoint = response.header.musicDetailHeaderRenderer.menu.menuRenderer.items.find { + shuffleEndpoint = header.menu.menuRenderer.topLevelButtons?.firstOrNull()?.buttonRenderer?.navigationEndpoint?.watchPlaylistEndpoint!!, + radioEndpoint = header.menu.menuRenderer.items.find { it.menuNavigationItemRenderer?.icon?.iconType == "MIX" }?.menuNavigationItemRenderer?.navigationEndpoint?.watchPlaylistEndpoint!! ), @@ -295,6 +297,16 @@ object YouTube { }.orEmpty() } + suspend fun likedPlaylists(): Result> = runCatching { + val response = innerTube.browse(WEB_REMIX, browseId = "FEmusic_liked_playlists").body() + response.contents?.singleColumnBrowseResultsRenderer?.tabs?.firstOrNull()?.tabRenderer?.content?.sectionListRenderer?.contents?.firstOrNull()?.gridRenderer?.items!! + .drop(1) // the first item is "create new playlist" + .mapNotNull(GridRenderer.Item::musicTwoRowItemRenderer) + .mapNotNull { + ArtistItemsPage.fromMusicTwoRowItemRenderer(it) as? PlaylistItem + } + } + suspend fun player(videoId: String, playlistId: String? = null): Result = runCatching { val playerResponse = innerTube.player(ANDROID_MUSIC, videoId, playlistId).body() if (playerResponse.playabilityStatus.status == "OK") { diff --git a/innertube/src/main/java/com/zionhuang/innertube/models/MusicTwoRowItemRenderer.kt b/innertube/src/main/java/com/zionhuang/innertube/models/MusicTwoRowItemRenderer.kt index c07e06d7e..22b86f2a6 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/models/MusicTwoRowItemRenderer.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/models/MusicTwoRowItemRenderer.kt @@ -14,9 +14,9 @@ import kotlinx.serialization.Serializable @Serializable data class MusicTwoRowItemRenderer( val title: Runs, - val subtitle: Runs, + val subtitle: Runs?, val subtitleBadges: List?, - val menu: Menu, + val menu: Menu?, val thumbnailRenderer: ThumbnailRenderer, val navigationEndpoint: NavigationEndpoint, val thumbnailOverlay: MusicResponsiveListItemRenderer.Overlay?, diff --git a/innertube/src/main/java/com/zionhuang/innertube/models/SectionListRenderer.kt b/innertube/src/main/java/com/zionhuang/innertube/models/SectionListRenderer.kt index 63fdd7a62..b85f79f20 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/models/SectionListRenderer.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/models/SectionListRenderer.kt @@ -12,7 +12,7 @@ data class SectionListRenderer( ) { @Serializable data class Header( - val chipCloudRenderer: ChipCloudRenderer, + val chipCloudRenderer: ChipCloudRenderer?, ) { @Serializable data class ChipCloudRenderer( diff --git a/innertube/src/main/java/com/zionhuang/innertube/models/YTItem.kt b/innertube/src/main/java/com/zionhuang/innertube/models/YTItem.kt index e71e088e2..d35765650 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/models/YTItem.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/models/YTItem.kt @@ -49,7 +49,7 @@ data class AlbumItem( data class PlaylistItem( override val id: String, override val title: String, - val author: Artist, + val author: Artist?, val songCountText: String?, override val thumbnail: String, val playEndpoint: WatchEndpoint?, diff --git a/innertube/src/main/java/com/zionhuang/innertube/models/response/AccountMenuResponse.kt b/innertube/src/main/java/com/zionhuang/innertube/models/response/AccountMenuResponse.kt index 7c1118771..b343157ce 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/models/response/AccountMenuResponse.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/models/response/AccountMenuResponse.kt @@ -34,8 +34,8 @@ data class AccountMenuResponse( val email: Runs, ) { fun toAccountInfo() = AccountInfo( - accountName.toString(), - email.toString() + accountName.runs!!.first().text, + email.runs!!.first().text ) } } diff --git a/innertube/src/main/java/com/zionhuang/innertube/models/response/BrowseResponse.kt b/innertube/src/main/java/com/zionhuang/innertube/models/response/BrowseResponse.kt index bf9380546..897c8bcc5 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/models/response/BrowseResponse.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/models/response/BrowseResponse.kt @@ -1,6 +1,14 @@ package com.zionhuang.innertube.models.response -import com.zionhuang.innertube.models.* +import com.zionhuang.innertube.models.Button +import com.zionhuang.innertube.models.Continuation +import com.zionhuang.innertube.models.Menu +import com.zionhuang.innertube.models.MusicShelfRenderer +import com.zionhuang.innertube.models.ResponseContext +import com.zionhuang.innertube.models.Runs +import com.zionhuang.innertube.models.SectionListRenderer +import com.zionhuang.innertube.models.Tabs +import com.zionhuang.innertube.models.ThumbnailRenderer import kotlinx.serialization.Serializable @Serializable @@ -39,6 +47,7 @@ data class BrowseResponse( data class Header( val musicImmersiveHeaderRenderer: MusicImmersiveHeaderRenderer?, val musicDetailHeaderRenderer: MusicDetailHeaderRenderer?, + val musicEditablePlaylistDetailHeaderRenderer: MusicEditablePlaylistDetailHeaderRenderer?, val musicVisualHeaderRenderer: MusicVisualHeaderRenderer?, val musicHeaderRenderer: MusicHeaderRenderer?, ) { @@ -62,6 +71,16 @@ data class BrowseResponse( val menu: Menu, ) + @Serializable + data class MusicEditablePlaylistDetailHeaderRenderer( + val header: Header, + ) { + @Serializable + data class Header( + val musicDetailHeaderRenderer: MusicDetailHeaderRenderer, + ) + } + @Serializable data class MusicVisualHeaderRenderer( val title: Runs, diff --git a/innertube/src/main/java/com/zionhuang/innertube/pages/ArtistItemsPage.kt b/innertube/src/main/java/com/zionhuang/innertube/pages/ArtistItemsPage.kt index bb4866d66..04a7d6dca 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/pages/ArtistItemsPage.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/pages/ArtistItemsPage.kt @@ -1,6 +1,15 @@ package com.zionhuang.innertube.pages -import com.zionhuang.innertube.models.* +import com.zionhuang.innertube.models.Album +import com.zionhuang.innertube.models.AlbumItem +import com.zionhuang.innertube.models.Artist +import com.zionhuang.innertube.models.MusicResponsiveListItemRenderer +import com.zionhuang.innertube.models.MusicTwoRowItemRenderer +import com.zionhuang.innertube.models.PlaylistItem +import com.zionhuang.innertube.models.SongItem +import com.zionhuang.innertube.models.YTItem +import com.zionhuang.innertube.models.oddElements +import com.zionhuang.innertube.models.splitBySeparator import com.zionhuang.innertube.utils.parseTime data class ArtistItemsPage( @@ -48,7 +57,7 @@ data class ArtistItemsPage( ?.watchPlaylistEndpoint?.playlistId ?: return null, title = renderer.title.runs?.firstOrNull()?.text ?: return null, artists = null, - year = renderer.subtitle.runs?.lastOrNull()?.text?.toIntOrNull(), + year = renderer.subtitle?.runs?.lastOrNull()?.text?.toIntOrNull(), thumbnail = renderer.thumbnailRenderer.musicThumbnailRenderer?.getThumbnailUrl() ?: return null, explicit = renderer.subtitleBadges?.find { it.musicInlineBadgeRenderer.icon.iconType == "MUSIC_EXPLICIT_BADGE" @@ -58,7 +67,7 @@ data class ArtistItemsPage( renderer.isSong -> SongItem( id = renderer.navigationEndpoint.watchEndpoint?.videoId ?: return null, title = renderer.title.runs?.firstOrNull()?.text ?: return null, - artists = renderer.subtitle.runs?.splitBySeparator()?.firstOrNull()?.oddElements()?.map { + artists = renderer.subtitle?.runs?.splitBySeparator()?.firstOrNull()?.oddElements()?.map { Artist( name = it.text, id = it.navigationEndpoint?.browseEndpoint?.browseId @@ -72,19 +81,19 @@ data class ArtistItemsPage( renderer.isPlaylist -> PlaylistItem( id = renderer.navigationEndpoint.browseEndpoint?.browseId?.removePrefix("VL") ?: return null, title = renderer.title.runs?.firstOrNull()?.text ?: return null, - author = renderer.subtitle.runs?.getOrNull(2)?.let { + author = renderer.subtitle?.runs?.getOrNull(2)?.let { Artist( name = it.text, id = it.navigationEndpoint?.browseEndpoint?.browseId ) - } ?: return null, - songCountText = renderer.subtitle.runs.getOrNull(4)?.text, + }, + songCountText = renderer.subtitle?.runs?.getOrNull(4)?.text, thumbnail = renderer.thumbnailRenderer.musicThumbnailRenderer?.getThumbnailUrl() ?: return null, playEndpoint = renderer.thumbnailOverlay ?.musicItemThumbnailOverlayRenderer?.content ?.musicPlayButtonRenderer?.playNavigationEndpoint ?.watchPlaylistEndpoint ?: return null, - shuffleEndpoint = renderer.menu.menuRenderer.items.find { + shuffleEndpoint = renderer.menu?.menuRenderer?.items?.find { it.menuNavigationItemRenderer?.icon?.iconType == "MUSIC_SHUFFLE" }?.menuNavigationItemRenderer?.navigationEndpoint?.watchPlaylistEndpoint ?: return null, radioEndpoint = renderer.menu.menuRenderer.items.find { diff --git a/innertube/src/main/java/com/zionhuang/innertube/pages/ArtistPage.kt b/innertube/src/main/java/com/zionhuang/innertube/pages/ArtistPage.kt index 0e1ba7d7e..8eee23723 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/pages/ArtistPage.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/pages/ArtistPage.kt @@ -1,6 +1,19 @@ package com.zionhuang.innertube.pages -import com.zionhuang.innertube.models.* +import com.zionhuang.innertube.models.Album +import com.zionhuang.innertube.models.AlbumItem +import com.zionhuang.innertube.models.Artist +import com.zionhuang.innertube.models.ArtistItem +import com.zionhuang.innertube.models.BrowseEndpoint +import com.zionhuang.innertube.models.MusicCarouselShelfRenderer +import com.zionhuang.innertube.models.MusicResponsiveListItemRenderer +import com.zionhuang.innertube.models.MusicShelfRenderer +import com.zionhuang.innertube.models.MusicTwoRowItemRenderer +import com.zionhuang.innertube.models.PlaylistItem +import com.zionhuang.innertube.models.SectionListRenderer +import com.zionhuang.innertube.models.SongItem +import com.zionhuang.innertube.models.YTItem +import com.zionhuang.innertube.models.oddElements data class ArtistSection( val title: String, @@ -76,7 +89,7 @@ data class ArtistPage( SongItem( id = renderer.navigationEndpoint.watchEndpoint?.videoId ?: return null, title = renderer.title.runs?.firstOrNull()?.text ?: return null, - artists = listOfNotNull(renderer.subtitle.runs?.firstOrNull()?.let { + artists = listOfNotNull(renderer.subtitle?.runs?.firstOrNull()?.let { Artist( name = it.text, id = it.navigationEndpoint?.browseEndpoint?.browseId @@ -99,7 +112,7 @@ data class ArtistPage( ?.watchPlaylistEndpoint?.playlistId ?: return null, title = renderer.title.runs?.firstOrNull()?.text ?: return null, artists = null, - year = renderer.subtitle.runs?.lastOrNull()?.text?.toIntOrNull(), + year = renderer.subtitle?.runs?.lastOrNull()?.text?.toIntOrNull(), thumbnail = renderer.thumbnailRenderer.musicThumbnailRenderer?.getThumbnailUrl() ?: return null, explicit = renderer.subtitleBadges?.find { it.musicInlineBadgeRenderer.icon.iconType == "MUSIC_EXPLICIT_BADGE" @@ -112,7 +125,7 @@ data class ArtistPage( id = renderer.navigationEndpoint.browseEndpoint?.browseId?.removePrefix("VL") ?: return null, title = renderer.title.runs?.firstOrNull()?.text ?: return null, author = Artist( - name = renderer.subtitle.runs?.lastOrNull()?.text ?: return null, + name = renderer.subtitle?.runs?.lastOrNull()?.text ?: return null, id = null ), songCountText = null, @@ -121,7 +134,7 @@ data class ArtistPage( ?.musicItemThumbnailOverlayRenderer?.content ?.musicPlayButtonRenderer?.playNavigationEndpoint ?.watchPlaylistEndpoint ?: return null, - shuffleEndpoint = renderer.menu.menuRenderer.items.find { + shuffleEndpoint = renderer.menu?.menuRenderer?.items?.find { it.menuNavigationItemRenderer?.icon?.iconType == "MUSIC_SHUFFLE" }?.menuNavigationItemRenderer?.navigationEndpoint?.watchPlaylistEndpoint ?: return null, radioEndpoint = renderer.menu.menuRenderer.items.find { @@ -134,7 +147,7 @@ data class ArtistPage( id = renderer.navigationEndpoint.browseEndpoint?.browseId ?: return null, title = renderer.title.runs?.lastOrNull()?.text ?: return null, thumbnail = renderer.thumbnailRenderer.musicThumbnailRenderer?.getThumbnailUrl() ?: return null, - shuffleEndpoint = renderer.menu.menuRenderer.items.find { + shuffleEndpoint = renderer.menu?.menuRenderer?.items?.find { it.menuNavigationItemRenderer?.icon?.iconType == "MUSIC_SHUFFLE" }?.menuNavigationItemRenderer?.navigationEndpoint?.watchPlaylistEndpoint ?: return null, radioEndpoint = renderer.menu.menuRenderer.items.find { diff --git a/innertube/src/main/java/com/zionhuang/innertube/pages/NewReleaseAlbumPage.kt b/innertube/src/main/java/com/zionhuang/innertube/pages/NewReleaseAlbumPage.kt index fbf840d61..04d8d5c58 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/pages/NewReleaseAlbumPage.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/pages/NewReleaseAlbumPage.kt @@ -1,6 +1,10 @@ package com.zionhuang.innertube.pages -import com.zionhuang.innertube.models.* +import com.zionhuang.innertube.models.AlbumItem +import com.zionhuang.innertube.models.Artist +import com.zionhuang.innertube.models.MusicTwoRowItemRenderer +import com.zionhuang.innertube.models.oddElements +import com.zionhuang.innertube.models.splitBySeparator object NewReleaseAlbumPage { fun fromMusicTwoRowItemRenderer(renderer: MusicTwoRowItemRenderer): AlbumItem? { @@ -11,7 +15,7 @@ object NewReleaseAlbumPage { ?.musicPlayButtonRenderer?.playNavigationEndpoint ?.watchPlaylistEndpoint?.playlistId ?: return null, title = renderer.title.runs?.firstOrNull()?.text ?: return null, - artists = renderer.subtitle.runs?.splitBySeparator()?.getOrNull(1)?.oddElements()?.map { + artists = renderer.subtitle?.runs?.splitBySeparator()?.getOrNull(1)?.oddElements()?.map { Artist( name = it.text, id = it.navigationEndpoint?.browseEndpoint?.browseId diff --git a/innertube/src/main/java/com/zionhuang/innertube/pages/RelatedPage.kt b/innertube/src/main/java/com/zionhuang/innertube/pages/RelatedPage.kt index 9460c9350..ea493709c 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/pages/RelatedPage.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/pages/RelatedPage.kt @@ -1,6 +1,15 @@ package com.zionhuang.innertube.pages -import com.zionhuang.innertube.models.* +import com.zionhuang.innertube.models.Album +import com.zionhuang.innertube.models.AlbumItem +import com.zionhuang.innertube.models.Artist +import com.zionhuang.innertube.models.ArtistItem +import com.zionhuang.innertube.models.MusicResponsiveListItemRenderer +import com.zionhuang.innertube.models.MusicTwoRowItemRenderer +import com.zionhuang.innertube.models.PlaylistItem +import com.zionhuang.innertube.models.SongItem +import com.zionhuang.innertube.models.YTItem +import com.zionhuang.innertube.models.oddElements data class RelatedPage( val songs: List, @@ -44,7 +53,7 @@ data class RelatedPage( ?.watchPlaylistEndpoint?.playlistId ?: return null, title = renderer.title.runs?.firstOrNull()?.text ?: return null, artists = null, - year = renderer.subtitle.runs?.lastOrNull()?.text?.toIntOrNull(), + year = renderer.subtitle?.runs?.lastOrNull()?.text?.toIntOrNull(), thumbnail = renderer.thumbnailRenderer.musicThumbnailRenderer?.getThumbnailUrl() ?: return null, explicit = renderer.subtitleBadges?.find { it.musicInlineBadgeRenderer.icon.iconType == "MUSIC_EXPLICIT_BADGE" @@ -53,7 +62,7 @@ data class RelatedPage( renderer.isPlaylist -> PlaylistItem( id = renderer.navigationEndpoint.browseEndpoint?.browseId?.removePrefix("VL") ?: return null, title = renderer.title.runs?.firstOrNull()?.text ?: return null, - author = renderer.subtitle.runs?.getOrNull(2)?.let { + author = renderer.subtitle?.runs?.getOrNull(2)?.let { Artist( name = it.text, id = it.navigationEndpoint?.browseEndpoint?.browseId @@ -65,7 +74,7 @@ data class RelatedPage( ?.musicItemThumbnailOverlayRenderer?.content ?.musicPlayButtonRenderer?.playNavigationEndpoint ?.watchPlaylistEndpoint ?: return null, - shuffleEndpoint = renderer.menu.menuRenderer.items.find { + shuffleEndpoint = renderer.menu?.menuRenderer?.items?.find { it.menuNavigationItemRenderer?.icon?.iconType == "MUSIC_SHUFFLE" }?.menuNavigationItemRenderer?.navigationEndpoint?.watchPlaylistEndpoint ?: return null, radioEndpoint = renderer.menu.menuRenderer.items.find { @@ -77,7 +86,7 @@ data class RelatedPage( id = renderer.navigationEndpoint.browseEndpoint?.browseId ?: return null, title = renderer.title.runs?.firstOrNull()?.text ?: return null, thumbnail = renderer.thumbnailRenderer.musicThumbnailRenderer?.getThumbnailUrl() ?: return null, - shuffleEndpoint = renderer.menu.menuRenderer.items.find { + shuffleEndpoint = renderer.menu?.menuRenderer?.items?.find { it.menuNavigationItemRenderer?.icon?.iconType == "MUSIC_SHUFFLE" }?.menuNavigationItemRenderer?.navigationEndpoint?.watchPlaylistEndpoint ?: return null, radioEndpoint = renderer.menu.menuRenderer.items.find { From 714af09afc476cacdba15639d252ba406cdd5fd8 Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Sat, 15 Jul 2023 23:24:17 +0800 Subject: [PATCH 27/84] Fix crash if cookie is empty --- .../main/java/com/zionhuang/innertube/utils/Utils.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/innertube/src/main/java/com/zionhuang/innertube/utils/Utils.kt b/innertube/src/main/java/com/zionhuang/innertube/utils/Utils.kt index 8f63cc50b..2e727f0e6 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/utils/Utils.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/utils/Utils.kt @@ -26,10 +26,12 @@ fun ByteArray.toHex(): String = joinToString(separator = "") { eachByte -> "%02x fun sha1(str: String): String = MessageDigest.getInstance("SHA-1").digest(str.toByteArray()).toHex() fun parseCookieString(cookie: String): Map = - cookie.split("; ").associate { - val (key, value) = it.split("=") - key to value - } + cookie.split("; ") + .filter { it.isNotEmpty() } + .associate { + val (key, value) = it.split("=") + key to value + } fun String.parseTime(): Int? { try { From 8d0f723e556927afce8e642d03cda97e5a1a443b Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Mon, 17 Jul 2023 17:20:42 +0800 Subject: [PATCH 28/84] Remove unnecessary BitmapProvider --- .../music/playback/BitmapProvider.kt | 60 ------------------- .../zionhuang/music/playback/MusicService.kt | 4 -- .../music/playback/PlayerConnection.kt | 9 --- 3 files changed, 73 deletions(-) delete mode 100644 app/src/main/java/com/zionhuang/music/playback/BitmapProvider.kt diff --git a/app/src/main/java/com/zionhuang/music/playback/BitmapProvider.kt b/app/src/main/java/com/zionhuang/music/playback/BitmapProvider.kt deleted file mode 100644 index 6043651de..000000000 --- a/app/src/main/java/com/zionhuang/music/playback/BitmapProvider.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.zionhuang.music.playback - -import android.content.Context -import android.graphics.Bitmap -import android.graphics.drawable.BitmapDrawable -import android.util.LruCache -import coil.imageLoader -import coil.request.Disposable -import coil.request.ImageRequest - -class BitmapProvider(private val context: Context) { - var currentUrl: String? = null - var currentBitmap: Bitmap? = null - private val map = LruCache(MAX_CACHE_SIZE) - private var disposable: Disposable? = null - var onBitmapChanged: (Bitmap?) -> Unit = {} - set(value) { - field = value - value(currentBitmap) - } - - fun load(url: String, callback: (Bitmap) -> Unit): Bitmap? { - if (url == currentUrl) return map.get(url) - currentUrl = url - disposable?.dispose() - val cache = map.get(url) - if (cache == null) { - disposable = context.imageLoader.enqueue( - ImageRequest.Builder(context) - .data(url) - .allowHardware(false) - .target( - onSuccess = { drawable -> - val bitmap = (drawable as BitmapDrawable).bitmap - map.put(url, bitmap) - callback(bitmap) - currentBitmap = bitmap - onBitmapChanged(bitmap) - } - ) - .build() - ) - } else { - currentBitmap = cache - onBitmapChanged(cache) - } - return cache - } - - fun clear() { - disposable?.dispose() - currentUrl = null - currentBitmap = null - onBitmapChanged(null) - } - - companion object { - const val MAX_CACHE_SIZE = 15 - } -} \ No newline at end of file diff --git a/app/src/main/java/com/zionhuang/music/playback/MusicService.kt b/app/src/main/java/com/zionhuang/music/playback/MusicService.kt index b85b9bdb9..219738c80 100644 --- a/app/src/main/java/com/zionhuang/music/playback/MusicService.kt +++ b/app/src/main/java/com/zionhuang/music/playback/MusicService.kt @@ -171,7 +171,6 @@ class MusicService : MediaLibraryService(), private val binder = MusicBinder() private lateinit var connectivityManager: ConnectivityManager - val bitmapProvider = BitmapProvider(this) private val audioQuality by enumPreference(this, AudioQualityKey, AudioQuality.AUTO) @@ -503,9 +502,6 @@ class MusicService : MediaLibraryService(), } } } - if (mediaItem == null) { - bitmapProvider.clear() - } if (pauseWhenSongEnd) { pauseWhenSongEnd = false player.pause() diff --git a/app/src/main/java/com/zionhuang/music/playback/PlayerConnection.kt b/app/src/main/java/com/zionhuang/music/playback/PlayerConnection.kt index 1fa3de085..402eec084 100644 --- a/app/src/main/java/com/zionhuang/music/playback/PlayerConnection.kt +++ b/app/src/main/java/com/zionhuang/music/playback/PlayerConnection.kt @@ -1,6 +1,5 @@ package com.zionhuang.music.playback -import android.graphics.Bitmap import androidx.media3.common.MediaItem import androidx.media3.common.PlaybackException import androidx.media3.common.Player @@ -63,17 +62,10 @@ class PlayerConnection( val canSkipPrevious = MutableStateFlow(true) val canSkipNext = MutableStateFlow(true) - var onBitmapChanged: (Bitmap?) -> Unit = {} - set(value) { - field = value - service.bitmapProvider.onBitmapChanged = value - } - val error = MutableStateFlow(null) init { player.addListener(this) - service.bitmapProvider.onBitmapChanged = onBitmapChanged playbackState.value = player.playbackState playWhenReady.value = player.playWhenReady @@ -174,7 +166,6 @@ class PlayerConnection( } fun dispose() { - service.bitmapProvider.onBitmapChanged = {} player.removeListener(this) } } From 39e83b661d17f2baea8fe61b5fa4189ca37192f1 Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Mon, 17 Jul 2023 21:52:15 +0800 Subject: [PATCH 29/84] Mood and genres --- .../java/com/zionhuang/music/MainActivity.kt | 24 ++- .../zionhuang/music/ui/screens/HomeScreen.kt | 29 +-- .../music/ui/screens/MoodAndGenresScreen.kt | 136 ++++++++++++++ .../music/ui/screens/YouTubeBrowseScreen.kt | 173 ++++++++++++++++++ .../viewmodels/MoodAndGenresViewModel.kt | 25 +++ .../viewmodels/YouTubeBrowseViewModel.kt | 31 ++++ app/src/main/res/drawable/mood.xml | 9 + app/src/main/res/values-DE/strings.xml | 1 + app/src/main/res/values-cs/strings.xml | 1 + app/src/main/res/values-es-rUS/strings.xml | 1 + app/src/main/res/values-es/strings.xml | 1 + app/src/main/res/values-fa-rIR/strings.xml | 1 + app/src/main/res/values-fi-rFI/strings.xml | 1 + app/src/main/res/values-fr-rFR/strings.xml | 1 + app/src/main/res/values-hu/strings.xml | 1 + app/src/main/res/values-id/strings.xml | 1 + app/src/main/res/values-it/strings.xml | 1 + app/src/main/res/values-ja-rJP/strings.xml | 1 + app/src/main/res/values-ko-rKR/strings.xml | 1 + app/src/main/res/values-ml-rIN/strings.xml | 1 + app/src/main/res/values-nl/strings.xml | 1 + app/src/main/res/values-or-rIN/strings.xml | 1 + app/src/main/res/values-pa/strings.xml | 1 + app/src/main/res/values-pt-rBR/strings.xml | 1 + app/src/main/res/values-ru-rRU/strings.xml | 1 + app/src/main/res/values-sv-rSE/strings.xml | 1 + app/src/main/res/values-tr/strings.xml | 1 + app/src/main/res/values-uk-rUA/strings.xml | 1 + app/src/main/res/values-zh-rCN/strings.xml | 1 + app/src/main/res/values-zh-rTW/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + .../java/com/zionhuang/innertube/YouTube.kt | 39 ++++ .../zionhuang/innertube/pages/ArtistPage.kt | 7 +- .../zionhuang/innertube/pages/BrowseResult.kt | 13 ++ .../innertube/pages/MoodAndGenres.kt | 30 +++ .../zionhuang/innertube/pages/RelatedPage.kt | 4 +- 36 files changed, 523 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/com/zionhuang/music/ui/screens/MoodAndGenresScreen.kt create mode 100644 app/src/main/java/com/zionhuang/music/ui/screens/YouTubeBrowseScreen.kt create mode 100644 app/src/main/java/com/zionhuang/music/viewmodels/MoodAndGenresViewModel.kt create mode 100644 app/src/main/java/com/zionhuang/music/viewmodels/YouTubeBrowseViewModel.kt create mode 100644 app/src/main/res/drawable/mood.xml create mode 100644 innertube/src/main/java/com/zionhuang/innertube/pages/BrowseResult.kt create mode 100644 innertube/src/main/java/com/zionhuang/innertube/pages/MoodAndGenres.kt diff --git a/app/src/main/java/com/zionhuang/music/MainActivity.kt b/app/src/main/java/com/zionhuang/music/MainActivity.kt index 359380711..cbd3f45d3 100644 --- a/app/src/main/java/com/zionhuang/music/MainActivity.kt +++ b/app/src/main/java/com/zionhuang/music/MainActivity.kt @@ -415,6 +415,12 @@ class MainActivity : ComponentActivity() { composable("stats") { StatsScreen(navController) } + composable("mood_and_genres") { + MoodAndGenresScreen(navController, scrollBehavior) + } + composable("account") { + AccountScreen(navController, scrollBehavior) + } composable("new_release") { NewReleaseScreen(navController, scrollBehavior) } @@ -506,6 +512,21 @@ class MainActivity : ComponentActivity() { LocalPlaylistScreen(navController, scrollBehavior) } } + composable( + route = "youtube_browse/{browseId}?params={params}", + arguments = listOf( + navArgument("browseId") { + type = NavType.StringType + nullable = true + }, + navArgument("params") { + type = NavType.StringType + nullable = true + } + ) + ) { + YouTubeBrowseScreen(navController, scrollBehavior) + } composable("settings") { SettingsScreen(navController, scrollBehavior) } @@ -533,9 +554,6 @@ class MainActivity : ComponentActivity() { composable("login") { LoginScreen(navController) } - composable("account") { - AccountScreen(navController, scrollBehavior) - } } AnimatedVisibility( diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/HomeScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/HomeScreen.kt index d6d09f583..e4dbc133e 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/HomeScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/HomeScreen.kt @@ -15,7 +15,6 @@ import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -96,7 +95,7 @@ fun HomeScreen( Spacer(Modifier.height(LocalPlayerAwareWindowInsets.current.asPaddingValues().calculateTopPadding())) Row( - horizontalArrangement = Arrangement.spacedBy(12.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier .windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Horizontal)) .padding(horizontal = 12.dp, vertical = 6.dp) @@ -115,6 +114,13 @@ fun HomeScreen( modifier = Modifier.weight(1f) ) + NavigationTile( + title = stringResource(R.string.mood_and_genres), + icon = R.drawable.mood, + onClick = { navController.navigate("mood_and_genres") }, + modifier = Modifier.weight(1f) + ) + if (isLoggedIn) { NavigationTile( title = stringResource(R.string.account), @@ -303,28 +309,23 @@ fun NavigationTile( @DrawableRes icon: Int, onClick: () -> Unit, modifier: Modifier = Modifier, - enabled: Boolean = true, ) { - Row( - horizontalArrangement = Arrangement.spacedBy(4.dp), - verticalAlignment = Alignment.CenterVertically, + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(4.dp), modifier = modifier - .clip(RoundedCornerShape(6.dp)) - .background(MaterialTheme.colorScheme.surfaceColorAtElevation(6.dp)) - .clickable(enabled = enabled, onClick = onClick) - .padding(12.dp) - .alpha(if (enabled) 1f else 0.5f) + .clip(RoundedCornerShape(4.dp)) + .clickable(onClick = onClick) + .padding(4.dp), ) { Icon( painter = painterResource(icon), contentDescription = null ) - Spacer(Modifier.height(6.dp)) - Text( text = title, - style = MaterialTheme.typography.labelLarge, + style = MaterialTheme.typography.labelMedium, maxLines = 1, overflow = TextOverflow.Ellipsis ) diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/MoodAndGenresScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/MoodAndGenresScreen.kt new file mode 100644 index 000000000..beef4b710 --- /dev/null +++ b/app/src/main/java/com/zionhuang/music/ui/screens/MoodAndGenresScreen.kt @@ -0,0 +1,136 @@ +package com.zionhuang.music.ui.screens + +import android.content.res.Configuration.ORIENTATION_LANDSCAPE +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.material3.surfaceColorAtElevation +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavController +import com.zionhuang.music.LocalPlayerAwareWindowInsets +import com.zionhuang.music.R +import com.zionhuang.music.ui.component.shimmer.ListItemPlaceHolder +import com.zionhuang.music.ui.component.shimmer.ShimmerHost +import com.zionhuang.music.viewmodels.MoodAndGenresViewModel + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MoodAndGenresScreen( + navController: NavController, + scrollBehavior: TopAppBarScrollBehavior, + viewModel: MoodAndGenresViewModel = hiltViewModel(), +) { + val localConfiguration = LocalConfiguration.current + val itemsPerRow = if (localConfiguration.orientation == ORIENTATION_LANDSCAPE) 3 else 2 + + val moodAndGenresList by viewModel.moodAndGenres.collectAsState() + + LazyColumn( + contentPadding = LocalPlayerAwareWindowInsets.current.asPaddingValues() + ) { + if (moodAndGenresList == null) { + item { + ShimmerHost { + repeat(8) { + ListItemPlaceHolder() + } + } + } + } + + moodAndGenresList?.forEach { moodAndGenres -> + item { + Text( + text = moodAndGenres.title, + style = MaterialTheme.typography.headlineMedium, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 8.dp) + ) + + Column( + modifier = Modifier.padding(horizontal = 6.dp) + ) { + moodAndGenres.items.chunked(itemsPerRow).forEach { row -> + Row { + row.forEach { + MoodAndGenresButton( + title = it.title, + onClick = { + navController.navigate("youtube_browse/${it.endpoint.browseId}?params=${it.endpoint.params}") + }, + modifier = Modifier.weight(1f) + ) + } + + repeat(itemsPerRow - row.size) { + Spacer(Modifier.weight(1f)) + } + } + } + } + } + } + } + + TopAppBar( + title = { Text(stringResource(R.string.mood_and_genres)) }, + navigationIcon = { + IconButton(onClick = navController::navigateUp) { + Icon( + painterResource(R.drawable.arrow_back), + contentDescription = null + ) + } + }, + scrollBehavior = scrollBehavior + ) +} + +@Composable +fun MoodAndGenresButton( + title: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Box( + modifier = modifier + .padding(6.dp) + .clip(RoundedCornerShape(6.dp)) + .background(MaterialTheme.colorScheme.surfaceColorAtElevation(6.dp)) + .clickable(onClick = onClick) + .padding(12.dp) + ) { + Text( + text = title, + style = MaterialTheme.typography.labelLarge, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } +} diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/YouTubeBrowseScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/YouTubeBrowseScreen.kt new file mode 100644 index 000000000..02a948503 --- /dev/null +++ b/app/src/main/java/com/zionhuang/music/ui/screens/YouTubeBrowseScreen.kt @@ -0,0 +1,173 @@ +package com.zionhuang.music.ui.screens + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavController +import com.zionhuang.innertube.models.AlbumItem +import com.zionhuang.innertube.models.ArtistItem +import com.zionhuang.innertube.models.PlaylistItem +import com.zionhuang.innertube.models.SongItem +import com.zionhuang.innertube.models.WatchEndpoint +import com.zionhuang.music.LocalPlayerAwareWindowInsets +import com.zionhuang.music.LocalPlayerConnection +import com.zionhuang.music.R +import com.zionhuang.music.extensions.togglePlayPause +import com.zionhuang.music.models.toMediaMetadata +import com.zionhuang.music.playback.queues.YouTubeQueue +import com.zionhuang.music.ui.component.LocalMenuState +import com.zionhuang.music.ui.component.YouTubeListItem +import com.zionhuang.music.ui.component.shimmer.ListItemPlaceHolder +import com.zionhuang.music.ui.component.shimmer.ShimmerHost +import com.zionhuang.music.ui.menu.YouTubeAlbumMenu +import com.zionhuang.music.ui.menu.YouTubeArtistMenu +import com.zionhuang.music.ui.menu.YouTubePlaylistMenu +import com.zionhuang.music.ui.menu.YouTubeSongMenu +import com.zionhuang.music.viewmodels.YouTubeBrowseViewModel + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) +@Composable +fun YouTubeBrowseScreen( + navController: NavController, + scrollBehavior: TopAppBarScrollBehavior, + viewModel: YouTubeBrowseViewModel = hiltViewModel(), +) { + val menuState = LocalMenuState.current + val playerConnection = LocalPlayerConnection.current ?: return + val isPlaying by playerConnection.isPlaying.collectAsState() + val mediaMetadata by playerConnection.mediaMetadata.collectAsState() + + val browseResult by viewModel.result.collectAsState() + + val coroutineScope = rememberCoroutineScope() + + LazyColumn( + contentPadding = LocalPlayerAwareWindowInsets.current.asPaddingValues() + ) { + if (browseResult == null) { + item { + ShimmerHost { + repeat(8) { + ListItemPlaceHolder() + } + } + } + } + + browseResult?.items?.forEach { + it.title?.let { title -> + item { + Text( + text = title, + style = MaterialTheme.typography.headlineMedium, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 8.dp) + ) + } + } + + items(it.items) { item -> + YouTubeListItem( + item = item, + isActive = when (item) { + is SongItem -> mediaMetadata?.id == item.id + is AlbumItem -> mediaMetadata?.album?.id == item.id + else -> false + }, + isPlaying = isPlaying, + trailingContent = { + IconButton( + onClick = { + menuState.show { + when (item) { + is SongItem -> YouTubeSongMenu( + song = item, + navController = navController, + playerConnection = playerConnection, + onDismiss = menuState::dismiss + ) + + is AlbumItem -> YouTubeAlbumMenu( + album = item, + navController = navController, + playerConnection = playerConnection, + onDismiss = menuState::dismiss + ) + + is ArtistItem -> YouTubeArtistMenu( + artist = item, + playerConnection = playerConnection, + onDismiss = menuState::dismiss + ) + + is PlaylistItem -> YouTubePlaylistMenu( + playlist = item, + playerConnection = playerConnection, + coroutineScope = coroutineScope, + onDismiss = menuState::dismiss + ) + } + } + } + ) { + Icon( + painter = painterResource(R.drawable.more_vert), + contentDescription = null + ) + } + }, + modifier = Modifier + .clickable { + when (item) { + is SongItem -> { + if (item.id == mediaMetadata?.id) { + playerConnection.player.togglePlayPause() + } else { + playerConnection.playQueue(YouTubeQueue(WatchEndpoint(videoId = item.id), item.toMediaMetadata())) + } + } + + is AlbumItem -> navController.navigate("album/${item.id}") + is ArtistItem -> navController.navigate("artist/${item.id}") + is PlaylistItem -> navController.navigate("online_playlist/${item.id}") + } + } + .animateItemPlacement() + ) + } + } + } + + TopAppBar( + title = { Text(browseResult?.title.orEmpty()) }, + navigationIcon = { + IconButton(onClick = navController::navigateUp) { + Icon( + painterResource(R.drawable.arrow_back), + contentDescription = null + ) + } + }, + scrollBehavior = scrollBehavior + ) +} diff --git a/app/src/main/java/com/zionhuang/music/viewmodels/MoodAndGenresViewModel.kt b/app/src/main/java/com/zionhuang/music/viewmodels/MoodAndGenresViewModel.kt new file mode 100644 index 000000000..ea2a29d51 --- /dev/null +++ b/app/src/main/java/com/zionhuang/music/viewmodels/MoodAndGenresViewModel.kt @@ -0,0 +1,25 @@ +package com.zionhuang.music.viewmodels + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.zionhuang.innertube.YouTube +import com.zionhuang.innertube.pages.MoodAndGenres +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class MoodAndGenresViewModel @Inject constructor() : ViewModel() { + val moodAndGenres = MutableStateFlow?>(null) + + init { + viewModelScope.launch { + YouTube.moodAndGenres().onSuccess { + moodAndGenres.value = it + }.onFailure { + it.printStackTrace() + } + } + } +} diff --git a/app/src/main/java/com/zionhuang/music/viewmodels/YouTubeBrowseViewModel.kt b/app/src/main/java/com/zionhuang/music/viewmodels/YouTubeBrowseViewModel.kt new file mode 100644 index 000000000..2e53332d3 --- /dev/null +++ b/app/src/main/java/com/zionhuang/music/viewmodels/YouTubeBrowseViewModel.kt @@ -0,0 +1,31 @@ +package com.zionhuang.music.viewmodels + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.zionhuang.innertube.YouTube +import com.zionhuang.innertube.pages.BrowseResult +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class YouTubeBrowseViewModel @Inject constructor( + savedStateHandle: SavedStateHandle, +) : ViewModel() { + private val browseId = savedStateHandle.get("browseId")!! + private val params = savedStateHandle.get("params") + + val result = MutableStateFlow(null) + + init { + viewModelScope.launch { + YouTube.browse(browseId, params).onSuccess { + result.value = it + }.onFailure { + it.printStackTrace() + } + } + } +} diff --git a/app/src/main/res/drawable/mood.xml b/app/src/main/res/drawable/mood.xml new file mode 100644 index 000000000..adb1afa06 --- /dev/null +++ b/app/src/main/res/drawable/mood.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values-DE/strings.xml b/app/src/main/res/values-DE/strings.xml index 6a9cb870b..94ab83ee2 100644 --- a/app/src/main/res/values-DE/strings.xml +++ b/app/src/main/res/values-DE/strings.xml @@ -15,6 +15,7 @@ Wiedergabeverlauf Statistiken + Mood and Genres Account Schnellauswahl Hören Sie sich einige Songs an, um Ihre Schnellauswahl zu treffen diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 356e76ee2..4b7927c5b 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -17,6 +17,7 @@ History Stats + Mood and Genres Account Quick picks Listen some songs to generate your quick picks diff --git a/app/src/main/res/values-es-rUS/strings.xml b/app/src/main/res/values-es-rUS/strings.xml index e5bc93d18..3e9e24c86 100644 --- a/app/src/main/res/values-es-rUS/strings.xml +++ b/app/src/main/res/values-es-rUS/strings.xml @@ -15,6 +15,7 @@ History Stats + Mood and Genres Account Quick picks Listen some songs to generate your quick picks diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index ebab4eeea..7c3f536fb 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -15,6 +15,7 @@ Historial Estadísticas + Mood and Genres Account Selecciones rápidas Escucha algunas canciones para generar tus selecciones rápidas diff --git a/app/src/main/res/values-fa-rIR/strings.xml b/app/src/main/res/values-fa-rIR/strings.xml index 768bfd308..e9d39f78a 100644 --- a/app/src/main/res/values-fa-rIR/strings.xml +++ b/app/src/main/res/values-fa-rIR/strings.xml @@ -15,6 +15,7 @@ History Stats + Mood and Genres Account Quick picks Listen some songs to generate your quick picks diff --git a/app/src/main/res/values-fi-rFI/strings.xml b/app/src/main/res/values-fi-rFI/strings.xml index 72c009f7c..de3f9d596 100644 --- a/app/src/main/res/values-fi-rFI/strings.xml +++ b/app/src/main/res/values-fi-rFI/strings.xml @@ -15,6 +15,7 @@ History Stats + Mood and Genres Account Quick picks Listen some songs to generate your quick picks diff --git a/app/src/main/res/values-fr-rFR/strings.xml b/app/src/main/res/values-fr-rFR/strings.xml index 19c496d8c..0eab2292e 100644 --- a/app/src/main/res/values-fr-rFR/strings.xml +++ b/app/src/main/res/values-fr-rFR/strings.xml @@ -16,6 +16,7 @@ History Stats + Mood and Genres Account Quick picks Listen some songs to generate your quick picks diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 26146e1c2..f204afadf 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -15,6 +15,7 @@ Előzmény Statisztika + Mood and Genres Account Gyors választás Hallgasson meg néhány dalt a gyors választások elkészítéséhez diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index 2663723a6..55d6bc0b4 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -14,6 +14,7 @@ History Stats + Mood and Genres Account Quick picks Listen some songs to generate your quick picks diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 9e8ebac98..3dd7caa7a 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -15,6 +15,7 @@ Cronologia Statistiche + Mood and Genres Account Scelte rapide Ascolta alcuni brani per generare le tue scelte rapide diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index 0fc80e316..32807e68b 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -14,6 +14,7 @@ 履歴 統計 + Mood and Genres Account ピックアップ 何曲か再生するとピックアップが生成できます diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml index a89011ece..93bae5aa3 100644 --- a/app/src/main/res/values-ko-rKR/strings.xml +++ b/app/src/main/res/values-ko-rKR/strings.xml @@ -14,6 +14,7 @@ History Stats + Mood and Genres Account Quick picks Listen some songs to generate your quick picks diff --git a/app/src/main/res/values-ml-rIN/strings.xml b/app/src/main/res/values-ml-rIN/strings.xml index 86d7e027a..0bd5d2416 100644 --- a/app/src/main/res/values-ml-rIN/strings.xml +++ b/app/src/main/res/values-ml-rIN/strings.xml @@ -15,6 +15,7 @@ History Stats + Mood and Genres Account Quick picks Listen some songs to generate your quick picks diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index a1e52306f..95ac85fbe 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -15,6 +15,7 @@ Geschiedenis Statistieken + Mood and Genres Account Snelle keuzes Beluister een aantal nummers om je snelle keuzes te genereren diff --git a/app/src/main/res/values-or-rIN/strings.xml b/app/src/main/res/values-or-rIN/strings.xml index cb550b428..94d79a9f2 100644 --- a/app/src/main/res/values-or-rIN/strings.xml +++ b/app/src/main/res/values-or-rIN/strings.xml @@ -15,6 +15,7 @@ History Stats + Mood and Genres Account Quick picks Listen some songs to generate your quick picks diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index 7e24dc101..8466b0527 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -15,6 +15,7 @@ ਅਤੀਤ ਅੰਕੜੇ + Mood and Genres Account ਉਂਗਲਾਂ ਤੇ ਚੁਣੇ ਗੀਤ Listen some songs to generate your quick picks diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index a83d6a7c8..6afbd4bed 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -15,6 +15,7 @@ History Stats + Mood and Genres Account Quick picks Listen some songs to generate your quick picks diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index 0fe7971be..3c97c88b4 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -17,6 +17,7 @@ История Статистика + Mood and Genres Account Быстрый выбор Послушайте несколько песен, чтобы создать ваш быстрый выбор diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index 2653b05a4..a2920c4ff 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -15,6 +15,7 @@ History Stats + Mood and Genres Account Quick picks Listen some songs to generate your quick picks diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 8e2bfd955..cf5ff59cc 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -15,6 +15,7 @@ Geçmiş İstatistikler + Mood and Genres Account Hızlı seçimler Hızlı seçimlerinizi oluşturmak için birkaç şarkı dinleyin diff --git a/app/src/main/res/values-uk-rUA/strings.xml b/app/src/main/res/values-uk-rUA/strings.xml index 5b5d32a98..6f9072e62 100644 --- a/app/src/main/res/values-uk-rUA/strings.xml +++ b/app/src/main/res/values-uk-rUA/strings.xml @@ -17,6 +17,7 @@ Історія Статистика + Mood and Genres Account Швидкий вибір Послухайте кілька пісень, щоб створити ваш швидкий вибір diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 3fcdbca3b..21728b7b9 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -14,6 +14,7 @@ 历史记录 统计 + Mood and Genres Account 歌曲快选 Listen some songs to generate your quick picks diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 4b8f450b0..ab03c867c 100755 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -14,6 +14,7 @@ 歷史記錄 統計 + 情境與類型 帳號 歌曲快選 聽一些音樂讓我們知道您的喜好 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fe5648dfc..0a5351245 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -14,6 +14,7 @@ History Stats + Mood and Genres Account Quick picks Listen some songs to generate your quick picks diff --git a/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt b/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt index 427ad7a65..093c7c434 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt @@ -6,6 +6,7 @@ import com.zionhuang.innertube.models.Artist import com.zionhuang.innertube.models.ArtistItem import com.zionhuang.innertube.models.BrowseEndpoint import com.zionhuang.innertube.models.GridRenderer +import com.zionhuang.innertube.models.MusicCarouselShelfRenderer import com.zionhuang.innertube.models.PlaylistItem import com.zionhuang.innertube.models.SearchSuggestions import com.zionhuang.innertube.models.SongItem @@ -32,6 +33,8 @@ import com.zionhuang.innertube.pages.AlbumPage import com.zionhuang.innertube.pages.ArtistItemsContinuationPage import com.zionhuang.innertube.pages.ArtistItemsPage import com.zionhuang.innertube.pages.ArtistPage +import com.zionhuang.innertube.pages.BrowseResult +import com.zionhuang.innertube.pages.MoodAndGenres import com.zionhuang.innertube.pages.NewReleaseAlbumPage import com.zionhuang.innertube.pages.NextPage import com.zionhuang.innertube.pages.NextResult @@ -297,6 +300,42 @@ object YouTube { }.orEmpty() } + suspend fun moodAndGenres(): Result> = runCatching { + val response = innerTube.browse(WEB_REMIX, browseId = "FEmusic_moods_and_genres").body() + response.contents?.singleColumnBrowseResultsRenderer?.tabs?.firstOrNull()?.tabRenderer?.content?.sectionListRenderer?.contents!! + .mapNotNull(MoodAndGenres.Companion::fromSectionListRendererContent) + } + + suspend fun browse(browseId: String, params: String?): Result = runCatching { + val response = innerTube.browse(WEB_REMIX, browseId = browseId, params = params).body() + BrowseResult( + title = response.header?.musicHeaderRenderer?.title?.runs?.firstOrNull()?.text, + items = response.contents?.singleColumnBrowseResultsRenderer?.tabs?.firstOrNull()?.tabRenderer?.content?.sectionListRenderer?.contents?.mapNotNull { content -> + when { + content.gridRenderer != null -> { + BrowseResult.Item( + title = content.gridRenderer.header?.gridHeaderRenderer?.title?.runs?.firstOrNull()?.text, + items = content.gridRenderer.items + .mapNotNull(GridRenderer.Item::musicTwoRowItemRenderer) + .mapNotNull(RelatedPage.Companion::fromMusicTwoRowItemRenderer) + ) + } + + content.musicCarouselShelfRenderer != null -> { + BrowseResult.Item( + title = content.musicCarouselShelfRenderer.header?.musicCarouselShelfBasicHeaderRenderer?.title?.runs?.firstOrNull()?.text, + items = content.musicCarouselShelfRenderer.contents + .mapNotNull(MusicCarouselShelfRenderer.Content::musicTwoRowItemRenderer) + .mapNotNull(RelatedPage.Companion::fromMusicTwoRowItemRenderer) + ) + } + + else -> null + } + }.orEmpty() + ) + } + suspend fun likedPlaylists(): Result> = runCatching { val response = innerTube.browse(WEB_REMIX, browseId = "FEmusic_liked_playlists").body() response.contents?.singleColumnBrowseResultsRenderer?.tabs?.firstOrNull()?.tabRenderer?.content?.sectionListRenderer?.contents?.firstOrNull()?.gridRenderer?.items!! diff --git a/innertube/src/main/java/com/zionhuang/innertube/pages/ArtistPage.kt b/innertube/src/main/java/com/zionhuang/innertube/pages/ArtistPage.kt index 8eee23723..fec6bdb54 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/pages/ArtistPage.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/pages/ArtistPage.kt @@ -103,11 +103,11 @@ data class ArtistPage( } != null ) } + renderer.isAlbum -> { AlbumItem( browseId = renderer.navigationEndpoint.browseEndpoint?.browseId ?: return null, - playlistId = renderer.thumbnailOverlay - ?.musicItemThumbnailOverlayRenderer?.content + playlistId = renderer.thumbnailOverlay?.musicItemThumbnailOverlayRenderer?.content ?.musicPlayButtonRenderer?.playNavigationEndpoint ?.watchPlaylistEndpoint?.playlistId ?: return null, title = renderer.title.runs?.firstOrNull()?.text ?: return null, @@ -119,6 +119,7 @@ data class ArtistPage( } != null ) } + renderer.isPlaylist -> { // Playlist from YouTube Music PlaylistItem( @@ -142,6 +143,7 @@ data class ArtistPage( }?.menuNavigationItemRenderer?.navigationEndpoint?.watchPlaylistEndpoint ?: return null ) } + renderer.isArtist -> { ArtistItem( id = renderer.navigationEndpoint.browseEndpoint?.browseId ?: return null, @@ -155,6 +157,7 @@ data class ArtistPage( }?.menuNavigationItemRenderer?.navigationEndpoint?.watchPlaylistEndpoint ?: return null, ) } + else -> null } } diff --git a/innertube/src/main/java/com/zionhuang/innertube/pages/BrowseResult.kt b/innertube/src/main/java/com/zionhuang/innertube/pages/BrowseResult.kt new file mode 100644 index 000000000..2099183ec --- /dev/null +++ b/innertube/src/main/java/com/zionhuang/innertube/pages/BrowseResult.kt @@ -0,0 +1,13 @@ +package com.zionhuang.innertube.pages + +import com.zionhuang.innertube.models.YTItem + +data class BrowseResult( + val title: String?, + val items: List, +) { + data class Item( + val title: String?, + val items: List, + ) +} diff --git a/innertube/src/main/java/com/zionhuang/innertube/pages/MoodAndGenres.kt b/innertube/src/main/java/com/zionhuang/innertube/pages/MoodAndGenres.kt new file mode 100644 index 000000000..8288790b7 --- /dev/null +++ b/innertube/src/main/java/com/zionhuang/innertube/pages/MoodAndGenres.kt @@ -0,0 +1,30 @@ +package com.zionhuang.innertube.pages + +import com.zionhuang.innertube.models.BrowseEndpoint +import com.zionhuang.innertube.models.SectionListRenderer + +data class MoodAndGenres( + val title: String, + val items: List, +) { + data class Item( + val title: String, + val stripeColor: Long, + val endpoint: BrowseEndpoint, + ) + + companion object { + fun fromSectionListRendererContent(content: SectionListRenderer.Content): MoodAndGenres? { + return MoodAndGenres( + title = content.gridRenderer?.header?.gridHeaderRenderer?.title?.runs?.firstOrNull()?.text ?: return null, + items = content.gridRenderer.items.map { + Item( + title = it.musicNavigationButtonRenderer?.buttonText?.runs?.firstOrNull()?.text ?: return null, + stripeColor = it.musicNavigationButtonRenderer.solid?.leftStripeColor ?: return null, + endpoint = it.musicNavigationButtonRenderer.clickCommand.browseEndpoint ?: return null, + ) + } + ) + } + } +} diff --git a/innertube/src/main/java/com/zionhuang/innertube/pages/RelatedPage.kt b/innertube/src/main/java/com/zionhuang/innertube/pages/RelatedPage.kt index ea493709c..3272f54f5 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/pages/RelatedPage.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/pages/RelatedPage.kt @@ -67,8 +67,8 @@ data class RelatedPage( name = it.text, id = it.navigationEndpoint?.browseEndpoint?.browseId ) - } ?: return null, - songCountText = renderer.subtitle.runs.getOrNull(4)?.text, + }, + songCountText = renderer.subtitle?.runs?.getOrNull(4)?.text, thumbnail = renderer.thumbnailRenderer.musicThumbnailRenderer?.getThumbnailUrl() ?: return null, playEndpoint = renderer.thumbnailOverlay ?.musicItemThumbnailOverlayRenderer?.content From 452a93e07232ef2ce73c107ca6e5a6fcdb5b9a7b Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Mon, 17 Jul 2023 22:01:15 +0800 Subject: [PATCH 30/84] Fix #788 --- .../main/java/com/zionhuang/music/extensions/MediaItemExt.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/com/zionhuang/music/extensions/MediaItemExt.kt b/app/src/main/java/com/zionhuang/music/extensions/MediaItemExt.kt index 50ae92c12..f0717300f 100644 --- a/app/src/main/java/com/zionhuang/music/extensions/MediaItemExt.kt +++ b/app/src/main/java/com/zionhuang/music/extensions/MediaItemExt.kt @@ -22,6 +22,7 @@ fun Song.toMediaItem() = MediaItem.Builder() .setSubtitle(artists.joinToString { it.name }) .setArtist(artists.joinToString { it.name }) .setArtworkUri(song.thumbnailUrl?.toUri()) + .setAlbumTitle(song.albumName) .setMediaType(MEDIA_TYPE_MUSIC) .build() ) @@ -38,6 +39,7 @@ fun SongItem.toMediaItem() = MediaItem.Builder() .setSubtitle(artists.joinToString { it.name }) .setArtist(artists.joinToString { it.name }) .setArtworkUri(thumbnail.toUri()) + .setAlbumTitle(album?.name) .setMediaType(MEDIA_TYPE_MUSIC) .build() ) @@ -54,6 +56,7 @@ fun MediaMetadata.toMediaItem() = MediaItem.Builder() .setSubtitle(artists.joinToString { it.name }) .setArtist(artists.joinToString { it.name }) .setArtworkUri(thumbnailUrl?.toUri()) + .setAlbumTitle(album?.title) .setMediaType(MEDIA_TYPE_MUSIC) .build() ) From ee1895bbdbf96b201d39bbf1f01a3a5b6dec9eba Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Tue, 18 Jul 2023 13:36:29 +0800 Subject: [PATCH 31/84] Add mood and genres section in home screen --- .../music/ui/component/HideOnScrollFAB.kt | 82 ++++++++++ .../music/ui/component/NavigationTile.kt | 47 ++++++ .../music/ui/component/NavigationTitle.kt | 54 +++++++ .../zionhuang/music/ui/screens/HomeScreen.kt | 150 +++++++----------- .../music/ui/screens/MoodAndGenresScreen.kt | 11 +- .../ui/screens/artist/ArtistSongsScreen.kt | 46 ++---- .../screens/library/LibraryPlaylistsScreen.kt | 38 +---- .../ui/screens/library/LibrarySongsScreen.kt | 43 ++--- .../screens/playlist/BuiltInPlaylistScreen.kt | 47 ++---- .../{LazyListStateUtils.kt => ScrollUtils.kt} | 15 +- .../music/viewmodels/HomeViewModel.kt | 8 +- app/src/main/res/values-cs/strings.xml | 2 +- app/src/main/res/values-es-rUS/strings.xml | 2 +- app/src/main/res/values-fa-rIR/strings.xml | 2 +- app/src/main/res/values-fi-rFI/strings.xml | 2 +- app/src/main/res/values-fr-rFR/strings.xml | 2 +- app/src/main/res/values-id/strings.xml | 2 +- app/src/main/res/values-ko-rKR/strings.xml | 2 +- app/src/main/res/values-ml-rIN/strings.xml | 2 +- app/src/main/res/values-or-rIN/strings.xml | 2 +- app/src/main/res/values-pa/strings.xml | 2 +- app/src/main/res/values-pt-rBR/strings.xml | 2 +- app/src/main/res/values-sv-rSE/strings.xml | 2 +- app/src/main/res/values-zh-rCN/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- .../java/com/zionhuang/innertube/YouTube.kt | 21 ++- .../zionhuang/innertube/pages/ExplorePage.kt | 8 + .../innertube/pages/MoodAndGenres.kt | 20 ++- 28 files changed, 353 insertions(+), 265 deletions(-) create mode 100644 app/src/main/java/com/zionhuang/music/ui/component/HideOnScrollFAB.kt create mode 100644 app/src/main/java/com/zionhuang/music/ui/component/NavigationTile.kt create mode 100644 app/src/main/java/com/zionhuang/music/ui/component/NavigationTitle.kt rename app/src/main/java/com/zionhuang/music/ui/utils/{LazyListStateUtils.kt => ScrollUtils.kt} (73%) create mode 100644 innertube/src/main/java/com/zionhuang/innertube/pages/ExplorePage.kt diff --git a/app/src/main/java/com/zionhuang/music/ui/component/HideOnScrollFAB.kt b/app/src/main/java/com/zionhuang/music/ui/component/HideOnScrollFAB.kt new file mode 100644 index 000000000..64581e51b --- /dev/null +++ b/app/src/main/java/com/zionhuang/music/ui/component/HideOnScrollFAB.kt @@ -0,0 +1,82 @@ +package com.zionhuang.music.ui.component + +import androidx.annotation.DrawableRes +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.only +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import com.zionhuang.music.LocalPlayerAwareWindowInsets +import com.zionhuang.music.ui.utils.isScrollingUp + +@Composable +fun BoxScope.HideOnScrollFAB( + visible: Boolean = true, + lazyListState: LazyListState, + @DrawableRes icon: Int, + onClick: () -> Unit, +) { + AnimatedVisibility( + visible = visible && lazyListState.isScrollingUp(), + enter = slideInVertically { it }, + exit = slideOutVertically { it }, + modifier = Modifier + .align(Alignment.BottomEnd) + .windowInsetsPadding( + LocalPlayerAwareWindowInsets.current + .only(WindowInsetsSides.Bottom + WindowInsetsSides.Horizontal) + ) + ) { + FloatingActionButton( + modifier = Modifier.padding(16.dp), + onClick = onClick + ) { + Icon( + painter = painterResource(icon), + contentDescription = null + ) + } + } +} + +@Composable +fun BoxScope.HideOnScrollFAB( + visible: Boolean = true, + scrollState: ScrollState, + @DrawableRes icon: Int, + onClick: () -> Unit, +) { + AnimatedVisibility( + visible = visible && scrollState.isScrollingUp(), + enter = slideInVertically { it }, + exit = slideOutVertically { it }, + modifier = Modifier + .align(Alignment.BottomEnd) + .windowInsetsPadding( + LocalPlayerAwareWindowInsets.current + .only(WindowInsetsSides.Bottom + WindowInsetsSides.Horizontal) + ) + ) { + FloatingActionButton( + modifier = Modifier.padding(16.dp), + onClick = onClick + ) { + Icon( + painter = painterResource(icon), + contentDescription = null + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/zionhuang/music/ui/component/NavigationTile.kt b/app/src/main/java/com/zionhuang/music/ui/component/NavigationTile.kt new file mode 100644 index 000000000..ecd3771f9 --- /dev/null +++ b/app/src/main/java/com/zionhuang/music/ui/component/NavigationTile.kt @@ -0,0 +1,47 @@ +package com.zionhuang.music.ui.component + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp + +@Composable +fun NavigationTile( + title: String, + @DrawableRes icon: Int, + onClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(4.dp), + modifier = modifier + .clip(RoundedCornerShape(4.dp)) + .clickable(onClick = onClick) + .padding(4.dp), + ) { + Icon( + painter = painterResource(icon), + contentDescription = null + ) + + Text( + text = title, + style = MaterialTheme.typography.labelMedium, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } +} diff --git a/app/src/main/java/com/zionhuang/music/ui/component/NavigationTitle.kt b/app/src/main/java/com/zionhuang/music/ui/component/NavigationTitle.kt new file mode 100644 index 000000000..1d8da84ab --- /dev/null +++ b/app/src/main/java/com/zionhuang/music/ui/component/NavigationTitle.kt @@ -0,0 +1,54 @@ +package com.zionhuang.music.ui.component + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.WindowInsetsSides +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.only +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import com.zionhuang.music.R + +@Composable +fun NavigationTitle( + title: String, + onClick: (() -> Unit)? = null, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Horizontal)) + .clickable(enabled = onClick != null) { + onClick?.invoke() + } + .padding(12.dp) + ) { + Column( + modifier = Modifier.weight(1f) + ) { + Text( + text = title, + style = MaterialTheme.typography.headlineSmall + ) + } + + if (onClick != null) { + Icon( + painter = painterResource(R.drawable.navigate_next), + contentDescription = null + ) + } + } +} diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/HomeScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/HomeScreen.kt index e4dbc133e..bb14d2757 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/HomeScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/HomeScreen.kt @@ -1,6 +1,5 @@ package com.zionhuang.music.ui.screens -import androidx.annotation.DrawableRes import androidx.compose.foundation.* import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior import androidx.compose.foundation.layout.* @@ -10,15 +9,12 @@ import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController @@ -36,7 +32,10 @@ import com.zionhuang.music.extensions.togglePlayPause import com.zionhuang.music.models.toMediaMetadata import com.zionhuang.music.playback.queues.YouTubeAlbumRadio import com.zionhuang.music.playback.queues.YouTubeQueue +import com.zionhuang.music.ui.component.HideOnScrollFAB import com.zionhuang.music.ui.component.LocalMenuState +import com.zionhuang.music.ui.component.NavigationTile +import com.zionhuang.music.ui.component.NavigationTitle import com.zionhuang.music.ui.component.SongListItem import com.zionhuang.music.ui.component.YouTubeGridItem import com.zionhuang.music.ui.menu.SongMenu @@ -60,7 +59,7 @@ fun HomeScreen( val mediaMetadata by playerConnection.mediaMetadata.collectAsState() val quickPicks by viewModel.quickPicks.collectAsState() - val newReleaseAlbums by viewModel.newReleaseAlbums.collectAsState() + val explorePage by viewModel.explorePage.collectAsState() val isRefreshing by viewModel.isRefreshing.collectAsState() val mostPlayedLazyGridState = rememberLazyGridState() @@ -70,6 +69,8 @@ fun HomeScreen( "SAPISID" in parseCookieString(innerTubeCookie) } + val scrollState = rememberScrollState() + SwipeRefresh( state = rememberSwipeRefreshState(isRefreshing), onRefresh = viewModel::refresh, @@ -90,7 +91,7 @@ fun HomeScreen( } Column( - modifier = Modifier.verticalScroll(rememberScrollState()) + modifier = Modifier.verticalScroll(scrollState) ) { Spacer(Modifier.height(LocalPlayerAwareWindowInsets.current.asPaddingValues().calculateTopPadding())) @@ -114,13 +115,6 @@ fun HomeScreen( modifier = Modifier.weight(1f) ) - NavigationTile( - title = stringResource(R.string.mood_and_genres), - icon = R.drawable.mood, - onClick = { navController.navigate("mood_and_genres") }, - modifier = Modifier.weight(1f) - ) - if (isLoggedIn) { NavigationTile( title = stringResource(R.string.account), @@ -133,12 +127,8 @@ fun HomeScreen( } } - Text( - text = stringResource(R.string.quick_picks), - style = MaterialTheme.typography.headlineSmall, - modifier = Modifier - .windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Horizontal)) - .padding(12.dp) + NavigationTitle( + title = stringResource(R.string.quick_picks) ) quickPicks?.let { quickPicks -> @@ -211,31 +201,13 @@ fun HomeScreen( } } - if (newReleaseAlbums.isNotEmpty()) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Horizontal)) - .clickable { - navController.navigate("new_release") - } - .padding(12.dp) - ) { - Column( - modifier = Modifier.weight(1f) - ) { - Text( - text = stringResource(R.string.new_release_albums), - style = MaterialTheme.typography.headlineSmall - ) + explorePage?.newReleaseAlbums?.let { newReleaseAlbums -> + NavigationTitle( + title = stringResource(R.string.new_release_albums), + onClick = { + navController.navigate("new_release") } - - Icon( - painter = painterResource(R.drawable.navigate_next), - contentDescription = null - ) - } + ) LazyRow( contentPadding = WindowInsets.systemBars @@ -272,62 +244,50 @@ fun HomeScreen( } } - Spacer(Modifier.height(LocalPlayerAwareWindowInsets.current.asPaddingValues().calculateBottomPadding())) - } - - if (!quickPicks.isNullOrEmpty() || newReleaseAlbums.isNotEmpty()) { - FloatingActionButton( - modifier = Modifier - .align(Alignment.BottomEnd) - .windowInsetsPadding( - LocalPlayerAwareWindowInsets.current - .only(WindowInsetsSides.Bottom + WindowInsetsSides.Horizontal) - ) - .padding(16.dp), - onClick = { - if (Random.nextBoolean() && !quickPicks.isNullOrEmpty()) { - val song = quickPicks!!.random() - playerConnection.playQueue(YouTubeQueue(WatchEndpoint(videoId = song.id), song.toMediaMetadata())) - } else if (newReleaseAlbums.isNotEmpty()) { - val album = newReleaseAlbums.random() - playerConnection.playQueue(YouTubeAlbumRadio(album.playlistId)) + explorePage?.moodAndGenres?.let { moodAndGenres -> + NavigationTitle( + title = stringResource(R.string.mood_and_genres), + onClick = { + navController.navigate("mood_and_genres") } - }) { - Icon( - painter = painterResource(R.drawable.casino), - contentDescription = null ) + + LazyHorizontalGrid( + rows = GridCells.Fixed(4), + contentPadding = PaddingValues(6.dp), + modifier = Modifier.height(MoodAndGenresButtonHeight * 4 + 12.dp) + ) { + items(moodAndGenres) { + MoodAndGenresButton( + title = it.title, + onClick = { + navController.navigate("youtube_browse/${it.endpoint.browseId}?params=${it.endpoint.params}") + }, + modifier = Modifier + .padding(6.dp) + .width(180.dp) + ) + } + } } - } - } - } -} -@Composable -fun NavigationTile( - title: String, - @DrawableRes icon: Int, - onClick: () -> Unit, - modifier: Modifier = Modifier, -) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(4.dp), - modifier = modifier - .clip(RoundedCornerShape(4.dp)) - .clickable(onClick = onClick) - .padding(4.dp), - ) { - Icon( - painter = painterResource(icon), - contentDescription = null - ) + Spacer(Modifier.height(LocalPlayerAwareWindowInsets.current.asPaddingValues().calculateBottomPadding())) + } - Text( - text = title, - style = MaterialTheme.typography.labelMedium, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) + HideOnScrollFAB( + visible = !quickPicks.isNullOrEmpty() || explorePage?.newReleaseAlbums?.isNotEmpty() == true, + scrollState = scrollState, + icon = R.drawable.casino, + onClick = { + if (Random.nextBoolean() && !quickPicks.isNullOrEmpty()) { + val song = quickPicks!!.random() + playerConnection.playQueue(YouTubeQueue(WatchEndpoint(videoId = song.id), song.toMediaMetadata())) + } else if (explorePage?.newReleaseAlbums?.isNotEmpty() == true) { + val album = explorePage?.newReleaseAlbums!!.random() + playerConnection.playQueue(YouTubeAlbumRadio(album.playlistId)) + } + } + ) + } } } diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/MoodAndGenresScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/MoodAndGenresScreen.kt index beef4b710..429b666d6 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/MoodAndGenresScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/MoodAndGenresScreen.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.RoundedCornerShape @@ -23,6 +24,7 @@ import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalConfiguration @@ -84,7 +86,9 @@ fun MoodAndGenresScreen( onClick = { navController.navigate("youtube_browse/${it.endpoint.browseId}?params=${it.endpoint.params}") }, - modifier = Modifier.weight(1f) + modifier = Modifier + .weight(1f) + .padding(6.dp) ) } @@ -119,8 +123,9 @@ fun MoodAndGenresButton( modifier: Modifier = Modifier, ) { Box( + contentAlignment = Alignment.CenterStart, modifier = modifier - .padding(6.dp) + .height(MoodAndGenresButtonHeight) .clip(RoundedCornerShape(6.dp)) .background(MaterialTheme.colorScheme.surfaceColorAtElevation(6.dp)) .clickable(onClick = onClick) @@ -134,3 +139,5 @@ fun MoodAndGenresButton( ) } } + +val MoodAndGenresButtonHeight = 54.dp \ No newline at end of file diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistSongsScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistSongsScreen.kt index c839f6301..0e3ba8458 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistSongsScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistSongsScreen.kt @@ -1,23 +1,15 @@ package com.zionhuang.music.ui.screens.artist -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.only -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Text @@ -26,12 +18,10 @@ import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.pluralStringResource -import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.zionhuang.music.LocalPlayerAwareWindowInsets @@ -45,11 +35,11 @@ import com.zionhuang.music.constants.CONTENT_TYPE_SONG import com.zionhuang.music.extensions.toMediaItem import com.zionhuang.music.extensions.togglePlayPause import com.zionhuang.music.playback.queues.ListQueue +import com.zionhuang.music.ui.component.HideOnScrollFAB import com.zionhuang.music.ui.component.LocalMenuState import com.zionhuang.music.ui.component.SongListItem import com.zionhuang.music.ui.component.SortHeader import com.zionhuang.music.ui.menu.SongMenu -import com.zionhuang.music.ui.utils.isScrollingUp import com.zionhuang.music.utils.rememberEnumPreference import com.zionhuang.music.utils.rememberPreference import com.zionhuang.music.viewmodels.ArtistSongsViewModel @@ -163,33 +153,17 @@ fun ArtistSongsScreen( scrollBehavior = scrollBehavior ) - AnimatedVisibility( - visible = lazyListState.isScrollingUp(), - enter = slideInVertically { it }, - exit = slideOutVertically { it }, - modifier = Modifier - .align(Alignment.BottomEnd) - .windowInsetsPadding( - LocalPlayerAwareWindowInsets.current - .only(WindowInsetsSides.Bottom + WindowInsetsSides.Horizontal) - ) - ) { - FloatingActionButton( - modifier = Modifier.padding(16.dp), - onClick = { - playerConnection.playQueue( - ListQueue( - title = artist?.name, - items = songs.shuffled().map { it.toMediaItem() }, - ) + HideOnScrollFAB( + lazyListState = lazyListState, + icon = R.drawable.shuffle, + onClick = { + playerConnection.playQueue( + ListQueue( + title = artist?.name, + items = songs.shuffled().map { it.toMediaItem() }, ) - } - ) { - Icon( - painter = painterResource(R.drawable.shuffle), - contentDescription = null ) } - } + ) } } \ No newline at end of file diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/library/LibraryPlaylistsScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/library/LibraryPlaylistsScreen.kt index 65942a76b..68580b118 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/library/LibraryPlaylistsScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/library/LibraryPlaylistsScreen.kt @@ -1,23 +1,15 @@ package com.zionhuang.music.ui.screens.library -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.only -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -29,12 +21,10 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.zionhuang.music.LocalDatabase @@ -49,13 +39,13 @@ import com.zionhuang.music.constants.PlaylistSortTypeKey import com.zionhuang.music.db.entities.PlaylistEntity import com.zionhuang.music.db.entities.PlaylistEntity.Companion.DOWNLOADED_PLAYLIST_ID import com.zionhuang.music.db.entities.PlaylistEntity.Companion.LIKED_PLAYLIST_ID +import com.zionhuang.music.ui.component.HideOnScrollFAB import com.zionhuang.music.ui.component.ListItem import com.zionhuang.music.ui.component.LocalMenuState import com.zionhuang.music.ui.component.PlaylistListItem import com.zionhuang.music.ui.component.SortHeader import com.zionhuang.music.ui.component.TextFieldDialog import com.zionhuang.music.ui.menu.PlaylistMenu -import com.zionhuang.music.ui.utils.isScrollingUp import com.zionhuang.music.utils.rememberEnumPreference import com.zionhuang.music.utils.rememberPreference import com.zionhuang.music.viewmodels.LibraryPlaylistsViewModel @@ -208,26 +198,12 @@ fun LibraryPlaylistsScreen( } } - AnimatedVisibility( - visible = lazyListState.isScrollingUp(), - enter = slideInVertically { it }, - exit = slideOutVertically { it }, - modifier = Modifier - .align(Alignment.BottomEnd) - .windowInsetsPadding( - LocalPlayerAwareWindowInsets.current - .only(WindowInsetsSides.Bottom + WindowInsetsSides.Horizontal) - ) - ) { - FloatingActionButton( - modifier = Modifier.padding(16.dp), - onClick = { showAddPlaylistDialog = true } - ) { - Icon( - painter = painterResource(R.drawable.add), - contentDescription = null - ) + HideOnScrollFAB( + lazyListState = lazyListState, + icon = R.drawable.add, + onClick = { + showAddPlaylistDialog = true } - } + ) } } diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/library/LibrarySongsScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/library/LibrarySongsScreen.kt index 90d0a44ae..4503ffc35 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/library/LibrarySongsScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/library/LibrarySongsScreen.kt @@ -1,26 +1,20 @@ package com.zionhuang.music.ui.screens.library -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.pluralStringResource -import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.zionhuang.music.LocalPlayerAwareWindowInsets @@ -30,11 +24,11 @@ import com.zionhuang.music.constants.* import com.zionhuang.music.extensions.toMediaItem import com.zionhuang.music.extensions.togglePlayPause import com.zionhuang.music.playback.queues.ListQueue +import com.zionhuang.music.ui.component.HideOnScrollFAB import com.zionhuang.music.ui.component.LocalMenuState import com.zionhuang.music.ui.component.SongListItem import com.zionhuang.music.ui.component.SortHeader import com.zionhuang.music.ui.menu.SongMenu -import com.zionhuang.music.ui.utils.isScrollingUp import com.zionhuang.music.utils.rememberEnumPreference import com.zionhuang.music.utils.rememberPreference import com.zionhuang.music.viewmodels.LibrarySongsViewModel @@ -134,33 +128,18 @@ fun LibrarySongsScreen( } } - AnimatedVisibility( - visible = songs.isNotEmpty() && lazyListState.isScrollingUp(), - enter = slideInVertically { it }, - exit = slideOutVertically { it }, - modifier = Modifier - .align(Alignment.BottomEnd) - .windowInsetsPadding( - LocalPlayerAwareWindowInsets.current - .only(WindowInsetsSides.Bottom + WindowInsetsSides.Horizontal) - ) - ) { - FloatingActionButton( - modifier = Modifier.padding(16.dp), - onClick = { - playerConnection.playQueue( - ListQueue( - title = context.getString(R.string.queue_all_songs), - items = songs.shuffled().map { it.toMediaItem() }, - ) + HideOnScrollFAB( + visible = songs.isNotEmpty(), + lazyListState = lazyListState, + icon = R.drawable.shuffle, + onClick = { + playerConnection.playQueue( + ListQueue( + title = context.getString(R.string.queue_all_songs), + items = songs.shuffled().map { it.toMediaItem() }, ) - } - ) { - Icon( - painter = painterResource(R.drawable.shuffle), - contentDescription = null ) } - } + ) } } diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/playlist/BuiltInPlaylistScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/playlist/BuiltInPlaylistScreen.kt index f930a7ec4..41e68f994 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/playlist/BuiltInPlaylistScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/playlist/BuiltInPlaylistScreen.kt @@ -1,23 +1,15 @@ package com.zionhuang.music.ui.screens.playlist -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.only -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Text @@ -27,12 +19,10 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.pluralStringResource -import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastSumBy import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController @@ -50,11 +40,11 @@ import com.zionhuang.music.db.entities.PlaylistEntity.Companion.LIKED_PLAYLIST_I import com.zionhuang.music.extensions.toMediaItem import com.zionhuang.music.extensions.togglePlayPause import com.zionhuang.music.playback.queues.ListQueue +import com.zionhuang.music.ui.component.HideOnScrollFAB import com.zionhuang.music.ui.component.LocalMenuState import com.zionhuang.music.ui.component.SongListItem import com.zionhuang.music.ui.component.SortHeader import com.zionhuang.music.ui.menu.SongMenu -import com.zionhuang.music.ui.utils.isScrollingUp import com.zionhuang.music.utils.joinByBullet import com.zionhuang.music.utils.makeTimeString import com.zionhuang.music.utils.rememberEnumPreference @@ -207,33 +197,18 @@ fun BuiltInPlaylistScreen( scrollBehavior = scrollBehavior ) - AnimatedVisibility( - visible = songs.isNotEmpty() && lazyListState.isScrollingUp(), - enter = slideInVertically { it }, - exit = slideOutVertically { it }, - modifier = Modifier - .align(Alignment.BottomEnd) - .windowInsetsPadding( - LocalPlayerAwareWindowInsets.current - .only(WindowInsetsSides.Bottom + WindowInsetsSides.Horizontal) - ) - ) { - FloatingActionButton( - modifier = Modifier.padding(16.dp), - onClick = { - playerConnection.playQueue( - ListQueue( - title = playlistName, - items = songs.shuffled().map { it.toMediaItem() }, - ) + HideOnScrollFAB( + visible = songs.isNotEmpty(), + lazyListState = lazyListState, + icon = R.drawable.shuffle, + onClick = { + playerConnection.playQueue( + ListQueue( + title = playlistName, + items = songs.shuffled().map { it.toMediaItem() }, ) - } - ) { - Icon( - painter = painterResource(R.drawable.shuffle), - contentDescription = null ) } - } + ) } } diff --git a/app/src/main/java/com/zionhuang/music/ui/utils/LazyListStateUtils.kt b/app/src/main/java/com/zionhuang/music/ui/utils/ScrollUtils.kt similarity index 73% rename from app/src/main/java/com/zionhuang/music/ui/utils/LazyListStateUtils.kt rename to app/src/main/java/com/zionhuang/music/ui/utils/ScrollUtils.kt index cc7061c2e..975e195f4 100644 --- a/app/src/main/java/com/zionhuang/music/ui/utils/LazyListStateUtils.kt +++ b/app/src/main/java/com/zionhuang/music/ui/utils/ScrollUtils.kt @@ -1,5 +1,6 @@ package com.zionhuang.music.ui.utils +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf @@ -24,4 +25,16 @@ fun LazyListState.isScrollingUp(): Boolean { } } }.value -} \ No newline at end of file +} + +@Composable +fun ScrollState.isScrollingUp(): Boolean { + var previousScrollOffset by remember(this) { mutableStateOf(value) } + return remember(this) { + derivedStateOf { + (previousScrollOffset >= value).also { + previousScrollOffset = value + } + } + }.value +} diff --git a/app/src/main/java/com/zionhuang/music/viewmodels/HomeViewModel.kt b/app/src/main/java/com/zionhuang/music/viewmodels/HomeViewModel.kt index 9aa7de8c4..5803c55e9 100644 --- a/app/src/main/java/com/zionhuang/music/viewmodels/HomeViewModel.kt +++ b/app/src/main/java/com/zionhuang/music/viewmodels/HomeViewModel.kt @@ -3,7 +3,7 @@ package com.zionhuang.music.viewmodels import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.zionhuang.innertube.YouTube -import com.zionhuang.innertube.models.AlbumItem +import com.zionhuang.innertube.pages.ExplorePage import com.zionhuang.music.db.MusicDatabase import com.zionhuang.music.db.entities.Song import dagger.hilt.android.lifecycle.HiltViewModel @@ -20,12 +20,12 @@ class HomeViewModel @Inject constructor( val isRefreshing = MutableStateFlow(false) val quickPicks = MutableStateFlow?>(null) - val newReleaseAlbums = MutableStateFlow>(emptyList()) + val explorePage = MutableStateFlow(null) private suspend fun load() { quickPicks.value = database.quickPicks().first().shuffled().take(20) - YouTube.newReleaseAlbumsPreview().onSuccess { - newReleaseAlbums.value = it + YouTube.explore().onSuccess { + explorePage.value = it } } diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 4b7927c5b..87f8a35a5 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -20,7 +20,7 @@ Mood and Genres Account Quick picks - Listen some songs to generate your quick picks + Listen to songs to generate your quick picks New release albums diff --git a/app/src/main/res/values-es-rUS/strings.xml b/app/src/main/res/values-es-rUS/strings.xml index 3e9e24c86..25009b1aa 100644 --- a/app/src/main/res/values-es-rUS/strings.xml +++ b/app/src/main/res/values-es-rUS/strings.xml @@ -18,7 +18,7 @@ Mood and Genres Account Quick picks - Listen some songs to generate your quick picks + Listen to songs to generate your quick picks New release albums diff --git a/app/src/main/res/values-fa-rIR/strings.xml b/app/src/main/res/values-fa-rIR/strings.xml index e9d39f78a..b1b858360 100644 --- a/app/src/main/res/values-fa-rIR/strings.xml +++ b/app/src/main/res/values-fa-rIR/strings.xml @@ -18,7 +18,7 @@ Mood and Genres Account Quick picks - Listen some songs to generate your quick picks + Listen to songs to generate your quick picks New release albums diff --git a/app/src/main/res/values-fi-rFI/strings.xml b/app/src/main/res/values-fi-rFI/strings.xml index de3f9d596..85d8105ca 100644 --- a/app/src/main/res/values-fi-rFI/strings.xml +++ b/app/src/main/res/values-fi-rFI/strings.xml @@ -18,7 +18,7 @@ Mood and Genres Account Quick picks - Listen some songs to generate your quick picks + Listen to songs to generate your quick picks New release albums diff --git a/app/src/main/res/values-fr-rFR/strings.xml b/app/src/main/res/values-fr-rFR/strings.xml index 0eab2292e..5f87d3eae 100644 --- a/app/src/main/res/values-fr-rFR/strings.xml +++ b/app/src/main/res/values-fr-rFR/strings.xml @@ -19,7 +19,7 @@ Mood and Genres Account Quick picks - Listen some songs to generate your quick picks + Listen to songs to generate your quick picks New release albums diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index 55d6bc0b4..54e909358 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -17,7 +17,7 @@ Mood and Genres Account Quick picks - Listen some songs to generate your quick picks + Listen to songs to generate your quick picks New release albums diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml index 93bae5aa3..910c80fb0 100644 --- a/app/src/main/res/values-ko-rKR/strings.xml +++ b/app/src/main/res/values-ko-rKR/strings.xml @@ -17,7 +17,7 @@ Mood and Genres Account Quick picks - Listen some songs to generate your quick picks + Listen to songs to generate your quick picks New release albums diff --git a/app/src/main/res/values-ml-rIN/strings.xml b/app/src/main/res/values-ml-rIN/strings.xml index 0bd5d2416..2e746fa59 100644 --- a/app/src/main/res/values-ml-rIN/strings.xml +++ b/app/src/main/res/values-ml-rIN/strings.xml @@ -18,7 +18,7 @@ Mood and Genres Account Quick picks - Listen some songs to generate your quick picks + Listen to songs to generate your quick picks New release albums diff --git a/app/src/main/res/values-or-rIN/strings.xml b/app/src/main/res/values-or-rIN/strings.xml index 94d79a9f2..8999b3f0e 100644 --- a/app/src/main/res/values-or-rIN/strings.xml +++ b/app/src/main/res/values-or-rIN/strings.xml @@ -18,7 +18,7 @@ Mood and Genres Account Quick picks - Listen some songs to generate your quick picks + Listen to songs to generate your quick picks New release albums diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index 8466b0527..185905700 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -18,7 +18,7 @@ Mood and Genres Account ਉਂਗਲਾਂ ਤੇ ਚੁਣੇ ਗੀਤ - Listen some songs to generate your quick picks + Listen to songs to generate your quick picks ਨਵੀਆਂ ਜਾਰੀ ਐਲਬਮਾਂ diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 6afbd4bed..8d6e79c27 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -18,7 +18,7 @@ Mood and Genres Account Quick picks - Listen some songs to generate your quick picks + Listen to songs to generate your quick picks New release álbuns diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index a2920c4ff..d16f32e19 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -18,7 +18,7 @@ Mood and Genres Account Quick picks - Listen some songs to generate your quick picks + Listen to songs to generate your quick picks New release albums diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 21728b7b9..90eb760fb 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -17,7 +17,7 @@ Mood and Genres Account 歌曲快选 - Listen some songs to generate your quick picks + Listen to songs to generate your quick picks 新专辑 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0a5351245..234f531a2 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -17,7 +17,7 @@ Mood and Genres Account Quick picks - Listen some songs to generate your quick picks + Listen to songs to generate your quick picks New release albums diff --git a/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt b/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt index 093c7c434..78d3f663e 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt @@ -34,6 +34,7 @@ import com.zionhuang.innertube.pages.ArtistItemsContinuationPage import com.zionhuang.innertube.pages.ArtistItemsPage import com.zionhuang.innertube.pages.ArtistPage import com.zionhuang.innertube.pages.BrowseResult +import com.zionhuang.innertube.pages.ExplorePage import com.zionhuang.innertube.pages.MoodAndGenres import com.zionhuang.innertube.pages.NewReleaseAlbumPage import com.zionhuang.innertube.pages.NextPage @@ -281,14 +282,20 @@ object YouTube { ) } - - suspend fun newReleaseAlbumsPreview(): Result> = runCatching { + suspend fun explore(): Result = runCatching { val response = innerTube.browse(WEB_REMIX, browseId = "FEmusic_explore").body() - response.contents?.singleColumnBrowseResultsRenderer?.tabs?.firstOrNull()?.tabRenderer?.content?.sectionListRenderer?.contents?.getOrNull(1)?.musicCarouselShelfRenderer?.contents?.mapNotNull { - it.musicTwoRowItemRenderer?.let { renderer -> - NewReleaseAlbumPage.fromMusicTwoRowItemRenderer(renderer) - } - }.orEmpty() + ExplorePage( + newReleaseAlbums = response.contents?.singleColumnBrowseResultsRenderer?.tabs?.firstOrNull()?.tabRenderer?.content?.sectionListRenderer?.contents?.getOrNull(1)?.musicCarouselShelfRenderer?.contents + ?.mapNotNull { + it.musicTwoRowItemRenderer?.let { renderer -> + NewReleaseAlbumPage.fromMusicTwoRowItemRenderer(renderer) + } + }.orEmpty(), + moodAndGenres = response.contents?.singleColumnBrowseResultsRenderer?.tabs?.firstOrNull()?.tabRenderer?.content?.sectionListRenderer?.contents?.getOrNull(2)?.musicCarouselShelfRenderer?.contents + ?.mapNotNull(MusicCarouselShelfRenderer.Content::musicNavigationButtonRenderer) + ?.mapNotNull(MoodAndGenres.Companion::fromMusicNavigationButtonRenderer) + .orEmpty() + ) } suspend fun newReleaseAlbums(): Result> = runCatching { diff --git a/innertube/src/main/java/com/zionhuang/innertube/pages/ExplorePage.kt b/innertube/src/main/java/com/zionhuang/innertube/pages/ExplorePage.kt new file mode 100644 index 000000000..faee829de --- /dev/null +++ b/innertube/src/main/java/com/zionhuang/innertube/pages/ExplorePage.kt @@ -0,0 +1,8 @@ +package com.zionhuang.innertube.pages + +import com.zionhuang.innertube.models.AlbumItem + +data class ExplorePage( + val newReleaseAlbums: List, + val moodAndGenres: List, +) diff --git a/innertube/src/main/java/com/zionhuang/innertube/pages/MoodAndGenres.kt b/innertube/src/main/java/com/zionhuang/innertube/pages/MoodAndGenres.kt index 8288790b7..ecaed16e4 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/pages/MoodAndGenres.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/pages/MoodAndGenres.kt @@ -1,6 +1,8 @@ package com.zionhuang.innertube.pages import com.zionhuang.innertube.models.BrowseEndpoint +import com.zionhuang.innertube.models.GridRenderer +import com.zionhuang.innertube.models.MusicNavigationButtonRenderer import com.zionhuang.innertube.models.SectionListRenderer data class MoodAndGenres( @@ -17,13 +19,17 @@ data class MoodAndGenres( fun fromSectionListRendererContent(content: SectionListRenderer.Content): MoodAndGenres? { return MoodAndGenres( title = content.gridRenderer?.header?.gridHeaderRenderer?.title?.runs?.firstOrNull()?.text ?: return null, - items = content.gridRenderer.items.map { - Item( - title = it.musicNavigationButtonRenderer?.buttonText?.runs?.firstOrNull()?.text ?: return null, - stripeColor = it.musicNavigationButtonRenderer.solid?.leftStripeColor ?: return null, - endpoint = it.musicNavigationButtonRenderer.clickCommand.browseEndpoint ?: return null, - ) - } + items = content.gridRenderer.items + .mapNotNull(GridRenderer.Item::musicNavigationButtonRenderer) + .mapNotNull(::fromMusicNavigationButtonRenderer) + ) + } + + fun fromMusicNavigationButtonRenderer(renderer: MusicNavigationButtonRenderer): Item? { + return Item( + title = renderer.buttonText.runs?.firstOrNull()?.text ?: return null, + stripeColor = renderer.solid?.leftStripeColor ?: return null, + endpoint = renderer.clickCommand.browseEndpoint ?: return null, ) } } From 9b5d21cba02c582671d496b96e22791afaa893ed Mon Sep 17 00:00:00 2001 From: metezd <37701679+metezd@users.noreply.github.com> Date: Tue, 18 Jul 2023 09:44:06 +0300 Subject: [PATCH 32/84] Update strings.xml --- app/src/main/res/values-tr/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index cf5ff59cc..c8b48e118 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -15,7 +15,7 @@ Geçmiş İstatistikler - Mood and Genres + Ruh Hali ve Türler Account Hızlı seçimler Hızlı seçimlerinizi oluşturmak için birkaç şarkı dinleyin @@ -144,7 +144,7 @@ Çalma listesi içe aktarıldı Çalma listesinden \"%s\" kaldırıldı - Playlist synced + Çalma listesi senkronize edildi Geri al @@ -235,8 +235,8 @@ Google Takeout\'tan bir csv dosyası seçin Çalma listesi içe aktarıldı - Imported \"%s\" with %d song - Imported \"%s\" with %d songs + \"%s\" %d şarkı ile içe aktarıldı + "%s\" %d şarkı ile içe aktarıldı Yedekleme başarıyla oluşturuldu Yedekleme oluşturulamadı From d59987744276ff983259bea553b04704d7ec7056 Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Tue, 18 Jul 2023 17:29:06 +0800 Subject: [PATCH 33/84] Choose stat period in stats screen --- .../zionhuang/music/constants/StatPeriod.kt | 18 ++++++ .../com/zionhuang/music/db/DatabaseDao.kt | 41 ++++++++---- .../music/ui/component/NavigationTitle.kt | 3 +- .../music/ui/screens/HistoryScreen.kt | 11 +--- .../zionhuang/music/ui/screens/StatsScreen.kt | 62 +++++++++++++------ .../music/ui/screens/artist/ArtistScreen.kt | 55 ++++------------ .../ui/screens/search/OnlineSearchResult.kt | 20 +----- .../music/viewmodels/StatsViewModel.kt | 20 ++++-- app/src/main/res/values-DE/strings.xml | 12 ++++ app/src/main/res/values-cs/strings.xml | 12 ++++ app/src/main/res/values-es-rUS/strings.xml | 12 ++++ app/src/main/res/values-es/strings.xml | 12 ++++ app/src/main/res/values-fa-rIR/strings.xml | 12 ++++ app/src/main/res/values-fi-rFI/strings.xml | 12 ++++ app/src/main/res/values-fr-rFR/strings.xml | 12 ++++ app/src/main/res/values-hu/strings.xml | 12 ++++ app/src/main/res/values-id/strings.xml | 12 ++++ app/src/main/res/values-it/strings.xml | 12 ++++ app/src/main/res/values-ja-rJP/strings.xml | 12 ++++ app/src/main/res/values-ko-rKR/strings.xml | 12 ++++ app/src/main/res/values-ml-rIN/strings.xml | 12 ++++ app/src/main/res/values-nl/strings.xml | 12 ++++ app/src/main/res/values-or-rIN/strings.xml | 12 ++++ app/src/main/res/values-pa/strings.xml | 12 ++++ app/src/main/res/values-pt-rBR/strings.xml | 12 ++++ app/src/main/res/values-ru-rRU/strings.xml | 12 ++++ app/src/main/res/values-sv-rSE/strings.xml | 12 ++++ app/src/main/res/values-tr/strings.xml | 12 ++++ app/src/main/res/values-uk-rUA/strings.xml | 12 ++++ app/src/main/res/values-zh-rCN/strings.xml | 12 ++++ app/src/main/res/values-zh-rTW/strings.xml | 9 +++ app/src/main/res/values/strings.xml | 12 ++++ 32 files changed, 408 insertions(+), 107 deletions(-) create mode 100644 app/src/main/java/com/zionhuang/music/constants/StatPeriod.kt diff --git a/app/src/main/java/com/zionhuang/music/constants/StatPeriod.kt b/app/src/main/java/com/zionhuang/music/constants/StatPeriod.kt new file mode 100644 index 000000000..6127aae46 --- /dev/null +++ b/app/src/main/java/com/zionhuang/music/constants/StatPeriod.kt @@ -0,0 +1,18 @@ +package com.zionhuang.music.constants + +import java.time.LocalDateTime +import java.time.ZoneOffset + +enum class StatPeriod { + `1_WEEK`, `1_MONTH`, `3_MONTH`, `6_MONTH`, `1_YEAR`, ALL; + + fun toTimeMillis(): Long = + when (this) { + `1_WEEK` -> LocalDateTime.now().minusWeeks(1).toInstant(ZoneOffset.UTC).toEpochMilli() + `1_MONTH` -> LocalDateTime.now().minusMonths(1).toInstant(ZoneOffset.UTC).toEpochMilli() + `3_MONTH` -> LocalDateTime.now().minusMonths(3).toInstant(ZoneOffset.UTC).toEpochMilli() + `6_MONTH` -> LocalDateTime.now().minusMonths(6).toInstant(ZoneOffset.UTC).toEpochMilli() + `1_YEAR` -> LocalDateTime.now().minusMonths(12).toInstant(ZoneOffset.UTC).toEpochMilli() + ALL -> 0 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt b/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt index 961dc5191..72a016433 100644 --- a/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt +++ b/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt @@ -43,6 +43,7 @@ interface DatabaseDao { song.artists.joinToString(separator = "") { it.name } } } + SongSortType.PLAY_TIME -> songsByPlayTimeAsc() }.map { it.reversed(descending) } @@ -71,6 +72,7 @@ interface DatabaseDao { song.artists.joinToString(separator = "") { it.name } } } + SongSortType.PLAY_TIME -> likedSongsByPlayTimeAsc() }.map { it.reversed(descending) } @@ -142,29 +144,44 @@ interface DatabaseDao { fun quickPicks(now: Long = System.currentTimeMillis()): Flow> @Transaction - @Query("SELECT * FROM song ORDER BY totalPlayTime DESC LIMIT :limit") - fun mostPlayedSongs(limit: Int = 6): Flow> + @Query( + """ + SELECT * + FROM song + WHERE id IN (SELECT songId + FROM event + WHERE timestamp > :fromTimeStamp + GROUP BY songId + ORDER BY SUM(playTime) DESC + LIMIT :limit) + """ + ) + fun mostPlayedSongs(fromTimeStamp: Long, limit: Int = 6): Flow> @Transaction @Query( """ - SELECT artist.*, + SELECT *, (SELECT COUNT(1) FROM song_artist_map JOIN song ON song_artist_map.songId = song.id WHERE artistId = artist.id AND song.inLibrary IS NOT NULL) AS songCount - FROM (SELECT artistId, SUM(playtime) AS totalPlaytime - FROM (SELECT *, (SELECT totalPlayTime FROM song WHERE id = songId) AS playtime - FROM song_artist_map) - GROUP BY artistId) - JOIN artist - ON artist.id = artistId - ORDER BY totalPlaytime DESC - LIMIT :limit + FROM artist + JOIN(SELECT artistId, SUM(songTotalPlayTime) AS totalPlayTime + FROM song_artist_map + JOIN (SELECT songId, SUM(playTime) AS songTotalPlayTime + FROM event + WHERE timestamp > :fromTimeStamp + GROUP BY songId) AS e + ON song_artist_map.songId = e.songId + GROUP BY artistId + ORDER BY totalPlayTime DESC + LIMIT :limit) + ON artist.id = artistId """ ) - fun mostPlayedArtists(limit: Int = 6): Flow> + fun mostPlayedArtists(fromTimeStamp: Long, limit: Int = 6): Flow> @Transaction @Query("SELECT * FROM song WHERE id = :songId") diff --git a/app/src/main/java/com/zionhuang/music/ui/component/NavigationTitle.kt b/app/src/main/java/com/zionhuang/music/ui/component/NavigationTitle.kt index 1d8da84ab..37551bdc8 100644 --- a/app/src/main/java/com/zionhuang/music/ui/component/NavigationTitle.kt +++ b/app/src/main/java/com/zionhuang/music/ui/component/NavigationTitle.kt @@ -23,11 +23,12 @@ import com.zionhuang.music.R @Composable fun NavigationTitle( title: String, + modifier: Modifier = Modifier, onClick: (() -> Unit)? = null, ) { Row( verticalAlignment = Alignment.CenterVertically, - modifier = Modifier + modifier = modifier .fillMaxWidth() .windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Horizontal)) .clickable(enabled = onClick != null) { diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/HistoryScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/HistoryScreen.kt index 65437edf3..56ccaf206 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/HistoryScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/HistoryScreen.kt @@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.only -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -23,8 +22,6 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.zionhuang.innertube.models.WatchEndpoint @@ -35,6 +32,7 @@ import com.zionhuang.music.extensions.togglePlayPause import com.zionhuang.music.models.toMediaMetadata import com.zionhuang.music.playback.queues.YouTubeQueue import com.zionhuang.music.ui.component.LocalMenuState +import com.zionhuang.music.ui.component.NavigationTitle import com.zionhuang.music.ui.component.SongListItem import com.zionhuang.music.ui.menu.SongMenu import com.zionhuang.music.viewmodels.DateAgo @@ -60,20 +58,17 @@ fun HistoryScreen( ) { events.forEach { (dateAgo, events) -> stickyHeader { - Text( - text = when (dateAgo) { + NavigationTitle( + title = when (dateAgo) { DateAgo.Today -> stringResource(R.string.today) DateAgo.Yesterday -> stringResource(R.string.yesterday) DateAgo.ThisWeek -> stringResource(R.string.this_week) DateAgo.LastWeek -> stringResource(R.string.last_week) is DateAgo.Other -> dateAgo.date.format(DateTimeFormatter.ofPattern("yyyy/MM")) }, - style = MaterialTheme.typography.headlineMedium, - overflow = TextOverflow.Ellipsis, modifier = Modifier .fillMaxWidth() .background(MaterialTheme.colorScheme.background) - .padding(horizontal = 12.dp, vertical = 8.dp) ) } diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/StatsScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/StatsScreen.kt index 46c6143ac..6c0bec951 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/StatsScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/StatsScreen.kt @@ -1,18 +1,23 @@ package com.zionhuang.music.ui.screens import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.only -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.rememberScrollState import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilterChip +import androidx.compose.material3.FilterChipDefaults import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -23,8 +28,8 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController @@ -32,11 +37,13 @@ import com.zionhuang.innertube.models.WatchEndpoint import com.zionhuang.music.LocalPlayerAwareWindowInsets import com.zionhuang.music.LocalPlayerConnection import com.zionhuang.music.R +import com.zionhuang.music.constants.StatPeriod import com.zionhuang.music.extensions.togglePlayPause import com.zionhuang.music.models.toMediaMetadata import com.zionhuang.music.playback.queues.YouTubeQueue import com.zionhuang.music.ui.component.ArtistListItem import com.zionhuang.music.ui.component.LocalMenuState +import com.zionhuang.music.ui.component.NavigationTitle import com.zionhuang.music.ui.component.SongListItem import com.zionhuang.music.ui.menu.SongMenu import com.zionhuang.music.viewmodels.StatsViewModel @@ -52,6 +59,7 @@ fun StatsScreen( val isPlaying by playerConnection.isPlaying.collectAsState() val mediaMetadata by playerConnection.mediaMetadata.collectAsState() + val statPeriod by viewModel.statPeriod.collectAsState() val mostPlayedSongs by viewModel.mostPlayedSongs.collectAsState() val mostPlayedArtists by viewModel.mostPlayedArtists.collectAsState() @@ -60,15 +68,39 @@ fun StatsScreen( modifier = Modifier.windowInsetsPadding(LocalPlayerAwareWindowInsets.current.only(WindowInsetsSides.Top)) ) { item { - Text( - text = stringResource(R.string.most_played_songs), - style = MaterialTheme.typography.headlineMedium, - overflow = TextOverflow.Ellipsis, + Row( modifier = Modifier .fillMaxWidth() - .background(MaterialTheme.colorScheme.background) - .padding(horizontal = 12.dp, vertical = 8.dp) - ) + .horizontalScroll(rememberScrollState()) + ) { + Spacer(Modifier.width(8.dp)) + + StatPeriod.values().forEach { period -> + FilterChip( + label = { + Text( + when (period) { + StatPeriod.`1_WEEK` -> pluralStringResource(R.plurals.n_week, 1, 1) + StatPeriod.`1_MONTH` -> pluralStringResource(R.plurals.n_month, 1, 1) + StatPeriod.`3_MONTH` -> pluralStringResource(R.plurals.n_month, 3, 3) + StatPeriod.`6_MONTH` -> pluralStringResource(R.plurals.n_month, 6, 6) + StatPeriod.`1_YEAR` -> pluralStringResource(R.plurals.n_year, 1, 1) + StatPeriod.ALL -> stringResource(R.string.filter_all) + } + ) + }, + selected = statPeriod == period, + colors = FilterChipDefaults.filterChipColors(containerColor = MaterialTheme.colorScheme.background), + onClick = { + viewModel.statPeriod.value = period + } + ) + Spacer(Modifier.width(8.dp)) + } + } + } + item { + NavigationTitle(stringResource(R.string.most_played_songs)) } items( items = mostPlayedSongs, @@ -115,15 +147,7 @@ fun StatsScreen( ) } item { - Text( - text = stringResource(R.string.most_played_artists), - style = MaterialTheme.typography.headlineMedium, - overflow = TextOverflow.Ellipsis, - modifier = Modifier - .fillMaxWidth() - .background(MaterialTheme.colorScheme.background) - .padding(horizontal = 12.dp, vertical = 8.dp) - ) + NavigationTitle(stringResource(R.string.most_played_artists)) } items( items = mostPlayedArtists, diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistScreen.kt index 7efc0238e..6b337fa52 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistScreen.kt @@ -69,6 +69,7 @@ import com.zionhuang.music.playback.queues.YouTubeQueue import com.zionhuang.music.ui.component.AutoResizeText import com.zionhuang.music.ui.component.FontSizeRange import com.zionhuang.music.ui.component.LocalMenuState +import com.zionhuang.music.ui.component.NavigationTitle import com.zionhuang.music.ui.component.SongListItem import com.zionhuang.music.ui.component.YouTubeGridItem import com.zionhuang.music.ui.component.YouTubeListItem @@ -197,28 +198,12 @@ fun ArtistScreen( if (librarySongs.isNotEmpty()) { item { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .clickable { - navController.navigate("artist/${viewModel.artistId}/songs") - } - .padding(12.dp) - ) { - Column( - modifier = Modifier.weight(1f) - ) { - Text( - text = stringResource(R.string.from_your_library), - style = MaterialTheme.typography.headlineMedium - ) + NavigationTitle( + title = stringResource(R.string.from_your_library), + onClick = { + navController.navigate("artist/${viewModel.artistId}/songs") } - Icon( - painter = painterResource(R.drawable.navigate_next), - contentDescription = null - ) - } + ) } items( @@ -264,30 +249,12 @@ fun ArtistScreen( artistPage.sections.fastForEach { section -> item { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .clickable(enabled = section.moreEndpoint != null) { - navController.navigate("artist/${viewModel.artistId}/items?browseId=${section.moreEndpoint?.browseId}?params=${section.moreEndpoint?.params}") - } - .padding(12.dp) - ) { - Column( - modifier = Modifier.weight(1f) - ) { - Text( - text = section.title, - style = MaterialTheme.typography.headlineMedium - ) - } - if (section.moreEndpoint != null) { - Icon( - painter = painterResource(R.drawable.navigate_next), - contentDescription = null - ) + NavigationTitle( + title = section.title, + onClick = { + navController.navigate("artist/${viewModel.artistId}/items?browseId=${section.moreEndpoint?.browseId}?params=${section.moreEndpoint?.params}") } - } + ) } if ((section.items.firstOrNull() as? SongItem)?.album != null) { diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/search/OnlineSearchResult.kt b/app/src/main/java/com/zionhuang/music/ui/screens/search/OnlineSearchResult.kt index f02282d0a..ffec47295 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/search/OnlineSearchResult.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/search/OnlineSearchResult.kt @@ -3,7 +3,6 @@ package com.zionhuang.music.ui.screens.search import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable import androidx.compose.foundation.horizontalScroll -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets @@ -11,7 +10,6 @@ import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.add import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars @@ -37,7 +35,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.snapshotFlow -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -60,13 +57,13 @@ import com.zionhuang.music.LocalPlayerAwareWindowInsets import com.zionhuang.music.LocalPlayerConnection import com.zionhuang.music.R import com.zionhuang.music.constants.AppBarHeight -import com.zionhuang.music.constants.ListItemHeight import com.zionhuang.music.constants.SearchFilterHeight import com.zionhuang.music.extensions.togglePlayPause import com.zionhuang.music.models.toMediaMetadata import com.zionhuang.music.playback.queues.YouTubeQueue import com.zionhuang.music.ui.component.EmptyPlaceholder import com.zionhuang.music.ui.component.LocalMenuState +import com.zionhuang.music.ui.component.NavigationTitle import com.zionhuang.music.ui.component.YouTubeListItem import com.zionhuang.music.ui.component.shimmer.ListItemPlaceHolder import com.zionhuang.music.ui.component.shimmer.ShimmerHost @@ -189,20 +186,7 @@ fun OnlineSearchResult( if (searchFilter == null) { searchSummary?.summaries?.forEach { summary -> item { - Box( - contentAlignment = Alignment.CenterStart, - modifier = Modifier - .fillMaxWidth() - .height(ListItemHeight) - .padding(12.dp) - .animateItemPlacement() - ) { - Text( - text = summary.title, - style = MaterialTheme.typography.headlineMedium, - maxLines = 1 - ) - } + NavigationTitle(summary.title) } items( diff --git a/app/src/main/java/com/zionhuang/music/viewmodels/StatsViewModel.kt b/app/src/main/java/com/zionhuang/music/viewmodels/StatsViewModel.kt index 499a08417..a254e442d 100644 --- a/app/src/main/java/com/zionhuang/music/viewmodels/StatsViewModel.kt +++ b/app/src/main/java/com/zionhuang/music/viewmodels/StatsViewModel.kt @@ -3,23 +3,33 @@ package com.zionhuang.music.viewmodels import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.zionhuang.innertube.YouTube +import com.zionhuang.music.constants.StatPeriod import com.zionhuang.music.db.MusicDatabase import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import java.time.Duration import java.time.LocalDateTime import javax.inject.Inject +@OptIn(ExperimentalCoroutinesApi::class) @HiltViewModel class StatsViewModel @Inject constructor( val database: MusicDatabase, ) : ViewModel() { - val mostPlayedSongs = database.mostPlayedSongs() - .stateIn(viewModelScope, SharingStarted.Lazily, emptyList()) - val mostPlayedArtists = database.mostPlayedArtists() - .stateIn(viewModelScope, SharingStarted.Lazily, emptyList()) + val statPeriod = MutableStateFlow(StatPeriod.`1_WEEK`) + + val mostPlayedSongs = statPeriod.flatMapLatest { period -> + database.mostPlayedSongs(period.toTimeMillis()) + }.stateIn(viewModelScope, SharingStarted.Lazily, emptyList()) + + val mostPlayedArtists = statPeriod.flatMapLatest { period -> + database.mostPlayedArtists(period.toTimeMillis()) + }.stateIn(viewModelScope, SharingStarted.Lazily, emptyList()) init { viewModelScope.launch { @@ -39,4 +49,4 @@ class StatsViewModel @Inject constructor( } } } -} \ No newline at end of file +} diff --git a/app/src/main/res/values-DE/strings.xml b/app/src/main/res/values-DE/strings.xml index 94ab83ee2..53f29c240 100644 --- a/app/src/main/res/values-DE/strings.xml +++ b/app/src/main/res/values-DE/strings.xml @@ -140,6 +140,18 @@ %d Wiedergabeliste %d Wiedergabelisten + + %d week + %d weeks + + + %d month + %d months + + + %d year + %d years + Wiedergabeliste importiert diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 87f8a35a5..167a94f29 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -150,6 +150,18 @@ %d playlistů %d playlistů + + %d week + %d weeks + + + %d month + %d months + + + %d year + %d years + Playlist importován diff --git a/app/src/main/res/values-es-rUS/strings.xml b/app/src/main/res/values-es-rUS/strings.xml index 25009b1aa..c27187883 100644 --- a/app/src/main/res/values-es-rUS/strings.xml +++ b/app/src/main/res/values-es-rUS/strings.xml @@ -140,6 +140,18 @@ %d playlist %d playlists + + %d week + %d weeks + + + %d month + %d months + + + %d year + %d years + Playlist imported diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 7c3f536fb..9ccd681af 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -140,6 +140,18 @@ %d lista de reproducción %d listas de reproducción + + %d week + %d weeks + + + %d month + %d months + + + %d year + %d years + Lista de reproducción importada diff --git a/app/src/main/res/values-fa-rIR/strings.xml b/app/src/main/res/values-fa-rIR/strings.xml index b1b858360..966a6920f 100644 --- a/app/src/main/res/values-fa-rIR/strings.xml +++ b/app/src/main/res/values-fa-rIR/strings.xml @@ -140,6 +140,18 @@ %d لیست‌پخش %d لیست‌پخش‌ها + + %d week + %d weeks + + + %d month + %d months + + + %d year + %d years + لیست‌پخش واردشد diff --git a/app/src/main/res/values-fi-rFI/strings.xml b/app/src/main/res/values-fi-rFI/strings.xml index 85d8105ca..9749aaff2 100644 --- a/app/src/main/res/values-fi-rFI/strings.xml +++ b/app/src/main/res/values-fi-rFI/strings.xml @@ -140,6 +140,18 @@ %d playlist %d playlists + + %d week + %d weeks + + + %d month + %d months + + + %d year + %d years + Playlist imported diff --git a/app/src/main/res/values-fr-rFR/strings.xml b/app/src/main/res/values-fr-rFR/strings.xml index 5f87d3eae..278d56822 100644 --- a/app/src/main/res/values-fr-rFR/strings.xml +++ b/app/src/main/res/values-fr-rFR/strings.xml @@ -145,6 +145,18 @@ %d playlists %d albums + + %d week + %d weeks + + + %d month + %d months + + + %d year + %d years + Playlist imported diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index f204afadf..a674853c1 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -140,6 +140,18 @@ %d lejátszólista %d lejátszólista + + %d week + %d weeks + + + %d month + %d months + + + %d year + %d years + Lista importálva diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index 54e909358..e7986caa8 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -135,6 +135,18 @@ %d daftar putar + + %d week + %d weeks + + + %d month + %d months + + + %d year + %d years + Daftar putar dipindahkan diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 3dd7caa7a..e241a1005 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -140,6 +140,18 @@ %d playlist %d playlist + + %d week + %d weeks + + + %d month + %d months + + + %d year + %d years + Playlist importata diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index 32807e68b..19434a6ab 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -135,6 +135,18 @@ %d プレイリスト + + %d week + %d weeks + + + %d month + %d months + + + %d year + %d years + プレイリストをインポートしました diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml index 910c80fb0..9eb032c6e 100644 --- a/app/src/main/res/values-ko-rKR/strings.xml +++ b/app/src/main/res/values-ko-rKR/strings.xml @@ -135,6 +135,18 @@ %d playlists + + %d week + %d weeks + + + %d month + %d months + + + %d year + %d years + Playlist imported diff --git a/app/src/main/res/values-ml-rIN/strings.xml b/app/src/main/res/values-ml-rIN/strings.xml index 2e746fa59..8b88c3337 100644 --- a/app/src/main/res/values-ml-rIN/strings.xml +++ b/app/src/main/res/values-ml-rIN/strings.xml @@ -140,6 +140,18 @@ %d പ്ലേലിസ്റ്റ് %d പ്ലേലിസ്റ്റുകൾ + + %d week + %d weeks + + + %d month + %d months + + + %d year + %d years + പ്ലേലിസ്റ്റ് ഇറക്കുമതി ചെയ്തു diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 95ac85fbe..1a98acd54 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -140,6 +140,18 @@ %d afspeellijst %d afspeellijsten + + %d week + %d weeks + + + %d month + %d months + + + %d year + %d years + Afspeellijst geimporteerd diff --git a/app/src/main/res/values-or-rIN/strings.xml b/app/src/main/res/values-or-rIN/strings.xml index 8999b3f0e..188ba3e14 100644 --- a/app/src/main/res/values-or-rIN/strings.xml +++ b/app/src/main/res/values-or-rIN/strings.xml @@ -140,6 +140,18 @@ %d playlist %d playlists + + %d week + %d weeks + + + %d month + %d months + + + %d year + %d years + ପ୍ଲେଲିଷ୍ଟ ଆମଦାନୀ ହୋଇଛି diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index 185905700..d69e44aff 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -140,6 +140,18 @@ %d ਪਲੇਲਿਸਟ %d ਪਲੇਲਿਸਟਾਂ + + %d week + %d weeks + + + %d month + %d months + + + %d year + %d years + ਪਲੇਲਿਸਟ ਅਯਾਤ ਕੀਤੀ ਗਈ diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 8d6e79c27..a037c9533 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -140,6 +140,18 @@ %d playlist %d playlists + + %d week + %d weeks + + + %d month + %d months + + + %d year + %d years + Playlist importada diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index 3c97c88b4..b6f25cbc7 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -150,6 +150,18 @@ %d плейлистов %d плейлистов + + %d week + %d weeks + + + %d month + %d months + + + %d year + %d years + Плейлист импортирован diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index d16f32e19..5a7779493 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -140,6 +140,18 @@ %d playlist %d playlists + + %d week + %d weeks + + + %d month + %d months + + + %d year + %d years + Playlist imported diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index cf5ff59cc..3f086830b 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -140,6 +140,18 @@ %d çalma listesi %d çalma listesi + + %d week + %d weeks + + + %d month + %d months + + + %d year + %d years + Çalma listesi içe aktarıldı diff --git a/app/src/main/res/values-uk-rUA/strings.xml b/app/src/main/res/values-uk-rUA/strings.xml index 6f9072e62..b011cb365 100644 --- a/app/src/main/res/values-uk-rUA/strings.xml +++ b/app/src/main/res/values-uk-rUA/strings.xml @@ -150,6 +150,18 @@ %d плейлистів %d плейлистів + + %d week + %d weeks + + + %d month + %d months + + + %d year + %d years + Плейлист імпортовано diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 90eb760fb..8a6eb95aa 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -135,6 +135,18 @@ %d 个播放列表 + + %d week + %d weeks + + + %d month + %d months + + + %d year + %d years + 已导入此播放列表 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index ab03c867c..0aa96aad6 100755 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -135,6 +135,15 @@ %d 個播放清單 + + %d 週 + + + %d 個月 + + + %d 年 + 已匯入此播放清單 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 234f531a2..9e9b4640e 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -139,6 +139,18 @@ %d playlist %d playlists + + %d week + %d weeks + + + %d month + %d months + + + %d year + %d years + Playlist imported From 61cc54f6957845be84aa92e51bff0210f3fcb64a Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Tue, 18 Jul 2023 17:31:43 +0800 Subject: [PATCH 34/84] Print stack trace --- app/src/main/java/com/zionhuang/music/MainActivity.kt | 4 ++++ .../main/java/com/zionhuang/music/viewmodels/HomeViewModel.kt | 2 ++ .../com/zionhuang/music/viewmodels/NewReleaseViewModel.kt | 2 ++ 3 files changed, 8 insertions(+) diff --git a/app/src/main/java/com/zionhuang/music/MainActivity.kt b/app/src/main/java/com/zionhuang/music/MainActivity.kt index cbd3f45d3..db7d04134 100644 --- a/app/src/main/java/com/zionhuang/music/MainActivity.kt +++ b/app/src/main/java/com/zionhuang/music/MainActivity.kt @@ -344,6 +344,8 @@ class MainActivity : ComponentActivity() { songs.firstOrNull()?.album?.id?.let { browseId -> navController.navigate("album/$browseId") } + }.onFailure { + it.printStackTrace() } } } else { @@ -365,6 +367,8 @@ class MainActivity : ComponentActivity() { YouTube.queue(listOf(videoId)) }.onSuccess { sharedSong = it.firstOrNull() + }.onFailure { + it.printStackTrace() } } } diff --git a/app/src/main/java/com/zionhuang/music/viewmodels/HomeViewModel.kt b/app/src/main/java/com/zionhuang/music/viewmodels/HomeViewModel.kt index 5803c55e9..cabe66ff9 100644 --- a/app/src/main/java/com/zionhuang/music/viewmodels/HomeViewModel.kt +++ b/app/src/main/java/com/zionhuang/music/viewmodels/HomeViewModel.kt @@ -26,6 +26,8 @@ class HomeViewModel @Inject constructor( quickPicks.value = database.quickPicks().first().shuffled().take(20) YouTube.explore().onSuccess { explorePage.value = it + }.onFailure { + it.printStackTrace() } } diff --git a/app/src/main/java/com/zionhuang/music/viewmodels/NewReleaseViewModel.kt b/app/src/main/java/com/zionhuang/music/viewmodels/NewReleaseViewModel.kt index 8153e8cb6..8770dfe64 100644 --- a/app/src/main/java/com/zionhuang/music/viewmodels/NewReleaseViewModel.kt +++ b/app/src/main/java/com/zionhuang/music/viewmodels/NewReleaseViewModel.kt @@ -19,6 +19,8 @@ class NewReleaseViewModel @Inject constructor() : ViewModel() { viewModelScope.launch { YouTube.newReleaseAlbums().onSuccess { _newReleaseAlbums.value = it + }.onFailure { + it.printStackTrace() } } } From ea183e9438bae8ce0245efd32e159f4c7cb24f39 Mon Sep 17 00:00:00 2001 From: metezd <37701679+metezd@users.noreply.github.com> Date: Tue, 18 Jul 2023 18:00:21 +0300 Subject: [PATCH 35/84] Update strings.xml --- app/src/main/res/values-tr/strings.xml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index c8b48e118..4ce87d1b5 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -16,7 +16,7 @@ Geçmiş İstatistikler Ruh Hali ve Türler - Account + Hesap Hızlı seçimler Hızlı seçimlerinizi oluşturmak için birkaç şarkı dinleyin Yeni çıkan albümler @@ -63,7 +63,7 @@ Düzenle Radyo başlat Çal - Sonraki olarak çal + Bir sonra çal Sıraya ekle Kütüphaneye ekle Kütüphaneden kaldır @@ -140,6 +140,18 @@ %d çalma listesi %d çalma listesi + + %d hafta + %d hafta + + + %d ay + %d ay + + + %d yıl + %d yıl + Çalma listesi içe aktarıldı @@ -235,8 +247,8 @@ Google Takeout\'tan bir csv dosyası seçin Çalma listesi içe aktarıldı - \"%s\" %d şarkı ile içe aktarıldı - "%s\" %d şarkı ile içe aktarıldı + "\"%s\" %d şarkı ile içe aktarıldı + "\"%s\" %d şarkı ile içe aktarıldı Yedekleme başarıyla oluşturuldu Yedekleme oluşturulamadı From 73f289d12c077ec13aea139f97d4b0dc09d23e0f Mon Sep 17 00:00:00 2001 From: Oleksandr Tkachenko Date: Tue, 18 Jul 2023 21:45:59 +0300 Subject: [PATCH 36/84] Update strings.xml --- app/src/main/res/values-ru-rRU/strings.xml | 28 +++++++++++++--------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index b6f25cbc7..6f3b35fed 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -17,8 +17,8 @@ История Статистика - Mood and Genres - Account + Настроение и жанры + Аккаунт Быстрый выбор Послушайте несколько песен, чтобы создать ваш быстрый выбор Новые релизы альбомов @@ -151,22 +151,28 @@ %d плейлистов - %d week - %d weeks + %d неделя + %d недели + %d недель + %d недель - %d month - %d months + %d месяц + %d месяца + %d месяцев + %d месяцев - %d year - %d years + %d год + %d года + %d лет + %d лет Плейлист импортирован «%s» удалено из списка воспроизведения - Playlist synced + Плейлист синхронизирован Отменить @@ -259,8 +265,8 @@ Выбрать csv-файл из Google Takeout Импортированный плейлист - Imported \"%s\" with %d song - Imported \"%s\" with %d songs + Импортировано «%s» с %d композицией + Импортировано «%s» с %d композициями Резервная копия создана успешно Не удалось создать резервную копию From df2d08f29e0e95f8eee70770a4eed96e0f0c2651 Mon Sep 17 00:00:00 2001 From: Oleksandr Tkachenko Date: Tue, 18 Jul 2023 21:46:30 +0300 Subject: [PATCH 37/84] Update strings.xml --- app/src/main/res/values-uk-rUA/strings.xml | 28 +++++++++++++--------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/app/src/main/res/values-uk-rUA/strings.xml b/app/src/main/res/values-uk-rUA/strings.xml index b011cb365..5b76440e8 100644 --- a/app/src/main/res/values-uk-rUA/strings.xml +++ b/app/src/main/res/values-uk-rUA/strings.xml @@ -17,8 +17,8 @@ Історія Статистика - Mood and Genres - Account + Настрій та жанри + Акаунт Швидкий вибір Послухайте кілька пісень, щоб створити ваш швидкий вибір Нові релізи альбомів @@ -151,22 +151,28 @@ %d плейлистів - %d week - %d weeks + %d тиждень + %d тижні + %d тижнів + %d тижнів - %d month - %d months + %d місяць + %d місяці + %d місяців + %d місяців - %d year - %d years + %d рік + %d роки + %d років + %d років Плейлист імпортовано «%s» видалено зі списку відтворення - Playlist synced + Плейлист синхронізовано Скасувати @@ -259,8 +265,8 @@ Вибрати csv-файл із Google Takeout Імпортований плейлист - Imported \"%s\" with %d song - Imported \"%s\" with %d songs + Імпортовано «%s» з %d композицією + Імпортовано «%s» з %d композиціями Резервну копію створено успішно Не вдалося створити резервну копію From d5acb33d959ebfc5b15b8cd2baf40b0f281e43a1 Mon Sep 17 00:00:00 2001 From: Pavel Kesso Date: Wed, 19 Jul 2023 20:41:03 +0300 Subject: [PATCH 38/84] Add belarusian translation --- app/src/main/res/values-be/strings.xml | 277 +++++++++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 app/src/main/res/values-be/strings.xml diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml new file mode 100644 index 000000000..f54e4744e --- /dev/null +++ b/app/src/main/res/values-be/strings.xml @@ -0,0 +1,277 @@ + + + Галоўнае + Песні + Выканаўцы + Альбомы + Плэй-лісты + + + + %d абрана + %d абраны + %d абрана + %d абрана + + + + Гісторыя + Статыстыка + Настроі і жанры + Уліковы запіс + Хуткі выбар + Праслухайце якія-небудзь кампазіцыі, каб стварыць ваш хуткі выбар. + Новыя рэлізы альбомаў + + + Сёння + Учора + На гэтым тыднія + На тым тыдні + + + Лепшыя кампазіцыі + Лепшыя выканаўцы + + + Пошук + Пошук у YouTube Music… + Пошук у бібліятэцы… + Усе + Песні + Відэа + Альбомы + Выканаўцы + Плэй-лісты + Плэй-лісты aд cупольнасці + Вартыя ўвагі плэй-лісты + Вынікі не знойдзены + + + З вашай бібліятэкі + + + Упадабаныя кампазіцыi + Спампаваныя кампазіцыі + Плай-ліст пусты + + + Паўтарыць + Радыё + Перамяшаць + + + Падрабязнасці + Змяніць + Уключыць радыё + Прайграць + Прайграць наступным + Дадаць у чаргу + Дадаць у бібліятэку + Выдаліць з бібліятэкі + Спампаваць + Спампоўка + Выдаліць са спамповак + Імпартаваць плей-ліст + Дадаць у плэй-ліст + Перайсці да выканаўца + Перайсці да альбома + Абнавіць + Абагуліць + Выдаліць + Выдаліць з гісторыі + Шукаць у сетцы + Cінхранізацыя + + + Нядаўна дададзена + Назва + Выканаўца + Год + Колькасць песен + Працягласць + Колькасць прайграванняў + Уласны парадак + + + Iдэнтыфікатар медыя + Тып MIME + Кодэкі + Хуткасць + Частата дыскрэтызацыі + Гучнасць + Узровень гучнасці + Памер файла + Невядомы + Скапіявана + + Змяниць тэкст песні + Пошук тэкста песні + + Змяніць песню + Назва песні + Выканаўца песні + Песня павінна мець назву. + Песня павінна мець выканаўца. + Захаваць + + Выбраць плэй-ліст + Змяніць плей-ліст + Стварыць плей-ліст + Назва плей-ліста + Плей-ліст павінен мець назву. + + Змяніць выканайца + Імя ваканаўца + Выканаўца павінен мець імя. + + + + %d песня + %d песні + %d песен + %d песні + + + %d выканаўца + %d выканаўцы + %d выканаўцаў + %d выканаўцы + + + %d альбом + %d альбомы + %d альбомаў + %d альбомы + + + %d плей-ліст + %d плей-ліста + %d плей-лістоў + %d плей-ліста + + + %d тыдзень + %d тыдні + %d тыдняў + %d тыдні + + + %d месяц + %d месяцы + %d месяцаў + %d месяцы + + + %d год + %d гады + %d raдоў + %d гады + + + + Плей-ліст імпартаваны + \"%s\" выдалена з плей-ліста + Плей-ліст сінхранізаваны + Адрабіць + + + Тэкст песні не знойдзены + Таймер рэжыму сну + Канец песні + + 1 хвіліна + %d хвіліны + %d хвілін + %d хвілін + + Няма даступных плыняў + Няма падлучэння да сеткі + Час чакання + Невядомая памылка + + + Упадабаць + Выдаліць з упадабаных + + + Усе песні + Шуканыя кампазіцыі + + + Музычны прайгравальнік + + + Налады + Выгляд + Уключыць дынамічную каляровую тэму + Цёмная каляровая тэма + Укл. + Выкл. + Прытрымлівацца сістэмнай каляровай тэмы + Рэжым чыстага чорнага колеру + Прадвызначаная укладка + Дапасаваць укладак + Пазіцыя тэкста песні + Па левым краі + Па цэнтры + Па правым краі + + Змесціва + Лагін + Мова змесціва + Краіна змесціва + Прытрымлівацца сістэмнай + Уключыць проксі + Тып проксі + URL проксі + Перазапусціць каб ужыць змяненні + + Прайгравальнік ды аўдыя + Якасць аўдыя + Аўта + Высокая + Нізкая + Сталая чарга + Прапусціць цішыню + Нармалізацыя аўдыя + Эквалайзер + + Сховішча + Кеш + Кеш выяў + Кеш песен + Максімальны памер кэшу + Безліміт + Ачысціць усе спампоўкі + Максімальны памер кэшу выяў + Ачысціць кэш выяў + Максімальны памер кэшу песен + Ачысціць кэш песен + %s выкарыстоўваецца + + Прыватнасць + Прыпыніць гісторыю праслухоўвання + Ачысціць гісторыю праслухоўвання + Сапраўды ачысціць гісторыю праслухоўвання? + Прыпыніць гісторыю пошуку + Ачысціць гісторыю пошуку + Сапраўды ачысціць гісторыю пошуку? + Шукаць тэксты песен у KuGou + + Рэзервовае капіраванне ды аднеўленне + Рэзервовае капіраванне + Аднаўленне з рэзервовай копіі + Выбраць cvs-файл з Google Takeout + Імпартаваны плей-ліст + + \"%s\" імпаправаны з %d песняй + \"%s\" імпаправаны з %d песнямі + + Рэзервовная копія паспяхова створана + Немагчыма стапрыць рэзервовую копію + Немагчыма ужывіць рэзервовую копію + + Аб праграме + Версія праграмы + From 6e6373619ff9bf202e4bb7ee7acb1fe995175ad9 Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Thu, 20 Jul 2023 12:05:05 +0800 Subject: [PATCH 39/84] Show most played albums in stats screen --- .../com/zionhuang/music/db/DatabaseDao.kt | 20 ++++++++- .../zionhuang/music/ui/screens/StatsScreen.kt | 45 +++++++++++++++++++ .../music/viewmodels/StatsViewModel.kt | 10 +++++ app/src/main/res/values-DE/strings.xml | 1 + app/src/main/res/values-cs/strings.xml | 1 + app/src/main/res/values-es-rUS/strings.xml | 1 + app/src/main/res/values-es/strings.xml | 1 + app/src/main/res/values-fa-rIR/strings.xml | 1 + app/src/main/res/values-fi-rFI/strings.xml | 1 + app/src/main/res/values-fr-rFR/strings.xml | 1 + app/src/main/res/values-hu/strings.xml | 1 + app/src/main/res/values-id/strings.xml | 1 + app/src/main/res/values-it/strings.xml | 1 + app/src/main/res/values-ja-rJP/strings.xml | 1 + app/src/main/res/values-ko-rKR/strings.xml | 1 + app/src/main/res/values-ml-rIN/strings.xml | 1 + app/src/main/res/values-nl/strings.xml | 1 + app/src/main/res/values-or-rIN/strings.xml | 1 + app/src/main/res/values-pa/strings.xml | 1 + app/src/main/res/values-pt-rBR/strings.xml | 1 + app/src/main/res/values-ru-rRU/strings.xml | 1 + app/src/main/res/values-sv-rSE/strings.xml | 1 + app/src/main/res/values-tr/strings.xml | 1 + app/src/main/res/values-uk-rUA/strings.xml | 1 + app/src/main/res/values-zh-rCN/strings.xml | 1 + app/src/main/res/values-zh-rTW/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + .../java/com/zionhuang/innertube/YouTube.kt | 4 +- 28 files changed, 100 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt b/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt index 72a016433..f18a4559f 100644 --- a/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt +++ b/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt @@ -161,7 +161,7 @@ interface DatabaseDao { @Transaction @Query( """ - SELECT *, + SELECT artist.*, (SELECT COUNT(1) FROM song_artist_map JOIN song ON song_artist_map.songId = song.id @@ -183,6 +183,24 @@ interface DatabaseDao { ) fun mostPlayedArtists(fromTimeStamp: Long, limit: Int = 6): Flow> + @Transaction + @Query( + """ + SELECT albumId + FROM song + JOIN (SELECT songId, SUM(playTime) AS songTotalPlayTime + FROM event + WHERE timestamp > :fromTimeStamp + GROUP BY songId) AS e + ON song.id = e.songId + WHERE albumId IS NOT NULL + GROUP BY albumId + ORDER BY SUM(songTotalPlayTime) DESC + LIMIT :limit + """ + ) + fun mostPlayedAlbums(fromTimeStamp: Long, limit: Int = 6): Flow> + @Transaction @Query("SELECT * FROM song WHERE id = :songId") fun song(songId: String?): Flow diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/StatsScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/StatsScreen.kt index 6c0bec951..a72f2b024 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/StatsScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/StatsScreen.kt @@ -45,7 +45,9 @@ import com.zionhuang.music.ui.component.ArtistListItem import com.zionhuang.music.ui.component.LocalMenuState import com.zionhuang.music.ui.component.NavigationTitle import com.zionhuang.music.ui.component.SongListItem +import com.zionhuang.music.ui.component.YouTubeListItem import com.zionhuang.music.ui.menu.SongMenu +import com.zionhuang.music.ui.menu.YouTubeAlbumMenu import com.zionhuang.music.viewmodels.StatsViewModel @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @@ -62,6 +64,7 @@ fun StatsScreen( val statPeriod by viewModel.statPeriod.collectAsState() val mostPlayedSongs by viewModel.mostPlayedSongs.collectAsState() val mostPlayedArtists by viewModel.mostPlayedArtists.collectAsState() + val mostPlayedAlbums by viewModel.mostPlayedAlbums.collectAsState() LazyColumn( contentPadding = LocalPlayerAwareWindowInsets.current.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Bottom).asPaddingValues(), @@ -99,6 +102,7 @@ fun StatsScreen( } } } + item { NavigationTitle(stringResource(R.string.most_played_songs)) } @@ -146,6 +150,7 @@ fun StatsScreen( .animateItemPlacement() ) } + item { NavigationTitle(stringResource(R.string.most_played_artists)) } @@ -163,6 +168,46 @@ fun StatsScreen( .animateItemPlacement() ) } + + if (mostPlayedAlbums.isNotEmpty()) { + item { + NavigationTitle(stringResource(R.string.most_played_albums)) + } + items( + items = mostPlayedAlbums, + key = { it.id } + ) { item -> + YouTubeListItem( + item = item, + isActive = mediaMetadata?.album?.id == item.id, + isPlaying = isPlaying, + trailingContent = { + IconButton( + onClick = { + menuState.show { + YouTubeAlbumMenu( + album = item, + navController = navController, + playerConnection = playerConnection, + onDismiss = menuState::dismiss + ) + } + } + ) { + Icon( + painter = painterResource(R.drawable.more_vert), + contentDescription = null + ) + } + }, + modifier = Modifier + .clickable { + navController.navigate("album/${item.id}") + } + .animateItemPlacement() + ) + } + } } TopAppBar( diff --git a/app/src/main/java/com/zionhuang/music/viewmodels/StatsViewModel.kt b/app/src/main/java/com/zionhuang/music/viewmodels/StatsViewModel.kt index a254e442d..d8dda558d 100644 --- a/app/src/main/java/com/zionhuang/music/viewmodels/StatsViewModel.kt +++ b/app/src/main/java/com/zionhuang/music/viewmodels/StatsViewModel.kt @@ -10,6 +10,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import java.time.Duration @@ -31,6 +32,15 @@ class StatsViewModel @Inject constructor( database.mostPlayedArtists(period.toTimeMillis()) }.stateIn(viewModelScope, SharingStarted.Lazily, emptyList()) + + val mostPlayedAlbums = statPeriod.flatMapLatest { period -> + database.mostPlayedAlbums(period.toTimeMillis()) + }.map { albums -> + albums.mapNotNull { id -> + YouTube.album(id, withSongs = false).getOrNull()?.album + } + }.stateIn(viewModelScope, SharingStarted.Lazily, emptyList()) + init { viewModelScope.launch { mostPlayedArtists.collect { artists -> diff --git a/app/src/main/res/values-DE/strings.xml b/app/src/main/res/values-DE/strings.xml index 53f29c240..91956d310 100644 --- a/app/src/main/res/values-DE/strings.xml +++ b/app/src/main/res/values-DE/strings.xml @@ -30,6 +30,7 @@ Meist gespielte Songs Meist gespielte Künstler + Most played albums Suche diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 167a94f29..ac442eef0 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -32,6 +32,7 @@ Most played songs Most played artists + Most played albums Vyhledávání diff --git a/app/src/main/res/values-es-rUS/strings.xml b/app/src/main/res/values-es-rUS/strings.xml index c27187883..72f6d0757 100644 --- a/app/src/main/res/values-es-rUS/strings.xml +++ b/app/src/main/res/values-es-rUS/strings.xml @@ -30,6 +30,7 @@ Most played songs Most played artists + Most played albums Buscar diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 9ccd681af..6f5acecfe 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -30,6 +30,7 @@ Canciones más reproducidas Artistas más reproducidos + Most played albums Buscar diff --git a/app/src/main/res/values-fa-rIR/strings.xml b/app/src/main/res/values-fa-rIR/strings.xml index 966a6920f..6119ae183 100644 --- a/app/src/main/res/values-fa-rIR/strings.xml +++ b/app/src/main/res/values-fa-rIR/strings.xml @@ -30,6 +30,7 @@ Most played songs Most played artists + Most played albums جستجو diff --git a/app/src/main/res/values-fi-rFI/strings.xml b/app/src/main/res/values-fi-rFI/strings.xml index 9749aaff2..3e10ad5ab 100644 --- a/app/src/main/res/values-fi-rFI/strings.xml +++ b/app/src/main/res/values-fi-rFI/strings.xml @@ -30,6 +30,7 @@ Most played songs Most played artists + Most played albums Etsi diff --git a/app/src/main/res/values-fr-rFR/strings.xml b/app/src/main/res/values-fr-rFR/strings.xml index 278d56822..ef0a46350 100644 --- a/app/src/main/res/values-fr-rFR/strings.xml +++ b/app/src/main/res/values-fr-rFR/strings.xml @@ -31,6 +31,7 @@ Most played songs Most played artists + Most played albums Recherche diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index a674853c1..d3e6e5a1b 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -30,6 +30,7 @@ A legtöbbet játszott dalok A legtöbbet játszott előadók + Most played albums Keresés diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index e7986caa8..7bd3e5aab 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -29,6 +29,7 @@ Most played songs Most played artists + Most played albums Caru diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index e241a1005..769ebf631 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -30,6 +30,7 @@ Brani più ascoltati Artisti più ascoltati + Most played albums Cerca diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index 19434a6ab..6864ad314 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -29,6 +29,7 @@ 最も再生された曲 最も再生されたアーティスト + Most played albums 検索 diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml index 9eb032c6e..91832347e 100644 --- a/app/src/main/res/values-ko-rKR/strings.xml +++ b/app/src/main/res/values-ko-rKR/strings.xml @@ -29,6 +29,7 @@ Most played songs Most played artists + Most played albums 검색 diff --git a/app/src/main/res/values-ml-rIN/strings.xml b/app/src/main/res/values-ml-rIN/strings.xml index 8b88c3337..fb805bb0f 100644 --- a/app/src/main/res/values-ml-rIN/strings.xml +++ b/app/src/main/res/values-ml-rIN/strings.xml @@ -30,6 +30,7 @@ Most played songs Most played artists + Most played albums തിരയുക diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 1a98acd54..19ba9e86c 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -30,6 +30,7 @@ Meest afgespeelde nummers Meest afgespeelde artiesten + Most played albums Zoeken diff --git a/app/src/main/res/values-or-rIN/strings.xml b/app/src/main/res/values-or-rIN/strings.xml index 188ba3e14..dcc217e37 100644 --- a/app/src/main/res/values-or-rIN/strings.xml +++ b/app/src/main/res/values-or-rIN/strings.xml @@ -30,6 +30,7 @@ Most played songs Most played artists + Most played albums ସନ୍ଧାନ କରନ୍ତୁ diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index d69e44aff..1f3bc0015 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -30,6 +30,7 @@ ਵੱਧ ਸੁਣੇ ਗਏ ਗੀਤ Most played artists + Most played albums ਖੋਜੋ diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index a037c9533..a68bc7db3 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -30,6 +30,7 @@ Most played songs Most played artists + Most played albums Pesquisar diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index b6f25cbc7..7a93d09f9 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -32,6 +32,7 @@ Самые популярные песни Самые популярные исполнители + Most played albums Поиск diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index 5a7779493..56ff8e4b5 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -30,6 +30,7 @@ Most played songs Most played artists + Most played albums Sök diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 3f086830b..674f8ea9a 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -30,6 +30,7 @@ En çok dinlenen şarkılar En çok dinlenen sanatçılar + Most played albums Arama diff --git a/app/src/main/res/values-uk-rUA/strings.xml b/app/src/main/res/values-uk-rUA/strings.xml index b011cb365..38dc763f8 100644 --- a/app/src/main/res/values-uk-rUA/strings.xml +++ b/app/src/main/res/values-uk-rUA/strings.xml @@ -32,6 +32,7 @@ Найпопулярніші пісні Найпопулярніші виконавці + Most played albums Пошук diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 8a6eb95aa..39536f960 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -29,6 +29,7 @@ 最常播放 Most played artists + Most played albums 搜索 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 0aa96aad6..f3330bd9d 100755 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -29,6 +29,7 @@ 最常播放的歌曲 最常播放的藝人 + 最常播放的專輯 搜尋 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9e9b4640e..7101979dc 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -29,6 +29,7 @@ Most played songs Most played artists + Most played albums Search diff --git a/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt b/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt index 78d3f663e..29cb837d9 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt @@ -148,7 +148,7 @@ object YouTube { ) } - suspend fun album(browseId: String): Result = runCatching { + suspend fun album(browseId: String, withSongs: Boolean = true): Result = runCatching { val response = innerTube.browse(WEB_REMIX, browseId).body() val playlistId = response.microformat?.microformatDataRenderer?.urlCanonical?.substringAfterLast('=')!! AlbumPage( @@ -165,7 +165,7 @@ object YouTube { year = response.header.musicDetailHeaderRenderer.subtitle.runs.lastOrNull()?.text?.toIntOrNull(), thumbnail = response.header.musicDetailHeaderRenderer.thumbnail.croppedSquareThumbnailRenderer?.getThumbnailUrl()!! ), - songs = albumSongs(playlistId).getOrThrow() + songs = if (withSongs) albumSongs(playlistId).getOrThrow() else emptyList() ) } From 00cc1a20e76004eb7dc6f8a9ad872221494ff7e1 Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Thu, 20 Jul 2023 12:07:42 +0800 Subject: [PATCH 40/84] Fix artist screen navigation title --- .../com/zionhuang/music/ui/screens/artist/ArtistScreen.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistScreen.kt index 6b337fa52..729222fea 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistScreen.kt @@ -251,8 +251,10 @@ fun ArtistScreen( item { NavigationTitle( title = section.title, - onClick = { - navController.navigate("artist/${viewModel.artistId}/items?browseId=${section.moreEndpoint?.browseId}?params=${section.moreEndpoint?.params}") + onClick = section.moreEndpoint?.let { + { + navController.navigate("artist/${viewModel.artistId}/items?browseId=${it.browseId}?params=${it.params}") + } } ) } From cf53ffd5a249302f83935ad288f297f58ada7f1a Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Fri, 21 Jul 2023 11:48:46 +0800 Subject: [PATCH 41/84] Remove import from Google Takeout --- .../ui/screens/settings/BackupAndRestore.kt | 13 ---- .../viewmodels/BackupRestoreViewModel.kt | 69 ------------------- app/src/main/res/values-DE/strings.xml | 5 -- app/src/main/res/values-cs/strings.xml | 5 -- app/src/main/res/values-es-rUS/strings.xml | 5 -- app/src/main/res/values-es/strings.xml | 5 -- app/src/main/res/values-fa-rIR/strings.xml | 5 -- app/src/main/res/values-fi-rFI/strings.xml | 5 -- app/src/main/res/values-fr-rFR/strings.xml | 5 -- app/src/main/res/values-hu/strings.xml | 5 -- app/src/main/res/values-id/strings.xml | 5 -- app/src/main/res/values-it/strings.xml | 5 -- app/src/main/res/values-ja-rJP/strings.xml | 5 -- app/src/main/res/values-ko-rKR/strings.xml | 5 -- app/src/main/res/values-ml-rIN/strings.xml | 5 -- app/src/main/res/values-nl/strings.xml | 5 -- app/src/main/res/values-or-rIN/strings.xml | 5 -- app/src/main/res/values-pa/strings.xml | 5 -- app/src/main/res/values-pt-rBR/strings.xml | 5 -- app/src/main/res/values-ru-rRU/strings.xml | 5 -- app/src/main/res/values-sv-rSE/strings.xml | 5 -- app/src/main/res/values-tr/strings.xml | 5 -- app/src/main/res/values-uk-rUA/strings.xml | 5 -- app/src/main/res/values-zh-rCN/strings.xml | 5 -- app/src/main/res/values-zh-rTW/strings.xml | 4 -- app/src/main/res/values/strings.xml | 5 -- 26 files changed, 201 deletions(-) diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/settings/BackupAndRestore.kt b/app/src/main/java/com/zionhuang/music/ui/screens/settings/BackupAndRestore.kt index c3adc8ac7..2d8123902 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/settings/BackupAndRestore.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/settings/BackupAndRestore.kt @@ -39,11 +39,6 @@ fun BackupAndRestore( viewModel.restore(context, uri) } } - val importLauncher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> - if (uri != null) { - viewModel.import(context, uri) - } - } Column( Modifier @@ -65,14 +60,6 @@ fun BackupAndRestore( restoreLauncher.launch(arrayOf("application/octet-stream")) } ) - PreferenceEntry( - title = stringResource(R.string.import_playlist), - description = stringResource(R.string.choose_csv_file_from_google_takeout), - icon = R.drawable.input, - onClick = { - importLauncher.launch(arrayOf("*/*")) - } - ) } TopAppBar( diff --git a/app/src/main/java/com/zionhuang/music/viewmodels/BackupRestoreViewModel.kt b/app/src/main/java/com/zionhuang/music/viewmodels/BackupRestoreViewModel.kt index a1483f963..60b92321f 100644 --- a/app/src/main/java/com/zionhuang/music/viewmodels/BackupRestoreViewModel.kt +++ b/app/src/main/java/com/zionhuang/music/viewmodels/BackupRestoreViewModel.kt @@ -3,31 +3,20 @@ package com.zionhuang.music.viewmodels import android.content.Context import android.content.Intent import android.net.Uri -import android.provider.OpenableColumns import android.widget.Toast import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.zionhuang.innertube.YouTube -import com.zionhuang.innertube.YouTube.MAX_GET_QUEUE_SIZE -import com.zionhuang.innertube.models.SongItem import com.zionhuang.music.MainActivity import com.zionhuang.music.R import com.zionhuang.music.db.InternalDatabase import com.zionhuang.music.db.MusicDatabase -import com.zionhuang.music.db.entities.PlaylistEntity -import com.zionhuang.music.db.entities.PlaylistEntity.Companion.generatePlaylistId -import com.zionhuang.music.db.entities.PlaylistSongMap import com.zionhuang.music.extensions.div import com.zionhuang.music.extensions.zipInputStream import com.zionhuang.music.extensions.zipOutputStream -import com.zionhuang.music.models.toMediaMetadata import com.zionhuang.music.playback.MusicService import com.zionhuang.music.playback.MusicService.Companion.PERSISTENT_QUEUE_FILE import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withContext import java.io.FileInputStream import java.io.FileOutputStream import java.util.zip.ZipEntry @@ -100,64 +89,6 @@ class BackupRestoreViewModel @Inject constructor( } } - fun import(context: Context, uri: Uri) { - runCatching { - val videoIds = mutableListOf() - context.applicationContext.contentResolver.openInputStream(uri)?.use { inputStream -> - val br = inputStream.bufferedReader() - repeat(8) { - br.readLine() - } - var line = br.readLine() - while (line != null) { - line.split(",").firstOrNull() - ?.takeIf { it.isNotEmpty() } - ?.let { - videoIds.add(it.trim()) - } - line = br.readLine() - } - } - val playlistName = context.contentResolver.query(uri, null, null, null, null)?.use { cursor -> - val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) - cursor.moveToFirst() - cursor.getString(nameIndex) - }?.removeSuffix(".csv") ?: context.getString(R.string.imported_playlist) - viewModelScope.launch { - val songs = videoIds.chunked(MAX_GET_QUEUE_SIZE).flatMap { - withContext(Dispatchers.IO) { - YouTube.queue(videoIds = it) - }.getOrNull().orEmpty() - } - database.transaction { - val playlistId = generatePlaylistId() - var position = 0 - insert( - PlaylistEntity( - id = playlistId, - name = playlistName - ) - ) - songs.map(SongItem::toMediaMetadata) - .onEach(::insert) - .forEach { - insert( - PlaylistSongMap( - playlistId = playlistId, - songId = it.id, - position = position++ - ) - ) - } - } - Toast.makeText(context, context.resources.getQuantityString(R.plurals.import_success, songs.size, playlistName, songs.size), Toast.LENGTH_SHORT).show() - } - }.onFailure { - it.printStackTrace() - Toast.makeText(context, R.string.restore_failed, Toast.LENGTH_SHORT).show() - } - } - companion object { const val SETTINGS_FILENAME = "settings.preferences_pb" } diff --git a/app/src/main/res/values-DE/strings.xml b/app/src/main/res/values-DE/strings.xml index 91956d310..6ab52387a 100644 --- a/app/src/main/res/values-DE/strings.xml +++ b/app/src/main/res/values-DE/strings.xml @@ -245,12 +245,7 @@ Sichern und Wiederherstellen Datensicherung Wiederherstellen - Wählen Sie eine csv-Datei von Google Takeout Importierte Wiedergabeliste - - Imported \"%s\" with %d song - Imported \"%s\" with %d songs - Sicherung erfolgreich erstellt Konnte keine Sicherung erstellen Wiederherstellung der Sicherung fehlgeschlagen diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index ac442eef0..9bfaadd4b 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -256,12 +256,7 @@ Záloha a obnovení Zálohovat Obnovit - Choose a csv file from Google Takeout Imported playlist - - Imported \"%s\" with %d song - Imported \"%s\" with %d songs - Záloha úspěšně vytvořena Nepodařilo se vytvořit zálohu Nepodařilo se obnovit zálohu diff --git a/app/src/main/res/values-es-rUS/strings.xml b/app/src/main/res/values-es-rUS/strings.xml index 72f6d0757..94fbae973 100644 --- a/app/src/main/res/values-es-rUS/strings.xml +++ b/app/src/main/res/values-es-rUS/strings.xml @@ -245,12 +245,7 @@ Backup and restore Backup Restore - Choose a csv file from Google Takeout Imported playlist - - Imported \"%s\" with %d song - Imported \"%s\" with %d songs - Backup created successfully Couldn\'t create backup Failed to restore backup diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 6f5acecfe..74f3e23ce 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -245,12 +245,7 @@ Copias de seguridad y restauración Hacer copia de seguridad Restaurar - Selecciona un archivo csv de Google Takeout Lista de reproducción importada - - Imported \"%s\" with %d song - Imported \"%s\" with %d songs - Copia de seguridad creada con éxito No se ha podido crear la copia de seguridad Error al restaurar la copia de seguridad diff --git a/app/src/main/res/values-fa-rIR/strings.xml b/app/src/main/res/values-fa-rIR/strings.xml index 6119ae183..5f61dcbdf 100644 --- a/app/src/main/res/values-fa-rIR/strings.xml +++ b/app/src/main/res/values-fa-rIR/strings.xml @@ -245,12 +245,7 @@ پشتیبان‌گیری و بازگردانی پشتیبان‌گیری بازگردانی - Choose a csv file from Google Takeout Imported playlist - - Imported \"%s\" with %d song - Imported \"%s\" with %d songs - پشتیبان باموفقیت ایجادشد پشتیبان ایجاد نشد بازیابی پشتیبان انجام‌نشد diff --git a/app/src/main/res/values-fi-rFI/strings.xml b/app/src/main/res/values-fi-rFI/strings.xml index 3e10ad5ab..850b0421b 100644 --- a/app/src/main/res/values-fi-rFI/strings.xml +++ b/app/src/main/res/values-fi-rFI/strings.xml @@ -245,12 +245,7 @@ Backup and restore Backup Restore - Choose a csv file from Google Takeout Imported playlist - - Imported \"%s\" with %d song - Imported \"%s\" with %d songs - Backup created successfully Couldn\'t create backup Failed to restore backup diff --git a/app/src/main/res/values-fr-rFR/strings.xml b/app/src/main/res/values-fr-rFR/strings.xml index ef0a46350..e6a268f3a 100644 --- a/app/src/main/res/values-fr-rFR/strings.xml +++ b/app/src/main/res/values-fr-rFR/strings.xml @@ -251,12 +251,7 @@ Backup and restore Backup Restore - Choose a csv file from Google Takeout Imported playlist - - Imported \"%s\" with %d song - Imported \"%s\" with %d songs - Backup created successfully Couldn\'t create backup Failed to restore backup diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index d3e6e5a1b..98b4c4caf 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -245,12 +245,7 @@ Bizt.mentés és visszaállítás Bizt.mentés Visszaállítás - Válasszon egy csv-fájlt a Google Takeout-ból Importált lejátszási lista - - Imported \"%s\" with %d song - Imported \"%s\" with %d songs - A bizt.mentés sikeresen létrehozva Nem sikerült biztonsági mentést készíteni Nem sikerült visszaállítani a biztonsági másolatot diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index 7bd3e5aab..0ddad030f 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -239,12 +239,7 @@ Cadangkan dan pulihkan Cadangkan Pulihkan - Choose a csv file from Google Takeout Imported playlist - - Imported \"%s\" with %d song - Imported \"%s\" with %d songs - Cadangan berhasil dibuat Tidak dapat membuat cadangan Gagal untuk memulihkan cadangan diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 769ebf631..e83c2e89d 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -245,12 +245,7 @@ Backup e ripristino Backup Ripristina - Scegli un file csv da Google Takeout Playlist importata - - Imported \"%s\" with %d song - Imported \"%s\" with %d songs - Backup creato con successo Impossibile eseguire il backup Impossibile eseguire il ripristino dal backup diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index 6864ad314..f8ac56ecd 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -239,12 +239,7 @@ バックアップとリストア バックアップ リストア - Google TakeoutからCSVファイルを選択してください インポートしたプレイリスト - - Imported \"%s\" with %d song - Imported \"%s\" with %d songs - バックアップの作成に成功しました バックアップを作成できませんでした バックアップのリストアに失敗しました diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml index 91832347e..389c5c058 100644 --- a/app/src/main/res/values-ko-rKR/strings.xml +++ b/app/src/main/res/values-ko-rKR/strings.xml @@ -239,12 +239,7 @@ Backup and restore Backup Restore - Choose a csv file from Google Takeout Imported playlist - - Imported \"%s\" with %d song - Imported \"%s\" with %d songs - Backup created successfully Couldn\'t create backup Failed to restore backup diff --git a/app/src/main/res/values-ml-rIN/strings.xml b/app/src/main/res/values-ml-rIN/strings.xml index fb805bb0f..5e639018d 100644 --- a/app/src/main/res/values-ml-rIN/strings.xml +++ b/app/src/main/res/values-ml-rIN/strings.xml @@ -245,12 +245,7 @@ ബാക്കപ്പും വീണ്ടെടുക്കലും ബാക്കപ്പ് വീണ്ടെടുക്കൽ - Choose a csv file from Google Takeout Imported playlist - - Imported \"%s\" with %d song - Imported \"%s\" with %d songs - ബാക്കപ്പ് സൃഷ്‌ടിച്ചു ബാക്കപ്പ് സൃഷ്ടിക്കാൻ കഴിഞ്ഞില്ല ബാക്കപ്പ് പുനഃസ്ഥാപിക്കാൻ കഴിഞ്ഞില്ല diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 19ba9e86c..3bcd12365 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -245,12 +245,7 @@ Backup en herstel Backup Herstellen - Kies een csv-bestand van Google Takeout Afspeellijst geimporteerd - - Imported \"%s\" with %d song - Imported \"%s\" with %d songs - Back-up succesvol gemaakt. Back-up maken mislukt. Fout bij terugzetten back-up. diff --git a/app/src/main/res/values-or-rIN/strings.xml b/app/src/main/res/values-or-rIN/strings.xml index dcc217e37..415e34cea 100644 --- a/app/src/main/res/values-or-rIN/strings.xml +++ b/app/src/main/res/values-or-rIN/strings.xml @@ -245,12 +245,7 @@ ନକଲ ସଂରକ୍ଷଣ ଏବଂ ପୁନରୁଦ୍ଧାର କରନ୍ତୁ ନକଲ ସଂରକ୍ଷଣ ପୁନରୁଦ୍ଧାର କରନ୍ତୁ - Choose a csv file from Google Takeout Imported playlist - - Imported \"%s\" with %d song - Imported \"%s\" with %d songs - ବ୍ୟାକଅପ୍ ସଫଳତାର ସହିତ ସୃଷ୍ଟି ହେଲା ବ୍ୟାକଅପ୍ ସୃଷ୍ଟି କରିପାରିବ ନାହିଁ ବ୍ୟାକଅପ୍ ପୁନରୁଦ୍ଧାର କରିବାରେ ବିଫଳ diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index 1f3bc0015..c5a24cd3b 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -245,12 +245,7 @@ ਬੈਕਅੱਪ ਅਤੇ ਰੀਸਟੋਰ ਬੈਕਅੱਪ ਰੀਸਟੋਰ - Choose a csv file from Google Takeout Imported playlist - - Imported \"%s\" with %d song - Imported \"%s\" with %d songs - ਬੈਕਅੱਪ ਸਫਲਤਾਪੂਰਵਕ ਬਣਾਇਆ ਗਿਆ ਬੈਕਅੱਪ ਨਹੀਂ ਬਣਾਇਆ ਜਾ ਸਕਿਆ ਬੈਕਅੱਪ ਰੀਸਟੋਰ ਕਰਨਾ ਅਸਫਲ ਰਿਹਾ diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index a68bc7db3..f605cf80a 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -245,12 +245,7 @@ Backup e restauração Backup Restaurar - Choose a csv file from Google Takeout Imported playlist - - Imported \"%s\" with %d song - Imported \"%s\" with %d songs - Backup criado com sucesso Não foi possível criar o backup Falha ao restaurar o backup diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index 7a93d09f9..c3c47e457 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -257,12 +257,7 @@ Резервное копирование Создать резервную копию Восстановить из резервной копии - Выбрать csv-файл из Google Takeout Импортированный плейлист - - Imported \"%s\" with %d song - Imported \"%s\" with %d songs - Резервная копия создана успешно Не удалось создать резервную копию Не удалось восстановить резервную копию diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index 56ff8e4b5..c9baa8d75 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -245,12 +245,7 @@ Backup and restore Backup Restore - Choose a csv file from Google Takeout Imported playlist - - Imported \"%s\" with %d song - Imported \"%s\" with %d songs - Backup created successfully Couldn\'t create backup Failed to restore backup diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 674f8ea9a..1cea87bd7 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -245,12 +245,7 @@ Yedekleme ve geri yükleme Yedekle Geri yükle - Google Takeout\'tan bir csv dosyası seçin Çalma listesi içe aktarıldı - - Imported \"%s\" with %d song - Imported \"%s\" with %d songs - Yedekleme başarıyla oluşturuldu Yedekleme oluşturulamadı Yedekleme geri yüklenemedi diff --git a/app/src/main/res/values-uk-rUA/strings.xml b/app/src/main/res/values-uk-rUA/strings.xml index 38dc763f8..ac0bfcb27 100644 --- a/app/src/main/res/values-uk-rUA/strings.xml +++ b/app/src/main/res/values-uk-rUA/strings.xml @@ -257,12 +257,7 @@ Резервне копіювання Створити резервну копію Відновити з резервної копії - Вибрати csv-файл із Google Takeout Імпортований плейлист - - Imported \"%s\" with %d song - Imported \"%s\" with %d songs - Резервну копію створено успішно Не вдалося створити резервну копію Не вдалося відновити з резервної копії diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 39536f960..164fe8dd8 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -239,12 +239,7 @@ 备份与还原 备份 还原 - Choose a csv file from Google Takeout Imported playlist - - Imported \"%s\" with %d song - Imported \"%s\" with %d songs - 成功新建备份 无法新建备份 无法还原备份 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index f3330bd9d..00bca50c4 100755 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -236,11 +236,7 @@ 備份與還原 備份 還原 - 選擇從 Google Takeout 匯出的 csv 檔 已匯入的播放清單 - - 已匯入「%s」,有%d首歌曲 - 成功建立備份 無法建立備份 無法還原備份 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7101979dc..1eca7e5a7 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -244,12 +244,7 @@ Backup and restore Backup Restore - Choose a csv file from Google Takeout Imported playlist - - Imported \"%s\" with %d song - Imported \"%s\" with %d songs - Backup created successfully Couldn\'t create backup Failed to restore backup From 4a4a5351af18274c5812191a25156f60007d2013 Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Fri, 21 Jul 2023 18:34:02 +0800 Subject: [PATCH 42/84] Adjust pitch --- .../com/zionhuang/music/ui/player/Queue.kt | 59 ++++++++++++++++++- app/src/main/res/drawable/remove.xml | 9 +++ app/src/main/res/drawable/tune.xml | 9 +++ 3 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 app/src/main/res/drawable/remove.xml create mode 100644 app/src/main/res/drawable/tune.xml diff --git a/app/src/main/java/com/zionhuang/music/ui/player/Queue.kt b/app/src/main/java/com/zionhuang/music/ui/player/Queue.kt index 444c5c393..7d7701d44 100644 --- a/app/src/main/java/com/zionhuang/music/ui/player/Queue.kt +++ b/app/src/main/java/com/zionhuang/music/ui/player/Queue.kt @@ -72,11 +72,13 @@ import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.DialogProperties import androidx.core.net.toUri +import androidx.media3.common.PlaybackParameters import androidx.media3.exoplayer.offline.DownloadRequest import androidx.media3.exoplayer.offline.DownloadService import androidx.media3.exoplayer.source.ShuffleOrder.DefaultShuffleOrder @@ -114,6 +116,9 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import kotlin.math.log2 +import kotlin.math.pow +import kotlin.math.round import kotlin.math.roundToInt @OptIn(ExperimentalAnimationApi::class, ExperimentalMaterial3Api::class) @@ -624,13 +629,16 @@ fun PlayerMenu( } } + var transposeValue by remember { + mutableStateOf(round(12 * log2(playerConnection.player.playbackParameters.pitch)).toInt()) + } + Row( horizontalArrangement = Arrangement.spacedBy(24.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() - .padding(horizontal = 24.dp) - .padding(top = 24.dp, bottom = 12.dp) + .padding(horizontal = 24.dp, vertical = 24.dp) ) { Icon( painter = painterResource(R.drawable.volume_up), @@ -645,6 +653,53 @@ fun PlayerMenu( ) } + Row( + horizontalArrangement = Arrangement.spacedBy(24.dp), + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp, vertical = 6.dp) + ) { + Icon( + painter = painterResource(R.drawable.tune), + contentDescription = null, + modifier = Modifier.size(28.dp) + ) + + IconButton( + enabled = transposeValue > -12, + onClick = { + transposeValue-- + playerConnection.player.playbackParameters = PlaybackParameters(1f, 2f.pow(transposeValue.toFloat() / 12)) + } + ) { + Icon( + painter = painterResource(R.drawable.remove), + contentDescription = null + ) + } + + Text( + text = "${if (transposeValue > 0) "+" else ""}$transposeValue", + style = MaterialTheme.typography.bodyLarge, + textAlign = TextAlign.Center, + modifier = Modifier.weight(1f) + ) + + IconButton( + enabled = transposeValue < 12, + onClick = { + transposeValue++ + playerConnection.player.playbackParameters = PlaybackParameters(1f, 2f.pow(transposeValue.toFloat() / 12)) + } + ) { + Icon( + painter = painterResource(R.drawable.add), + contentDescription = null + ) + } + } + GridMenu( contentPadding = PaddingValues( start = 8.dp, diff --git a/app/src/main/res/drawable/remove.xml b/app/src/main/res/drawable/remove.xml new file mode 100644 index 000000000..b13fa153f --- /dev/null +++ b/app/src/main/res/drawable/remove.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/tune.xml b/app/src/main/res/drawable/tune.xml new file mode 100644 index 000000000..9ab9dfc71 --- /dev/null +++ b/app/src/main/res/drawable/tune.xml @@ -0,0 +1,9 @@ + + + From 7111c88af4e5c649eb0b779fdba27d262a0df2d4 Mon Sep 17 00:00:00 2001 From: atilluF <110931720+atilluF@users.noreply.github.com> Date: Fri, 21 Jul 2023 12:42:51 +0200 Subject: [PATCH 43/84] Update Italian strings --- app/src/main/res/values-it/strings.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index e83c2e89d..af7a69b6e 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -15,7 +15,7 @@ Cronologia Statistiche - Mood and Genres + Mood e generi Account Scelte rapide Ascolta alcuni brani per generare le tue scelte rapide @@ -30,7 +30,7 @@ Brani più ascoltati Artisti più ascoltati - Most played albums + Album più ascoltati Cerca @@ -142,16 +142,16 @@ %d playlist - %d week - %d weeks + %d settimana + %d settimane - %d month - %d months + %d mese + %d mesi - %d year - %d years + %d anno + %d anni From 7384e101cd59776709d16759a663ca19506a7887 Mon Sep 17 00:00:00 2001 From: simpel2 <140175421+simpel2@users.noreply.github.com> Date: Fri, 21 Jul 2023 15:04:02 +0200 Subject: [PATCH 44/84] Update strings.xml --- app/src/main/res/values-nl/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 3bcd12365..735dc3952 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -15,7 +15,7 @@ Geschiedenis Statistieken - Mood and Genres + Stemmingen en genres Account Snelle keuzes Beluister een aantal nummers om je snelle keuzes te genereren @@ -30,12 +30,12 @@ Meest afgespeelde nummers Meest afgespeelde artiesten - Most played albums + Meest afgespeelde artiesten Zoeken Zoeken via YouTube Music… - Zoeken in bibliotheek… + Zoeken in bibliotheek Alles Nummers Videos From 8136946408b9316e807a8dd2fc71d3ecfb9ceff3 Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Sat, 22 Jul 2023 14:43:33 +0800 Subject: [PATCH 45/84] Bookmark artist --- .../11.json | 796 ++++++++++++++++++ .../com/zionhuang/music/db/DatabaseDao.kt | 8 +- .../com/zionhuang/music/db/MusicDatabase.kt | 19 +- .../music/db/entities/ArtistEntity.kt | 6 +- .../zionhuang/music/db/entities/SongEntity.kt | 8 - .../com/zionhuang/music/ui/component/Items.kt | 4 +- .../music/ui/screens/artist/ArtistScreen.kt | 60 ++ .../music/viewmodels/ArtistViewModel.kt | 2 + app/src/main/res/drawable/bookmark.xml | 9 + app/src/main/res/drawable/bookmark_filled.xml | 9 + 10 files changed, 897 insertions(+), 24 deletions(-) create mode 100644 app/schemas/com.zionhuang.music.db.InternalDatabase/11.json create mode 100644 app/src/main/res/drawable/bookmark.xml create mode 100644 app/src/main/res/drawable/bookmark_filled.xml diff --git a/app/schemas/com.zionhuang.music.db.InternalDatabase/11.json b/app/schemas/com.zionhuang.music.db.InternalDatabase/11.json new file mode 100644 index 000000000..c3981e170 --- /dev/null +++ b/app/schemas/com.zionhuang.music.db.InternalDatabase/11.json @@ -0,0 +1,796 @@ +{ + "formatVersion": 1, + "database": { + "version": 11, + "identityHash": "de2e37d1206f721ad51de3a08f66f99c", + "entities": [ + { + "tableName": "song", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `duration` INTEGER NOT NULL, `thumbnailUrl` TEXT, `albumId` TEXT, `albumName` TEXT, `liked` INTEGER NOT NULL, `totalPlayTime` INTEGER NOT NULL, `inLibrary` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnailUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "albumId", + "columnName": "albumId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "albumName", + "columnName": "albumName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "liked", + "columnName": "liked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "totalPlayTime", + "columnName": "totalPlayTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "inLibrary", + "columnName": "inLibrary", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "artist", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `thumbnailUrl` TEXT, `lastUpdateTime` INTEGER NOT NULL, `bookmarkedAt` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnailUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastUpdateTime", + "columnName": "lastUpdateTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "bookmarkedAt", + "columnName": "bookmarkedAt", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "album", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `title` TEXT NOT NULL, `year` INTEGER, `thumbnailUrl` TEXT, `songCount` INTEGER NOT NULL, `duration` INTEGER NOT NULL, `createDate` INTEGER NOT NULL, `lastUpdateTime` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "year", + "columnName": "year", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "thumbnailUrl", + "columnName": "thumbnailUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "songCount", + "columnName": "songCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createDate", + "columnName": "createDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastUpdateTime", + "columnName": "lastUpdateTime", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "playlist", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `browseId` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "browseId", + "columnName": "browseId", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "song_artist_map", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`songId` TEXT NOT NULL, `artistId` TEXT NOT NULL, `position` INTEGER NOT NULL, PRIMARY KEY(`songId`, `artistId`), FOREIGN KEY(`songId`) REFERENCES `song`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`artistId`) REFERENCES `artist`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "songId", + "columnName": "songId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "artistId", + "columnName": "artistId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "songId", + "artistId" + ] + }, + "indices": [ + { + "name": "index_song_artist_map_songId", + "unique": false, + "columnNames": [ + "songId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_song_artist_map_songId` ON `${TABLE_NAME}` (`songId`)" + }, + { + "name": "index_song_artist_map_artistId", + "unique": false, + "columnNames": [ + "artistId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_song_artist_map_artistId` ON `${TABLE_NAME}` (`artistId`)" + } + ], + "foreignKeys": [ + { + "table": "song", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "songId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "artist", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "artistId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "song_album_map", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`songId` TEXT NOT NULL, `albumId` TEXT NOT NULL, `index` INTEGER NOT NULL, PRIMARY KEY(`songId`, `albumId`), FOREIGN KEY(`songId`) REFERENCES `song`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`albumId`) REFERENCES `album`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "songId", + "columnName": "songId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "albumId", + "columnName": "albumId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "index", + "columnName": "index", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "songId", + "albumId" + ] + }, + "indices": [ + { + "name": "index_song_album_map_songId", + "unique": false, + "columnNames": [ + "songId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_song_album_map_songId` ON `${TABLE_NAME}` (`songId`)" + }, + { + "name": "index_song_album_map_albumId", + "unique": false, + "columnNames": [ + "albumId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_song_album_map_albumId` ON `${TABLE_NAME}` (`albumId`)" + } + ], + "foreignKeys": [ + { + "table": "song", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "songId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "album", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "albumId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "album_artist_map", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`albumId` TEXT NOT NULL, `artistId` TEXT NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`albumId`, `artistId`), FOREIGN KEY(`albumId`) REFERENCES `album`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`artistId`) REFERENCES `artist`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "albumId", + "columnName": "albumId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "artistId", + "columnName": "artistId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "albumId", + "artistId" + ] + }, + "indices": [ + { + "name": "index_album_artist_map_albumId", + "unique": false, + "columnNames": [ + "albumId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_album_artist_map_albumId` ON `${TABLE_NAME}` (`albumId`)" + }, + { + "name": "index_album_artist_map_artistId", + "unique": false, + "columnNames": [ + "artistId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_album_artist_map_artistId` ON `${TABLE_NAME}` (`artistId`)" + } + ], + "foreignKeys": [ + { + "table": "album", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "albumId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "artist", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "artistId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "playlist_song_map", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `playlistId` TEXT NOT NULL, `songId` TEXT NOT NULL, `position` INTEGER NOT NULL, FOREIGN KEY(`playlistId`) REFERENCES `playlist`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`songId`) REFERENCES `song`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "playlistId", + "columnName": "playlistId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "songId", + "columnName": "songId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_playlist_song_map_playlistId", + "unique": false, + "columnNames": [ + "playlistId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_playlist_song_map_playlistId` ON `${TABLE_NAME}` (`playlistId`)" + }, + { + "name": "index_playlist_song_map_songId", + "unique": false, + "columnNames": [ + "songId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_playlist_song_map_songId` ON `${TABLE_NAME}` (`songId`)" + } + ], + "foreignKeys": [ + { + "table": "playlist", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "playlistId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "song", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "songId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "search_history", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `query` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "query", + "columnName": "query", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_search_history_query", + "unique": true, + "columnNames": [ + "query" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_search_history_query` ON `${TABLE_NAME}` (`query`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "format", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `itag` INTEGER NOT NULL, `mimeType` TEXT NOT NULL, `codecs` TEXT NOT NULL, `bitrate` INTEGER NOT NULL, `sampleRate` INTEGER, `contentLength` INTEGER NOT NULL, `loudnessDb` REAL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "itag", + "columnName": "itag", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "mimeType", + "columnName": "mimeType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "codecs", + "columnName": "codecs", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "bitrate", + "columnName": "bitrate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sampleRate", + "columnName": "sampleRate", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "contentLength", + "columnName": "contentLength", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "loudnessDb", + "columnName": "loudnessDb", + "affinity": "REAL", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "lyrics", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `lyrics` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lyrics", + "columnName": "lyrics", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "event", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `songId` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `playTime` INTEGER NOT NULL, FOREIGN KEY(`songId`) REFERENCES `song`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "songId", + "columnName": "songId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "playTime", + "columnName": "playTime", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_event_songId", + "unique": false, + "columnNames": [ + "songId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_event_songId` ON `${TABLE_NAME}` (`songId`)" + } + ], + "foreignKeys": [ + { + "table": "song", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "songId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "related_song_map", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `songId` TEXT NOT NULL, `relatedSongId` TEXT NOT NULL, FOREIGN KEY(`songId`) REFERENCES `song`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`relatedSongId`) REFERENCES `song`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "songId", + "columnName": "songId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "relatedSongId", + "columnName": "relatedSongId", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_related_song_map_songId", + "unique": false, + "columnNames": [ + "songId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_related_song_map_songId` ON `${TABLE_NAME}` (`songId`)" + }, + { + "name": "index_related_song_map_relatedSongId", + "unique": false, + "columnNames": [ + "relatedSongId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_related_song_map_relatedSongId` ON `${TABLE_NAME}` (`relatedSongId`)" + } + ], + "foreignKeys": [ + { + "table": "song", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "songId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "song", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "relatedSongId" + ], + "referencedColumns": [ + "id" + ] + } + ] + } + ], + "views": [ + { + "viewName": "sorted_song_artist_map", + "createSql": "CREATE VIEW `${VIEW_NAME}` AS SELECT * FROM song_artist_map ORDER BY position" + }, + { + "viewName": "sorted_song_album_map", + "createSql": "CREATE VIEW `${VIEW_NAME}` AS SELECT * FROM song_album_map ORDER BY `index`" + }, + { + "viewName": "playlist_song_map_preview", + "createSql": "CREATE VIEW `${VIEW_NAME}` AS SELECT * FROM playlist_song_map WHERE position <= 3 ORDER BY position" + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'de2e37d1206f721ad51de3a08f66f99c')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt b/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt index f18a4559f..6eb303547 100644 --- a/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt +++ b/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt @@ -216,15 +216,15 @@ interface DatabaseDao { fun lyrics(id: String?): Flow @Transaction - @Query("SELECT *, (SELECT COUNT(1) FROM song_artist_map JOIN song ON song_artist_map.songId = song.id WHERE artistId = artist.id AND song.inLibrary IS NOT NULL) AS songCount FROM artist WHERE songCount > 0 ORDER BY rowId") + @Query("SELECT *, (SELECT COUNT(1) FROM song_artist_map JOIN song ON song_artist_map.songId = song.id WHERE artistId = artist.id AND song.inLibrary IS NOT NULL) AS songCount FROM artist WHERE bookmarkedAt IS NOT NULL ORDER BY bookmarkedAt") fun artistsByCreateDateAsc(): Flow> @Transaction - @Query("SELECT *, (SELECT COUNT(1) FROM song_artist_map JOIN song ON song_artist_map.songId = song.id WHERE artistId = artist.id AND song.inLibrary IS NOT NULL) AS songCount FROM artist WHERE songCount > 0 ORDER BY name") + @Query("SELECT *, (SELECT COUNT(1) FROM song_artist_map JOIN song ON song_artist_map.songId = song.id WHERE artistId = artist.id AND song.inLibrary IS NOT NULL) AS songCount FROM artist WHERE bookmarkedAt IS NOT NULL ORDER BY name") fun artistsByNameAsc(): Flow> @Transaction - @Query("SELECT *, (SELECT COUNT(1) FROM song_artist_map JOIN song ON song_artist_map.songId = song.id WHERE artistId = artist.id AND song.inLibrary IS NOT NULL) AS songCount FROM artist WHERE songCount > 0 ORDER BY songCount") + @Query("SELECT *, (SELECT COUNT(1) FROM song_artist_map JOIN song ON song_artist_map.songId = song.id WHERE artistId = artist.id AND song.inLibrary IS NOT NULL) AS songCount FROM artist WHERE bookmarkedAt IS NOT NULL ORDER BY songCount") fun artistsBySongCountAsc(): Flow> fun artists(sortType: ArtistSortType, descending: Boolean) = @@ -312,7 +312,7 @@ interface DatabaseDao { fun searchSongs(query: String, previewSize: Int = Int.MAX_VALUE): Flow> @Transaction - @Query("SELECT *, (SELECT COUNT(1) FROM song_artist_map JOIN song ON song_artist_map.songId = song.id WHERE artistId = artist.id AND song.inLibrary IS NOT NULL) AS songCount FROM artist WHERE name LIKE '%' || :query || '%' AND songCount > 0 LIMIT :previewSize") + @Query("SELECT *, (SELECT COUNT(1) FROM song_artist_map JOIN song ON song_artist_map.songId = song.id WHERE artistId = artist.id AND song.inLibrary IS NOT NULL) AS songCount FROM artist WHERE name LIKE '%' || :query || '%' AND bookmarkedAt IS NOT NULL LIMIT :previewSize") fun searchArtists(query: String, previewSize: Int = Int.MAX_VALUE): Flow> @Transaction diff --git a/app/src/main/java/com/zionhuang/music/db/MusicDatabase.kt b/app/src/main/java/com/zionhuang/music/db/MusicDatabase.kt index 42597f518..a867fa5d8 100644 --- a/app/src/main/java/com/zionhuang/music/db/MusicDatabase.kt +++ b/app/src/main/java/com/zionhuang/music/db/MusicDatabase.kt @@ -59,7 +59,7 @@ class MusicDatabase( SortedSongAlbumMap::class, PlaylistSongMapPreview::class ], - version = 10, + version = 11, exportSchema = true, autoMigrations = [ AutoMigration(from = 2, to = 3), @@ -69,7 +69,8 @@ class MusicDatabase( AutoMigration(from = 6, to = 7, spec = Migration6To7::class), AutoMigration(from = 7, to = 8, spec = Migration7To8::class), AutoMigration(from = 8, to = 9), - AutoMigration(from = 9, to = 10, spec = Migration9To10::class) + AutoMigration(from = 9, to = 10, spec = Migration9To10::class), + AutoMigration(from = 10, to = 11, spec = Migration10To11::class) ] ) @TypeConverters(Converters::class) @@ -99,7 +100,7 @@ val MIGRATION_1_2 = object : Migration(1, 2) { val albumName: String? = null, val liked: Boolean = false, val totalPlayTime: Long = 0, // in milliseconds - val downloadState: Int = SongEntity.STATE_NOT_DOWNLOADED, + val downloadState: Int = 0, val createDate: LocalDateTime = LocalDateTime.now(), val modifyDate: LocalDateTime = LocalDateTime.now(), ) @@ -211,7 +212,7 @@ val MIGRATION_1_2 = object : Migration(1, 2) { "artist", SQLiteDatabase.CONFLICT_ABORT, contentValuesOf( "id" to artist.id, "name" to artist.name, - "createDate" to converters.dateToTimestamp(artist.createDate), + "createDate" to converters.dateToTimestamp(artist.lastUpdateTime), "lastUpdateTime" to converters.dateToTimestamp(artist.lastUpdateTime) ) ) @@ -305,4 +306,12 @@ class Migration7To8 : AutoMigrationSpec @DeleteTable.Entries( DeleteTable(tableName = "download") ) -class Migration9To10 : AutoMigrationSpec \ No newline at end of file +class Migration9To10 : AutoMigrationSpec + +@DeleteColumn.Entries( + DeleteColumn(tableName = "song", columnName = "downloadState"), + DeleteColumn(tableName = "artist", columnName = "bannerUrl"), + DeleteColumn(tableName = "artist", columnName = "description"), + DeleteColumn(tableName = "artist", columnName = "createDate") +) +class Migration10To11 : AutoMigrationSpec \ No newline at end of file diff --git a/app/src/main/java/com/zionhuang/music/db/entities/ArtistEntity.kt b/app/src/main/java/com/zionhuang/music/db/entities/ArtistEntity.kt index e054e2e22..e38a3a937 100644 --- a/app/src/main/java/com/zionhuang/music/db/entities/ArtistEntity.kt +++ b/app/src/main/java/com/zionhuang/music/db/entities/ArtistEntity.kt @@ -12,13 +12,9 @@ data class ArtistEntity( @PrimaryKey val id: String, val name: String, val thumbnailUrl: String? = null, - val bannerUrl: String? = null, - val description: String? = null, - val createDate: LocalDateTime = LocalDateTime.now(), val lastUpdateTime: LocalDateTime = LocalDateTime.now(), + val bookmarkedAt: LocalDateTime? = null, ) { - override fun toString(): String = name - val isYouTubeArtist: Boolean get() = id.startsWith("UC") diff --git a/app/src/main/java/com/zionhuang/music/db/entities/SongEntity.kt b/app/src/main/java/com/zionhuang/music/db/entities/SongEntity.kt index dfed7f0b7..c5a1d3681 100644 --- a/app/src/main/java/com/zionhuang/music/db/entities/SongEntity.kt +++ b/app/src/main/java/com/zionhuang/music/db/entities/SongEntity.kt @@ -16,7 +16,6 @@ data class SongEntity( val albumName: String? = null, val liked: Boolean = false, val totalPlayTime: Long = 0, // in milliseconds - val downloadState: Int = STATE_NOT_DOWNLOADED, val inLibrary: LocalDateTime? = null, ) { fun toggleLike() = copy( @@ -25,11 +24,4 @@ data class SongEntity( ) fun toggleLibrary() = copy(inLibrary = if (inLibrary == null) LocalDateTime.now() else null) - - companion object { - const val STATE_NOT_DOWNLOADED = 0 - const val STATE_PREPARING = 1 - const val STATE_DOWNLOADING = 2 - const val STATE_DOWNLOADED = 3 - } } diff --git a/app/src/main/java/com/zionhuang/music/ui/component/Items.kt b/app/src/main/java/com/zionhuang/music/ui/component/Items.kt index c67ff1697..1d35bf1aa 100644 --- a/app/src/main/java/com/zionhuang/music/ui/component/Items.kt +++ b/app/src/main/java/com/zionhuang/music/ui/component/Items.kt @@ -230,7 +230,7 @@ fun SongListItem( ) = ListItem( title = song.song.title, subtitle = joinByBullet( - song.artists.joinToString(), + song.artists.joinToString { it.name }, makeTimeString(song.song.duration * 1000L) ), badges = badges, @@ -308,7 +308,7 @@ fun AlbumListItem( ) = ListItem( title = album.album.title, subtitle = joinByBullet( - album.artists.joinToString(), + album.artists.joinToString { it.name }, pluralStringResource(R.plurals.n_song, album.album.songCount, album.album.songCount), album.album.year?.toString() ), diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistScreen.kt index 729222fea..14b0c453a 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/artist/ArtistScreen.kt @@ -1,5 +1,6 @@ package com.zionhuang.music.ui.screens.artist +import android.content.Intent import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -27,6 +28,7 @@ import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text @@ -42,6 +44,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -59,10 +62,12 @@ import com.zionhuang.innertube.models.ArtistItem import com.zionhuang.innertube.models.PlaylistItem import com.zionhuang.innertube.models.SongItem import com.zionhuang.innertube.models.WatchEndpoint +import com.zionhuang.music.LocalDatabase import com.zionhuang.music.LocalPlayerAwareWindowInsets import com.zionhuang.music.LocalPlayerConnection import com.zionhuang.music.R import com.zionhuang.music.constants.AppBarHeight +import com.zionhuang.music.db.entities.ArtistEntity import com.zionhuang.music.extensions.togglePlayPause import com.zionhuang.music.models.toMediaMetadata import com.zionhuang.music.playback.queues.YouTubeQueue @@ -85,6 +90,7 @@ import com.zionhuang.music.ui.menu.YouTubeSongMenu import com.zionhuang.music.ui.utils.fadingEdge import com.zionhuang.music.ui.utils.resize import com.zionhuang.music.viewmodels.ArtistViewModel +import java.time.LocalDateTime @OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) @Composable @@ -93,6 +99,8 @@ fun ArtistScreen( scrollBehavior: TopAppBarScrollBehavior, viewModel: ArtistViewModel = hiltViewModel(), ) { + val context = LocalContext.current + val database = LocalDatabase.current val menuState = LocalMenuState.current val coroutineScope = rememberCoroutineScope() val playerConnection = LocalPlayerConnection.current ?: return @@ -100,6 +108,7 @@ fun ArtistScreen( val mediaMetadata by playerConnection.mediaMetadata.collectAsState() val artistPage = viewModel.artistPage + val libraryArtist by viewModel.libraryArtist.collectAsState() val librarySongs by viewModel.librarySongs.collectAsState() val lazyListState = rememberLazyListState() @@ -419,6 +428,57 @@ fun ArtistScreen( ) } }, + actions = { + IconButton( + onClick = { + database.transaction { + val artist = libraryArtist + if (artist != null) { + update( + artist.copy( + bookmarkedAt = if (artist.bookmarkedAt != null) null else LocalDateTime.now() + ) + ) + } else { + artistPage?.artist?.let { + insert( + ArtistEntity( + id = it.id, + name = it.title, + thumbnailUrl = it.thumbnail, + bookmarkedAt = LocalDateTime.now() + ) + ) + } + } + } + } + ) { + Icon( + painter = painterResource(if (libraryArtist?.bookmarkedAt != null) R.drawable.bookmark_filled else R.drawable.bookmark), + tint = if (libraryArtist?.bookmarkedAt != null) MaterialTheme.colorScheme.primary else LocalContentColor.current, + contentDescription = null + ) + } + + IconButton( + onClick = { + viewModel.artistPage?.artist?.shareLink?.let { link -> + val intent = Intent().apply { + action = Intent.ACTION_SEND + type = "text/plain" + putExtra(Intent.EXTRA_TEXT, link) + } + context.startActivity(Intent.createChooser(intent, null)) + } + } + ) { + Icon( + painterResource(R.drawable.share), + contentDescription = null + ) + } + }, scrollBehavior = scrollBehavior, colors = if (transparentAppBar) { TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent) diff --git a/app/src/main/java/com/zionhuang/music/viewmodels/ArtistViewModel.kt b/app/src/main/java/com/zionhuang/music/viewmodels/ArtistViewModel.kt index d90d047ed..7f61d6e80 100644 --- a/app/src/main/java/com/zionhuang/music/viewmodels/ArtistViewModel.kt +++ b/app/src/main/java/com/zionhuang/music/viewmodels/ArtistViewModel.kt @@ -22,6 +22,8 @@ class ArtistViewModel @Inject constructor( ) : ViewModel() { val artistId = savedStateHandle.get("artistId")!! var artistPage by mutableStateOf(null) + val libraryArtist = database.artist(artistId) + .stateIn(viewModelScope, SharingStarted.Lazily, null) val librarySongs = database.artistSongsPreview(artistId) .stateIn(viewModelScope, SharingStarted.Lazily, emptyList()) diff --git a/app/src/main/res/drawable/bookmark.xml b/app/src/main/res/drawable/bookmark.xml new file mode 100644 index 000000000..e69165b59 --- /dev/null +++ b/app/src/main/res/drawable/bookmark.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/bookmark_filled.xml b/app/src/main/res/drawable/bookmark_filled.xml new file mode 100644 index 000000000..c3cb3d600 --- /dev/null +++ b/app/src/main/res/drawable/bookmark_filled.xml @@ -0,0 +1,9 @@ + + + From be73a3afc52ce2773e1a294fc573025cf29e3796 Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Sat, 22 Jul 2023 19:48:14 +0800 Subject: [PATCH 46/84] Fix #847 --- app/src/main/res/values-cs/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 9bfaadd4b..0442ed3ff 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -178,6 +178,7 @@ 1 minute %d minutes %d minutes + %d minutes Není dostupný žádný stream Není dostupné připojení k internetu From 60c5251d326af96c307b11c9c790f278a7ce8600 Mon Sep 17 00:00:00 2001 From: Lurux Date: Sat, 22 Jul 2023 21:57:18 +0200 Subject: [PATCH 47/84] Add "play time" to artists and albums screens --- .../music/constants/PreferenceKeys.kt | 4 +- .../com/zionhuang/music/db/DatabaseDao.kt | 37 +++++++++++++++++++ .../ui/screens/library/LibraryAlbumsScreen.kt | 1 + .../screens/library/LibraryArtistsScreen.kt | 1 + 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/zionhuang/music/constants/PreferenceKeys.kt b/app/src/main/java/com/zionhuang/music/constants/PreferenceKeys.kt index c10ffd84a..dc9f96d5b 100644 --- a/app/src/main/java/com/zionhuang/music/constants/PreferenceKeys.kt +++ b/app/src/main/java/com/zionhuang/music/constants/PreferenceKeys.kt @@ -64,7 +64,7 @@ enum class PlaylistSongSortType { } enum class ArtistSortType { - CREATE_DATE, NAME, SONG_COUNT + CREATE_DATE, NAME, SONG_COUNT, PLAY_TIME } enum class ArtistSongSortType { @@ -72,7 +72,7 @@ enum class ArtistSongSortType { } enum class AlbumSortType { - CREATE_DATE, NAME, ARTIST, YEAR, SONG_COUNT, LENGTH + CREATE_DATE, NAME, ARTIST, YEAR, SONG_COUNT, LENGTH, PLAY_TIME } enum class PlaylistSortType { diff --git a/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt b/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt index 6eb303547..39e2dba61 100644 --- a/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt +++ b/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt @@ -227,11 +227,34 @@ interface DatabaseDao { @Query("SELECT *, (SELECT COUNT(1) FROM song_artist_map JOIN song ON song_artist_map.songId = song.id WHERE artistId = artist.id AND song.inLibrary IS NOT NULL) AS songCount FROM artist WHERE bookmarkedAt IS NOT NULL ORDER BY songCount") fun artistsBySongCountAsc(): Flow> + @Transaction + @Query( + """ + SELECT artist.*, + (SELECT COUNT(1) + FROM song_artist_map + JOIN song ON song_artist_map.songId = song.id + WHERE artistId = artist.id + AND song.inLibrary IS NOT NULL) AS songCount + FROM artist + JOIN(SELECT artistId, SUM(totalPlayTime) AS totalPlayTime + FROM song_artist_map + JOIN song + ON song_artist_map.songId = song.id + GROUP BY artistId + ORDER BY totalPlayTime) + ON artist.id = artistId + WHERE bookmarkedAt IS NOT NULL + """ + ) + fun artistsByPlayTimeAsc(): Flow> + fun artists(sortType: ArtistSortType, descending: Boolean) = when (sortType) { ArtistSortType.CREATE_DATE -> artistsByCreateDateAsc() ArtistSortType.NAME -> artistsByNameAsc() ArtistSortType.SONG_COUNT -> artistsBySongCountAsc() + ArtistSortType.PLAY_TIME -> artistsByPlayTimeAsc() }.map { it.reversed(descending) } @Query("SELECT * FROM artist WHERE id = :id") @@ -261,6 +284,19 @@ interface DatabaseDao { @Query("SELECT * FROM album ORDER BY duration") fun albumsByLengthAsc(): Flow> + @Transaction + @Query( + """ + SELECT album.* + FROM album + JOIN song + ON song.albumId = album.id + GROUP BY album.id + ORDER BY SUM(song.totalPlayTime) + """ + ) + fun albumsByPlayTimeAsc(): Flow> + fun albums(sortType: AlbumSortType, descending: Boolean) = when (sortType) { AlbumSortType.CREATE_DATE -> albumsByCreateDateAsc() @@ -274,6 +310,7 @@ interface DatabaseDao { AlbumSortType.YEAR -> albumsByYearAsc() AlbumSortType.SONG_COUNT -> albumsBySongCountAsc() AlbumSortType.LENGTH -> albumsByLengthAsc() + AlbumSortType.PLAY_TIME -> albumsByPlayTimeAsc() }.map { it.reversed(descending) } @Transaction diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/library/LibraryAlbumsScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/library/LibraryAlbumsScreen.kt index 66a385115..7c222e653 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/library/LibraryAlbumsScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/library/LibraryAlbumsScreen.kt @@ -69,6 +69,7 @@ fun LibraryAlbumsScreen( AlbumSortType.YEAR -> R.string.sort_by_year AlbumSortType.SONG_COUNT -> R.string.sort_by_song_count AlbumSortType.LENGTH -> R.string.sort_by_length + AlbumSortType.PLAY_TIME -> R.string.sort_by_play_time } }, trailingText = pluralStringResource(R.plurals.n_album, albums.size, albums.size) diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/library/LibraryArtistsScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/library/LibraryArtistsScreen.kt index 5b3eed2e1..9615c7911 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/library/LibraryArtistsScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/library/LibraryArtistsScreen.kt @@ -55,6 +55,7 @@ fun LibraryArtistsScreen( ArtistSortType.CREATE_DATE -> R.string.sort_by_create_date ArtistSortType.NAME -> R.string.sort_by_name ArtistSortType.SONG_COUNT -> R.string.sort_by_song_count + ArtistSortType.PLAY_TIME -> R.string.sort_by_play_time } }, trailingText = pluralStringResource(R.plurals.n_artist, artists.size, artists.size) From 0ea94ead9bacad35ef1edd71b2f02c2c4ccf7c0c Mon Sep 17 00:00:00 2001 From: Lurux Date: Sat, 22 Jul 2023 23:03:26 +0200 Subject: [PATCH 48/84] Fix saved albums not showing saved songs --- app/src/main/java/com/zionhuang/music/ui/screens/AlbumScreen.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/AlbumScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/AlbumScreen.kt index 57244fae3..287f05e9e 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/AlbumScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/AlbumScreen.kt @@ -181,6 +181,7 @@ fun AlbumScreen( albumIndex = index + 1, isActive = song.id == mediaMetadata?.id, isPlaying = isPlaying, + showInLibraryIcon = true, trailingContent = { IconButton( onClick = { From c9b28ee6cc4baca63aa05e5881ad76e34afb073e Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Sun, 23 Jul 2023 14:31:55 +0800 Subject: [PATCH 49/84] Fix lint --- .../main/java/com/zionhuang/music/db/DatabaseDao.kt | 10 +++++----- app/src/main/res/values-be/strings.xml | 6 +----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt b/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt index 39e2dba61..9bf38942c 100644 --- a/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt +++ b/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt @@ -239,12 +239,12 @@ interface DatabaseDao { FROM artist JOIN(SELECT artistId, SUM(totalPlayTime) AS totalPlayTime FROM song_artist_map - JOIN song - ON song_artist_map.songId = song.id + JOIN song + ON song_artist_map.songId = song.id GROUP BY artistId ORDER BY totalPlayTime) ON artist.id = artistId - WHERE bookmarkedAt IS NOT NULL + WHERE bookmarkedAt IS NOT NULL """ ) fun artistsByPlayTimeAsc(): Flow> @@ -289,8 +289,8 @@ interface DatabaseDao { """ SELECT album.* FROM album - JOIN song - ON song.albumId = album.id + JOIN song + ON song.albumId = album.id GROUP BY album.id ORDER BY SUM(song.totalPlayTime) """ diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index f54e4744e..0d81a65ad 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -32,6 +32,7 @@ Лепшыя кампазіцыі Лепшыя выканаўцы + Most played albums Пошук @@ -262,12 +263,7 @@ Рэзервовае капіраванне ды аднеўленне Рэзервовае капіраванне Аднаўленне з рэзервовай копіі - Выбраць cvs-файл з Google Takeout Імпартаваны плей-ліст - - \"%s\" імпаправаны з %d песняй - \"%s\" імпаправаны з %d песнямі - Рэзервовная копія паспяхова створана Немагчыма стапрыць рэзервовую копію Немагчыма ужывіць рэзервовую копію From 86ff26b34a5cc701c76f8440e789046d809cd8a7 Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Sun, 23 Jul 2023 20:56:31 +0800 Subject: [PATCH 50/84] Adjust tempo --- .../com/zionhuang/music/ui/menu/PlayerMenu.kt | 387 ++++++++++++++++++ .../com/zionhuang/music/ui/player/Queue.kt | 280 +------------ .../main/res/drawable/slow_motion_video.xml | 9 + 3 files changed, 397 insertions(+), 279 deletions(-) create mode 100644 app/src/main/java/com/zionhuang/music/ui/menu/PlayerMenu.kt create mode 100644 app/src/main/res/drawable/slow_motion_video.xml diff --git a/app/src/main/java/com/zionhuang/music/ui/menu/PlayerMenu.kt b/app/src/main/java/com/zionhuang/music/ui/menu/PlayerMenu.kt new file mode 100644 index 000000000..057e75b66 --- /dev/null +++ b/app/src/main/java/com/zionhuang/music/ui/menu/PlayerMenu.kt @@ -0,0 +1,387 @@ +package com.zionhuang.music.ui.menu + +import android.content.Intent +import android.content.res.Configuration.ORIENTATION_LANDSCAPE +import android.media.audiofx.AudioEffect +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.annotation.DrawableRes +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.systemBars +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.core.net.toUri +import androidx.media3.common.PlaybackParameters +import androidx.media3.exoplayer.offline.DownloadRequest +import androidx.media3.exoplayer.offline.DownloadService +import androidx.navigation.NavController +import com.zionhuang.music.LocalDatabase +import com.zionhuang.music.LocalDownloadUtil +import com.zionhuang.music.R +import com.zionhuang.music.constants.ListItemHeight +import com.zionhuang.music.db.entities.PlaylistSongMap +import com.zionhuang.music.models.MediaMetadata +import com.zionhuang.music.playback.ExoDownloadService +import com.zionhuang.music.playback.PlayerConnection +import com.zionhuang.music.ui.component.BigSeekBar +import com.zionhuang.music.ui.component.BottomSheetState +import com.zionhuang.music.ui.component.DownloadGridMenu +import com.zionhuang.music.ui.component.GridMenu +import com.zionhuang.music.ui.component.GridMenuItem +import com.zionhuang.music.ui.component.ListDialog +import kotlin.math.log2 +import kotlin.math.pow +import kotlin.math.round + +@Composable +fun PlayerMenu( + mediaMetadata: MediaMetadata?, + navController: NavController, + playerBottomSheetState: BottomSheetState, + playerConnection: PlayerConnection, + onShowDetailsDialog: () -> Unit, + onDismiss: () -> Unit, +) { + mediaMetadata ?: return + val context = LocalContext.current + val database = LocalDatabase.current + val localConfiguration = LocalConfiguration.current + val playerVolume = playerConnection.service.playerVolume.collectAsState() + val activityResultLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { } + + val download by LocalDownloadUtil.current.getDownload(mediaMetadata.id).collectAsState(initial = null) + + var showChoosePlaylistDialog by rememberSaveable { + mutableStateOf(false) + } + + AddToPlaylistDialog( + isVisible = showChoosePlaylistDialog, + onAdd = { playlist -> + database.transaction { + insert(mediaMetadata) + insert( + PlaylistSongMap( + songId = mediaMetadata.id, + playlistId = playlist.id, + position = playlist.songCount + ) + ) + } + }, + onDismiss = { + showChoosePlaylistDialog = false + } + ) + + var showSelectArtistDialog by rememberSaveable { + mutableStateOf(false) + } + + if (showSelectArtistDialog) { + ListDialog( + onDismiss = { showSelectArtistDialog = false } + ) { + items(mediaMetadata.artists) { artist -> + Box( + contentAlignment = Alignment.CenterStart, + modifier = Modifier + .fillParentMaxWidth() + .height(ListItemHeight) + .clickable { + navController.navigate("artist/${artist.id}") + showSelectArtistDialog = false + playerBottomSheetState.collapseSoft() + onDismiss() + } + .padding(horizontal = 24.dp), + ) { + Text( + text = artist.name, + fontSize = 18.sp, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } + } + } + + var tempo by remember { + mutableStateOf(playerConnection.player.playbackParameters.speed) + } + var transposeValue by remember { + mutableStateOf(round(12 * log2(playerConnection.player.playbackParameters.pitch)).toInt()) + } + val updatePlaybackParameters = { + playerConnection.player.playbackParameters = PlaybackParameters(tempo, 2f.pow(transposeValue.toFloat() / 12)) + } + + Row( + horizontalArrangement = Arrangement.spacedBy(24.dp), + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp) + .padding(top = 24.dp, bottom = 6.dp) + ) { + Icon( + painter = painterResource(R.drawable.volume_up), + contentDescription = null, + modifier = Modifier.size(28.dp) + ) + + BigSeekBar( + progressProvider = playerVolume::value, + onProgressChange = { playerConnection.service.playerVolume.value = it }, + modifier = Modifier.weight(1f) + ) + } + + if (localConfiguration.orientation == ORIENTATION_LANDSCAPE) { + Row( + horizontalArrangement = Arrangement.spacedBy(24.dp), + modifier = Modifier.padding(horizontal = 24.dp, vertical = 6.dp) + ) { + ValueAdjuster( + icon = R.drawable.slow_motion_video, + currentValue = tempo, + values = listOf(0.25f, 0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f), + onValueUpdate = { + tempo = it + updatePlaybackParameters() + }, + valueText = { "x$it" }, + modifier = Modifier.weight(1f) + ) + + ValueAdjuster( + icon = R.drawable.tune, + currentValue = transposeValue, + values = (-12..12).toList(), + onValueUpdate = { + transposeValue = it + updatePlaybackParameters() + }, + valueText = { "${if (it > 0) "+" else ""}$it" }, + modifier = Modifier.weight(1f) + ) + } + } else { + ValueAdjuster( + icon = R.drawable.slow_motion_video, + currentValue = tempo, + values = listOf(0.25f, 0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f), + onValueUpdate = { + tempo = it + updatePlaybackParameters() + }, + valueText = { "x$it" }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp, vertical = 6.dp) + ) + + ValueAdjuster( + icon = R.drawable.tune, + currentValue = transposeValue, + values = (-12..12).toList(), + onValueUpdate = { + transposeValue = it + updatePlaybackParameters() + }, + valueText = { "${if (it > 0) "+" else ""}$it" }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 24.dp, vertical = 6.dp) + ) + } + + GridMenu( + contentPadding = PaddingValues( + start = 8.dp, + top = 8.dp, + end = 8.dp, + bottom = 8.dp + WindowInsets.systemBars.asPaddingValues().calculateBottomPadding() + ) + ) { + GridMenuItem( + icon = R.drawable.radio, + title = R.string.start_radio + ) { + playerConnection.service.startRadioSeamlessly() + onDismiss() + } + GridMenuItem( + icon = R.drawable.playlist_add, + title = R.string.add_to_playlist + ) { + showChoosePlaylistDialog = true + } + DownloadGridMenu( + state = download?.state, + onDownload = { + database.transaction { + insert(mediaMetadata) + } + val downloadRequest = DownloadRequest.Builder(mediaMetadata.id, mediaMetadata.id.toUri()) + .setCustomCacheKey(mediaMetadata.id) + .setData(mediaMetadata.title.toByteArray()) + .build() + DownloadService.sendAddDownload( + context, + ExoDownloadService::class.java, + downloadRequest, + false + ) + }, + onRemoveDownload = { + DownloadService.sendRemoveDownload( + context, + ExoDownloadService::class.java, + mediaMetadata.id, + false + ) + } + ) + GridMenuItem( + icon = R.drawable.artist, + title = R.string.view_artist + ) { + if (mediaMetadata.artists.size == 1) { + navController.navigate("artist/${mediaMetadata.artists[0].id}") + playerBottomSheetState.collapseSoft() + onDismiss() + } else { + showSelectArtistDialog = true + } + } + if (mediaMetadata.album != null) { + GridMenuItem( + icon = R.drawable.album, + title = R.string.view_album + ) { + navController.navigate("album/${mediaMetadata.album.id}") + playerBottomSheetState.collapseSoft() + onDismiss() + } + } + GridMenuItem( + icon = R.drawable.share, + title = R.string.share + ) { + val intent = Intent().apply { + action = Intent.ACTION_SEND + type = "text/plain" + putExtra(Intent.EXTRA_TEXT, "https://music.youtube.com/watch?v=${mediaMetadata.id}") + } + context.startActivity(Intent.createChooser(intent, null)) + onDismiss() + } + GridMenuItem( + icon = R.drawable.info, + title = R.string.details + ) { + onShowDetailsDialog() + onDismiss() + } + GridMenuItem( + icon = R.drawable.equalizer, + title = R.string.equalizer + ) { + val intent = Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL).apply { + putExtra(AudioEffect.EXTRA_AUDIO_SESSION, playerConnection.player.audioSessionId) + putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.packageName) + putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC) + } + if (intent.resolveActivity(context.packageManager) != null) { + activityResultLauncher.launch(intent) + } + onDismiss() + } + } +} + +@Composable +fun ValueAdjuster( + @DrawableRes icon: Int, + currentValue: T, + values: List, + onValueUpdate: (T) -> Unit, + valueText: (T) -> String, + modifier: Modifier, +) { + Row( + horizontalArrangement = Arrangement.spacedBy(24.dp), + verticalAlignment = Alignment.CenterVertically, + modifier = modifier + ) { + Icon( + painter = painterResource(icon), + contentDescription = null, + modifier = Modifier.size(28.dp) + ) + + IconButton( + enabled = currentValue != values.first(), + onClick = { + onValueUpdate(values[values.indexOf(currentValue) - 1]) + } + ) { + Icon( + painter = painterResource(R.drawable.remove), + contentDescription = null + ) + } + + Text( + text = valueText(currentValue), + style = MaterialTheme.typography.titleMedium, + textAlign = TextAlign.Center, + modifier = Modifier.weight(1f) + ) + + IconButton( + enabled = currentValue != values.last(), + onClick = { + onValueUpdate(values[values.indexOf(currentValue) + 1]) + } + ) { + Icon( + painter = painterResource(R.drawable.add), + contentDescription = null + ) + } + } +} diff --git a/app/src/main/java/com/zionhuang/music/ui/player/Queue.kt b/app/src/main/java/com/zionhuang/music/ui/player/Queue.kt index 7d7701d44..0719dc1b8 100644 --- a/app/src/main/java/com/zionhuang/music/ui/player/Queue.kt +++ b/app/src/main/java/com/zionhuang/music/ui/player/Queue.kt @@ -1,11 +1,7 @@ package com.zionhuang.music.ui.player -import android.content.Intent -import android.media.audiofx.AudioEffect import android.text.format.Formatter import android.widget.Toast -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.AnimatedContent import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.foundation.background @@ -14,7 +10,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets @@ -26,11 +21,9 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollState @@ -71,41 +64,23 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.compose.ui.window.DialogProperties -import androidx.core.net.toUri -import androidx.media3.common.PlaybackParameters -import androidx.media3.exoplayer.offline.DownloadRequest -import androidx.media3.exoplayer.offline.DownloadService import androidx.media3.exoplayer.source.ShuffleOrder.DefaultShuffleOrder import androidx.navigation.NavController -import com.zionhuang.music.LocalDatabase -import com.zionhuang.music.LocalDownloadUtil import com.zionhuang.music.LocalPlayerConnection import com.zionhuang.music.R import com.zionhuang.music.constants.ListItemHeight import com.zionhuang.music.constants.ShowLyricsKey -import com.zionhuang.music.db.entities.PlaylistSongMap import com.zionhuang.music.extensions.metadata import com.zionhuang.music.extensions.move import com.zionhuang.music.extensions.togglePlayPause -import com.zionhuang.music.models.MediaMetadata -import com.zionhuang.music.playback.ExoDownloadService -import com.zionhuang.music.playback.PlayerConnection -import com.zionhuang.music.ui.component.BigSeekBar import com.zionhuang.music.ui.component.BottomSheet import com.zionhuang.music.ui.component.BottomSheetState -import com.zionhuang.music.ui.component.DownloadGridMenu -import com.zionhuang.music.ui.component.GridMenu -import com.zionhuang.music.ui.component.GridMenuItem -import com.zionhuang.music.ui.component.ListDialog import com.zionhuang.music.ui.component.LocalMenuState import com.zionhuang.music.ui.component.MediaMetadataListItem -import com.zionhuang.music.ui.menu.AddToPlaylistDialog +import com.zionhuang.music.ui.menu.PlayerMenu import com.zionhuang.music.ui.utils.reordering.ReorderingLazyColumn import com.zionhuang.music.ui.utils.reordering.draggedItem import com.zionhuang.music.ui.utils.reordering.rememberReorderingState @@ -116,9 +91,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch -import kotlin.math.log2 -import kotlin.math.pow -import kotlin.math.round import kotlin.math.roundToInt @OptIn(ExperimentalAnimationApi::class, ExperimentalMaterial3Api::class) @@ -554,253 +526,3 @@ fun Queue( } } } - -@Composable -fun PlayerMenu( - mediaMetadata: MediaMetadata?, - navController: NavController, - playerBottomSheetState: BottomSheetState, - playerConnection: PlayerConnection, - onShowDetailsDialog: () -> Unit, - onDismiss: () -> Unit, -) { - mediaMetadata ?: return - val context = LocalContext.current - val database = LocalDatabase.current - val playerVolume = playerConnection.service.playerVolume.collectAsState() - val activityResultLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { } - - val download by LocalDownloadUtil.current.getDownload(mediaMetadata.id).collectAsState(initial = null) - - var showChoosePlaylistDialog by rememberSaveable { - mutableStateOf(false) - } - - AddToPlaylistDialog( - isVisible = showChoosePlaylistDialog, - onAdd = { playlist -> - database.transaction { - insert(mediaMetadata) - insert( - PlaylistSongMap( - songId = mediaMetadata.id, - playlistId = playlist.id, - position = playlist.songCount - ) - ) - } - }, - onDismiss = { - showChoosePlaylistDialog = false - } - ) - - var showSelectArtistDialog by rememberSaveable { - mutableStateOf(false) - } - - if (showSelectArtistDialog) { - ListDialog( - onDismiss = { showSelectArtistDialog = false } - ) { - items(mediaMetadata.artists) { artist -> - Box( - contentAlignment = Alignment.CenterStart, - modifier = Modifier - .fillParentMaxWidth() - .height(ListItemHeight) - .clickable { - navController.navigate("artist/${artist.id}") - showSelectArtistDialog = false - playerBottomSheetState.collapseSoft() - onDismiss() - } - .padding(horizontal = 24.dp), - ) { - Text( - text = artist.name, - fontSize = 18.sp, - fontWeight = FontWeight.Bold, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } - } - } - } - - var transposeValue by remember { - mutableStateOf(round(12 * log2(playerConnection.player.playbackParameters.pitch)).toInt()) - } - - Row( - horizontalArrangement = Arrangement.spacedBy(24.dp), - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 24.dp, vertical = 24.dp) - ) { - Icon( - painter = painterResource(R.drawable.volume_up), - contentDescription = null, - modifier = Modifier.size(28.dp) - ) - - BigSeekBar( - progressProvider = playerVolume::value, - onProgressChange = { playerConnection.service.playerVolume.value = it }, - modifier = Modifier.weight(1f) - ) - } - - Row( - horizontalArrangement = Arrangement.spacedBy(24.dp), - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 24.dp, vertical = 6.dp) - ) { - Icon( - painter = painterResource(R.drawable.tune), - contentDescription = null, - modifier = Modifier.size(28.dp) - ) - - IconButton( - enabled = transposeValue > -12, - onClick = { - transposeValue-- - playerConnection.player.playbackParameters = PlaybackParameters(1f, 2f.pow(transposeValue.toFloat() / 12)) - } - ) { - Icon( - painter = painterResource(R.drawable.remove), - contentDescription = null - ) - } - - Text( - text = "${if (transposeValue > 0) "+" else ""}$transposeValue", - style = MaterialTheme.typography.bodyLarge, - textAlign = TextAlign.Center, - modifier = Modifier.weight(1f) - ) - - IconButton( - enabled = transposeValue < 12, - onClick = { - transposeValue++ - playerConnection.player.playbackParameters = PlaybackParameters(1f, 2f.pow(transposeValue.toFloat() / 12)) - } - ) { - Icon( - painter = painterResource(R.drawable.add), - contentDescription = null - ) - } - } - - GridMenu( - contentPadding = PaddingValues( - start = 8.dp, - top = 8.dp, - end = 8.dp, - bottom = 8.dp + WindowInsets.systemBars.asPaddingValues().calculateBottomPadding() - ) - ) { - GridMenuItem( - icon = R.drawable.radio, - title = R.string.start_radio - ) { - playerConnection.service.startRadioSeamlessly() - onDismiss() - } - GridMenuItem( - icon = R.drawable.playlist_add, - title = R.string.add_to_playlist - ) { - showChoosePlaylistDialog = true - } - DownloadGridMenu( - state = download?.state, - onDownload = { - database.transaction { - insert(mediaMetadata) - } - val downloadRequest = DownloadRequest.Builder(mediaMetadata.id, mediaMetadata.id.toUri()) - .setCustomCacheKey(mediaMetadata.id) - .setData(mediaMetadata.title.toByteArray()) - .build() - DownloadService.sendAddDownload( - context, - ExoDownloadService::class.java, - downloadRequest, - false - ) - }, - onRemoveDownload = { - DownloadService.sendRemoveDownload( - context, - ExoDownloadService::class.java, - mediaMetadata.id, - false - ) - } - ) - GridMenuItem( - icon = R.drawable.artist, - title = R.string.view_artist - ) { - if (mediaMetadata.artists.size == 1) { - navController.navigate("artist/${mediaMetadata.artists[0].id}") - playerBottomSheetState.collapseSoft() - onDismiss() - } else { - showSelectArtistDialog = true - } - } - if (mediaMetadata.album != null) { - GridMenuItem( - icon = R.drawable.album, - title = R.string.view_album - ) { - navController.navigate("album/${mediaMetadata.album.id}") - playerBottomSheetState.collapseSoft() - onDismiss() - } - } - GridMenuItem( - icon = R.drawable.share, - title = R.string.share - ) { - val intent = Intent().apply { - action = Intent.ACTION_SEND - type = "text/plain" - putExtra(Intent.EXTRA_TEXT, "https://music.youtube.com/watch?v=${mediaMetadata.id}") - } - context.startActivity(Intent.createChooser(intent, null)) - onDismiss() - } - GridMenuItem( - icon = R.drawable.info, - title = R.string.details - ) { - onShowDetailsDialog() - onDismiss() - } - GridMenuItem( - icon = R.drawable.equalizer, - title = R.string.equalizer - ) { - val intent = Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL).apply { - putExtra(AudioEffect.EXTRA_AUDIO_SESSION, playerConnection.player.audioSessionId) - putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.packageName) - putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC) - } - if (intent.resolveActivity(context.packageManager) != null) { - activityResultLauncher.launch(intent) - } - onDismiss() - } - } -} diff --git a/app/src/main/res/drawable/slow_motion_video.xml b/app/src/main/res/drawable/slow_motion_video.xml new file mode 100644 index 000000000..69ed770ae --- /dev/null +++ b/app/src/main/res/drawable/slow_motion_video.xml @@ -0,0 +1,9 @@ + + + From ed68dadeedaba7fc3741b394cc8baee18b223a37 Mon Sep 17 00:00:00 2001 From: maboroshin <41102508+maboroshin@users.noreply.github.com> Date: Mon, 24 Jul 2023 08:50:08 +0900 Subject: [PATCH 51/84] Update strings.xml Japanese --- app/src/main/res/values-ja-rJP/strings.xml | 64 +++++++++++----------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index f8ac56ecd..15d9d505c 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -14,10 +14,10 @@ 履歴 統計 - Mood and Genres - Account - ピックアップ - 何曲か再生するとピックアップが生成できます + ムードとジャンル + アカウント + おすすめ + 何曲か再生するとおすすめを生成します 新作アルバム @@ -29,7 +29,7 @@ 最も再生された曲 最も再生されたアーティスト - Most played albums + 最も再生されたアルバム 検索 @@ -42,7 +42,7 @@ アーティスト プレイリスト コミュニティのプレイリスト - 注目のプレイリスト + おすすめのプレイリスト 見つかりませんでした @@ -68,18 +68,18 @@ ライブラリに追加 ライブラリから削除 ダウンロード - Downloading - ダウンロードを削除 + ダウンロード中 + ダウンロード削除 プレイリストをインポート プレイリストに追加 - アーティストを見る - アルバムを見る + アーティスト表示 + アルバムを表示 再取得 共有 削除 履歴から削除 オンラインで検索 - Sync + 同期 追加日時 @@ -98,7 +98,7 @@ ビットレート サンプリングレート ラウドネス - ボリューム + 音量 ファイルサイズ 不明 クリップボードにコピー @@ -109,8 +109,8 @@ 曲を編集 曲名 曲のアーティスト - 曲名は空白にできません。 - 曲のアーティストは空白にできません。 + 曲名は空にできません。 + 曲のアーティストは空にできません。 保存 プレイリストを選択 @@ -137,21 +137,21 @@ %d プレイリスト - %d week - %d weeks + %d週間 + %d週間 - %d month - %d months + %dか月 + %dか月 - %d year - %d years + %d年 + %d年 プレイリストをインポートしました - プレイリストから \"%s\" を削除しました + プレイリストから「%s」を削除しました プレイリストが同期されました 元に戻す @@ -165,7 +165,7 @@ ストリームが利用できません ネットワーク接続がありません タイムアウトしました - 不明なエラーです + 不明なエラー いいね @@ -181,7 +181,7 @@ 設定 外観 - ダイナミックテーマを有効化 + 動的なテーマを有効化 ダークテーマ オン オフ @@ -196,22 +196,22 @@ コンテンツ ログイン - デフォルトのコンテンツの言語 - デフォルトのコンテンツの国 + コンテンツの既定の言語 + コンテンツの既定の国 システムに従う プロキシを有効化 プロキシのタイプ プロキシのURL 再起動して反映 - プレイヤーとオーディオ - オーディオ品質 + プレイヤーと音声 + 音声品質 自動 再生キューを保持 無音部分をスキップ - オーディオノーマライゼーション + 音声の正規化 イコライザー ストレージ @@ -234,15 +234,15 @@ 検索履歴の記録を一時停止 検索履歴を削除 すべての検索履歴を削除しますか? - 酷狗からの歌詞取得を有効化 + 酷狗 (Kugou) からの歌詞取得を有効化 - バックアップとリストア + バックアップと復元 バックアップ - リストア + 復元 インポートしたプレイリスト バックアップの作成に成功しました バックアップを作成できませんでした - バックアップのリストアに失敗しました + バックアップの復元に失敗しました アプリについて アプリのバージョン From 26d4f396ab92fc628d08acf9d1052e15c3ff7cad Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Mon, 24 Jul 2023 12:11:20 +0800 Subject: [PATCH 52/84] Fix MoodAndGenresButton height --- .../main/java/com/zionhuang/music/ui/screens/HomeScreen.kt | 2 +- .../com/zionhuang/music/ui/screens/MoodAndGenresScreen.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/HomeScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/HomeScreen.kt index bb14d2757..191d0282d 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/HomeScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/HomeScreen.kt @@ -255,7 +255,7 @@ fun HomeScreen( LazyHorizontalGrid( rows = GridCells.Fixed(4), contentPadding = PaddingValues(6.dp), - modifier = Modifier.height(MoodAndGenresButtonHeight * 4 + 12.dp) + modifier = Modifier.height((MoodAndGenresButtonHeight + 12.dp) * 4 + 12.dp) ) { items(moodAndGenres) { MoodAndGenresButton( diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/MoodAndGenresScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/MoodAndGenresScreen.kt index 429b666d6..03adecc6f 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/MoodAndGenresScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/MoodAndGenresScreen.kt @@ -129,7 +129,7 @@ fun MoodAndGenresButton( .clip(RoundedCornerShape(6.dp)) .background(MaterialTheme.colorScheme.surfaceColorAtElevation(6.dp)) .clickable(onClick = onClick) - .padding(12.dp) + .padding(horizontal = 12.dp) ) { Text( text = title, @@ -140,4 +140,4 @@ fun MoodAndGenresButton( } } -val MoodAndGenresButtonHeight = 54.dp \ No newline at end of file +val MoodAndGenresButtonHeight = 48.dp \ No newline at end of file From 7098b5c7ce3d80b087876c3e897d14e26ba8800a Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Mon, 24 Jul 2023 12:23:21 +0800 Subject: [PATCH 53/84] Fix #846 --- .../main/java/com/zionhuang/music/ui/player/Thumbnail.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/com/zionhuang/music/ui/player/Thumbnail.kt b/app/src/main/java/com/zionhuang/music/ui/player/Thumbnail.kt index 07d761da2..d99245cf9 100644 --- a/app/src/main/java/com/zionhuang/music/ui/player/Thumbnail.kt +++ b/app/src/main/java/com/zionhuang/music/ui/player/Thumbnail.kt @@ -70,6 +70,12 @@ fun Thumbnail( } } + LaunchedEffect(showLyrics) { + if (!showLyrics) { + pagerState.scrollToPage(currentWindowIndex) + } + } + DisposableEffect(showLyrics) { currentView.keepScreenOn = showLyrics onDispose { From db7c6c19ed4ba73c4a0186cf101b8baddb5bf522 Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Mon, 24 Jul 2023 12:54:39 +0800 Subject: [PATCH 54/84] Swipeable mini player --- .../music/playback/PlayerConnection.kt | 2 +- .../zionhuang/music/ui/player/MiniPlayer.kt | 185 ++++++++++++------ .../com/zionhuang/music/ui/player/Player.kt | 1 - 3 files changed, 123 insertions(+), 65 deletions(-) diff --git a/app/src/main/java/com/zionhuang/music/playback/PlayerConnection.kt b/app/src/main/java/com/zionhuang/music/playback/PlayerConnection.kt index 402eec084..74c63cb2c 100644 --- a/app/src/main/java/com/zionhuang/music/playback/PlayerConnection.kt +++ b/app/src/main/java/com/zionhuang/music/playback/PlayerConnection.kt @@ -36,7 +36,7 @@ class PlayerConnection( val player = service.player val playbackState = MutableStateFlow(player.playbackState) - val playWhenReady = MutableStateFlow(player.playWhenReady) + private val playWhenReady = MutableStateFlow(player.playWhenReady) val isPlaying = combine(playbackState, playWhenReady) { playbackState, playWhenReady -> playWhenReady && playbackState != STATE_ENDED }.stateIn(scope, SharingStarted.Lazily, player.playWhenReady && player.playbackState != STATE_ENDED) diff --git a/app/src/main/java/com/zionhuang/music/ui/player/MiniPlayer.kt b/app/src/main/java/com/zionhuang/music/ui/player/MiniPlayer.kt index 830e3a928..69e984cbf 100644 --- a/app/src/main/java/com/zionhuang/music/ui/player/MiniPlayer.kt +++ b/app/src/main/java/com/zionhuang/music/ui/player/MiniPlayer.kt @@ -2,7 +2,9 @@ package com.zionhuang.music.ui.player import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -16,6 +18,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -23,8 +26,11 @@ import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -34,28 +40,61 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.media3.common.PlaybackException import androidx.media3.common.Player import coil.compose.AsyncImage import com.zionhuang.music.LocalPlayerConnection import com.zionhuang.music.R import com.zionhuang.music.constants.MiniPlayerHeight import com.zionhuang.music.constants.ThumbnailCornerRadius +import com.zionhuang.music.extensions.metadata import com.zionhuang.music.extensions.togglePlayPause import com.zionhuang.music.models.MediaMetadata +import com.zionhuang.music.ui.utils.HorizontalPager +import com.zionhuang.music.ui.utils.SnapLayoutInfoProvider +import kotlinx.coroutines.flow.drop +@OptIn(ExperimentalFoundationApi::class) @Composable fun MiniPlayer( - mediaMetadata: MediaMetadata?, position: Long, duration: Long, modifier: Modifier = Modifier, ) { - mediaMetadata ?: return val playerConnection = LocalPlayerConnection.current ?: return val isPlaying by playerConnection.isPlaying.collectAsState() val playbackState by playerConnection.playbackState.collectAsState() - val canSkipNext by playerConnection.canSkipNext.collectAsState() val error by playerConnection.error.collectAsState() + val windows by playerConnection.queueWindows.collectAsState() + val currentWindowIndex by playerConnection.currentWindowIndex.collectAsState() + + val pagerState = rememberPagerState( + initialPage = currentWindowIndex.takeIf { it != -1 } ?: 0 + ) + + val snapLayoutInfoProvider = remember(pagerState) { + SnapLayoutInfoProvider( + pagerState = pagerState, + positionInLayout = { _, _ -> 0f } + ) + } + + LaunchedEffect(pagerState, currentWindowIndex) { + if (windows.isNotEmpty()) { + try { + pagerState.animateScrollToPage(currentWindowIndex) + } catch (_: Exception) { + } + } + } + + LaunchedEffect(pagerState) { + snapshotFlow { pagerState.settledPage }.drop(1).collect { index -> + if (!pagerState.isScrollInProgress && index != currentWindowIndex && windows.isNotEmpty()) { + playerConnection.player.seekToDefaultPosition(windows[index].firstPeriodIndex) + } + } + } Box( modifier = modifier @@ -74,62 +113,25 @@ fun MiniPlayer( verticalAlignment = Alignment.CenterVertically, modifier = modifier .fillMaxSize() - .padding(horizontal = 6.dp), + .padding(end = 12.dp), ) { - Box(modifier = Modifier.padding(6.dp)) { - AsyncImage( - model = mediaMetadata.thumbnailUrl, - contentDescription = null, - modifier = Modifier - .size(48.dp) - .clip(RoundedCornerShape(ThumbnailCornerRadius)) - ) - androidx.compose.animation.AnimatedVisibility( - visible = error != null, - enter = fadeIn(), - exit = fadeOut() - ) { - Box( - Modifier - .size(48.dp) - .background( - color = Color.Black.copy(alpha = 0.6f), - shape = RoundedCornerShape(ThumbnailCornerRadius) - ) - ) { - Icon( - painter = painterResource(R.drawable.info), - contentDescription = null, - tint = MaterialTheme.colorScheme.error, - modifier = Modifier - .align(Alignment.Center) - ) - } + HorizontalPager( + state = pagerState, + flingBehavior = rememberSnapFlingBehavior(snapLayoutInfoProvider), + items = windows, + key = { it.uid.hashCode() }, + beyondBoundsPageCount = 2, + modifier = Modifier.weight(1f) + ) { window -> + window.mediaItem.metadata?.let { + MiniMediaInfo( + mediaMetadata = it, + error = error, + modifier = Modifier.padding(horizontal = 6.dp) + ) } } - Column( - modifier = Modifier - .weight(1f) - .padding(horizontal = 6.dp) - ) { - Text( - text = mediaMetadata.title, - color = MaterialTheme.colorScheme.onSurface, - fontSize = 16.sp, - fontWeight = FontWeight.Bold, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - Text( - text = mediaMetadata.artists.joinToString { it.name }, - color = MaterialTheme.colorScheme.secondary, - fontSize = 12.sp, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } - IconButton( onClick = { if (playbackState == Player.STATE_ENDED) { @@ -145,15 +147,72 @@ fun MiniPlayer( contentDescription = null ) } - IconButton( - enabled = canSkipNext, - onClick = playerConnection.player::seekToNext + } + } +} + +@Composable +fun MiniMediaInfo( + mediaMetadata: MediaMetadata, + error: PlaybackException?, + modifier: Modifier = Modifier, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = modifier + ) { + Box(modifier = Modifier.padding(6.dp)) { + AsyncImage( + model = mediaMetadata.thumbnailUrl, + contentDescription = null, + modifier = Modifier + .size(48.dp) + .clip(RoundedCornerShape(ThumbnailCornerRadius)) + ) + androidx.compose.animation.AnimatedVisibility( + visible = error != null, + enter = fadeIn(), + exit = fadeOut() ) { - Icon( - painter = painterResource(R.drawable.skip_next), - contentDescription = null - ) + Box( + Modifier + .size(48.dp) + .background( + color = Color.Black.copy(alpha = 0.6f), + shape = RoundedCornerShape(ThumbnailCornerRadius) + ) + ) { + Icon( + painter = painterResource(R.drawable.info), + contentDescription = null, + tint = MaterialTheme.colorScheme.error, + modifier = Modifier + .align(Alignment.Center) + ) + } } } + + Column( + modifier = Modifier + .weight(1f) + .padding(horizontal = 6.dp) + ) { + Text( + text = mediaMetadata.title, + color = MaterialTheme.colorScheme.onSurface, + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Text( + text = mediaMetadata.artists.joinToString { it.name }, + color = MaterialTheme.colorScheme.secondary, + fontSize = 12.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/zionhuang/music/ui/player/Player.kt b/app/src/main/java/com/zionhuang/music/ui/player/Player.kt index 71a388061..f5766cbfb 100644 --- a/app/src/main/java/com/zionhuang/music/ui/player/Player.kt +++ b/app/src/main/java/com/zionhuang/music/ui/player/Player.kt @@ -119,7 +119,6 @@ fun BottomSheetPlayer( }, collapsedContent = { MiniPlayer( - mediaMetadata = mediaMetadata, position = position, duration = duration ) From 070abf39c02191fb74d1b589ccc288e02fe36680 Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Mon, 24 Jul 2023 18:08:03 +0800 Subject: [PATCH 55/84] Fix #839 --- .../java/com/zionhuang/innertube/YouTube.kt | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt b/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt index 29cb837d9..c16f5eb66 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt @@ -285,14 +285,15 @@ object YouTube { suspend fun explore(): Result = runCatching { val response = innerTube.browse(WEB_REMIX, browseId = "FEmusic_explore").body() ExplorePage( - newReleaseAlbums = response.contents?.singleColumnBrowseResultsRenderer?.tabs?.firstOrNull()?.tabRenderer?.content?.sectionListRenderer?.contents?.getOrNull(1)?.musicCarouselShelfRenderer?.contents - ?.mapNotNull { - it.musicTwoRowItemRenderer?.let { renderer -> - NewReleaseAlbumPage.fromMusicTwoRowItemRenderer(renderer) - } - }.orEmpty(), - moodAndGenres = response.contents?.singleColumnBrowseResultsRenderer?.tabs?.firstOrNull()?.tabRenderer?.content?.sectionListRenderer?.contents?.getOrNull(2)?.musicCarouselShelfRenderer?.contents - ?.mapNotNull(MusicCarouselShelfRenderer.Content::musicNavigationButtonRenderer) + newReleaseAlbums = response.contents?.singleColumnBrowseResultsRenderer?.tabs?.firstOrNull()?.tabRenderer?.content?.sectionListRenderer?.contents?.find { + it.musicCarouselShelfRenderer?.header?.musicCarouselShelfBasicHeaderRenderer?.moreContentButton?.buttonRenderer?.navigationEndpoint?.browseEndpoint?.browseId == "FEmusic_new_releases_albums" + }?.musicCarouselShelfRenderer?.contents + ?.mapNotNull { it.musicTwoRowItemRenderer } + ?.mapNotNull(NewReleaseAlbumPage::fromMusicTwoRowItemRenderer).orEmpty(), + moodAndGenres = response.contents?.singleColumnBrowseResultsRenderer?.tabs?.firstOrNull()?.tabRenderer?.content?.sectionListRenderer?.contents?.find { + it.musicCarouselShelfRenderer?.header?.musicCarouselShelfBasicHeaderRenderer?.moreContentButton?.buttonRenderer?.navigationEndpoint?.browseEndpoint?.browseId == "FEmusic_moods_and_genres" + }?.musicCarouselShelfRenderer?.contents + ?.mapNotNull { it.musicNavigationButtonRenderer } ?.mapNotNull(MoodAndGenres.Companion::fromMusicNavigationButtonRenderer) .orEmpty() ) @@ -300,11 +301,10 @@ object YouTube { suspend fun newReleaseAlbums(): Result> = runCatching { val response = innerTube.browse(WEB_REMIX, browseId = "FEmusic_new_releases_albums").body() - response.contents?.singleColumnBrowseResultsRenderer?.tabs?.firstOrNull()?.tabRenderer?.content?.sectionListRenderer?.contents?.firstOrNull()?.gridRenderer?.items?.mapNotNull { - it.musicTwoRowItemRenderer?.let { renderer -> - NewReleaseAlbumPage.fromMusicTwoRowItemRenderer(renderer) - } - }.orEmpty() + response.contents?.singleColumnBrowseResultsRenderer?.tabs?.firstOrNull()?.tabRenderer?.content?.sectionListRenderer?.contents?.firstOrNull()?.gridRenderer?.items + ?.mapNotNull { it.musicTwoRowItemRenderer } + ?.mapNotNull(NewReleaseAlbumPage::fromMusicTwoRowItemRenderer) + .orEmpty() } suspend fun moodAndGenres(): Result> = runCatching { From 616f8651f7c8e90882e1dc8647151a2ca583a183 Mon Sep 17 00:00:00 2001 From: RD3V <47334312+RDevWasTaken@users.noreply.github.com> Date: Mon, 24 Jul 2023 18:18:44 +0200 Subject: [PATCH 56/84] Add Polish translation Polish language --- app/src/main/res/values-pl/strings.xml | 273 +++++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 app/src/main/res/values-pl/strings.xml diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml new file mode 100644 index 000000000..8db060d83 --- /dev/null +++ b/app/src/main/res/values-pl/strings.xml @@ -0,0 +1,273 @@ + + + Główna + Utwory + Wykonawcy + Albumy + Playlisty + + + + %d zaznaczony + %d zaznaczone + %d zaznaczonych + %d zaznaczonych + + + + Historia + Statystyki + Nastroje i gatunki + Konto + Szybki wybór + Słuchaj utworów, aby wygenerować szybkie wybory + Nowo wydane albumy + + + Dzisiaj + Wczoraj + Ten tydzień + Ostatni tydzień + + + Najczęściej słuchane utwory + Najczęściej słuchani wykonawcy + Najczęściej słuchane albumy + + + Szukaj + Szukaj w YouTube Music… + Szukaj w bibliotece… + Wszystko + Utwory + Filmy + Albumy + Wykonawcy + Playlisty + Playlisty tworzone przez społeczność + Polecane playlisty + Brak wyników + + + Z twojej biblioteki + + + Polubione utwory + Pobrane utwory + Playlista jest pusta + + + Spróbuj ponownie + Radio + Losowo + + + Szczegóły + Edytuj + Włącz radio + Odtwórz + Odtwórz jako następny + Dodaj do kolejki + Dodaj do biblioteki + Usuń z biblioteki + Pobierz + Pobieranie + Usuń z pobranych + Importuj playlistę + Dodaj do playlisty + Pokaż wykonawcę + Pokaż album + Odśwież + Udostępnij + Usuń + Usuń z historii + Szukaj online + Synchronizuj + + + Data dodania + Nazwa + Wykonawca + Rok + Liczba utworów + Długość + Długość utworu + Niestandardowa kolejność + + + ID media + Typ MIME + Kodeki + Przepływność + Częstotliwość próbkowania + Natężenie + Głośność + Rozmiar pliku + Nieznane + Skopiowano do schowka + + Edytuj tekst + Szukaj tekstu + + Edytuj utwór + Tytuł utworu + Wykonawcy utworu + Tytuł utworu nie może być pusty. + Wykonawca utworu nie może być pusty. + Zapisz + + Wybierz playlistę + Edytuj playlistę + Utwórz playlistę + Nazwa playlisty + Nazwa playlisty nie może być pusta. + + Edytuj wykonawcę + Nazwa wykonawcy + Nazwa wykonawcy nie może być pusta. + + + + %d utwór + %d utwory + %d utworów + %d utworów + + + %d wykonawca + %d wykonawców + %d wykonawców + %d wykonawców + + + %d album + %d albumy + %d albumów + %d albumów + + + %d playlista + %d playlisty + %d playlist + %d playlist + + + %d tydzień + %d tygodnie + %d tygodni + %d tygodni + + + %d miesiąc + %d miesiące + %d miesięcy + %d miesięcy + + + %d rok + %d lata + %d lat + %d lat + + + + Playlista zaimportowana + Usunięto \"%s\" z playlisty + Playlista zsynchronizowana + Cofnij + + + Nie znaleziono tekstu + Wyłącznik czasowy + Koniec utworu + + 1 minuta + %d minuty + %d minut + %d minut + + Brak dostępnego źródła + Brak połączenia z internetem + Upłynął limit czasu + Nieznany błąd + + + Polub + Usuń polubienie + + + Wszystkie utwory + Wyszukane utwory + + + Odtwarzacz + + + Ustawienia + Wygląd + Włącz motyw dynamiczny + Ciemny motyw + Włącz + Wyłącz + Zgodnie z systemem + Czysta czerń + Domyślnie otwarta zakładka + Modyfikuj zakładki nawigacji + Położenie tekstu utworu + Z lewej + Na środku + Z prawej + + Zawartość + Zaloguj się + Domyślny język zawartości + Domyślny kraj zawartości + Domyślny systemowy + Włącz proxy + Typ proxy + URL proxy + Uruchom ponownie, aby zastosować zmiany + + Odtwarzacz i audio + Jakość audio + Automatyczna + Wysoka + Niska + Trwała kolejka + Pomiń ciszę + Normalizacja audio + Korektor graficzny + + Pamięć + Cache + Cache obrazów + Cache utworów + Maksymalny rozmiar cache + Nieskończony + Wyczyść pobrane + Maksymalny rozmiar cache obrazów + Wyczyść cache obrazów + Maksymalny rozmiar cache utworów + Wyczyść cache utworów + %s w użyciu + + Prywatność + Wstrzymaj historię odtwarzania + Wyczyść historię odtwarzania + Czy na pewno chcesz wyczyścić całą historię odtwarzania? + Wstrzymaj historię wyszukiwania + Wyczyść historię wyszukiwania + Czy na pewno chcesz wyczyścić całą historię wyszukiwania? + Pobieraj teksty utworów z KuGou + + Kopia zapasowa i przywracanie + Utwórz kopię zapasową + Przywróć + Zaimportowane playlisty + Kopia zapasowa utworzona pomyślnie + Nie udało się stworzyć kopii zapasowej + Nie udało się przywrócić kopii zapasowej + + O aplikacji + Wersja aplikacji + From d7f5049b3f55c10e2119c08d141ea439ef62f6bf Mon Sep 17 00:00:00 2001 From: Pintu Das Date: Tue, 25 Jul 2023 19:07:48 +0530 Subject: [PATCH 57/84] added bangla translation --- app/src/main/res/values-bn-rIN/strings.xml | 255 +++++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 app/src/main/res/values-bn-rIN/strings.xml diff --git a/app/src/main/res/values-bn-rIN/strings.xml b/app/src/main/res/values-bn-rIN/strings.xml new file mode 100644 index 000000000..f513079dd --- /dev/null +++ b/app/src/main/res/values-bn-rIN/strings.xml @@ -0,0 +1,255 @@ + + + হোম + গান + শিল্পী + অ্যালবাম + প্লেলিস্ট + + + + %d নির্বাচিত + %d নির্বাচিত + + + + ইতিহাস + পরিসংখ্যান + মেজাজ এবং শৈলী + অ্যাকাউন্ট + দ্রুত পছন্দ + আপনার নিজের শর্টকাট তৈরি করতে কিছু টিউন শুনুন + নতুন অ্যালবাম ও সিঙ্গেল + + + আজ + গতকাল + এই সপ্তাহ + গত সপ্তাহ + + + সর্বাধিক শোনা গানগুলি + সর্বাধিক শোনা শিল্পী + সর্বাধিক শোনা অ্যালবাম + + + খুঁজুন + খুঁজুন YouTube Music এ + খুঁজুন লাইব্রেরি তে + সব + সংগীত + ভিডিও + অ্যালবাম + শিল্পী + প্লেলিস্ট + প্লেলিস্ট ক্রম + বিশিষ্ট প্লেলিস্ট + কিছু পাওয়া যায়নি + + + আপনার লাইব্রেরি থেকে + + + আপনার পছন্দের গান + ডাউনলোড করা গান + প্লেলিস্ট খালি + + + পুনরায় চেষ্টা করুন + বেতার + এলোমেলো + + + বিস্তারিত + সম্পাদনা + বেতার খুলুন + গান বাজান + পরবর্তী গান বাজান + পরবর্তী গান যোগ করুন + লাইব্রেরি তে যোগ করুন + লাইব্রেরি থেকে সরান + ডাউনলোড + ডাউনলোড হচ্ছে + ডাউনলোড মুছে ফেলুন + প্লেলিস্ট আমদানি করুন + প্লেলিস্টে যোগ করুন + শিল্পী দেখুন + অ্যালবাম দেখুন + আবার আনুন + শেয়ার + মুছুন + ইতিহাস থেকে অপসারণ + অনলাইন এ খুঁজুন + সুসংগত + + + সময় সম্পাদনা করা হয়েছে + নাম + শিল্পী + বছর + প্লে সংখ্যান + দৈর্ঘ্য + বাজানো হয়েছে + অনুকুলিত + + + Media ID + MIME Type + Codec + Bitrate + Sample Rate + শব্দের মাত্রা + ধ্বনির মাত্রা + ফাইলের আকার + অজানা + ক্লিপবোর্ডে কপি করা হয়েছে + + গানের কথা সম্পাদনা + গানের কথা অনুসন্ধান + + গান সম্পাদনা করুন + শিরোনাম + শিল্পী + গানের শিরোনাম খালি হতে পারে না। + গানের শিল্পী খালি থাকতে পারে না। + সংরক্ষণ + + একটি প্লেলিস্ট চয়ন করুন + প্লেলিস্ট সম্পাদনা করুন + প্লেলিস্ট তৈরি করুন + প্লেলিস্টের নাম + প্লেলিস্টের নাম খালি রাখা যাবে না। + + শিল্পী সম্পাদনা করুন + শিল্পীর নাম + শিল্পীর নাম খালি রাখা যাবে না। + + + + %d গান + %d গান + + + %d শিল্পী + %d শিল্পী + + + %d অ্যালবাম + %d অ্যালবাম + + + %d প্লেলিস্ট + %d প্লেলিস্ট + + + %d সপ্তাহ + %d সপ্তাহ + + + %d মাস + %d মাস + + + %d বছর + %d বছর + + + + আমদানি করা প্লেলিস্ট + প্লেলিস্ট থেকে সরানো হয়েছে + সিঙ্ক্রোনাইজ করা প্লেলিস্ট + বাতিল করুন + + + গানের কথা পাওয়া যায়নি + ঘুমের টাইমার + গানের শেষ + + ১ মিনিট + %d মিনিট + + স্ট্রিম উপলব্ধ নয় + নেটওয়ার্ক সংযোগ নেই + ত্রুটি সময়সীমা শেষ + অজানা ত্রুটি + + + পছন্দ + অপছন্দ + + + সব গান + অনুসন্ধান করা গান + + + প্লেয়ার + + + সেটিংস + দৃষ্টিগোচরতা + গতিশীল থিম সক্ষম করুন + অন্ধকার থিম + সক্রিয় + নিষ্ক্রিয় + সিস্টেম অনুসরণ করুন + কালো + ডিফল্ট প্রধান ট্যাব + নেভিগেশন ট্যাব কাস্টমাইজ করুন + গানের কথার অবস্থান + বাম + কেন্দ্র + ডান + + কনটেন্ট + সাইন ইন + ডিফল্ট কন্টেন্টের ভাষা + ডিফল্ট কনটেন্ট দেশ + সিস্টেমের ডিফল্ট + প্রক্সি সক্রিয় করুন + প্রক্সি ধরণ + প্রক্সি URL + পরিবর্তনগুলি প্রয়োগ করতে অ্যাপটি পুনরায় চালু করুন + + প্লেয়ার এবং অডিও + অডিওর মান + স্বয়ংক্রিয় + উচ্চ + নিম্ন + অবিরাম সারি + নীরবতা এড়িয়ে যান + অডিও স্বাভাবিকীকরণ + অডিও টিউনার + + স্টোরেজ + ক্যাশে + ইমেজ ক্যাশে + অডিও ক্যাশে + সর্বাধিক ক্যাশে আকার + সীমাহীন + সমস্ত ডাউনলোড মুছুন + ইমেজ ক্যাশে সর্বাধিক আকার + ইমেজ ক্যাশে সাফ করুন + অডিও ক্যাশে সর্বোচ্চ আকার + অডিও ক্যাশে সাফ করুন + %s ব্যবহৃত + + গোপনীয়তা + আপনার শোনার ইতিহাস থামান + আপনার শোনার ইতিহাস সাফ করুন + আপনি কি আপনার শোনার ইতিহাস মুছে ফেলার বিষয়ে নিশ্চিত? + আপনার অনুসন্ধান ইতিহাস স্থগিত + আপনার অনুসন্ধান ইতিহাস সাফ করুন + আপনি কি আপনার অনুসন্ধান ইতিহাস মুছে ফেলার বিষয়ে নিশ্চিত? + KuGou দ্বারা প্রদত্ত গানের কথা সক্রিয় করুন + + ব্যাকআপ এবং পুনঃস্থাপন + ব্যাকআপ + পুনঃস্থাপন + আমদানি করা প্লেলিস্ট + ব্যাকআপ সফলভাবে তৈরি করা হয়েছে + ব্যাকআপ করতে অক্ষম + ব্যাকআপ থেকে পুনরুদ্ধার করতে অক্ষম + + সম্পর্কিত + অ্যাপ সংস্করণ + From f7067ba53e789cb925d51750cd55b30ff4c743a6 Mon Sep 17 00:00:00 2001 From: K Gill <60492161+ShareASmile@users.noreply.github.com> Date: Wed, 26 Jul 2023 10:52:00 +0530 Subject: [PATCH 58/84] Update actions upload artifacts to v3.yml fix warnings on each run of GH actions workflow --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 901114823..30da26d6e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,7 +33,7 @@ jobs: MUSIC_DEBUG_SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }} - name: Upload APK - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: app - path: app/build/outputs/apk/debug/*.apk \ No newline at end of file + path: app/build/outputs/apk/debug/*.apk From 9f07d9a1d606eaea42483b0dd38d0b236e5bb954 Mon Sep 17 00:00:00 2001 From: K Gill <60492161+ShareASmile@users.noreply.github.com> Date: Wed, 26 Jul 2023 10:55:49 +0530 Subject: [PATCH 59/84] Update actions upload artifacts in build_pr workflow to v3 --- .github/workflows/build_pr.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_pr.yml b/.github/workflows/build_pr.yml index 2857a57a0..e2b0d83d1 100644 --- a/.github/workflows/build_pr.yml +++ b/.github/workflows/build_pr.yml @@ -21,7 +21,7 @@ jobs: run: ./gradlew assembleDebug lintDebug testDebugUnitTest --stacktrace -DskipFormatKtlint - name: Upload APK - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: app - path: app/build/outputs/apk/debug/*.apk \ No newline at end of file + path: app/build/outputs/apk/debug/*.apk From 70cc0a68d984e0004ec9661b5bc2ae5f0007b58d Mon Sep 17 00:00:00 2001 From: Pintu Das Date: Wed, 26 Jul 2023 16:53:44 +0530 Subject: [PATCH 60/84] fixed linting issue --- app/src/main/res/values-bn-rIN/strings.xml | 30 +++++++++++++++++----- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/values-bn-rIN/strings.xml b/app/src/main/res/values-bn-rIN/strings.xml index f513079dd..d3918e6a8 100644 --- a/app/src/main/res/values-bn-rIN/strings.xml +++ b/app/src/main/res/values-bn-rIN/strings.xml @@ -9,6 +9,8 @@ %d নির্বাচিত + %d নির্বাচিত + %d নির্বাচিত %d নির্বাচিত @@ -127,36 +129,50 @@ %d গান + %d গান + %d গান %d গান %d শিল্পী + %d শিল্পী + %d শিল্পী %d শিল্পী %d অ্যালবাম + %d অ্যালবাম + %d অ্যালবাম %d অ্যালবাম %d প্লেলিস্ট + %d প্লেলিস্ট + %d প্লেলিস্ট %d প্লেলিস্ট - + %d সপ্তাহ + %d সপ্তাহ + %d সপ্তাহ %d সপ্তাহ - + %d মাস + %d মাস + %d মাস %d মাস - + %d বছর + %d বছর + %d বছর %d বছর আমদানি করা প্লেলিস্ট - প্লেলিস্ট থেকে সরানো হয়েছে + প্লেলিস্ট থেকে \"%s\" সরানো হয়েছে সিঙ্ক্রোনাইজ করা প্লেলিস্ট বাতিল করুন @@ -165,7 +181,9 @@ ঘুমের টাইমার গানের শেষ - ১ মিনিট + %d মিনিট + %d মিনিট + %d মিনিট %d মিনিট স্ট্রিম উপলব্ধ নয় @@ -252,4 +270,4 @@ সম্পর্কিত অ্যাপ সংস্করণ - + \ No newline at end of file From e8f16febe015c5bc5985756346f24b1c691f06bb Mon Sep 17 00:00:00 2001 From: Javi <45560967+javdc@users.noreply.github.com> Date: Thu, 27 Jul 2023 21:48:36 +0200 Subject: [PATCH 61/84] Implement app shortcuts --- app/src/debug/res/xml-v25/shortcuts.xml | 56 +++++++++++++++++++ app/src/main/AndroidManifest.xml | 3 + .../java/com/zionhuang/music/MainActivity.kt | 28 +++++++++- .../zionhuang/music/ui/component/SearchBar.kt | 4 +- app/src/main/res/drawable/shortcut_albums.xml | 9 +++ .../main/res/drawable/shortcut_playlists.xml | 10 ++++ app/src/main/res/drawable/shortcut_search.xml | 9 +++ app/src/main/res/drawable/shortcut_songs.xml | 9 +++ app/src/main/res/xml-v25/shortcuts.xml | 56 +++++++++++++++++++ 9 files changed, 181 insertions(+), 3 deletions(-) create mode 100644 app/src/debug/res/xml-v25/shortcuts.xml create mode 100644 app/src/main/res/drawable/shortcut_albums.xml create mode 100644 app/src/main/res/drawable/shortcut_playlists.xml create mode 100644 app/src/main/res/drawable/shortcut_search.xml create mode 100644 app/src/main/res/drawable/shortcut_songs.xml create mode 100644 app/src/main/res/xml-v25/shortcuts.xml diff --git a/app/src/debug/res/xml-v25/shortcuts.xml b/app/src/debug/res/xml-v25/shortcuts.xml new file mode 100644 index 000000000..cfe2ee236 --- /dev/null +++ b/app/src/debug/res/xml-v25/shortcuts.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 302df9b66..d981d51ae 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -104,6 +104,9 @@ + + NavigationTab.SONG + "zionhuang.music.action.ALBUMS" -> NavigationTab.ALBUM + "zionhuang.music.action.PLAYLISTS" -> NavigationTab.PLAYLIST + else -> null + } + } val defaultOpenTab = remember { dataStore[DefaultOpenTabKey].toEnum(defaultValue = NavigationTab.HOME) } @@ -238,6 +247,8 @@ class MainActivity : ComponentActivity() { } var searchSource by rememberEnumPreference(SearchSourceKey, SearchSource.ONLINE) + val searchBarFocusRequester = remember { FocusRequester() } + val onSearch: (String) -> Unit = { if (it.isNotEmpty()) { onActiveChange(false) @@ -250,6 +261,10 @@ class MainActivity : ComponentActivity() { } } + var openSearchImmediately: Boolean by remember { + mutableStateOf(intent?.action == "zionhuang.music.action.SEARCH") + } + val shouldShowSearchBar = remember(active, navBackStackEntry) { active || navigationItems.fastAny { it.route == navBackStackEntry?.destination?.route } || navBackStackEntry?.destination?.route?.startsWith("search/") == true @@ -389,7 +404,7 @@ class MainActivity : ComponentActivity() { ) { NavHost( navController = navController, - startDestination = when (defaultOpenTab) { + startDestination = when (tabOpenedFromShortcut ?: defaultOpenTab) { NavigationTab.HOME -> Screens.Home NavigationTab.SONG -> Screens.Songs NavigationTab.ARTIST -> Screens.Artists @@ -653,7 +668,8 @@ class MainActivity : ComponentActivity() { } } }, - modifier = Modifier.align(Alignment.TopCenter) + focusRequester = searchBarFocusRequester, + modifier = Modifier.align(Alignment.TopCenter), ) { Crossfade( targetState = searchSource, @@ -771,6 +787,14 @@ class MainActivity : ComponentActivity() { } } } + + LaunchedEffect(shouldShowSearchBar, openSearchImmediately) { + if (shouldShowSearchBar && openSearchImmediately) { + onActiveChange(true) + searchBarFocusRequester.requestFocus() + openSearchImmediately = false + } + } } } } diff --git a/app/src/main/java/com/zionhuang/music/ui/component/SearchBar.kt b/app/src/main/java/com/zionhuang/music/ui/component/SearchBar.kt index e6db353d1..42db9b6c6 100644 --- a/app/src/main/java/com/zionhuang/music/ui/component/SearchBar.kt +++ b/app/src/main/java/com/zionhuang/music/ui/component/SearchBar.kt @@ -68,6 +68,7 @@ fun SearchBar( tonalElevation: Dp = SearchBarDefaults.Elevation, windowInsets: WindowInsets = WindowInsets.systemBars, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + focusRequester: FocusRequester = remember { FocusRequester() }, content: @Composable ColumnScope.() -> Unit, ) { val heightOffsetLimit = with(LocalDensity.current) { @@ -172,6 +173,7 @@ fun SearchBar( trailingIcon = trailingIcon, colors = colors.inputFieldColors, interactionSource = interactionSource, + focusRequester = focusRequester, ) if (animationProgress > 0) { @@ -204,8 +206,8 @@ private fun SearchBarInputField( trailingIcon: @Composable (() -> Unit)? = null, colors: TextFieldColors = SearchBarDefaults.inputFieldColors(), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + focusRequester: FocusRequester = remember { FocusRequester() } ) { - val focusRequester = remember { FocusRequester() } val searchSemantics = getString(Strings.SearchBarSearch) val suggestionsAvailableSemantics = getString(Strings.SuggestionsAvailable) val textColor = LocalTextStyle.current.color.takeOrElse { diff --git a/app/src/main/res/drawable/shortcut_albums.xml b/app/src/main/res/drawable/shortcut_albums.xml new file mode 100644 index 000000000..7aed64f3a --- /dev/null +++ b/app/src/main/res/drawable/shortcut_albums.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shortcut_playlists.xml b/app/src/main/res/drawable/shortcut_playlists.xml new file mode 100644 index 000000000..eba0fe7ea --- /dev/null +++ b/app/src/main/res/drawable/shortcut_playlists.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shortcut_search.xml b/app/src/main/res/drawable/shortcut_search.xml new file mode 100644 index 000000000..c56846b00 --- /dev/null +++ b/app/src/main/res/drawable/shortcut_search.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shortcut_songs.xml b/app/src/main/res/drawable/shortcut_songs.xml new file mode 100644 index 000000000..710ec0019 --- /dev/null +++ b/app/src/main/res/drawable/shortcut_songs.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/xml-v25/shortcuts.xml b/app/src/main/res/xml-v25/shortcuts.xml new file mode 100644 index 000000000..2fc445482 --- /dev/null +++ b/app/src/main/res/xml-v25/shortcuts.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From a97e222885a264d7e538cc49d1ed7d5e0cb8fe36 Mon Sep 17 00:00:00 2001 From: K Gill <60492161+ShareASmile@users.noreply.github.com> Date: Fri, 28 Jul 2023 20:28:30 +0530 Subject: [PATCH 62/84] ignore some paths in actions & add.. ..manual dispatch to actions workflow --- .github/workflows/build.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 30da26d6e..273a2f544 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,8 +1,16 @@ name: Build debug APK on: + workflow_dispatch: push: branches: - '**' + paths-ignore: + - 'README.md' + - 'fastlane/**' + - 'assets/**' + - '.github/**/*.md' + - '.github/FUNDING.yml' + - '.github/ISSUE_TEMPLATE/**' jobs: build: From b37abe52b193ca2049f6c8a9269274782580b555 Mon Sep 17 00:00:00 2001 From: thohnb <125535323+thohnb@users.noreply.github.com> Date: Sat, 29 Jul 2023 09:03:34 +0700 Subject: [PATCH 63/84] add Vietnamese translation --- app/src/main/res/values-vn/strings.xml | 255 +++++++++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 app/src/main/res/values-vn/strings.xml diff --git a/app/src/main/res/values-vn/strings.xml b/app/src/main/res/values-vn/strings.xml new file mode 100644 index 000000000..a91fe00a5 --- /dev/null +++ b/app/src/main/res/values-vn/strings.xml @@ -0,0 +1,255 @@ + + + Trang chủ + Bài hát + Tác giả + Albums + Danh sách phát + + + + Đã chọn %d + + + + Lịch sử + Thống kê + Tâm trạng và thể loại + Tài Khoản + Chọn nhanh + Nghe các bài hát để tạo danh sách chọn nhanh của bạn + + Các albums mới phát hành + + + Hôm nay + Hôm qua + Tuần này + Tuần trước + + + Bài hát được nghe nhiều nhất + Tác giả được nghe nhiều nhất + Album được nghe nhiều nhất + + + Tìm kiếm + Tìm kiếm YouTube Music… + Tìm kiếm thư viện… + Tất cả + Bài hát + Videos + Albums + Tác giả + Danh sách phát + Danh sách phát cộng đồng + Danh sách phát tiêu biểu + Không tìm thấy kết quả + + + Từ thư viện của bạn + + + Bài hát được yêu thích + Bài hát được tải về + Danh sách phát trống + + + Thử lại + Đài + Trộn + + + Chi tiết + Tuỳ chỉnh + Bắt đầu nghe đài + Chạy + Chạy bài tiếp theo + Thêm vào hàng chờ + Thêm vào thư viện + Xoá khỏi thư viện + Tải xuống + Đang tải xuống + Xoá tải + Thêm danh sách phát + Thêm vào danh sách phát + Xem tác giả + Xem album + Làm mới + Chia sẻ + Xoá + Xoá khỏi lịch sử + Tìm kiếm trực tuyến + Đồng bộ + + + Ngày thêm vào + Tên + Tác giả + Năm + Số lượng bài hát + Độ dài + Thời gian phát + Tuỳ chỉnh bộ lọc + + + Media id + MIME type + Codecs + Bitrate + Sample rate + Loudness + Volume + File size + Unknown + Copied to clipboard + + Sửa lời bài hát + Tìm kiếm lời bài hát + + Sửa bài hát + Tiêu đề bài hát + Tác giả bài hát + Tiêu đề không được để trống. + Tác giả không được để trống. + Lưu lại + + Lựa chọn danh sách phát + Tuỳ chọn danh sách phát + Tạo danh sách phát + Tên danh sách phát + Tên danh sách phát không được để trống. + + Tuỳ chỉnh tác giả + Tên tác giả + Tên tác giả không được để trống. + + + + Bài hát %d + Các bài hát %d + + + Tác giả %d + Các tác giả %d + + + %d album + %d albums + + + Danh sách phát %d + Các danh sách phát %d + + + %d tuần + %d tuần + + + %d tháng + %d tháng + + + %d năm + %d năm + + + + Danh sách phát đã được thêm vào + Đã xoá \"%s\" từ danh sách phát + Danh sách phát được đồng bộ + Làm lại + + + Không tìm thấy lời bài hát + Hẹn giờ + Kết thúc bài hát + + 1 phút + %d phút + + Không có stream + Không có kết nối mạng + Hết giờ + Lỗi không được xác định + + + Thích + Bỏ thích + + + Tất cả bài hát + Những bài hát được tìm kiếm + + + Music Player + + + Cài đặt + Hiện thị + Bật chủ đề theo màu sắc + Chủ đề tối + Bật + Tắt + Theo hệ thống + Tối hoàn toàn + Tab mặc định + Tuỳ chỉnh tab tuỳ biền + Vị trí lời bài hát + Trái + Canh giữa + Phải + + Nội dung + Đăng nhập + Ngôn ngữ nội dung mặc định + Quốc gia nội dung mặc định + Hệ thống mặc định + Bật proxy + Loại Proxy + Liên kết Proxy + Khởi động lại để áp dụng + + Player and audio + Chất lượng âm thanh + Tự động + Cao + Thấp + Hàng đợi liên tục + bỏ qua đoạn im lặng + Chuẩn hoá âm thanh + Bộ chỉnh âm + + Lưu trữ + Cache + Cache hình ảnh + Cache bài hát + Tối đa dung lượng cache + Không giới hạn + Xoá tất cả đã tải xuống + Dung lượng cache hình ảnh tối đa + Xoá hết cache hình ảnh + Dung lượng cache bài hát tối đa + Xoá hết cache nhạc + Đã sử dụng %s + + Riêng tư + Dừng lịch sử nghe + Xoá hết lịch sử nghe + Bạn có chắc muốn xoá hết lịch sử nghe? + Tạm dừng lịch sử tìm kiếm + Xoá hết lịch sử tìm kiếm + Bạn có chắc muốn xoá hết lịch sử tìm kiếm? + Kích hoạt nhà cung cấp lời bài hát KuGou + + Sao lưu và khôi phục + Sao lưu + Khôi phục + Đã thêm danh sách phát + Sao lưu thành công + Không thể sao lưu + Khôi phục thất bại + + Tác giả + Phiên bản ứng dụng + From f1fba82855bca5c56091296aca5fb53db9223de3 Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Sat, 29 Jul 2023 13:42:30 +0800 Subject: [PATCH 64/84] Remove unnecessary pager scroll animation --- .../main/java/com/zionhuang/music/ui/player/MiniPlayer.kt | 7 ++++++- .../main/java/com/zionhuang/music/ui/player/Thumbnail.kt | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/zionhuang/music/ui/player/MiniPlayer.kt b/app/src/main/java/com/zionhuang/music/ui/player/MiniPlayer.kt index 69e984cbf..badff73af 100644 --- a/app/src/main/java/com/zionhuang/music/ui/player/MiniPlayer.kt +++ b/app/src/main/java/com/zionhuang/music/ui/player/MiniPlayer.kt @@ -53,6 +53,7 @@ import com.zionhuang.music.models.MediaMetadata import com.zionhuang.music.ui.utils.HorizontalPager import com.zionhuang.music.ui.utils.SnapLayoutInfoProvider import kotlinx.coroutines.flow.drop +import kotlin.math.abs @OptIn(ExperimentalFoundationApi::class) @Composable @@ -82,7 +83,11 @@ fun MiniPlayer( LaunchedEffect(pagerState, currentWindowIndex) { if (windows.isNotEmpty()) { try { - pagerState.animateScrollToPage(currentWindowIndex) + if (abs(pagerState.currentPage - currentWindowIndex) <= 1) { + pagerState.animateScrollToPage(currentWindowIndex) + } else { + pagerState.scrollToPage(currentWindowIndex) + } } catch (_: Exception) { } } diff --git a/app/src/main/java/com/zionhuang/music/ui/player/Thumbnail.kt b/app/src/main/java/com/zionhuang/music/ui/player/Thumbnail.kt index d99245cf9..8950e644a 100644 --- a/app/src/main/java/com/zionhuang/music/ui/player/Thumbnail.kt +++ b/app/src/main/java/com/zionhuang/music/ui/player/Thumbnail.kt @@ -26,6 +26,7 @@ import com.zionhuang.music.ui.utils.HorizontalPager import com.zionhuang.music.ui.utils.SnapLayoutInfoProvider import com.zionhuang.music.utils.rememberPreference import kotlinx.coroutines.flow.drop +import kotlin.math.abs @OptIn(ExperimentalFoundationApi::class) @Composable @@ -56,7 +57,11 @@ fun Thumbnail( LaunchedEffect(pagerState, currentWindowIndex) { if (windows.isNotEmpty()) { try { - pagerState.animateScrollToPage(currentWindowIndex) + if (abs(pagerState.currentPage - currentWindowIndex) <= 1) { + pagerState.animateScrollToPage(currentWindowIndex) + } else { + pagerState.scrollToPage(currentWindowIndex) + } } catch (_: Exception) { } } From 93ed633c6065eff57473b530f2a545be28c4247e Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Sat, 29 Jul 2023 13:42:44 +0800 Subject: [PATCH 65/84] Use grid layout in account screen --- .../music/ui/screens/AccountScreen.kt | 88 +++++++++---------- 1 file changed, 40 insertions(+), 48 deletions(-) diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/AccountScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/AccountScreen.kt index ff4efb239..ad104f7f6 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/AccountScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/AccountScreen.kt @@ -1,10 +1,11 @@ package com.zionhuang.music.ui.screens -import androidx.compose.foundation.clickable +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.asPaddingValues -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -18,19 +19,21 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.zionhuang.music.LocalPlayerAwareWindowInsets import com.zionhuang.music.LocalPlayerConnection import com.zionhuang.music.R +import com.zionhuang.music.constants.GridThumbnailHeight import com.zionhuang.music.ui.component.LocalMenuState -import com.zionhuang.music.ui.component.YouTubeListItem -import com.zionhuang.music.ui.component.shimmer.ListItemPlaceHolder +import com.zionhuang.music.ui.component.YouTubeGridItem +import com.zionhuang.music.ui.component.shimmer.GridItemPlaceHolder import com.zionhuang.music.ui.component.shimmer.ShimmerHost import com.zionhuang.music.ui.menu.YouTubePlaylistMenu import com.zionhuang.music.viewmodels.AccountViewModel -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @Composable fun AccountScreen( navController: NavController, @@ -40,55 +43,44 @@ fun AccountScreen( val menuState = LocalMenuState.current val playerConnection = LocalPlayerConnection.current ?: return - val lazyListState = rememberLazyListState() val coroutineScope = rememberCoroutineScope() val playlists by viewModel.playlists.collectAsState() - LazyColumn( - state = lazyListState, + LazyVerticalGrid( + columns = GridCells.Adaptive(minSize = GridThumbnailHeight + 24.dp), contentPadding = LocalPlayerAwareWindowInsets.current.asPaddingValues() ) { - playlists.let { playlists -> - if (playlists == null) { - item(key = "loading") { - ShimmerHost { - repeat(8) { - ListItemPlaceHolder() - } - } - } - } else { - items( - items = playlists, - key = { it.id } - ) { item -> - YouTubeListItem( - item = item, - trailingContent = { - IconButton( - onClick = { - menuState.show { - YouTubePlaylistMenu( - playlist = item, - playerConnection = playerConnection, - coroutineScope = coroutineScope, - onDismiss = menuState::dismiss - ) - } - } - ) { - Icon( - painter = painterResource(R.drawable.more_vert), - contentDescription = null - ) - } + items( + items = playlists.orEmpty(), + key = { it.id } + ) { item -> + YouTubeGridItem( + item = item, + fillMaxWidth = true, + modifier = Modifier + .combinedClickable( + onClick = { + navController.navigate("online_playlist/${item.id}") }, - modifier = Modifier - .clickable { - navController.navigate("online_playlist/${item.id}") + onLongClick = { + menuState.show { + YouTubePlaylistMenu( + playlist = item, + playerConnection = playerConnection, + coroutineScope = coroutineScope, + onDismiss = menuState::dismiss + ) } + } ) + ) + } + + if (playlists == null) { + items(8) { + ShimmerHost { + GridItemPlaceHolder(fillMaxWidth = true) } } } @@ -106,4 +98,4 @@ fun AccountScreen( }, scrollBehavior = scrollBehavior ) -} \ No newline at end of file +} From 0e358d5f0c8a6c56a3d8a42baa09543bd23b7a70 Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Sat, 29 Jul 2023 13:43:20 +0800 Subject: [PATCH 66/84] Support special characters in search query --- app/src/main/java/com/zionhuang/music/MainActivity.kt | 10 +++++++--- .../music/viewmodels/OnlineSearchViewModel.kt | 3 ++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/zionhuang/music/MainActivity.kt b/app/src/main/java/com/zionhuang/music/MainActivity.kt index db7d04134..0d4a6b275 100644 --- a/app/src/main/java/com/zionhuang/music/MainActivity.kt +++ b/app/src/main/java/com/zionhuang/music/MainActivity.kt @@ -105,6 +105,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import java.net.URLDecoder +import java.net.URLEncoder import javax.inject.Inject @AndroidEntryPoint @@ -241,7 +243,7 @@ class MainActivity : ComponentActivity() { val onSearch: (String) -> Unit = { if (it.isNotEmpty()) { onActiveChange(false) - navController.navigate("search/$it") + navController.navigate("search/${URLEncoder.encode(it, "UTF-8")}") if (dataStore[PauseSearchHistoryKey] != true) { database.query { insert(SearchHistory(query = it)) @@ -288,7 +290,9 @@ class MainActivity : ComponentActivity() { LaunchedEffect(navBackStackEntry) { if (navBackStackEntry?.destination?.route?.startsWith("search/") == true) { - val searchQuery = navBackStackEntry?.arguments?.getString("query")!! + val searchQuery = withContext(Dispatchers.IO) { + URLDecoder.decode(navBackStackEntry?.arguments?.getString("query")!!, "UTF-8") + } onQueryChange(TextFieldValue(searchQuery, TextRange(searchQuery.length))) } else if (navigationItems.fastAny { it.route == navBackStackEntry?.destination?.route }) { onQueryChange(TextFieldValue()) @@ -675,7 +679,7 @@ class MainActivity : ComponentActivity() { onQueryChange = onQueryChange, navController = navController, onSearch = { - navController.navigate("search/$it") + navController.navigate("search/${URLEncoder.encode(it, "UTF-8")}") if (dataStore[PauseSearchHistoryKey] != true) { database.query { insert(SearchHistory(query = it)) diff --git a/app/src/main/java/com/zionhuang/music/viewmodels/OnlineSearchViewModel.kt b/app/src/main/java/com/zionhuang/music/viewmodels/OnlineSearchViewModel.kt index b41d6b701..0de0e189c 100644 --- a/app/src/main/java/com/zionhuang/music/viewmodels/OnlineSearchViewModel.kt +++ b/app/src/main/java/com/zionhuang/music/viewmodels/OnlineSearchViewModel.kt @@ -13,13 +13,14 @@ import com.zionhuang.music.models.ItemsPage import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch +import java.net.URLDecoder import javax.inject.Inject @HiltViewModel class OnlineSearchViewModel @Inject constructor( savedStateHandle: SavedStateHandle, ) : ViewModel() { - val query = savedStateHandle.get("query")!! + val query = URLDecoder.decode(savedStateHandle.get("query")!!, "UTF-8")!! val filter = MutableStateFlow(null) var summaryPage by mutableStateOf(null) val viewStateMap = mutableStateMapOf() From 2bc8a401ef7d049f709f1c1a8212c8c1c0541c01 Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Sat, 29 Jul 2023 15:58:09 +0800 Subject: [PATCH 67/84] Only use login cookie in needed requests --- .../java/com/zionhuang/innertube/InnerTube.kt | 31 ++++++++++--------- .../java/com/zionhuang/innertube/YouTube.kt | 18 +++++++++-- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/innertube/src/main/java/com/zionhuang/innertube/InnerTube.kt b/innertube/src/main/java/com/zionhuang/innertube/InnerTube.kt index 6d9333101..f97b3dc88 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/InnerTube.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/InnerTube.kt @@ -76,7 +76,7 @@ class InnerTube { } } - private fun HttpRequestBuilder.configYTClient(client: YouTubeClient) { + private fun HttpRequestBuilder.ytClient(client: YouTubeClient, setLogin: Boolean = false) { contentType(ContentType.Application.Json) headers { append("X-Goog-Api-Format-Version", "1") @@ -86,12 +86,14 @@ class InnerTube { if (client.referer != null) { append("Referer", client.referer) } - cookie?.let { cookie -> - append("cookie", cookie) - if ("SAPISID" !in cookieMap) return@let - val currentTime = System.currentTimeMillis() / 1000 - val sapisidHash = sha1("$currentTime ${cookieMap["SAPISID"]} https://music.youtube.com") - append("Authorization", "SAPISIDHASH ${currentTime}_${sapisidHash}") + if (setLogin) { + cookie?.let { cookie -> + append("cookie", cookie) + if ("SAPISID" !in cookieMap) return@let + val currentTime = System.currentTimeMillis() / 1000 + val sapisidHash = sha1("$currentTime ${cookieMap["SAPISID"]} https://music.youtube.com") + append("Authorization", "SAPISIDHASH ${currentTime}_${sapisidHash}") + } } } userAgent(client.userAgent) @@ -105,7 +107,7 @@ class InnerTube { params: String? = null, continuation: String? = null, ) = httpClient.post("search") { - configYTClient(client) + ytClient(client) setBody( SearchBody( context = client.toContext(locale, visitorData), @@ -122,7 +124,7 @@ class InnerTube { videoId: String, playlistId: String?, ) = httpClient.post("player") { - configYTClient(client) + ytClient(client, setLogin = true) setBody( PlayerBody( context = client.toContext(locale, visitorData).let { @@ -150,8 +152,9 @@ class InnerTube { browseId: String? = null, params: String? = null, continuation: String? = null, + setLogin: Boolean = false, ) = httpClient.post("browse") { - configYTClient(client) + ytClient(client, setLogin) setBody( BrowseBody( context = client.toContext(locale, visitorData), @@ -175,7 +178,7 @@ class InnerTube { params: String?, continuation: String? = null, ) = httpClient.post("next") { - configYTClient(client) + ytClient(client, setLogin = true) setBody( NextBody( context = client.toContext(locale, visitorData), @@ -193,7 +196,7 @@ class InnerTube { client: YouTubeClient, input: String, ) = httpClient.post("music/get_search_suggestions") { - configYTClient(client) + ytClient(client) setBody( GetSearchSuggestionsBody( context = client.toContext(locale, visitorData), @@ -207,7 +210,7 @@ class InnerTube { videoIds: List?, playlistId: String?, ) = httpClient.post("music/get_queue") { - configYTClient(client) + ytClient(client) setBody( GetQueueBody( context = client.toContext(locale, visitorData), @@ -236,7 +239,7 @@ class InnerTube { suspend fun getSwJsData() = httpClient.get("https://music.youtube.com/sw.js_data") suspend fun accountMenu(client: YouTubeClient) = httpClient.post("account/account_menu") { - configYTClient(client) + ytClient(client) setBody(AccountMenuBody(client.toContext(locale, visitorData))) } } diff --git a/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt b/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt index c16f5eb66..0376212e2 100644 --- a/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt +++ b/innertube/src/main/java/com/zionhuang/innertube/YouTube.kt @@ -239,7 +239,11 @@ object YouTube { } suspend fun playlist(playlistId: String): Result = runCatching { - val response = innerTube.browse(WEB_REMIX, "VL$playlistId").body() + val response = innerTube.browse( + client = WEB_REMIX, + browseId = "VL$playlistId", + setLogin = true + ).body() val header = response.header?.musicDetailHeaderRenderer ?: response.header?.musicEditablePlaylistDetailHeaderRenderer?.header?.musicDetailHeaderRenderer!! PlaylistPage( playlist = PlaylistItem( @@ -273,7 +277,11 @@ object YouTube { } suspend fun playlistContinuation(continuation: String) = runCatching { - val response = innerTube.browse(WEB_REMIX, continuation = continuation).body() + val response = innerTube.browse( + client = WEB_REMIX, + continuation = continuation, + setLogin = true + ).body() PlaylistContinuationPage( songs = response.continuationContents?.musicPlaylistShelfContinuation?.contents?.mapNotNull { PlaylistPage.fromMusicResponsiveListItemRenderer(it.musicResponsiveListItemRenderer) @@ -344,7 +352,11 @@ object YouTube { } suspend fun likedPlaylists(): Result> = runCatching { - val response = innerTube.browse(WEB_REMIX, browseId = "FEmusic_liked_playlists").body() + val response = innerTube.browse( + client = WEB_REMIX, + browseId = "FEmusic_liked_playlists", + setLogin = true + ).body() response.contents?.singleColumnBrowseResultsRenderer?.tabs?.firstOrNull()?.tabRenderer?.content?.sectionListRenderer?.contents?.firstOrNull()?.gridRenderer?.items!! .drop(1) // the first item is "create new playlist" .mapNotNull(GridRenderer.Item::musicTwoRowItemRenderer) From e159d194b85fe83c5728f6abb88b7e4476f16433 Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Sat, 29 Jul 2023 19:03:21 +0800 Subject: [PATCH 68/84] Tweak code --- app/src/debug/res/xml-v25/shortcuts.xml | 21 ++++--------------- .../java/com/zionhuang/music/MainActivity.kt | 21 ++++++++++++------- app/src/main/res/xml-v25/shortcuts.xml | 21 ++++--------------- 3 files changed, 22 insertions(+), 41 deletions(-) diff --git a/app/src/debug/res/xml-v25/shortcuts.xml b/app/src/debug/res/xml-v25/shortcuts.xml index cfe2ee236..a3ea2b929 100644 --- a/app/src/debug/res/xml-v25/shortcuts.xml +++ b/app/src/debug/res/xml-v25/shortcuts.xml @@ -1,56 +1,43 @@ - - - + android:action="com.zionhuang.music.action.SEARCH" /> - - - + android:action="com.zionhuang.music.action.SONGS" /> - - - + android:action="com.zionhuang.music.action.ALBUMS" /> - - - + android:action="com.zionhuang.music.action.PLAYLISTS" /> - \ No newline at end of file diff --git a/app/src/main/java/com/zionhuang/music/MainActivity.kt b/app/src/main/java/com/zionhuang/music/MainActivity.kt index b553e3d0c..4657854d1 100644 --- a/app/src/main/java/com/zionhuang/music/MainActivity.kt +++ b/app/src/main/java/com/zionhuang/music/MainActivity.kt @@ -220,17 +220,17 @@ class MainActivity : ComponentActivity() { val navigationItems = remember { listOf(Screens.Home, Screens.Songs, Screens.Artists, Screens.Albums, Screens.Playlists) } + val defaultOpenTab = remember { + dataStore[DefaultOpenTabKey].toEnum(defaultValue = NavigationTab.HOME) + } val tabOpenedFromShortcut = remember { when (intent?.action) { - "zionhuang.music.action.SONGS" -> NavigationTab.SONG - "zionhuang.music.action.ALBUMS" -> NavigationTab.ALBUM - "zionhuang.music.action.PLAYLISTS" -> NavigationTab.PLAYLIST + ACTION_SONGS -> NavigationTab.SONG + ACTION_ALBUMS -> NavigationTab.ALBUM + ACTION_PLAYLISTS -> NavigationTab.PLAYLIST else -> null } } - val defaultOpenTab = remember { - dataStore[DefaultOpenTabKey].toEnum(defaultValue = NavigationTab.HOME) - } val (query, onQueryChange) = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue()) @@ -264,7 +264,7 @@ class MainActivity : ComponentActivity() { } var openSearchImmediately: Boolean by remember { - mutableStateOf(intent?.action == "zionhuang.music.action.SEARCH") + mutableStateOf(intent?.action == ACTION_SEARCH) } val shouldShowSearchBar = remember(active, navBackStackEntry) { @@ -817,6 +817,13 @@ class MainActivity : ComponentActivity() { window.navigationBarColor = (if (isDark) Color.Transparent else Color.Black.copy(alpha = 0.2f)).toArgb() } } + + companion object { + const val ACTION_SEARCH = "com.zionhuang.music.action.SEARCH" + const val ACTION_SONGS = "com.zionhuang.music.action.SONGS" + const val ACTION_ALBUMS = "com.zionhuang.music.action.ALBUMS" + const val ACTION_PLAYLISTS = "com.zionhuang.music.action.PLAYLISTS" + } } val LocalDatabase = staticCompositionLocalOf { error("No database provided") } diff --git a/app/src/main/res/xml-v25/shortcuts.xml b/app/src/main/res/xml-v25/shortcuts.xml index 2fc445482..4d027a670 100644 --- a/app/src/main/res/xml-v25/shortcuts.xml +++ b/app/src/main/res/xml-v25/shortcuts.xml @@ -1,56 +1,43 @@ - - - + android:action="com.zionhuang.music.action.SEARCH" /> - - - + android:action="com.zionhuang.music.action.SONGS" /> - - - + android:action="com.zionhuang.music.action.ALBUMS" /> - - - + android:action="com.zionhuang.music.action.PLAYLISTS" /> - \ No newline at end of file From 412c883b58a7fe2729bada338e7d1a57d7507ad4 Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Sat, 29 Jul 2023 20:57:26 +0800 Subject: [PATCH 69/84] Add filters in library artists screen --- .../music/constants/PreferenceKeys.kt | 8 +- .../com/zionhuang/music/db/DatabaseDao.kt | 50 +++++++++++- .../screens/library/LibraryArtistsScreen.kt | 77 +++++++++++++++++-- .../music/viewmodels/LibraryViewModels.kt | 13 +++- app/src/main/res/values-DE/strings.xml | 1 + app/src/main/res/values-be/strings.xml | 1 + app/src/main/res/values-bn-rIN/strings.xml | 23 +----- app/src/main/res/values-cs/strings.xml | 1 + app/src/main/res/values-es-rUS/strings.xml | 22 ++++-- app/src/main/res/values-es/strings.xml | 18 ++++- app/src/main/res/values-fa-rIR/strings.xml | 1 + app/src/main/res/values-fi-rFI/strings.xml | 1 + app/src/main/res/values-fr-rFR/strings.xml | 1 + app/src/main/res/values-hu/strings.xml | 1 + app/src/main/res/values-id/strings.xml | 12 ++- app/src/main/res/values-it/strings.xml | 18 ++++- app/src/main/res/values-ja-rJP/strings.xml | 12 ++- app/src/main/res/values-ko-rKR/strings.xml | 12 ++- app/src/main/res/values-ml-rIN/strings.xml | 1 + app/src/main/res/values-nl/strings.xml | 1 + app/src/main/res/values-or-rIN/strings.xml | 1 + app/src/main/res/values-pa/strings.xml | 1 + app/src/main/res/values-pl/strings.xml | 1 + app/src/main/res/values-pt-rBR/strings.xml | 22 ++++-- app/src/main/res/values-ru-rRU/strings.xml | 1 + app/src/main/res/values-sv-rSE/strings.xml | 1 + app/src/main/res/values-tr/strings.xml | 3 +- app/src/main/res/values-uk-rUA/strings.xml | 1 + app/src/main/res/values-vn/strings.xml | 4 +- app/src/main/res/values-zh-rCN/strings.xml | 8 +- app/src/main/res/values-zh-rTW/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 32 files changed, 235 insertions(+), 84 deletions(-) diff --git a/app/src/main/java/com/zionhuang/music/constants/PreferenceKeys.kt b/app/src/main/java/com/zionhuang/music/constants/PreferenceKeys.kt index dc9f96d5b..4c1053692 100644 --- a/app/src/main/java/com/zionhuang/music/constants/PreferenceKeys.kt +++ b/app/src/main/java/com/zionhuang/music/constants/PreferenceKeys.kt @@ -49,6 +49,8 @@ val PlaylistSortDescendingKey = booleanPreferencesKey("playlistSortDescending") val ArtistSongSortTypeKey = stringPreferencesKey("artistSongSortType") val ArtistSongSortDescendingKey = booleanPreferencesKey("artistSongSortDescending") +val ArtistViewTypeKey = stringPreferencesKey("artistViewType") + val PlaylistEditLockKey = booleanPreferencesKey("playlistEditLock") enum class SongSortType { @@ -79,11 +81,13 @@ enum class PlaylistSortType { CREATE_DATE, NAME, SONG_COUNT } +enum class ArtistViewType { + ALL, BOOKMARKED +} + val ShowLyricsKey = booleanPreferencesKey("showLyrics") val LyricsTextPositionKey = stringPreferencesKey("lyricsTextPosition") -val NavTabConfigKey = stringPreferencesKey("navTabConfig") - val PlayerVolumeKey = floatPreferencesKey("playerVolume") val RepeatModeKey = intPreferencesKey("repeatMode") diff --git a/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt b/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt index 9bf38942c..7c86c4c31 100644 --- a/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt +++ b/app/src/main/java/com/zionhuang/music/db/DatabaseDao.kt @@ -216,15 +216,15 @@ interface DatabaseDao { fun lyrics(id: String?): Flow @Transaction - @Query("SELECT *, (SELECT COUNT(1) FROM song_artist_map JOIN song ON song_artist_map.songId = song.id WHERE artistId = artist.id AND song.inLibrary IS NOT NULL) AS songCount FROM artist WHERE bookmarkedAt IS NOT NULL ORDER BY bookmarkedAt") + @Query("SELECT *, (SELECT COUNT(1) FROM song_artist_map JOIN song ON song_artist_map.songId = song.id WHERE artistId = artist.id AND song.inLibrary IS NOT NULL) AS songCount FROM artist WHERE songCount > 0 ORDER BY bookmarkedAt") fun artistsByCreateDateAsc(): Flow> @Transaction - @Query("SELECT *, (SELECT COUNT(1) FROM song_artist_map JOIN song ON song_artist_map.songId = song.id WHERE artistId = artist.id AND song.inLibrary IS NOT NULL) AS songCount FROM artist WHERE bookmarkedAt IS NOT NULL ORDER BY name") + @Query("SELECT *, (SELECT COUNT(1) FROM song_artist_map JOIN song ON song_artist_map.songId = song.id WHERE artistId = artist.id AND song.inLibrary IS NOT NULL) AS songCount FROM artist WHERE songCount > 0 ORDER BY name") fun artistsByNameAsc(): Flow> @Transaction - @Query("SELECT *, (SELECT COUNT(1) FROM song_artist_map JOIN song ON song_artist_map.songId = song.id WHERE artistId = artist.id AND song.inLibrary IS NOT NULL) AS songCount FROM artist WHERE bookmarkedAt IS NOT NULL ORDER BY songCount") + @Query("SELECT *, (SELECT COUNT(1) FROM song_artist_map JOIN song ON song_artist_map.songId = song.id WHERE artistId = artist.id AND song.inLibrary IS NOT NULL) AS songCount FROM artist WHERE songCount > 0 ORDER BY songCount") fun artistsBySongCountAsc(): Flow> @Transaction @@ -244,11 +244,45 @@ interface DatabaseDao { GROUP BY artistId ORDER BY totalPlayTime) ON artist.id = artistId - WHERE bookmarkedAt IS NOT NULL + WHERE songCount > 0 """ ) fun artistsByPlayTimeAsc(): Flow> + @Transaction + @Query("SELECT *, (SELECT COUNT(1) FROM song_artist_map JOIN song ON song_artist_map.songId = song.id WHERE artistId = artist.id AND song.inLibrary IS NOT NULL) AS songCount FROM artist WHERE bookmarkedAt IS NOT NULL ORDER BY bookmarkedAt") + fun artistsBookmarkedByCreateDateAsc(): Flow> + + @Transaction + @Query("SELECT *, (SELECT COUNT(1) FROM song_artist_map JOIN song ON song_artist_map.songId = song.id WHERE artistId = artist.id AND song.inLibrary IS NOT NULL) AS songCount FROM artist WHERE bookmarkedAt IS NOT NULL ORDER BY name") + fun artistsBookmarkedByNameAsc(): Flow> + + @Transaction + @Query("SELECT *, (SELECT COUNT(1) FROM song_artist_map JOIN song ON song_artist_map.songId = song.id WHERE artistId = artist.id AND song.inLibrary IS NOT NULL) AS songCount FROM artist WHERE bookmarkedAt IS NOT NULL ORDER BY songCount") + fun artistsBookmarkedBySongCountAsc(): Flow> + + @Transaction + @Query( + """ + SELECT artist.*, + (SELECT COUNT(1) + FROM song_artist_map + JOIN song ON song_artist_map.songId = song.id + WHERE artistId = artist.id + AND song.inLibrary IS NOT NULL) AS songCount + FROM artist + JOIN(SELECT artistId, SUM(totalPlayTime) AS totalPlayTime + FROM song_artist_map + JOIN song + ON song_artist_map.songId = song.id + GROUP BY artistId + ORDER BY totalPlayTime) + ON artist.id = artistId + WHERE bookmarkedAt IS NOT NULL + """ + ) + fun artistsBookmarkedByPlayTimeAsc(): Flow> + fun artists(sortType: ArtistSortType, descending: Boolean) = when (sortType) { ArtistSortType.CREATE_DATE -> artistsByCreateDateAsc() @@ -257,6 +291,14 @@ interface DatabaseDao { ArtistSortType.PLAY_TIME -> artistsByPlayTimeAsc() }.map { it.reversed(descending) } + fun artistsBookmarked(sortType: ArtistSortType, descending: Boolean) = + when (sortType) { + ArtistSortType.CREATE_DATE -> artistsBookmarkedByCreateDateAsc() + ArtistSortType.NAME -> artistsBookmarkedByNameAsc() + ArtistSortType.SONG_COUNT -> artistsBookmarkedBySongCountAsc() + ArtistSortType.PLAY_TIME -> artistsBookmarkedByPlayTimeAsc() + }.map { it.reversed(descending) } + @Query("SELECT * FROM artist WHERE id = :id") fun artist(id: String): Flow diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/library/LibraryArtistsScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/library/LibraryArtistsScreen.kt index 9615c7911..cfdb49da7 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/library/LibraryArtistsScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/library/LibraryArtistsScreen.kt @@ -2,34 +2,60 @@ package com.zionhuang.music.ui.screens.library import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable +import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.rememberScrollState +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilterChip +import androidx.compose.material3.FilterChipDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.pluralStringResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController +import com.zionhuang.music.LocalDatabase import com.zionhuang.music.LocalPlayerAwareWindowInsets import com.zionhuang.music.R -import com.zionhuang.music.constants.* +import com.zionhuang.music.constants.ArtistSortDescendingKey +import com.zionhuang.music.constants.ArtistSortType +import com.zionhuang.music.constants.ArtistSortTypeKey +import com.zionhuang.music.constants.ArtistViewType +import com.zionhuang.music.constants.ArtistViewTypeKey +import com.zionhuang.music.constants.CONTENT_TYPE_ARTIST import com.zionhuang.music.ui.component.ArtistListItem import com.zionhuang.music.ui.component.SortHeader import com.zionhuang.music.utils.rememberEnumPreference import com.zionhuang.music.utils.rememberPreference import com.zionhuang.music.viewmodels.LibraryArtistsViewModel +import java.time.LocalDateTime -@OptIn(ExperimentalFoundationApi::class) +@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) @Composable fun LibraryArtistsScreen( navController: NavController, viewModel: LibraryArtistsViewModel = hiltViewModel(), ) { + val database = LocalDatabase.current + var viewType by rememberEnumPreference(ArtistViewTypeKey, ArtistViewType.ALL) val (sortType, onSortTypeChange) = rememberEnumPreference(ArtistSortTypeKey, ArtistSortType.CREATE_DATE) val (sortDescending, onSortDescendingChange) = rememberPreference(ArtistSortDescendingKey, true) @@ -41,10 +67,30 @@ fun LibraryArtistsScreen( LazyColumn( contentPadding = LocalPlayerAwareWindowInsets.current.asPaddingValues() ) { - item( - key = "header", - contentType = CONTENT_TYPE_HEADER - ) { + item(key = "viewType") { + Row( + modifier = Modifier + .fillMaxWidth() + .horizontalScroll(rememberScrollState()) + ) { + Spacer(Modifier.width(16.dp)) + + listOf( + ArtistViewType.ALL to stringResource(R.string.filter_all), + ArtistViewType.BOOKMARKED to stringResource(R.string.filter_bookmarked) + ).forEach { + FilterChip( + label = { Text(it.second) }, + selected = viewType == it.first, + colors = FilterChipDefaults.filterChipColors(containerColor = MaterialTheme.colorScheme.background), + onClick = { viewType = it.first } + ) + Spacer(Modifier.width(8.dp)) + } + } + } + + item(key = "header") { SortHeader( sortType = sortType, sortDescending = sortDescending, @@ -69,6 +115,25 @@ fun LibraryArtistsScreen( ) { artist -> ArtistListItem( artist = artist, + trailingContent = { + IconButton( + onClick = { + database.transaction { + update( + artist.artist.copy( + bookmarkedAt = if (artist.artist.bookmarkedAt != null) null else LocalDateTime.now() + ) + ) + } + } + ) { + Icon( + painter = painterResource(if (artist.artist.bookmarkedAt != null) R.drawable.bookmark_filled else R.drawable.bookmark), + tint = if (artist.artist.bookmarkedAt != null) MaterialTheme.colorScheme.primary else LocalContentColor.current, + contentDescription = null + ) + } + }, modifier = Modifier .fillMaxWidth() .clickable { diff --git a/app/src/main/java/com/zionhuang/music/viewmodels/LibraryViewModels.kt b/app/src/main/java/com/zionhuang/music/viewmodels/LibraryViewModels.kt index c6784e8e1..b5ff00181 100644 --- a/app/src/main/java/com/zionhuang/music/viewmodels/LibraryViewModels.kt +++ b/app/src/main/java/com/zionhuang/music/viewmodels/LibraryViewModels.kt @@ -44,11 +44,18 @@ class LibraryArtistsViewModel @Inject constructor( ) : ViewModel() { val allArtists = context.dataStore.data .map { - it[ArtistSortTypeKey].toEnum(ArtistSortType.CREATE_DATE) to (it[ArtistSortDescendingKey] ?: true) + Triple( + it[ArtistViewTypeKey].toEnum(ArtistViewType.ALL), + it[ArtistSortTypeKey].toEnum(ArtistSortType.CREATE_DATE), + it[ArtistSortDescendingKey] ?: true + ) } .distinctUntilChanged() - .flatMapLatest { (sortType, descending) -> - database.artists(sortType, descending) + .flatMapLatest { (viewType, sortType, descending) -> + when (viewType) { + ArtistViewType.ALL -> database.artists(sortType, descending) + ArtistViewType.BOOKMARKED -> database.artistsBookmarked(sortType, descending) + } } .stateIn(viewModelScope, SharingStarted.Lazily, emptyList()) diff --git a/app/src/main/res/values-DE/strings.xml b/app/src/main/res/values-DE/strings.xml index 6ab52387a..dc22824cf 100644 --- a/app/src/main/res/values-DE/strings.xml +++ b/app/src/main/res/values-DE/strings.xml @@ -44,6 +44,7 @@ Wiedergabelisten Community-Wiedergabelisten Ausgewählte Wiedergabelisten + Bookmarked Keine Ergebnisse gefunden diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index 0d81a65ad..62f0f17aa 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -46,6 +46,7 @@ Плэй-лісты Плэй-лісты aд cупольнасці Вартыя ўвагі плэй-лісты + Bookmarked Вынікі не знойдзены diff --git a/app/src/main/res/values-bn-rIN/strings.xml b/app/src/main/res/values-bn-rIN/strings.xml index d3918e6a8..9781ab940 100644 --- a/app/src/main/res/values-bn-rIN/strings.xml +++ b/app/src/main/res/values-bn-rIN/strings.xml @@ -1,4 +1,4 @@ - + হোম গান @@ -9,8 +9,6 @@ %d নির্বাচিত - %d নির্বাচিত - %d নির্বাচিত %d নির্বাচিত @@ -46,6 +44,7 @@ প্লেলিস্ট প্লেলিস্ট ক্রম বিশিষ্ট প্লেলিস্ট + Bookmarked কিছু পাওয়া যায়নি @@ -129,44 +128,30 @@ %d গান - %d গান - %d গান %d গান %d শিল্পী - %d শিল্পী - %d শিল্পী %d শিল্পী %d অ্যালবাম - %d অ্যালবাম - %d অ্যালবাম %d অ্যালবাম %d প্লেলিস্ট - %d প্লেলিস্ট - %d প্লেলিস্ট %d প্লেলিস্ট %d সপ্তাহ - %d সপ্তাহ - %d সপ্তাহ %d সপ্তাহ %d মাস - %d মাস - %d মাস %d মাস %d বছর - %d বছর - %d বছর %d বছর @@ -182,8 +167,6 @@ গানের শেষ %d মিনিট - %d মিনিট - %d মিনিট %d মিনিট স্ট্রিম উপলব্ধ নয় @@ -270,4 +253,4 @@ সম্পর্কিত অ্যাপ সংস্করণ - \ No newline at end of file + diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 0442ed3ff..a8dcfbe23 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -46,6 +46,7 @@ Playlisty Komunitní playlisty Doporučené playlisty + Bookmarked No results found diff --git a/app/src/main/res/values-es-rUS/strings.xml b/app/src/main/res/values-es-rUS/strings.xml index 94fbae973..8d51c6425 100644 --- a/app/src/main/res/values-es-rUS/strings.xml +++ b/app/src/main/res/values-es-rUS/strings.xml @@ -1,4 +1,4 @@ - + Home Canciones @@ -10,6 +10,7 @@ %d seleccionada %d seleccionadas + %d seleccionadas @@ -44,6 +45,7 @@ Playlists Community playlists Featured playlists + Bookmarked No results found @@ -74,7 +76,7 @@ Import playlist Añadir a una playlist View artist - View album + View álbum Refetch Share Eliminar @@ -128,30 +130,37 @@ %d canción %d canciones + %d canciones %d artist %d artists + %d artists - %d album + %d álbum %d albums + %d albums %d playlist %d playlists + %d playlists - + %d week %d weeks + %d weeks - + %d month %d months + %d months - + %d year %d years + %d years @@ -167,6 +176,7 @@ 1 minute %d minutes + %d minutes No stream available No network connection diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 74f3e23ce..a380ac06a 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -1,4 +1,4 @@ - + Inicio Canciones @@ -10,6 +10,7 @@ %d seleccionada %d seleccionadas + %d seleccionadas @@ -44,6 +45,7 @@ Listas de reproducción Listas de la comunidad Listas destacadas + Bookmarked No se han encontrado resultados @@ -128,30 +130,37 @@ %d canción %d canciones + %d canciones %d artista %d artistas + %d artistas %d álbum %d álbumes + %d álbumes %d lista de reproducción %d listas de reproducción + %d listas de reproducción - + %d week %d weeks + %d weeks - + %d month %d months + %d months - + %d year %d years + %d years @@ -167,6 +176,7 @@ 1 minuto %d minutos + %d minutos No hay un stream disponible No hay conexión a internet diff --git a/app/src/main/res/values-fa-rIR/strings.xml b/app/src/main/res/values-fa-rIR/strings.xml index 5f61dcbdf..822189d58 100644 --- a/app/src/main/res/values-fa-rIR/strings.xml +++ b/app/src/main/res/values-fa-rIR/strings.xml @@ -44,6 +44,7 @@ لیست‌پخش‌ها لیست‌پخش‌های انجمن لیست‌پخش‌های ویژه + Bookmarked No results found diff --git a/app/src/main/res/values-fi-rFI/strings.xml b/app/src/main/res/values-fi-rFI/strings.xml index 850b0421b..1c7282eb0 100644 --- a/app/src/main/res/values-fi-rFI/strings.xml +++ b/app/src/main/res/values-fi-rFI/strings.xml @@ -44,6 +44,7 @@ Soittolistat Community playlists Featured playlists + Bookmarked No results found diff --git a/app/src/main/res/values-fr-rFR/strings.xml b/app/src/main/res/values-fr-rFR/strings.xml index e6a268f3a..b04a0410a 100644 --- a/app/src/main/res/values-fr-rFR/strings.xml +++ b/app/src/main/res/values-fr-rFR/strings.xml @@ -45,6 +45,7 @@ Listes de lecture Community playlists Featured playlists + Bookmarked No results found diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 98b4c4caf..0c81875fe 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -44,6 +44,7 @@ Listák Közösségi listák Kiemelt lejátszási listák + Bookmarked Nincs találat diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index 0ddad030f..c539cc466 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -1,4 +1,4 @@ - + Beranda Lagu @@ -43,6 +43,7 @@ Daftar putar Daftar putar komunitas Daftar putar unggulan + Bookmarked No results found @@ -136,16 +137,13 @@ %d daftar putar - - %d week + %d weeks - - %d month + %d months - - %d year + %d years diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index af7a69b6e..13c2df346 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -1,4 +1,4 @@ - + Home Brani @@ -10,6 +10,7 @@ %d selezionato %d selezionati + %d selezionati @@ -44,6 +45,7 @@ Playlist Playlist della community Playlist in rilievo + Bookmarked Nessun risultato trovato @@ -128,30 +130,37 @@ %d brano %d brani + %d brani %d artista %d artisti + %d artisti %d album %d album + %d album %d playlist %d playlist + %d playlist - + %d settimana %d settimane + %d settimane - + %d mese %d mesi + %d mesi - + %d anno %d anni + %d anni @@ -167,6 +176,7 @@ 1 minuto %d minuti + %d minuti Stream non disponibile Nessuna connessione di rete diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index 15d9d505c..69484841a 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -1,4 +1,4 @@ - + ホーム @@ -43,6 +43,7 @@ プレイリスト コミュニティのプレイリスト おすすめのプレイリスト + Bookmarked 見つかりませんでした @@ -136,16 +137,13 @@ %d プレイリスト - - %d週間 + %d週間 - - %dか月 + %dか月 - - %d年 + %d年 diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml index 389c5c058..1d945c8cc 100644 --- a/app/src/main/res/values-ko-rKR/strings.xml +++ b/app/src/main/res/values-ko-rKR/strings.xml @@ -1,4 +1,4 @@ - + Home 노래 @@ -43,6 +43,7 @@ 플레이리스트 Community playlists Featured playlists + Bookmarked No results found @@ -136,16 +137,13 @@ %d playlists - - %d week + %d weeks - - %d month + %d months - - %d year + %d years diff --git a/app/src/main/res/values-ml-rIN/strings.xml b/app/src/main/res/values-ml-rIN/strings.xml index 5e639018d..5f0e1a7a6 100644 --- a/app/src/main/res/values-ml-rIN/strings.xml +++ b/app/src/main/res/values-ml-rIN/strings.xml @@ -44,6 +44,7 @@ പ്ലേലിസ്റ്റുകൾ കമ്മ്യൂണിറ്റി പ്ലേലിസ്റ്റുകൾ തിരഞ്ഞെടുത്ത പ്ലേലിസ്റ്റുകൾ + Bookmarked No results found diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 735dc3952..1feb5aca8 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -44,6 +44,7 @@ Afspeellijsten Afspeellijsten van de community Voorgestelde afspeellijsten + Bookmarked Geen resultaten gevonden diff --git a/app/src/main/res/values-or-rIN/strings.xml b/app/src/main/res/values-or-rIN/strings.xml index 415e34cea..6f9df84aa 100644 --- a/app/src/main/res/values-or-rIN/strings.xml +++ b/app/src/main/res/values-or-rIN/strings.xml @@ -44,6 +44,7 @@ ପ୍ଲେଲିଷ୍ଟ ଗୁଡ଼ିକ ସମ୍ପ୍ରଦାୟ ପ୍ଲେଲିଷ୍ଟଗୁଡିକ ବୈଶିଷ୍ଟ୍ୟଯୁକ୍ତ ତାଲିକାଗୁଡ଼ିକ + Bookmarked No results found diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index c5a24cd3b..878798c4b 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -44,6 +44,7 @@ ਪਲੇਲਿਸਟਾਂ ਕਮਿਊਨਿਟੀ ਪਲੇਲਿਸਟਾਂ ਪ੍ਰਦਰਸ਼ਿਤ ਪਲੇਲਿਸਟਾਂ + Bookmarked ਕੋਈ ਨਤੀਜੇ ਨਹੀਂ ਲੱਭੇ diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 8db060d83..22d7cabf8 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -46,6 +46,7 @@ Playlisty Playlisty tworzone przez społeczność Polecane playlisty + Bookmarked Brak wyników diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index f605cf80a..ed85e9b61 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -1,4 +1,4 @@ - + Início Músicas @@ -10,6 +10,7 @@ %d selecionada %d selecionadas + %d selecionadas @@ -30,7 +31,7 @@ Most played songs Most played artists - Most played albums + Most played álbuns Pesquisar @@ -44,6 +45,7 @@ Playlists Playlists da comunidade Playlists em destaque + Bookmarked No results found @@ -128,30 +130,37 @@ %d música %d músicas + %d músicas %d artista %d artistas + %d artistas %d álbum %d álbuns + %d álbuns %d playlist %d playlists + %d playlists - + %d week %d weeks + %d weeks - + %d month %d months + %d months - + %d year %d years + %d years @@ -165,8 +174,9 @@ Sleep timer End of song - 1 minute + %d minute %d minutes + %d minutes Nenhum canal de reprodução disponível Sem conexão com à internet diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index 0f6294f41..7256f0c74 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -46,6 +46,7 @@ Плейлисты Плейлисты сообщества Избранные плейлисты + Bookmarked Результаты не найдены diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index c9baa8d75..2d98d2d1e 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -44,6 +44,7 @@ Spellistor Community playlists Featured playlists + Bookmarked No results found diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 6db1e6af9..afcf3442e 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -44,6 +44,7 @@ Çalma Listeleri Topluluk çalma listeleri Öne çıkan listeler + Bookmarked Sonuç bulunamadı @@ -252,4 +253,4 @@ Hakkında Uygulama sürümü - \ No newline at end of file + diff --git a/app/src/main/res/values-uk-rUA/strings.xml b/app/src/main/res/values-uk-rUA/strings.xml index e994ff39c..c89353d2c 100644 --- a/app/src/main/res/values-uk-rUA/strings.xml +++ b/app/src/main/res/values-uk-rUA/strings.xml @@ -46,6 +46,7 @@ Плейлисти Плейлисти спільноти Обрані плейлисти + Bookmarked Результатів не знайдено diff --git a/app/src/main/res/values-vn/strings.xml b/app/src/main/res/values-vn/strings.xml index a91fe00a5..1f225dec7 100644 --- a/app/src/main/res/values-vn/strings.xml +++ b/app/src/main/res/values-vn/strings.xml @@ -17,8 +17,7 @@ Tâm trạng và thể loại Tài Khoản Chọn nhanh - Nghe các bài hát để tạo danh sách chọn nhanh của bạn - + Nghe các bài hát để tạo danh sách chọn nhanh của bạn Các albums mới phát hành @@ -44,6 +43,7 @@ Danh sách phát Danh sách phát cộng đồng Danh sách phát tiêu biểu + Bookmarked Không tìm thấy kết quả diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 164fe8dd8..e1ea86493 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -43,6 +43,7 @@ 播放列表 社区播放列表 精选播放列表 + Bookmarked 找不到结果 @@ -137,15 +138,12 @@ %d 个播放列表 - %d week %d weeks - - %d month + %d months - - %d year + %d years diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 00bca50c4..7bd955b9e 100755 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -43,6 +43,7 @@ 播放清單 社群播放清單 精選播放清單 + 收藏 找不到結果 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1eca7e5a7..0ce284017 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -43,6 +43,7 @@ Playlists Community playlists Featured playlists + Bookmarked No results found From cada08822c11c1590014462e54bde8de12e30495 Mon Sep 17 00:00:00 2001 From: RD3V <47334312+RDevWasTaken@users.noreply.github.com> Date: Sun, 30 Jul 2023 11:43:46 +0200 Subject: [PATCH 70/84] Update Polish language --- app/src/main/res/values-pl/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 22d7cabf8..1c0c94eaf 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -46,7 +46,7 @@ Playlisty Playlisty tworzone przez społeczność Polecane playlisty - Bookmarked + Ulubione Brak wyników From 110015355f2b27d76e90408e8b17d9f0dfccc512 Mon Sep 17 00:00:00 2001 From: Javi <45560967+javdc@users.noreply.github.com> Date: Sun, 30 Jul 2023 12:30:44 +0200 Subject: [PATCH 71/84] Update spanish and remove outdated latin american spanish translations --- app/src/main/res/values-es-rUS/strings.xml | 265 --------------------- app/src/main/res/values-es/strings.xml | 40 ++-- 2 files changed, 20 insertions(+), 285 deletions(-) delete mode 100644 app/src/main/res/values-es-rUS/strings.xml diff --git a/app/src/main/res/values-es-rUS/strings.xml b/app/src/main/res/values-es-rUS/strings.xml deleted file mode 100644 index 8d51c6425..000000000 --- a/app/src/main/res/values-es-rUS/strings.xml +++ /dev/null @@ -1,265 +0,0 @@ - - - Home - Canciones - Artistas - Albums - Playlists - - - - %d seleccionada - %d seleccionadas - %d seleccionadas - - - - History - Stats - Mood and Genres - Account - Quick picks - Listen to songs to generate your quick picks - New release albums - - - Today - Yesterday - This week - Last week - - - Most played songs - Most played artists - Most played albums - - - Buscar - Search YouTube Music… - Search library… - Todo - Canciones - Vídeos - Álbumes - Artistas - Playlists - Community playlists - Featured playlists - Bookmarked - No results found - - - From your library - - - Liked songs - Downloaded songs - The playlist is empty - - - Reintentar - Radio - Aleatorio - - - Details - Editar - Start radio - Play - Reproducir luego - Añadir a la cola - Añadir a la biblioteca - Remove from library - Descargar - Downloading - Remover archivo descargado - Import playlist - Añadir a una playlist - View artist - View álbum - Refetch - Share - Eliminar - Remove from history - Search online - Sync - - - Fecha de incorporación - Nombre - Artista - Year - Song count - Length - Play time - Custom order - - - Media id - MIME type - Codecs - Bitrate - Sample rate - Loudness - Volume - File size - Unknown - Copied to clipboard - - Edit lyrics - Search lyrics - - Editar canción - Título - Artista - El título no puede estar vacío. - El artista no puede estar vacío. - Guardar - - Elige una playlist - Editar playlist - Crear playlist - Nombre - El nombre no puede estar vacío. - - Editar artista - Nombre - El nombre no puede estar vacío. - - - - %d canción - %d canciones - %d canciones - - - %d artist - %d artists - %d artists - - - %d álbum - %d albums - %d albums - - - %d playlist - %d playlists - %d playlists - - - %d week - %d weeks - %d weeks - - - %d month - %d months - %d months - - - %d year - %d years - %d years - - - - Playlist imported - Removed \"%s\" from playlist - Playlist synced - Undo - - - Lyrics not found - Sleep timer - End of song - - 1 minute - %d minutes - %d minutes - - No stream available - No network connection - Timeout - Unknown error - - - Like - Remove like - - - All songs - Searched songs - - - Reproductor - - - Configuración - Apariencia - Enable dynamic theme - Modo oscuro - Encendido - Apagado - Predeterminado del sistema - Pure black - Default open tab - Customize navigation tabs - Lyrics text position - Left - Center - Right - - Contenido - Login - Idioma por defecto del contenido - País por defecto del contenido - Predeterminado del sistema - Enable proxy - Proxy type - Proxy URL - Restart to take effect - - Player and audio - Audio quality - Auto - High - Low - Persistent queue - Skip silence - Audio normalization - Equalizer - - Storage - Cache - Image Cache - Song Cache - Max cache size - Unlimited - Clear all downloads - Max image cache size - Clear image cache - Max song cache size - Clear song cache - %s used - - Privacy - Pause listen history - Clear listen history - Are you sure to clear all listen history? - Pause search history - Clear search history - Are you sure to clear all search history? - Enable KuGou lyrics provider - - Backup and restore - Backup - Restore - Imported playlist - Backup created successfully - Couldn\'t create backup - Failed to restore backup - - Acerca de - Versión de la aplicación - diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index a380ac06a..d2134eb00 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -9,15 +9,15 @@ %d seleccionada - %d seleccionadas %d seleccionadas + %d seleccionadas Historial Estadísticas - Mood and Genres - Account + Ánimo y géneros + Cuenta Selecciones rápidas Escucha algunas canciones para generar tus selecciones rápidas Nuevos lanzamientos @@ -31,12 +31,12 @@ Canciones más reproducidas Artistas más reproducidos - Most played albums + Álbumes más reproducidos Buscar Buscar en Youtube Music… - Buscar en biblioteca… + Buscar en la biblioteca… Todo Canciones Vídeos @@ -45,7 +45,7 @@ Listas de reproducción Listas de la comunidad Listas destacadas - Bookmarked + En marcadores No se han encontrado resultados @@ -129,38 +129,38 @@ %d canción - %d canciones %d canciones + %d canciones %d artista - %d artistas %d artistas + %d artistas %d álbum - %d álbumes %d álbumes + %d álbumes %d lista de reproducción - %d listas de reproducción %d listas de reproducción + %d listas de reproducción - %d week - %d weeks - %d weeks + %d semana + %d semanas + %d semanas - %d month - %d months - %d months + %d mes + %d meses + %d meses - %d year - %d years - %d years + %d año + %d años + %d años @@ -175,8 +175,8 @@ Al finalizar la canción 1 minuto - %d minutos %d minutos + %d minutos No hay un stream disponible No hay conexión a internet From 7019df8822a84c9fedcfc37ae9096d23017ba6f2 Mon Sep 17 00:00:00 2001 From: Oleksandr Tkachenko Date: Tue, 1 Aug 2023 06:52:03 +0300 Subject: [PATCH 72/84] Update strings.xml --- app/src/main/res/values-uk-rUA/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-uk-rUA/strings.xml b/app/src/main/res/values-uk-rUA/strings.xml index c89353d2c..7767795fb 100644 --- a/app/src/main/res/values-uk-rUA/strings.xml +++ b/app/src/main/res/values-uk-rUA/strings.xml @@ -46,7 +46,7 @@ Плейлисти Плейлисти спільноти Обрані плейлисти - Bookmarked + Додано в закладки Результатів не знайдено From 7e73dd235af8a4d747aa9949d9ce231da0c556ac Mon Sep 17 00:00:00 2001 From: Oleksandr Tkachenko Date: Tue, 1 Aug 2023 06:53:14 +0300 Subject: [PATCH 73/84] Update strings.xml --- app/src/main/res/values-ru-rRU/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index 7256f0c74..805aea080 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -46,7 +46,7 @@ Плейлисты Плейлисты сообщества Избранные плейлисты - Bookmarked + Добавлено в закладки Результаты не найдены From cfc868b484b5cf63b1cea317e2967f49e06a8ae2 Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Thu, 3 Aug 2023 18:31:05 +0800 Subject: [PATCH 74/84] Add firebase --- .github/workflows/build.yml | 12 +++++------- .github/workflows/build_pr.yml | 2 ++ .gitignore | 2 +- app/build.gradle.kts | 13 ++++++++----- build.gradle.kts | 1 + gradle/libs.versions.toml | 5 +++++ 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 273a2f544..9a63af9a4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,12 +18,11 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Decode google-services.json + run: echo ${{ secrets.GOOGLE_SERVICES }} | base64 -d >> app/google-services.json + - name: Decode Keystore - id: decode_keystore - uses: timheuer/base64-to-file@v1 - with: - fileName: 'Key/music-debug.jks' - encodedString: ${{ secrets.KEYSTORE }} + run: echo ${{ secrets.KEYSTORE }} | base64 -d >> app/music-debug.jks - name: set up JDK 11 uses: actions/setup-java@v3 @@ -35,9 +34,8 @@ jobs: - name: Build debug APK and run jvm tests run: ./gradlew assembleDebug lintDebug testDebugUnitTest --stacktrace -DskipFormatKtlint env: - MUSIC_DEBUG_KEYSTORE_FILE: 'Key/music-debug.jks' + MUSIC_DEBUG_KEYSTORE_FILE: 'music-debug.jks' MUSIC_DEBUG_SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }} - MUSIC_DEBUG_SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }} MUSIC_DEBUG_SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }} - name: Upload APK diff --git a/.github/workflows/build_pr.yml b/.github/workflows/build_pr.yml index e2b0d83d1..6fafe23f7 100644 --- a/.github/workflows/build_pr.yml +++ b/.github/workflows/build_pr.yml @@ -19,6 +19,8 @@ jobs: - name: Build debug APK and run jvm tests run: ./gradlew assembleDebug lintDebug testDebugUnitTest --stacktrace -DskipFormatKtlint + env: + PULL_REQUEST: 'true' - name: Upload APK uses: actions/upload-artifact@v3 diff --git a/.gitignore b/.gitignore index 7a638a3dd..ecfc60f0e 100755 --- a/.gitignore +++ b/.gitignore @@ -61,7 +61,7 @@ captures/ .cxx/ # Google Services (e.g. APIs or Firebase) -# google-services.json +google-services.json # Freeline freeline.py diff --git a/app/build.gradle.kts b/app/build.gradle.kts index eea305491..d47ac11b3 100755 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -6,6 +6,9 @@ plugins { kotlin("kapt") id("com.google.dagger.hilt.android") id("com.google.devtools.ksp") + if (System.getenv("PULL_REQUEST") == null) { + id("com.google.gms.google-services") + } } android { @@ -35,12 +38,9 @@ android { signingConfigs { getByName("debug") { if (System.getenv("MUSIC_DEBUG_SIGNING_STORE_PASSWORD") != null) { - val tmpFilePath = System.getProperty("user.home") + "/work/_temp/Key/" - val allFilesFromDir = File(tmpFilePath).listFiles() - val keystoreFile = allFilesFromDir?.first() - storeFile = keystoreFile ?: file(System.getenv("MUSIC_DEBUG_KEYSTORE_FILE")) + storeFile = file(System.getenv("MUSIC_DEBUG_KEYSTORE_FILE")) storePassword = System.getenv("MUSIC_DEBUG_SIGNING_STORE_PASSWORD") - keyAlias = System.getenv("MUSIC_DEBUG_SIGNING_KEY_ALIAS") + keyAlias = "debug" keyPassword = System.getenv("MUSIC_DEBUG_SIGNING_KEY_PASSWORD") } } @@ -124,5 +124,8 @@ dependencies { coreLibraryDesugaring(libs.desugaring) + implementation(platform(libs.firebase.bom)) + implementation(libs.firebase.analytics) + implementation(libs.timber) } diff --git a/build.gradle.kts b/build.gradle.kts index 42d159bfa..e104fe9ad 100755 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,6 +12,7 @@ buildscript { dependencies { classpath(libs.gradle) classpath(kotlin("gradle-plugin", libs.versions.kotlin.get())) + classpath(libs.google.services) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 002915e6f..b5a160386 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,6 @@ [versions] androidGradlePlugin = "7.4.2" +googleServices = "4.3.15" kotlin = "1.8.0" compose-compiler = "1.4.0" compose = "1.3.3" @@ -74,5 +75,9 @@ junit = { group = "junit", name = "junit", version = "4.13.2" } timber = { group = "com.jakewharton.timber", name = "timber", version = "4.7.1" } +google-services = { module = "com.google.gms:google-services", version.ref = "googleServices" } +firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version = "32.2.0" } +firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics-ktx" } + [plugins] kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } \ No newline at end of file From e1e4e0bed0743e561e2969c275598062ebc66b97 Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Thu, 3 Aug 2023 23:22:33 +0800 Subject: [PATCH 75/84] Check update automatically --- app/build.gradle.kts | 5 ++ .../java/com/zionhuang/music/MainActivity.kt | 64 ++++++++++++++++--- .../music/ui/component/Preference.kt | 55 +++++++++------- .../ui/screens/settings/AppearanceSettings.kt | 20 +++--- .../ui/screens/settings/BackupAndRestore.kt | 8 +-- .../ui/screens/settings/ContentSettings.kt | 18 +++--- .../ui/screens/settings/PlayerSettings.kt | 16 ++--- .../ui/screens/settings/PrivacySettings.kt | 20 +++--- .../ui/screens/settings/SettingsScreen.kt | 52 +++++++++++---- .../ui/screens/settings/StorageSettings.kt | 10 +-- app/src/main/res/drawable/update.xml | 9 +++ app/src/main/res/values-DE/strings.xml | 2 + app/src/main/res/values-be/strings.xml | 2 + app/src/main/res/values-bn-rIN/strings.xml | 2 + app/src/main/res/values-cs/strings.xml | 2 + app/src/main/res/values-es/strings.xml | 2 + app/src/main/res/values-fa-rIR/strings.xml | 2 + app/src/main/res/values-fi-rFI/strings.xml | 2 + app/src/main/res/values-fr-rFR/strings.xml | 2 + app/src/main/res/values-hu/strings.xml | 2 + app/src/main/res/values-id/strings.xml | 2 + app/src/main/res/values-it/strings.xml | 2 + app/src/main/res/values-ja-rJP/strings.xml | 2 + app/src/main/res/values-ko-rKR/strings.xml | 2 + app/src/main/res/values-ml-rIN/strings.xml | 2 + app/src/main/res/values-nl/strings.xml | 2 + app/src/main/res/values-or-rIN/strings.xml | 2 + app/src/main/res/values-pa/strings.xml | 2 + app/src/main/res/values-pl/strings.xml | 2 + app/src/main/res/values-pt-rBR/strings.xml | 2 + app/src/main/res/values-ru-rRU/strings.xml | 2 + app/src/main/res/values-sv-rSE/strings.xml | 2 + app/src/main/res/values-tr/strings.xml | 2 + app/src/main/res/values-uk-rUA/strings.xml | 2 + app/src/main/res/values-vn/strings.xml | 2 + app/src/main/res/values-zh-rCN/strings.xml | 2 + app/src/main/res/values-zh-rTW/strings.xml | 2 + app/src/main/res/values/strings.xml | 2 + build.gradle.kts | 2 + gradle/libs.versions.toml | 8 ++- 40 files changed, 247 insertions(+), 94 deletions(-) create mode 100644 app/src/main/res/drawable/update.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d47ac11b3..7ad5c77eb 100755 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -8,6 +8,8 @@ plugins { id("com.google.devtools.ksp") if (System.getenv("PULL_REQUEST") == null) { id("com.google.gms.google-services") + id("com.google.firebase.crashlytics") + id("com.google.firebase.firebase-perf") } } @@ -126,6 +128,9 @@ dependencies { implementation(platform(libs.firebase.bom)) implementation(libs.firebase.analytics) + implementation(libs.firebase.crashlytics) + implementation(libs.firebase.config) + implementation(libs.firebase.perf) implementation(libs.timber) } diff --git a/app/src/main/java/com/zionhuang/music/MainActivity.kt b/app/src/main/java/com/zionhuang/music/MainActivity.kt index 4657854d1..494a7bb83 100644 --- a/app/src/main/java/com/zionhuang/music/MainActivity.kt +++ b/app/src/main/java/com/zionhuang/music/MainActivity.kt @@ -17,8 +17,10 @@ import androidx.compose.animation.core.* import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.material3.* @@ -26,6 +28,7 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb @@ -61,6 +64,12 @@ import androidx.navigation.navArgument import coil.imageLoader import coil.request.ImageRequest import com.google.common.util.concurrent.MoreExecutors +import com.google.firebase.ktx.Firebase +import com.google.firebase.remoteconfig.ConfigUpdate +import com.google.firebase.remoteconfig.ConfigUpdateListener +import com.google.firebase.remoteconfig.FirebaseRemoteConfigException +import com.google.firebase.remoteconfig.ktx.remoteConfig +import com.google.firebase.remoteconfig.ktx.remoteConfigSettings import com.valentinilk.shimmer.LocalShimmerTheme import com.zionhuang.innertube.YouTube import com.zionhuang.innertube.models.SongItem @@ -109,6 +118,7 @@ import kotlinx.coroutines.withContext import java.net.URLDecoder import java.net.URLEncoder import javax.inject.Inject +import kotlin.time.Duration.Companion.hours @AndroidEntryPoint class MainActivity : ComponentActivity() { @@ -132,6 +142,7 @@ class MainActivity : ComponentActivity() { playerConnection = null } } + private var latestVersion by mutableStateOf(BuildConfig.VERSION_CODE.toLong()) override fun onStart() { super.onStart() @@ -162,6 +173,7 @@ class MainActivity : ComponentActivity() { MoreExecutors.directExecutor() ) + setupRemoteConfig() setContent { val enableDynamicTheme by rememberPreference(DynamicThemeKey, defaultValue = true) @@ -551,7 +563,7 @@ class MainActivity : ComponentActivity() { YouTubeBrowseScreen(navController, scrollBehavior) } composable("settings") { - SettingsScreen(navController, scrollBehavior) + SettingsScreen(latestVersion, navController, scrollBehavior) } composable("settings/appearance") { AppearanceSettings(navController, scrollBehavior) @@ -660,15 +672,28 @@ class MainActivity : ComponentActivity() { Screens.Playlists.route ) ) { - IconButton( - onClick = { - navController.navigate("settings") - } + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .size(48.dp) + .clip(CircleShape) + .clickable { + navController.navigate("settings") + } ) { - Icon( - painter = painterResource(R.drawable.settings), - contentDescription = null - ) + BadgedBox( + badge = { + if (latestVersion > BuildConfig.VERSION_CODE) { + Badge() + } + } + ) { + + Icon( + painter = painterResource(R.drawable.settings), + contentDescription = null + ) + } } } }, @@ -818,6 +843,27 @@ class MainActivity : ComponentActivity() { } } + private fun setupRemoteConfig() { + val remoteConfig = Firebase.remoteConfig + remoteConfig.setConfigSettingsAsync(remoteConfigSettings { + minimumFetchIntervalInSeconds = 12.hours.inWholeSeconds + }) + remoteConfig.fetchAndActivate() + .addOnCompleteListener(this) { task -> + if (task.isSuccessful) { + latestVersion = remoteConfig.getLong("latest_version") + } + } + remoteConfig.addOnConfigUpdateListener(object : ConfigUpdateListener { + override fun onError(error: FirebaseRemoteConfigException) {} + override fun onUpdate(configUpdate: ConfigUpdate) { + remoteConfig.activate().addOnCompleteListener { + latestVersion = remoteConfig.getLong("latest_version") + } + } + }) + } + companion object { const val ACTION_SEARCH = "com.zionhuang.music.action.SEARCH" const val ACTION_SONGS = "com.zionhuang.music.action.SONGS" diff --git a/app/src/main/java/com/zionhuang/music/ui/component/Preference.kt b/app/src/main/java/com/zionhuang/music/ui/component/Preference.kt index e0f0f8d4a..65f591d1e 100644 --- a/app/src/main/java/com/zionhuang/music/ui/component/Preference.kt +++ b/app/src/main/java/com/zionhuang/music/ui/component/Preference.kt @@ -1,15 +1,28 @@ package com.zionhuang.music.ui.component -import androidx.annotation.DrawableRes import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.items -import androidx.compose.material3.* -import androidx.compose.runtime.* +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ProvideTextStyle +import androidx.compose.material3.RadioButton +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha -import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp @@ -17,10 +30,10 @@ import androidx.compose.ui.unit.dp @Composable fun PreferenceEntry( modifier: Modifier = Modifier, - title: String, + title: @Composable () -> Unit, description: String? = null, content: (@Composable () -> Unit)? = null, - @DrawableRes icon: Int? = null, + icon: (@Composable () -> Unit)? = null, trailingContent: (@Composable () -> Unit)? = null, onClick: () -> Unit, isEnabled: Boolean = true, @@ -40,10 +53,7 @@ fun PreferenceEntry( Box( modifier = Modifier.padding(horizontal = 4.dp) ) { - Icon( - painter = painterResource(icon), - contentDescription = null - ) + icon() } Spacer(Modifier.width(12.dp)) @@ -53,10 +63,9 @@ fun PreferenceEntry( verticalArrangement = Arrangement.Center, modifier = Modifier.weight(1f) ) { - Text( - text = title, - style = MaterialTheme.typography.titleMedium - ) + ProvideTextStyle(MaterialTheme.typography.titleMedium) { + title() + } if (description != null) { Text( @@ -80,8 +89,8 @@ fun PreferenceEntry( @Composable fun ListPreference( modifier: Modifier = Modifier, - title: String, - @DrawableRes icon: Int? = null, + title: @Composable () -> Unit, + icon: (@Composable () -> Unit)? = null, selectedValue: T, values: List, valueText: @Composable (T) -> String, @@ -134,8 +143,8 @@ fun ListPreference( @Composable inline fun > EnumListPreference( modifier: Modifier = Modifier, - title: String, - @DrawableRes icon: Int, + noinline title: @Composable () -> Unit, + noinline icon: (@Composable () -> Unit)?, selectedValue: T, noinline valueText: @Composable (T) -> String, noinline onValueSelected: (T) -> Unit, @@ -156,9 +165,9 @@ inline fun > EnumListPreference( @Composable fun SwitchPreference( modifier: Modifier = Modifier, - title: String, + title: @Composable () -> Unit, description: String? = null, - @DrawableRes icon: Int? = null, + icon: (@Composable () -> Unit)? = null, checked: Boolean, onCheckedChange: (Boolean) -> Unit, isEnabled: Boolean = true, @@ -182,8 +191,8 @@ fun SwitchPreference( @Composable fun EditTextPreference( modifier: Modifier = Modifier, - title: String, - @DrawableRes icon: Int? = null, + title: @Composable () -> Unit, + icon: (@Composable () -> Unit)? = null, value: String, onValueChange: (String) -> Unit, singleLine: Boolean = true, diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/settings/AppearanceSettings.kt b/app/src/main/java/com/zionhuang/music/ui/screens/settings/AppearanceSettings.kt index 9a010bb04..460e2a05b 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/settings/AppearanceSettings.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/settings/AppearanceSettings.kt @@ -40,14 +40,14 @@ fun AppearanceSettings( .verticalScroll(rememberScrollState()) ) { SwitchPreference( - title = stringResource(R.string.enable_dynamic_theme), - icon = R.drawable.palette, + title = { Text(stringResource(R.string.enable_dynamic_theme)) }, + icon = { Icon(painterResource(R.drawable.palette), null) }, checked = dynamicTheme, onCheckedChange = onDynamicThemeChange ) EnumListPreference( - title = stringResource(R.string.dark_theme), - icon = R.drawable.dark_mode, + title = { Text(stringResource(R.string.dark_theme)) }, + icon = { Icon(painterResource(R.drawable.dark_mode), null) }, selectedValue = darkMode, onValueSelected = onDarkModeChange, valueText = { @@ -59,14 +59,14 @@ fun AppearanceSettings( } ) SwitchPreference( - title = stringResource(R.string.pure_black), - icon = R.drawable.contrast, + title = { Text(stringResource(R.string.pure_black)) }, + icon = { Icon(painterResource(R.drawable.contrast), null) }, checked = pureBlack, onCheckedChange = onPureBlackChange ) EnumListPreference( - title = stringResource(R.string.default_open_tab), - icon = R.drawable.tab, + title = { Text(stringResource(R.string.default_open_tab)) }, + icon = { Icon(painterResource(R.drawable.tab), null) }, selectedValue = defaultOpenTab, onValueSelected = onDefaultOpenTabChange, valueText = { @@ -80,8 +80,8 @@ fun AppearanceSettings( } ) EnumListPreference( - title = stringResource(R.string.lyrics_text_position), - icon = R.drawable.lyrics, + title = { Text(stringResource(R.string.lyrics_text_position)) }, + icon = { Icon(painterResource(R.drawable.lyrics), null) }, selectedValue = lyricsPosition, onValueSelected = onLyricsPositionChange, valueText = { diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/settings/BackupAndRestore.kt b/app/src/main/java/com/zionhuang/music/ui/screens/settings/BackupAndRestore.kt index 2d8123902..8480cfe85 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/settings/BackupAndRestore.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/settings/BackupAndRestore.kt @@ -46,16 +46,16 @@ fun BackupAndRestore( .verticalScroll(rememberScrollState()) ) { PreferenceEntry( - title = stringResource(R.string.backup), - icon = R.drawable.backup, + title = { Text(stringResource(R.string.backup)) }, + icon = { Icon(painterResource(R.drawable.backup), null) }, onClick = { val formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss") backupLauncher.launch("${context.getString(R.string.app_name)}_${LocalDateTime.now().format(formatter)}.backup") } ) PreferenceEntry( - title = stringResource(R.string.restore), - icon = R.drawable.restore, + title = { Text(stringResource(R.string.restore)) }, + icon = { Icon(painterResource(R.drawable.restore), null) }, onClick = { restoreLauncher.launch(arrayOf("application/octet-stream")) } diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/settings/ContentSettings.kt b/app/src/main/java/com/zionhuang/music/ui/screens/settings/ContentSettings.kt index 65a25e90f..42e3e7952 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/settings/ContentSettings.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/settings/ContentSettings.kt @@ -50,14 +50,14 @@ fun ContentSettings( .verticalScroll(rememberScrollState()) ) { PreferenceEntry( - title = if (isLoggedIn) accountName else stringResource(R.string.login), + title = { Text(if (isLoggedIn) accountName else stringResource(R.string.login)) }, description = if (isLoggedIn) accountEmail else null, - icon = R.drawable.person, + icon = { Icon(painterResource(R.drawable.person), null) }, onClick = { navController.navigate("login") } ) ListPreference( - title = stringResource(R.string.content_language), - icon = R.drawable.language, + title = { Text(stringResource(R.string.content_language)) }, + icon = { Icon(painterResource(R.drawable.language), null) }, selectedValue = contentLanguage, values = listOf(SYSTEM_DEFAULT) + LanguageCodeToName.keys.toList(), valueText = { @@ -68,8 +68,8 @@ fun ContentSettings( onValueSelected = onContentLanguageChange ) ListPreference( - title = stringResource(R.string.content_country), - icon = R.drawable.location_on, + title = { Text(stringResource(R.string.content_country)) }, + icon = { Icon(painterResource(R.drawable.location_on), null) }, selectedValue = contentCountry, values = listOf(SYSTEM_DEFAULT) + CountryCodeToName.keys.toList(), valueText = { @@ -85,21 +85,21 @@ fun ContentSettings( ) SwitchPreference( - title = stringResource(R.string.enable_proxy), + title = { Text(stringResource(R.string.enable_proxy)) }, checked = proxyEnabled, onCheckedChange = onProxyEnabledChange ) if (proxyEnabled) { ListPreference( - title = stringResource(R.string.proxy_type), + title = { Text(stringResource(R.string.proxy_type)) }, selectedValue = proxyType, values = listOf(Proxy.Type.HTTP, Proxy.Type.SOCKS), valueText = { it.name }, onValueSelected = onProxyTypeChange ) EditTextPreference( - title = stringResource(R.string.proxy_url), + title = { Text(stringResource(R.string.proxy_url)) }, value = proxyUrl, onValueChange = onProxyUrlChange ) diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/settings/PlayerSettings.kt b/app/src/main/java/com/zionhuang/music/ui/screens/settings/PlayerSettings.kt index 5679864a8..49a988efb 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/settings/PlayerSettings.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/settings/PlayerSettings.kt @@ -39,8 +39,8 @@ fun PlayerSettings( .verticalScroll(rememberScrollState()) ) { EnumListPreference( - title = stringResource(R.string.audio_quality), - icon = R.drawable.graphic_eq, + title = { Text(stringResource(R.string.audio_quality)) }, + icon = { Icon(painterResource(R.drawable.graphic_eq), null) }, selectedValue = audioQuality, onValueSelected = onAudioQualityChange, valueText = { @@ -52,20 +52,20 @@ fun PlayerSettings( } ) SwitchPreference( - title = stringResource(R.string.persistent_queue), - icon = R.drawable.queue_music, + title = { Text(stringResource(R.string.persistent_queue)) }, + icon = { Icon(painterResource(R.drawable.queue_music), null) }, checked = persistentQueue, onCheckedChange = onPersistentQueueChange ) SwitchPreference( - title = stringResource(R.string.skip_silence), - icon = R.drawable.skip_next, + title = { Text(stringResource(R.string.skip_silence)) }, + icon = { Icon(painterResource(R.drawable.skip_next), null) }, checked = skipSilence, onCheckedChange = onSkipSilenceChange ) SwitchPreference( - title = stringResource(R.string.audio_normalization), - icon = R.drawable.volume_up, + title = { Text(stringResource(R.string.audio_normalization)) }, + icon = { Icon(painterResource(R.drawable.volume_up), null) }, checked = audioNormalization, onCheckedChange = onAudioNormalizationChange ) diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/settings/PrivacySettings.kt b/app/src/main/java/com/zionhuang/music/ui/screens/settings/PrivacySettings.kt index d8e1fd7db..e43a25b1d 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/settings/PrivacySettings.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/settings/PrivacySettings.kt @@ -110,30 +110,30 @@ fun PrivacySettings( .verticalScroll(rememberScrollState()) ) { SwitchPreference( - title = stringResource(R.string.pause_listen_history), - icon = R.drawable.history, + title = { Text(stringResource(R.string.pause_listen_history)) }, + icon = { Icon(painterResource(R.drawable.history), null) }, checked = pauseListenHistory, onCheckedChange = onPauseListenHistoryChange ) PreferenceEntry( - title = stringResource(R.string.clear_listen_history), - icon = R.drawable.clear_all, + title = { Text(stringResource(R.string.clear_listen_history)) }, + icon = { Icon(painterResource(R.drawable.clear_all), null) }, onClick = { showClearListenHistoryDialog = true } ) SwitchPreference( - title = stringResource(R.string.pause_search_history), - icon = R.drawable.manage_search, + title = { Text(stringResource(R.string.pause_search_history)) }, + icon = { Icon(painterResource(R.drawable.manage_search), null) }, checked = pauseSearchHistory, onCheckedChange = onPauseSearchHistoryChange ) PreferenceEntry( - title = stringResource(R.string.clear_search_history), - icon = R.drawable.clear_all, + title = { Text(stringResource(R.string.clear_search_history)) }, + icon = { Icon(painterResource(R.drawable.clear_all), null) }, onClick = { showClearSearchHistoryDialog = true } ) SwitchPreference( - title = stringResource(R.string.enable_kugou), - icon = R.drawable.lyrics, + title = { Text(stringResource(R.string.enable_kugou)) }, + icon = { Icon(painterResource(R.drawable.lyrics), null) }, checked = enableKugou, onCheckedChange = onEnableKugouChange ) diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/settings/SettingsScreen.kt b/app/src/main/java/com/zionhuang/music/ui/screens/settings/SettingsScreen.kt index 4bb4c24bb..698323091 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/settings/SettingsScreen.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/settings/SettingsScreen.kt @@ -7,9 +7,11 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.navigation.NavController +import com.zionhuang.music.BuildConfig import com.zionhuang.music.LocalPlayerAwareWindowInsets import com.zionhuang.music.R import com.zionhuang.music.ui.component.PreferenceEntry @@ -17,49 +19,71 @@ import com.zionhuang.music.ui.component.PreferenceEntry @OptIn(ExperimentalMaterial3Api::class) @Composable fun SettingsScreen( + latestVersion: Long, navController: NavController, scrollBehavior: TopAppBarScrollBehavior, ) { + val uriHandler = LocalUriHandler.current + Column( modifier = Modifier .windowInsetsPadding(LocalPlayerAwareWindowInsets.current) .verticalScroll(rememberScrollState()) ) { PreferenceEntry( - title = stringResource(R.string.appearance), - icon = R.drawable.palette, + title = { Text(stringResource(R.string.appearance)) }, + icon = { Icon(painterResource(R.drawable.palette), null) }, onClick = { navController.navigate("settings/appearance") } ) PreferenceEntry( - title = stringResource(R.string.content), - icon = R.drawable.language, + title = { Text(stringResource(R.string.content)) }, + icon = { Icon(painterResource(R.drawable.language), null) }, onClick = { navController.navigate("settings/content") } ) PreferenceEntry( - title = stringResource(R.string.player_and_audio), - icon = R.drawable.play, + title = { Text(stringResource(R.string.player_and_audio)) }, + icon = { Icon(painterResource(R.drawable.play), null) }, onClick = { navController.navigate("settings/player") } ) PreferenceEntry( - title = stringResource(R.string.storage), - icon = R.drawable.storage, + title = { Text(stringResource(R.string.storage)) }, + icon = { Icon(painterResource(R.drawable.storage), null) }, onClick = { navController.navigate("settings/storage") } ) PreferenceEntry( - title = stringResource(R.string.privacy), - icon = R.drawable.security, + title = { Text(stringResource(R.string.privacy)) }, + icon = { Icon(painterResource(R.drawable.security), null) }, onClick = { navController.navigate("settings/privacy") } ) PreferenceEntry( - title = stringResource(R.string.backup_restore), - icon = R.drawable.restore, + title = { Text(stringResource(R.string.backup_restore)) }, + icon = { Icon(painterResource(R.drawable.restore), null) }, onClick = { navController.navigate("settings/backup_restore") } ) PreferenceEntry( - title = stringResource(R.string.about), - icon = R.drawable.info, + title = { Text(stringResource(R.string.about)) }, + icon = { Icon(painterResource(R.drawable.info), null) }, onClick = { navController.navigate("settings/about") } ) + if (latestVersion > BuildConfig.VERSION_CODE) { + PreferenceEntry( + title = { + Text( + text = stringResource(R.string.new_version_available), + ) + }, + icon = { + BadgedBox( + badge = { Badge() } + ) { + Icon(painterResource(R.drawable.update), null) + } + }, + onClick = { + uriHandler.openUri("https://github.com/z-huang/InnerTune/releases/latest") + } + ) + } } TopAppBar( diff --git a/app/src/main/java/com/zionhuang/music/ui/screens/settings/StorageSettings.kt b/app/src/main/java/com/zionhuang/music/ui/screens/settings/StorageSettings.kt index 5ea11de3a..2ec8fa899 100644 --- a/app/src/main/java/com/zionhuang/music/ui/screens/settings/StorageSettings.kt +++ b/app/src/main/java/com/zionhuang/music/ui/screens/settings/StorageSettings.kt @@ -105,7 +105,7 @@ fun StorageSettings( ) PreferenceEntry( - title = stringResource(R.string.clear_all_downloads), + title = { Text(stringResource(R.string.clear_all_downloads)) }, onClick = { coroutineScope.launch(Dispatchers.IO) { downloadCache.keys.forEach { key -> @@ -141,7 +141,7 @@ fun StorageSettings( } ListPreference( - title = stringResource(R.string.max_cache_size), + title = { Text(stringResource(R.string.max_cache_size)) }, selectedValue = maxSongCacheSize, values = listOf(128, 256, 512, 1024, 2048, 4096, 8192, -1), valueText = { @@ -151,7 +151,7 @@ fun StorageSettings( ) PreferenceEntry( - title = stringResource(R.string.clear_song_cache), + title = { Text(stringResource(R.string.clear_song_cache)) }, onClick = { coroutineScope.launch(Dispatchers.IO) { playerCache.keys.forEach { key -> @@ -179,7 +179,7 @@ fun StorageSettings( ) ListPreference( - title = stringResource(R.string.max_cache_size), + title = { Text(stringResource(R.string.max_cache_size)) }, selectedValue = maxImageCacheSize, values = listOf(128, 256, 512, 1024, 2048, 4096, 8192), valueText = { formatFileSize(it * 1024 * 1024L) }, @@ -187,7 +187,7 @@ fun StorageSettings( ) PreferenceEntry( - title = stringResource(R.string.clear_image_cache), + title = { Text(stringResource(R.string.clear_image_cache)) }, onClick = { coroutineScope.launch(Dispatchers.IO) { imageDiskCache.clear() diff --git a/app/src/main/res/drawable/update.xml b/app/src/main/res/drawable/update.xml new file mode 100644 index 000000000..65fd69ac7 --- /dev/null +++ b/app/src/main/res/drawable/update.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values-DE/strings.xml b/app/src/main/res/values-DE/strings.xml index dc22824cf..206db0322 100644 --- a/app/src/main/res/values-DE/strings.xml +++ b/app/src/main/res/values-DE/strings.xml @@ -253,4 +253,6 @@ Über App-Version + + New version available diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index 62f0f17aa..5e4f5bfec 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -271,4 +271,6 @@ Аб праграме Версія праграмы + + New version available diff --git a/app/src/main/res/values-bn-rIN/strings.xml b/app/src/main/res/values-bn-rIN/strings.xml index 9781ab940..9088e7ba1 100644 --- a/app/src/main/res/values-bn-rIN/strings.xml +++ b/app/src/main/res/values-bn-rIN/strings.xml @@ -253,4 +253,6 @@ সম্পর্কিত অ্যাপ সংস্করণ + + New version available diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index a8dcfbe23..a7642851e 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -265,4 +265,6 @@ O aplikaci Verze aplikace + + New version available diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index d2134eb00..90924cfd5 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -262,4 +262,6 @@ Acerca de Versión de la app + + New version available diff --git a/app/src/main/res/values-fa-rIR/strings.xml b/app/src/main/res/values-fa-rIR/strings.xml index 822189d58..152449763 100644 --- a/app/src/main/res/values-fa-rIR/strings.xml +++ b/app/src/main/res/values-fa-rIR/strings.xml @@ -253,4 +253,6 @@ درباره نسخه‌ی برنامه + + New version available diff --git a/app/src/main/res/values-fi-rFI/strings.xml b/app/src/main/res/values-fi-rFI/strings.xml index 1c7282eb0..2e830364e 100644 --- a/app/src/main/res/values-fi-rFI/strings.xml +++ b/app/src/main/res/values-fi-rFI/strings.xml @@ -253,4 +253,6 @@ Tietoa Sovelluksen versio + + New version available diff --git a/app/src/main/res/values-fr-rFR/strings.xml b/app/src/main/res/values-fr-rFR/strings.xml index b04a0410a..342970be2 100644 --- a/app/src/main/res/values-fr-rFR/strings.xml +++ b/app/src/main/res/values-fr-rFR/strings.xml @@ -259,4 +259,6 @@ À propos Version de l\'application + + New version available diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 0c81875fe..cc35c1078 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -253,4 +253,6 @@ Rólunk App verzió + + New version available diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index c539cc466..95bede859 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -244,4 +244,6 @@ Tentang Versi aplikasi + + New version available diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 13c2df346..b82d543f7 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -262,4 +262,6 @@ Informazioni Versione dell\'app + + New version available diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index 69484841a..f95b6b252 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -244,4 +244,6 @@ アプリについて アプリのバージョン + + New version available diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml index 1d945c8cc..62486122a 100644 --- a/app/src/main/res/values-ko-rKR/strings.xml +++ b/app/src/main/res/values-ko-rKR/strings.xml @@ -244,4 +244,6 @@ 정보 앱 버전 + + New version available diff --git a/app/src/main/res/values-ml-rIN/strings.xml b/app/src/main/res/values-ml-rIN/strings.xml index 5f0e1a7a6..02283bfca 100644 --- a/app/src/main/res/values-ml-rIN/strings.xml +++ b/app/src/main/res/values-ml-rIN/strings.xml @@ -253,4 +253,6 @@ കുറിച്ച് അപ്ലിക്കേഷൻ പതിപ്പ് + + New version available diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 1feb5aca8..cee9296e8 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -253,4 +253,6 @@ Over App versie + + New version available diff --git a/app/src/main/res/values-or-rIN/strings.xml b/app/src/main/res/values-or-rIN/strings.xml index 6f9df84aa..940e2e4c2 100644 --- a/app/src/main/res/values-or-rIN/strings.xml +++ b/app/src/main/res/values-or-rIN/strings.xml @@ -253,4 +253,6 @@ ବିଷୟରେ ଆପ୍ ସଂସ୍କରଣ + + New version available diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index 878798c4b..716626bb7 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -253,4 +253,6 @@ ਦੇ ਬਾਰੇ ਐਪ ਸੰਸਕਰਣ + + New version available diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 1c0c94eaf..a6e5b128d 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -271,4 +271,6 @@ O aplikacji Wersja aplikacji + + New version available diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index ed85e9b61..e671ed8ba 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -262,4 +262,6 @@ Sobre Versão do aplicativo + + New version available diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index 805aea080..806a66cb0 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -271,4 +271,6 @@ О приложении Версия приложения + + New version available diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index 2d98d2d1e..eac93c378 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -253,4 +253,6 @@ Om App version + + New version available diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index afcf3442e..82fbc5f40 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -253,4 +253,6 @@ Hakkında Uygulama sürümü + + New version available diff --git a/app/src/main/res/values-uk-rUA/strings.xml b/app/src/main/res/values-uk-rUA/strings.xml index 7767795fb..8677e98f5 100644 --- a/app/src/main/res/values-uk-rUA/strings.xml +++ b/app/src/main/res/values-uk-rUA/strings.xml @@ -271,4 +271,6 @@ Про програму Версія застосунку + + New version available diff --git a/app/src/main/res/values-vn/strings.xml b/app/src/main/res/values-vn/strings.xml index 1f225dec7..659aff00b 100644 --- a/app/src/main/res/values-vn/strings.xml +++ b/app/src/main/res/values-vn/strings.xml @@ -252,4 +252,6 @@ Tác giả Phiên bản ứng dụng + + New version available diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index e1ea86493..423d76a33 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -244,4 +244,6 @@ 关于 应用版本 + + New version available diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 7bd955b9e..2cd8eadef 100755 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -244,4 +244,6 @@ 關於 應用程式版本 + + 發現新版本 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0ce284017..eda7659dc 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -252,4 +252,6 @@ About App version + + New version available diff --git a/build.gradle.kts b/build.gradle.kts index e104fe9ad..c5b81cacb 100755 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,6 +13,8 @@ buildscript { classpath(libs.gradle) classpath(kotlin("gradle-plugin", libs.versions.kotlin.get())) classpath(libs.google.services) + classpath(libs.firebase.crashlytics.plugin) + classpath(libs.firebase.perf.plugin) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b5a160386..31f77b1d0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,5 @@ [versions] androidGradlePlugin = "7.4.2" -googleServices = "4.3.15" kotlin = "1.8.0" compose-compiler = "1.4.0" compose = "1.3.3" @@ -75,9 +74,14 @@ junit = { group = "junit", name = "junit", version = "4.13.2" } timber = { group = "com.jakewharton.timber", name = "timber", version = "4.7.1" } -google-services = { module = "com.google.gms:google-services", version.ref = "googleServices" } +google-services = { module = "com.google.gms:google-services", version = "4.3.15" } firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version = "32.2.0" } firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics-ktx" } +firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics-ktx" } +firebase-crashlytics-plugin = { module = "com.google.firebase:firebase-crashlytics-gradle", version = "2.9.7" } +firebase-config = { group = "com.google.firebase", name = "firebase-config-ktx" } +firebase-perf = { group = "com.google.firebase", name = "firebase-perf-ktx" } +firebase-perf-plugin = { module = "com.google.firebase:perf-plugin", version = "1.4.2" } [plugins] kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } \ No newline at end of file From f6df388dc70a67b7e790fbdb4b17f59a3dbb9eda Mon Sep 17 00:00:00 2001 From: RD3V <47334312+RDevWasTaken@users.noreply.github.com> Date: Thu, 3 Aug 2023 19:26:45 +0200 Subject: [PATCH 76/84] Update Polish and Turkish translation --- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-tr/strings.xml | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index a6e5b128d..234a85b47 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -272,5 +272,5 @@ O aplikacji Wersja aplikacji - New version available + Dostępna nowa wersja diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 82fbc5f40..42441d045 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -30,7 +30,7 @@ En çok dinlenen şarkılar En çok dinlenen sanatçılar - Most played albums + En çok dinlenen albümler Arama @@ -44,7 +44,7 @@ Çalma Listeleri Topluluk çalma listeleri Öne çıkan listeler - Bookmarked + Favoriler Sonuç bulunamadı @@ -143,16 +143,16 @@ %d çalma listesi - %d week - %d weeks + %d hafta + %d hafta - %d month - %d months + %d ay + %d ay - %d year - %d years + %d yıl + %d yıl @@ -254,5 +254,5 @@ Hakkında Uygulama sürümü - New version available + Yeni sürüm mevcut From ee44cce44204898532bdbc652d634d14450a9ee1 Mon Sep 17 00:00:00 2001 From: siggi1984 <103368536+siggi1984@users.noreply.github.com> Date: Thu, 3 Aug 2023 20:40:30 +0200 Subject: [PATCH 77/84] Update strings.xml --- app/src/main/res/values-DE/strings.xml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/src/main/res/values-DE/strings.xml b/app/src/main/res/values-DE/strings.xml index 206db0322..bf0594890 100644 --- a/app/src/main/res/values-DE/strings.xml +++ b/app/src/main/res/values-DE/strings.xml @@ -13,9 +13,9 @@ - Wiedergabeverlauf + Hörverlaufverlauf Statistiken - Mood and Genres + Stimmungen & Genres Account Schnellauswahl Hören Sie sich einige Songs an, um Ihre Schnellauswahl zu treffen @@ -30,7 +30,7 @@ Meist gespielte Songs Meist gespielte Künstler - Most played albums + Meist gespielte Alben Suche @@ -147,12 +147,12 @@ %d weeks - %d month - %d months + %d Monat + %d Monate - %d year - %d years + %d Jahr + %d Jahre @@ -223,9 +223,9 @@ Speicher Zwischenspeicher - Image Cache - Song Cache - Max cache size + Bild-Cache + Song-Cache + Maximale Cache-Größe Unbegrenzt Alle Downloads entfernen Maximale Größe des Bild-Caches From 01ef2102a06311d1dfc0e91d7b2d4b14fb287fbd Mon Sep 17 00:00:00 2001 From: siggi1984 <103368536+siggi1984@users.noreply.github.com> Date: Thu, 3 Aug 2023 20:44:18 +0200 Subject: [PATCH 78/84] Update strings.xml --- app/src/main/res/values-DE/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-DE/strings.xml b/app/src/main/res/values-DE/strings.xml index bf0594890..54325065f 100644 --- a/app/src/main/res/values-DE/strings.xml +++ b/app/src/main/res/values-DE/strings.xml @@ -13,7 +13,7 @@ - Hörverlaufverlauf + Hörverlauf Statistiken Stimmungen & Genres Account From b53a2981c293da388ab9dd587ad7d3cf8222f2a1 Mon Sep 17 00:00:00 2001 From: siggi1984 <103368536+siggi1984@users.noreply.github.com> Date: Thu, 3 Aug 2023 20:48:18 +0200 Subject: [PATCH 79/84] Update strings.xml --- app/src/main/res/values-DE/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-DE/strings.xml b/app/src/main/res/values-DE/strings.xml index 54325065f..281ccaa8e 100644 --- a/app/src/main/res/values-DE/strings.xml +++ b/app/src/main/res/values-DE/strings.xml @@ -15,7 +15,7 @@ Hörverlauf Statistiken - Stimmungen & Genres + Stimmungen und Genres Account Schnellauswahl Hören Sie sich einige Songs an, um Ihre Schnellauswahl zu treffen From 9347a650b08462cb45bc03016117ebc03e4be560 Mon Sep 17 00:00:00 2001 From: siggi1984 <103368536+siggi1984@users.noreply.github.com> Date: Thu, 3 Aug 2023 21:02:34 +0200 Subject: [PATCH 80/84] Update strings.xml --- app/src/main/res/values-DE/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-DE/strings.xml b/app/src/main/res/values-DE/strings.xml index 281ccaa8e..adc998ec7 100644 --- a/app/src/main/res/values-DE/strings.xml +++ b/app/src/main/res/values-DE/strings.xml @@ -143,8 +143,8 @@ %d Wiedergabelisten - %d week - %d weeks + %d Woche + %d Wochen %d Monat From 43e359b989921bd0c47d5b5be13408231728b8e1 Mon Sep 17 00:00:00 2001 From: Oleksandr Tkachenko Date: Thu, 3 Aug 2023 23:02:57 +0300 Subject: [PATCH 81/84] Update strings.xml --- app/src/main/res/values-ru-rRU/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index 806a66cb0..bae17e29e 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -272,5 +272,5 @@ О приложении Версия приложения - New version available + Доступна новая версия From 3b7b92ca37e6a3df4bcb4ba051414582985d3b83 Mon Sep 17 00:00:00 2001 From: Oleksandr Tkachenko Date: Thu, 3 Aug 2023 23:03:03 +0300 Subject: [PATCH 82/84] Update strings.xml --- app/src/main/res/values-uk-rUA/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-uk-rUA/strings.xml b/app/src/main/res/values-uk-rUA/strings.xml index 8677e98f5..686cf8591 100644 --- a/app/src/main/res/values-uk-rUA/strings.xml +++ b/app/src/main/res/values-uk-rUA/strings.xml @@ -272,5 +272,5 @@ Про програму Версія застосунку - New version available + Доступна нова версія From d91f4c81fd49046ab46bec68374e3079c930ed0f Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Fri, 4 Aug 2023 11:48:31 +0800 Subject: [PATCH 83/84] Update readme --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f40518fe7..442d9899c 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,13 @@ A Material 3 YouTube Music client for Android - Play songs from YT/YT Music without ads - Background playback - Search songs, videos, albums, and playlists from YouTube Music +- Login support - Library management - Cache and download songs for offline playback - Synchronized lyrics - Skip silence - Audio normalization +- Adjust tempo/pitch - Dynamic theme - Localization - Android Auto support @@ -66,7 +68,8 @@ before you create a pull request. ## Donate -If you like InnerTune, you're welcome to send a donation. +If you like InnerTune, you're welcome to send a donation. Donations will support the development, +including bug fixes and new features. Liberapay Liberapay From 01722e98ae09f012c187a430c4eeb1b607f965ca Mon Sep 17 00:00:00 2001 From: Zion Huang Date: Fri, 4 Aug 2023 11:51:35 +0800 Subject: [PATCH 84/84] Bump to version 0.5.1 --- app/build.gradle.kts | 4 ++-- fastlane/metadata/android/en-US/changelogs/17.txt | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/17.txt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7ad5c77eb..5b368d687 100755 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -21,8 +21,8 @@ android { applicationId = "com.zionhuang.music" minSdk = 24 targetSdk = 33 - versionCode = 16 - versionName = "0.5.0" + versionCode = 17 + versionName = "0.5.1" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/fastlane/metadata/android/en-US/changelogs/17.txt b/fastlane/metadata/android/en-US/changelogs/17.txt new file mode 100644 index 000000000..70719318c --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/17.txt @@ -0,0 +1,5 @@ +- Login support +- Mood & Genres +- Better stats screen +- Bookmark artists +- Minor enhancement and bug fixes \ No newline at end of file