diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/api/HomeFeed.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/api/HomeFeed.kt index 1b969d71d..7a4f6d763 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/api/HomeFeed.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/api/HomeFeed.kt @@ -20,9 +20,11 @@ import com.toasterofbread.spmp.model.mediaitem.enums.MediaItemType import com.toasterofbread.spmp.model.mediaitem.enums.SongType import com.toasterofbread.spmp.resources.uilocalisation.LocalisedYoutubeString import com.toasterofbread.spmp.ui.component.MediaItemLayout +import com.toasterofbread.spmp.ui.layout.mainpage.FilterChip import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import okhttp3.Request +import okio.use import java.time.Duration private val CACHE_LIFETIME = Duration.ofDays(1) @@ -33,7 +35,7 @@ suspend fun getHomeFeed( allow_cached: Boolean = true, params: String? = null, continuation: String? = null -): Result, String?, List>?>> = withContext(Dispatchers.IO) { +): Result, String?, List?>> = withContext(Dispatchers.IO) { val hl = SpMp.data_language val suffix = params ?: "" val rows_cache_key = "feed_rows$suffix" @@ -41,21 +43,15 @@ suspend fun getHomeFeed( val chips_cache_key = "feed_chips$suffix" if (allow_cached && continuation == null) { - val cached_rows = Cache.get(rows_cache_key) - if (cached_rows != null) { + Cache.get(rows_cache_key)?.use { cached_rows -> val rows = Api.klaxon.parseArray(cached_rows)!! - cached_rows.close() - val ctoken = Cache.get(ctoken_cache_key)?.run { - val ctoken = readText() - close() - ctoken + val ctoken = Cache.get(ctoken_cache_key)?.use { + it.readText() } - val chips = Cache.get(chips_cache_key)?.run { - val chips: List> = Api.klaxon.parseArray(this)!! - close() - chips.map { Pair(it[0] as Int, it[1] as String) } + val chips: List? = Cache.get(chips_cache_key)?.use { + Api.klaxon.parseArray(it)!! } return@withContext Result.success(Triple(rows, ctoken, chips)) @@ -154,14 +150,14 @@ suspend fun getHomeFeed( if (continuation == null) { Cache.set(rows_cache_key, Api.klaxon.toJsonString(rows).reader(), CACHE_LIFETIME) Cache.set(ctoken_cache_key, ctoken?.reader(), CACHE_LIFETIME) - Cache.set(chips_cache_key, chips?.let { Api.klaxon.toJsonString(it.map { chip -> listOf(chip.first, chip.second) }).reader() }, CACHE_LIFETIME) + Cache.set(chips_cache_key, Api.klaxon.toJsonString(chips).reader(), CACHE_LIFETIME) } return@runCatching Triple(rows, ctoken, chips) } } -private fun processRows(rows: List, hl: String): List { +private suspend fun processRows(rows: List, hl: String): List { val ret = mutableListOf() for (row in rows) { if (!row.implemented) { @@ -199,8 +195,8 @@ private fun processRows(rows: List, hl: String): List, hl: String): List, hl: String): List Artist.fromId(browse_endpoint.browseId) - "MUSIC_PAGE_TYPE_PLAYLIST" -> AccountPlaylist.fromId(browse_endpoint.browseId).editPlaylistData { supplyTitle(header.title.first_text) } - else -> throw NotImplementedError(browse_endpoint.toString()) + val media_item = MediaItemType.fromBrowseEndpointType(page_type)!!.fromId(browse_endpoint.browseId).apply { + editData { + header.title.runs?.getOrNull(0)?.also { title -> + supplyTitle(title.text) + } + } } val thumbnail_source = @@ -242,8 +240,8 @@ private fun processRows(rows: List, hl: String): List>? = + fun getHeaderChips(): List? = contents?.singleColumnBrowseResultsRenderer?.tabs?.first()?.tabRenderer?.content?.sectionListRenderer?.header?.chipCloudRenderer?.chips?.map { - Pair( - LocalisedYoutubeString.filterChip(it.chipCloudChipRenderer.text!!.first_text) ?: throw NotImplementedError(it.chipCloudChipRenderer.text.first_text), + FilterChip( + LocalisedYoutubeString.Type.FILTER_CHIP.create(it.chipCloudChipRenderer.text!!.first_text), it.chipCloudChipRenderer.navigationEndpoint.browseEndpoint!!.params!! ) } diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/api/LoadMediaitem.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/api/LoadMediaitem.kt index 1ef0e9580..181050926 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/api/LoadMediaitem.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/api/LoadMediaitem.kt @@ -84,7 +84,7 @@ suspend fun loadBrowseId(browse_id: String, params: String? = null): Result Int): YoutubeUIL en to "Focus", ja to "フォーカス" ) + add( + en to "Podcasts", + ja to "ポッドキャスト" + ) } } diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/resources/uilocalisation/YoutubeUILocalisation.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/resources/uilocalisation/YoutubeUILocalisation.kt index 1659bfa2d..dd4148943 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/resources/uilocalisation/YoutubeUILocalisation.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/resources/uilocalisation/YoutubeUILocalisation.kt @@ -5,9 +5,10 @@ import com.toasterofbread.spmp.model.Settings import com.toasterofbread.spmp.model.mediaitem.enums.MediaItemType import com.toasterofbread.spmp.resources.getString -class LocalisedYoutubeString( +class LocalisedYoutubeString private constructor( val key: String, val type: Type, + @Suppress("MemberVisibilityCanBePrivate") val source_language: Int? = null ) { private var localised: Pair? = null @@ -18,7 +19,11 @@ class LocalisedYoutubeString( HOME_FEED, OWN_CHANNEL, ARTIST_PAGE, - SEARCH_PAGE + SEARCH_PAGE, + FILTER_CHIP; + + fun create(key: String, source_language: Int? = current_source_language): LocalisedYoutubeString = + LocalisedYoutubeString(key, this, source_language) } init { @@ -27,60 +32,52 @@ class LocalisedYoutubeString( } } - private fun getLocalised() { + private fun getLocalised(): Pair { if (localised == null) { - localised = when (type) { - Type.RAW -> Pair(key, null) - Type.APP -> Pair(getString(key), null) - Type.HOME_FEED -> { - val localisation = SpMp.yt_ui_localisation.localiseHomeFeedString(key, source_language!!) - if (localisation != null) { - localisation - } else { - println("WARNING: Using raw key '$key' as home feed string") - Pair(key, null) - } + // TODO + val data = SpMp.yt_ui_localisation + + val strings = when (type) { + Type.RAW -> { + localised = Pair(key, null) + return localised!! + } + Type.APP -> { + localised = Pair(getString(key), null) + return localised!! } - Type.OWN_CHANNEL -> SpMp.yt_ui_localisation.localiseOwnChannelString(key, source_language!!) - Type.ARTIST_PAGE -> SpMp.yt_ui_localisation.localiseArtistPageString(key, source_language!!) - Type.SEARCH_PAGE -> SpMp.yt_ui_localisation.localiseSearchPageString(key, source_language!!) + Type.HOME_FEED -> data.HOME_FEED_STRINGS + Type.OWN_CHANNEL -> data.OWN_CHANNEL_STRINGS + Type.ARTIST_PAGE -> data.ARTIST_PAGE_STRINGS + Type.SEARCH_PAGE -> data.SEARCH_PAGE_STRINGS + Type.FILTER_CHIP -> data.FILTER_CHIPS } + + localised = data.getLocalised(key, strings, source_language!!) if (localised == null) { - println("WARNING: Localised string key '$key' of type $type has not been implemented. Source lang: ${SpMp.getLanguageCode(source_language!!)}") localised = Pair(key, null) + SpMp.Log.warning("String key '$key' of type $type has not been localised (source lang=${SpMp.getLanguageCode(source_language)})") } } + + return localised!! } fun getString(): String { - getLocalised() - return localised!!.first + return getLocalised().first } fun getID(): YoutubeUILocalisation.StringID? { - getLocalised() - return localised!!.second + return getLocalised().second } companion object { private val current_source_language: Int get() = Settings.KEY_LANG_DATA.get() - fun temp(string: String): LocalisedYoutubeString = LocalisedYoutubeString("$string // TEMP", Type.RAW, current_source_language) - - fun raw(string: String): LocalisedYoutubeString = LocalisedYoutubeString(string, Type.RAW, current_source_language) - fun app(key: String): LocalisedYoutubeString = LocalisedYoutubeString(key, Type.APP, current_source_language) - fun homeFeed(key: String): LocalisedYoutubeString = LocalisedYoutubeString(key, Type.HOME_FEED, current_source_language) - fun ownChannel(key: String): LocalisedYoutubeString = LocalisedYoutubeString(key, Type.OWN_CHANNEL, current_source_language) - fun artistPage(key: String): LocalisedYoutubeString = LocalisedYoutubeString(key, Type.ARTIST_PAGE, current_source_language) - fun searchPage(key: String): LocalisedYoutubeString = LocalisedYoutubeString(key, Type.SEARCH_PAGE, current_source_language) - - fun filterChip(key: String): Int? = SpMp.yt_ui_localisation.getFilterChipIndex(key, current_source_language) - fun filterChip(index: Int): String = SpMp.yt_ui_localisation.getFilterChip(index) - fun mediaItemPage(key: String, item_type: MediaItemType): LocalisedYoutubeString = when (item_type) { - MediaItemType.ARTIST, MediaItemType.PLAYLIST_BROWSEPARAMS -> artistPage(key) + MediaItemType.ARTIST, MediaItemType.PLAYLIST_BROWSEPARAMS -> Type.ARTIST_PAGE.create(key) else -> throw NotImplementedError(item_type.name) } } @@ -120,13 +117,13 @@ class YoutubeUILocalisation(languages: List) { check(it != -1) } - private val HOME_FEED_STRINGS: LocalisationSet = getYoutubeHomeFeedLocalisations { getLanguage(it, languages) } - private val OWN_CHANNEL_STRINGS: LocalisationSet = getYoutubeOwnChannelLocalisations { getLanguage(it, languages) } - private val ARTIST_PAGE_STRINGS: LocalisationSet = getYoutubeArtistPageLocalisations { getLanguage(it, languages) } - private val SEARCH_PAGE_STRINGS: LocalisationSet = getYoutubeSearchPageLocalisations { getLanguage(it, languages) } - private val FILTER_CHIPS: LocalisationSet = getYoutubeFilterChipsLocalisations { getLanguage(it, languages) } + internal val HOME_FEED_STRINGS: LocalisationSet = getYoutubeHomeFeedLocalisations { getLanguage(it, languages) } + internal val OWN_CHANNEL_STRINGS: LocalisationSet = getYoutubeOwnChannelLocalisations { getLanguage(it, languages) } + internal val ARTIST_PAGE_STRINGS: LocalisationSet = getYoutubeArtistPageLocalisations { getLanguage(it, languages) } + internal val SEARCH_PAGE_STRINGS: LocalisationSet = getYoutubeSearchPageLocalisations { getLanguage(it, languages) } + internal val FILTER_CHIPS: LocalisationSet = getYoutubeFilterChipsLocalisations { getLanguage(it, languages) } - private fun getLocalised(string: String, localisations: LocalisationSet, source_language: Int): Pair? { + internal fun getLocalised(string: String, localisations: LocalisationSet, source_language: Int): Pair? { val target: Int = Settings.KEY_LANG_UI.get() for (localisation in localisations.items.withIndex()) { @@ -139,21 +136,4 @@ class YoutubeUILocalisation(languages: List) { return null } - - fun localiseHomeFeedString(string: String, source_language: Int): Pair? = getLocalised(string, HOME_FEED_STRINGS, source_language) - fun localiseOwnChannelString(string: String, source_language: Int): Pair? = getLocalised(string, OWN_CHANNEL_STRINGS, source_language) - fun localiseArtistPageString(string: String, source_language: Int): Pair? = getLocalised(string, ARTIST_PAGE_STRINGS, source_language) - fun localiseSearchPageString(string: String, source_language: Int): Pair? = getLocalised(string, SEARCH_PAGE_STRINGS, source_language) - - fun getFilterChipIndex(string: String, source_language: Int): Int? { - val index = FILTER_CHIPS.items.indexOfFirst { it[source_language]?.first == string } - if (index == -1) { - return null - } - return index - } - fun getFilterChip(index: Int): String { - val chip = FILTER_CHIPS.items.elementAt(index)[Settings.KEY_LANG_UI.get()]!! - return chip.second ?: chip.first - } } diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/mainpage/FilterChip.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/mainpage/FilterChip.kt new file mode 100644 index 000000000..0eca3e3c0 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/mainpage/FilterChip.kt @@ -0,0 +1,8 @@ +package com.toasterofbread.spmp.ui.layout.mainpage + +import com.toasterofbread.spmp.resources.uilocalisation.LocalisedYoutubeString + +data class FilterChip( + val text: LocalisedYoutubeString, + val params: String +) diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/mainpage/MainPage.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/mainpage/MainPage.kt index 013d19ed6..ced3b0b85 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/mainpage/MainPage.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/mainpage/MainPage.kt @@ -58,7 +58,7 @@ fun MainPage( feed_load_state: MutableState, feed_load_error: Throwable?, can_continue_feed: Boolean, - getFilterChips: () -> List>?, + getFilterChips: () -> List?, getSelectedFilterChip: () -> Int?, pill_menu: PillMenu, loadFeed: (filter_chip: Int?, continuation: Boolean) -> Unit diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/mainpage/MainPageTopBar.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/mainpage/MainPageTopBar.kt index 9a1e71c72..a8f5222e3 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/mainpage/MainPageTopBar.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/mainpage/MainPageTopBar.kt @@ -26,7 +26,7 @@ import com.toasterofbread.spmp.ui.theme.Theme @Composable fun MainPageTopBar( auth_info: YoutubeMusicAuthInfo, - getFilterChips: () -> List>?, + getFilterChips: () -> List?, getSelectedFilterChip: () -> Int?, onFilterChipSelected: (Int?) -> Unit, modifier: Modifier = Modifier @@ -98,34 +98,35 @@ private fun RadioBuilderButton() { @OptIn(ExperimentalMaterial3Api::class) @Composable -private fun ColumnScope.FilterChipsRow(getFilterChips: () -> List>?, getSelectedFilterChip: () -> Int?, onFilterChipSelected: (Int?) -> Unit) { +private fun ColumnScope.FilterChipsRow(getFilterChips: () -> List?, getSelectedFilterChip: () -> Int?, onFilterChipSelected: (Int?) -> Unit) { val enabled: Boolean by mutableSettingsState(Settings.KEY_FEED_SHOW_FILTERS) val filter_chips = getFilterChips() - AnimatedVisibility(enabled && filter_chips?.isNotEmpty() == true) { - LazyRow(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) { - val selected_filter_chip = getSelectedFilterChip() + Crossfade(if (enabled) filter_chips else null) { chips -> + if (chips?.isNotEmpty() == true) { + LazyRow(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) { + val selected_filter_chip = getSelectedFilterChip() - items(filter_chips?.size ?: 0) { i -> - val chip_index = filter_chips!![i].first - ElevatedFilterChip( - i == selected_filter_chip, - { - onFilterChipSelected(if (i == selected_filter_chip) null else i) - }, - { Text(LocalisedYoutubeString.filterChip(chip_index)) }, - colors = with(Theme.current) { - FilterChipDefaults.elevatedFilterChipColors( - containerColor = background, - labelColor = on_background, - selectedContainerColor = accent, - selectedLabelColor = on_accent + items(chips.size) { i -> + ElevatedFilterChip( + i == selected_filter_chip, + { + onFilterChipSelected(if (i == selected_filter_chip) null else i) + }, + { Text(chips[i].text.getString()) }, + colors = with(Theme.current) { + FilterChipDefaults.elevatedFilterChipColors( + containerColor = background, + labelColor = on_background, + selectedContainerColor = accent, + selectedLabelColor = on_accent + ) + }, + border = FilterChipDefaults.filterChipBorder( + borderColor = Theme.current.on_background ) - }, - border = FilterChipDefaults.filterChipBorder( - borderColor = Theme.current.on_background ) - ) + } } } } diff --git a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/mainpage/PlayerStateImpl.kt b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/mainpage/PlayerStateImpl.kt index 01af93407..faa411530 100644 --- a/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/mainpage/PlayerStateImpl.kt +++ b/shared/src/commonMain/kotlin/com/toasterofbread/spmp/ui/layout/mainpage/PlayerStateImpl.kt @@ -507,7 +507,7 @@ class PlayerStateImpl(private val context: PlatformContext): PlayerState(null, n private val main_page_scroll_state = LazyListState() private var main_page_layouts: List? by mutableStateOf(null) private var main_page_load_error: Throwable? by mutableStateOf(null) - private var main_page_filter_chips: List>? by mutableStateOf(null) + private var main_page_filter_chips: List? by mutableStateOf(null) private var main_page_selected_filter_chip: Int? by mutableStateOf(null) private val feed_load_state = mutableStateOf(FeedLoadState.PREINIT) @@ -526,7 +526,7 @@ class PlayerStateImpl(private val context: PlatformContext): PlayerState(null, n check(feed_load_state.value == FeedLoadState.PREINIT || feed_load_state.value == FeedLoadState.NONE) - val filter_params = filter_chip?.let { main_page_filter_chips!![it].second } + val filter_params = filter_chip?.let { main_page_filter_chips!![it].params } feed_load_state.value = if (continue_feed) FeedLoadState.CONTINUING else FeedLoadState.LOADING coroutineContext.job.invokeOnCompletion { @@ -662,7 +662,7 @@ private suspend fun loadFeedLayouts( allow_cached: Boolean, params: String?, continuation: String? = null, -): Result, String?, List>?>> { +): Result, String?, List?>> { val result = getHomeFeed( allow_cached = allow_cached, min_rows = min_rows,