diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/DestinationFragmentView.kt b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/DestinationFragmentView.kt index 1975eeaad..b9a1a4795 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/browsing/DestinationFragmentView.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/browsing/DestinationFragmentView.kt @@ -9,19 +9,19 @@ import android.util.AttributeSet import android.widget.FrameLayout import androidx.core.os.BundleCompat import androidx.core.os.ParcelCompat -import androidx.core.os.bundleOf import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentContainerView import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentTransaction import org.jellyfin.androidtv.R import org.jellyfin.androidtv.ui.navigation.NavigationAction +import org.jellyfin.androidtv.util.createBundle import timber.log.Timber import java.util.Stack private class HistoryEntry( val name: Class, - val arguments: Bundle = bundleOf(), + val arguments: Bundle = createBundle(), var fragment: Fragment? = null, var savedState: Fragment.SavedState? = null, @@ -169,10 +169,10 @@ class DestinationFragmentView @JvmOverloads constructor( saveCurrentFragmentState() // Save state - return bundleOf( - BUNDLE_SUPER to super.onSaveInstanceState(), - BUNDLE_HISTORY to ArrayList(history), - ) + return createBundle { + putParcelable(BUNDLE_SUPER, super.onSaveInstanceState()) + putParcelableArrayList(BUNDLE_HISTORY, ArrayList(history)) + } } override fun onRestoreInstanceState(state: Parcelable?) { diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/navigation/ActivityDestinations.kt b/app/src/main/java/org/jellyfin/androidtv/ui/navigation/ActivityDestinations.kt index 57b0468e5..991b37c18 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/navigation/ActivityDestinations.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/navigation/ActivityDestinations.kt @@ -2,17 +2,17 @@ package org.jellyfin.androidtv.ui.navigation import android.content.Context import android.content.Intent -import androidx.core.os.bundleOf import org.jellyfin.androidtv.ui.playback.ExternalPlayerActivity import org.jellyfin.androidtv.ui.startup.StartupActivity +import org.jellyfin.androidtv.util.createBundle import kotlin.time.Duration object ActivityDestinations { fun externalPlayer(context: Context, position: Duration = Duration.ZERO) = Intent(context, ExternalPlayerActivity::class.java).apply { putExtras( - bundleOf( - ExternalPlayerActivity.EXTRA_POSITION to position.inWholeMilliseconds - ) + createBundle { + putLong(ExternalPlayerActivity.EXTRA_POSITION, position.inWholeMilliseconds) + } ) } diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/navigation/Destination.kt b/app/src/main/java/org/jellyfin/androidtv/ui/navigation/Destination.kt index 023b94584..9b3395652 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/navigation/Destination.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/navigation/Destination.kt @@ -1,20 +1,20 @@ package org.jellyfin.androidtv.ui.navigation import android.os.Bundle -import androidx.core.os.bundleOf import androidx.fragment.app.Fragment +import org.jellyfin.androidtv.util.createBundle import kotlin.reflect.KClass sealed interface Destination { data class Fragment( val fragment: KClass, - val arguments: Bundle = bundleOf(), + val arguments: Bundle = createBundle(), ) : Destination } inline fun fragmentDestination( - vararg arguments: Pair, + noinline arguments: (Bundle.() -> Unit)? = null, ) = Destination.Fragment( fragment = T::class, - arguments = bundleOf(*arguments), + arguments = createBundle(arguments), ) diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/navigation/Destinations.kt b/app/src/main/java/org/jellyfin/androidtv/ui/navigation/Destinations.kt index c6f89fe25..529a7383c 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/navigation/Destinations.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/navigation/Destinations.kt @@ -33,86 +33,88 @@ import java.util.UUID object Destinations { // General val home = fragmentDestination() - fun search(query: String? = null) = fragmentDestination( - SearchFragment.EXTRA_QUERY to query, - ) + fun search(query: String? = null) = fragmentDestination { + putString(SearchFragment.EXTRA_QUERY, query) + } // Browsing // TODO only pass item id instead of complete JSON to browsing destinations fun libraryBrowser(item: BaseItemDto, includeType: String? = null) = - fragmentDestination( - Extras.Folder to Json.encodeToString(item), - Extras.IncludeType to includeType, - ) + fragmentDestination { + putString(Extras.Folder, Json.encodeToString(item)) + putString(Extras.IncludeType, includeType) + } // TODO only pass item id instead of complete JSON to browsing destinations - fun librarySmartScreen(item: BaseItemDto) = fragmentDestination( - Extras.Folder to Json.encodeToString(item), - ) + fun librarySmartScreen(item: BaseItemDto) = fragmentDestination { + putString(Extras.Folder, Json.encodeToString(item)) + } // TODO only pass item id instead of complete JSON to browsing destinations - fun collectionBrowser(item: BaseItemDto) = fragmentDestination( - Extras.Folder to Json.encodeToString(item), - ) + fun collectionBrowser(item: BaseItemDto) = fragmentDestination { + putString(Extras.Folder, Json.encodeToString(item)) + } // TODO only pass item id instead of complete JSON to browsing destinations - fun folderBrowser(item: BaseItemDto) = fragmentDestination( - Extras.Folder to Json.encodeToString(item), - ) + fun folderBrowser(item: BaseItemDto) = fragmentDestination { + putString(Extras.Folder, Json.encodeToString(item)) + } // TODO only pass item id instead of complete JSON to browsing destinations fun libraryByGenres(item: BaseItemDto, includeType: String) = - fragmentDestination( - Extras.Folder to Json.encodeToString(item), - Extras.IncludeType to includeType, - ) + fragmentDestination { + putString(Extras.Folder, Json.encodeToString(item)) + putString(Extras.IncludeType, includeType) + } // TODO only pass item id instead of complete JSON to browsing destinations fun libraryByLetter(item: BaseItemDto, includeType: String) = - fragmentDestination( - Extras.Folder to Json.encodeToString(item), - Extras.IncludeType to includeType, - ) + fragmentDestination { + putString(Extras.Folder, Json.encodeToString(item)) + putString(Extras.IncludeType, includeType) + } // TODO only pass item id instead of complete JSON to browsing destinations fun librarySuggestions(item: BaseItemDto) = - fragmentDestination( - Extras.Folder to Json.encodeToString(item), - ) + fragmentDestination { + putString(Extras.Folder, Json.encodeToString(item)) + } // Item details - fun itemDetails(item: UUID) = fragmentDestination( - "ItemId" to item.toString(), - ) + fun itemDetails(item: UUID) = fragmentDestination { + putString("ItemId", item.toString()) + } // TODO only pass item id instead of complete JSON to browsing destinations fun channelDetails(item: UUID, channel: UUID, programInfo: BaseItemDto) = - fragmentDestination( - "ItemId" to item.toString(), - "ChannelId" to channel.toString(), - "ProgramInfo" to Json.encodeToString(programInfo), - ) + fragmentDestination { + putString("ItemId", item.toString()) + putString("ChannelId", channel.toString()) + putString("ProgramInfo", Json.encodeToString(programInfo)) + } // TODO only pass item id instead of complete JSON to browsing destinations fun seriesTimerDetails(item: UUID, seriesTimer: SeriesTimerInfoDto) = - fragmentDestination( - "ItemId" to item.toString(), - "SeriesTimer" to Json.encodeToString(seriesTimer), - ) + fragmentDestination { + putString("ItemId", item.toString()) + putString("SeriesTimer", Json.encodeToString(seriesTimer)) + } - fun itemList(item: UUID) = fragmentDestination( - "ItemId" to item.toString(), - ) + fun itemList(item: UUID) = fragmentDestination { + putString("ItemId", item.toString()) + } - fun musicFavorites(parent: UUID) = fragmentDestination( - "ParentId" to parent.toString(), - ) + fun musicFavorites(parent: UUID) = fragmentDestination { + putString("ParentId", parent.toString()) + } // Live TV val liveTvGuide = fragmentDestination() val liveTvSchedule = fragmentDestination() val liveTvRecordings = fragmentDestination() - val liveTvSeriesRecordings = fragmentDestination(Extras.IsLiveTvSeriesRecordings to true) + val liveTvSeriesRecordings = fragmentDestination { + putBoolean(Extras.IsLiveTvSeriesRecordings, true) + } // Playback val nowPlaying = fragmentDestination() @@ -122,26 +124,26 @@ object Destinations { autoPlay: Boolean, albumSortBy: ItemSortBy?, albumSortOrder: SortOrder?, - ) = fragmentDestination( - PhotoPlayerFragment.ARGUMENT_ITEM_ID to item.toString(), - PhotoPlayerFragment.ARGUMENT_ALBUM_SORT_BY to albumSortBy?.serialName, - PhotoPlayerFragment.ARGUMENT_ALBUM_SORT_ORDER to albumSortOrder?.serialName, - PhotoPlayerFragment.ARGUMENT_AUTO_PLAY to autoPlay, - ) + ) = fragmentDestination { + putString(PhotoPlayerFragment.ARGUMENT_ITEM_ID, item.toString()) + putString(PhotoPlayerFragment.ARGUMENT_ALBUM_SORT_BY, albumSortBy?.serialName) + putString(PhotoPlayerFragment.ARGUMENT_ALBUM_SORT_ORDER, albumSortOrder?.serialName) + putBoolean(PhotoPlayerFragment.ARGUMENT_AUTO_PLAY, autoPlay) + } - fun videoPlayer(position: Int?) = fragmentDestination( - "Position" to (position ?: 0) - ) + fun videoPlayer(position: Int?) = fragmentDestination { + putInt("Position", position ?: 0) + } - fun videoPlayerNew(position: Int?) = fragmentDestination( - VideoPlayerFragment.EXTRA_POSITION to position - ) + fun videoPlayerNew(position: Int?) = fragmentDestination { + putInt(VideoPlayerFragment.EXTRA_POSITION, position ?: 0) + } - fun nextUp(item: UUID) = fragmentDestination( - NextUpFragment.ARGUMENT_ITEM_ID to item.toString() - ) + fun nextUp(item: UUID) = fragmentDestination { + putString(NextUpFragment.ARGUMENT_ITEM_ID, item.toString()) + } - fun stillWatching(item: UUID) = fragmentDestination( - NextUpFragment.ARGUMENT_ITEM_ID to item.toString() - ) + fun stillWatching(item: UUID) = fragmentDestination { + putString(NextUpFragment.ARGUMENT_ITEM_ID, item.toString()) + } } diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/startup/StartupActivity.kt b/app/src/main/java/org/jellyfin/androidtv/ui/startup/StartupActivity.kt index 4242925d0..d4d6d5a91 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/startup/StartupActivity.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/startup/StartupActivity.kt @@ -6,7 +6,6 @@ import android.content.Intent import android.os.Bundle import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts -import androidx.core.os.bundleOf import androidx.core.view.isVisible import androidx.fragment.app.FragmentActivity import androidx.fragment.app.add @@ -41,6 +40,7 @@ import org.jellyfin.androidtv.ui.startup.fragment.ServerFragment import org.jellyfin.androidtv.ui.startup.fragment.SplashFragment import org.jellyfin.androidtv.ui.startup.fragment.StartupToolbarFragment import org.jellyfin.androidtv.util.applyTheme +import org.jellyfin.androidtv.util.createBundle import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.extensions.userLibraryApi import org.jellyfin.sdk.model.serializer.toUUIDOrNull @@ -185,9 +185,9 @@ class StartupActivity : FragmentActivity() { private fun showServer(id: UUID) = supportFragmentManager.commit { replace(R.id.content_view) add( - R.id.content_view, null, bundleOf( - ServerFragment.ARG_SERVER_ID to id.toString() - ) + R.id.content_view, null, createBundle { + putString(ServerFragment.ARG_SERVER_ID, id.toString()) + } ) } diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/startup/fragment/SelectServerFragment.kt b/app/src/main/java/org/jellyfin/androidtv/ui/startup/fragment/SelectServerFragment.kt index e22ebb1c2..e2c0c22b8 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/startup/fragment/SelectServerFragment.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/startup/fragment/SelectServerFragment.kt @@ -17,7 +17,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.colorResource import androidx.compose.ui.unit.dp -import androidx.core.os.bundleOf import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.add @@ -46,6 +45,7 @@ import org.jellyfin.androidtv.ui.base.Text import org.jellyfin.androidtv.ui.startup.StartupViewModel import org.jellyfin.androidtv.util.ListAdapter import org.jellyfin.androidtv.util.MenuBuilder +import org.jellyfin.androidtv.util.createBundle import org.jellyfin.androidtv.util.getSummary import org.koin.androidx.viewmodel.ext.android.activityViewModel import org.koin.compose.koinInject @@ -73,9 +73,9 @@ class SelectServerFragment : Fragment() { add( R.id.content_view, null, - bundleOf( - ServerFragment.ARG_SERVER_ID to server.id.toString() - ) + createBundle { + putString(ServerFragment.ARG_SERVER_ID, server.id.toString()) + } ) addToBackStack(null) } @@ -102,9 +102,9 @@ class SelectServerFragment : Fragment() { add( R.id.content_view, null, - bundleOf( - ServerFragment.ARG_SERVER_ID to state.id.toString() - ) + createBundle { + putString(ServerFragment.ARG_SERVER_ID, state.id.toString()) + } ) } } else { @@ -115,12 +115,16 @@ class SelectServerFragment : Fragment() { // Show error as toast if (state is UnableToConnectState) { - Toast.makeText(requireContext(), getString( - R.string.server_connection_failed_candidates, - state.addressCandidates - .map { "${it.key} ${it.value.getSummary(requireContext())}" } - .joinToString(prefix = "\n", separator = "\n") - ), Toast.LENGTH_LONG).show() + Toast.makeText( + requireContext(), + getString( + R.string.server_connection_failed_candidates, + state.addressCandidates + .map { "${it.key} ${it.value.getSummary(requireContext())}" } + .joinToString(prefix = "\n", separator = "\n") + ), + Toast.LENGTH_LONG, + ).show() } } }.launchIn(lifecycleScope) diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/startup/fragment/ServerAddFragment.kt b/app/src/main/java/org/jellyfin/androidtv/ui/startup/fragment/ServerAddFragment.kt index 02b2ccb4f..85e6e01fb 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/startup/fragment/ServerAddFragment.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/startup/fragment/ServerAddFragment.kt @@ -5,7 +5,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.inputmethod.EditorInfo -import androidx.core.os.bundleOf import androidx.fragment.app.Fragment import androidx.fragment.app.add import androidx.fragment.app.commit @@ -19,6 +18,7 @@ import org.jellyfin.androidtv.auth.model.ConnectingState import org.jellyfin.androidtv.auth.model.UnableToConnectState import org.jellyfin.androidtv.databinding.FragmentServerAddBinding import org.jellyfin.androidtv.ui.startup.ServerAddViewModel +import org.jellyfin.androidtv.util.createBundle import org.jellyfin.androidtv.util.getSummary import org.koin.androidx.viewmodel.ext.android.viewModel @@ -96,9 +96,9 @@ class ServerAddFragment : Fragment() { add( R.id.content_view, null, - bundleOf( - ServerFragment.ARG_SERVER_ID to state.id.toString() - ) + createBundle { + putString(ServerFragment.ARG_SERVER_ID, state.id.toString()) + } ) } diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/startup/fragment/ServerFragment.kt b/app/src/main/java/org/jellyfin/androidtv/ui/startup/fragment/ServerFragment.kt index 811368161..e31eae181 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/startup/fragment/ServerFragment.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/startup/fragment/ServerFragment.kt @@ -6,7 +6,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast -import androidx.core.os.bundleOf import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.fragment.app.Fragment @@ -41,6 +40,7 @@ import org.jellyfin.androidtv.ui.card.UserCardView import org.jellyfin.androidtv.ui.startup.StartupViewModel import org.jellyfin.androidtv.util.ListAdapter import org.jellyfin.androidtv.util.MarkdownRenderer +import org.jellyfin.androidtv.util.createBundle import org.jellyfin.sdk.model.serializer.toUUIDOrNull import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.activityViewModel @@ -78,10 +78,10 @@ class ServerFragment : Fragment() { AuthenticatingState -> Unit AuthenticatedState -> Unit // Actions - RequireSignInState -> navigateFragment(bundleOf( - UserLoginFragment.ARG_SERVER_ID to server.id.toString(), - UserLoginFragment.ARG_USERNAME to user.name, - )) + RequireSignInState -> navigateFragment(createBundle { + putString(UserLoginFragment.ARG_SERVER_ID, server.id.toString()) + putString(UserLoginFragment.ARG_USERNAME, user.name) + }) // Errors ServerUnavailableState, is ApiClientErrorLoginState -> Toast.makeText(context, R.string.server_connection_failed, Toast.LENGTH_LONG).show() @@ -142,10 +142,10 @@ class ServerFragment : Fragment() { binding.addUserButton.setOnClickListener { navigateFragment( - args = bundleOf( - UserLoginFragment.ARG_SERVER_ID to server.id.toString(), - UserLoginFragment.ARG_USERNAME to null - ) + args = createBundle { + putString(UserLoginFragment.ARG_SERVER_ID, server.id.toString()) + putString(UserLoginFragment.ARG_USERNAME, null) + } ) } @@ -169,7 +169,7 @@ class ServerFragment : Fragment() { } private inline fun navigateFragment( - args: Bundle = bundleOf(), + args: Bundle = createBundle(), keepToolbar: Boolean = false, keepHistory: Boolean = true, ) { diff --git a/app/src/main/java/org/jellyfin/androidtv/util/BundleExtensions.kt b/app/src/main/java/org/jellyfin/androidtv/util/BundleExtensions.kt index 17a449edb..83ff77e44 100644 --- a/app/src/main/java/org/jellyfin/androidtv/util/BundleExtensions.kt +++ b/app/src/main/java/org/jellyfin/androidtv/util/BundleExtensions.kt @@ -8,3 +8,7 @@ inline fun Bundle.getValue(key: String): T? = when { Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> getParcelable(key, T::class.java) else -> get(key) as T? } + +fun createBundle(init: (Bundle.() -> Unit)? = null) = Bundle().also { bundle -> + if (init != null) bundle.init() +}