refactor: Use Navigation Safe Args

This commit is contained in:
Isira Seneviratne 2023-02-19 18:42:42 +05:30
parent c7e9b9d7b2
commit f573ec7434
10 changed files with 84 additions and 72 deletions

View File

@ -4,6 +4,7 @@ plugins {
id("kotlin-android")
id("kotlinx-serialization")
id("kotlin-parcelize")
id("androidx.navigation.safeargs.kotlin")
}
android {

View File

@ -11,6 +11,7 @@ import androidx.core.os.bundleOf
import androidx.core.os.postDelayed
import androidx.fragment.app.commitNow
import androidx.fragment.app.replace
import com.github.libretube.NavDirections
import com.github.libretube.R
import com.github.libretube.constants.IntentData
import com.github.libretube.constants.PreferenceKeys
@ -29,8 +30,7 @@ object NavigationHelper {
if (channelId == null) return
val activity = ContextHelper.unwrapActivity(context)
val bundle = bundleOf(IntentData.channelId to channelId)
activity.navController.navigate(R.id.channelFragment, bundle)
activity.navController.navigate(NavDirections.openChannel(channelId))
try {
if (activity.binding.mainMotionLayout.progress == 0.toFloat()) {
activity.binding.mainMotionLayout.transitionToEnd()
@ -86,11 +86,7 @@ object NavigationHelper {
if (playlistId == null) return
val activity = ContextHelper.unwrapActivity(context)
val bundle = bundleOf(
IntentData.playlistId to playlistId,
IntentData.playlistType to playlistType
)
activity.navController.navigate(R.id.playlistFragment, bundle)
activity.navController.navigate(NavDirections.openPlaylist(playlistId, playlistType))
}
/**

View File

@ -22,6 +22,7 @@ import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.NavDirections
import com.github.libretube.R
import com.github.libretube.compat.PictureInPictureCompat
import com.github.libretube.constants.IntentData
@ -269,11 +270,8 @@ class MainActivity : BaseActivity() {
searchView = searchItem.actionView as SearchView
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
navController.navigate(
R.id.searchResultFragment,
bundleOf(IntentData.query to query)
)
override fun onQueryTextSubmit(query: String): Boolean {
navController.navigate(NavDirections.showSearchResults(query))
searchView.clearFocus()
return true
}
@ -406,22 +404,13 @@ class MainActivity : BaseActivity() {
}
intent?.getStringExtra(IntentData.channelId)?.let {
navController.navigate(
R.id.channelFragment,
bundleOf(IntentData.channelId to it)
)
navController.navigate(NavDirections.openChannel(channelId = it))
}
intent?.getStringExtra(IntentData.channelName)?.let {
navController.navigate(
R.id.channelFragment,
bundleOf(IntentData.channelName to it)
)
navController.navigate(NavDirections.openChannel(channelName = it))
}
intent?.getStringExtra(IntentData.playlistId)?.let {
navController.navigate(
R.id.playlistFragment,
bundleOf(IntentData.playlistId to it)
)
navController.navigate(NavDirections.openPlaylist(playlistId = it))
}
intent?.getStringExtra(IntentData.videoId)?.let {
NavigationHelper.navigateVideo(

View File

@ -10,6 +10,7 @@ import androidx.core.view.children
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.GridLayoutManager
import com.github.libretube.R
import com.github.libretube.api.RetrofitInstance
@ -32,16 +33,17 @@ import com.github.libretube.ui.dialogs.ShareDialog
import com.github.libretube.ui.extensions.setupSubscriptionButton
import com.github.libretube.ui.sheets.AddChannelToGroupSheet
import com.github.libretube.util.deArrow
import java.io.IOException
import kotlin.math.ceil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import retrofit2.HttpException
import java.io.IOException
import kotlin.math.ceil
class ChannelFragment : DynamicLayoutManagerFragment() {
private var _binding: FragmentChannelBinding? = null
private val binding get() = _binding!!
private val args by navArgs<ChannelFragmentArgs>()
private var channelId: String? = null
private var channelName: String? = null
@ -61,12 +63,8 @@ class ChannelFragment : DynamicLayoutManagerFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
channelId = it.getString(IntentData.channelId)?.toID()
channelName = it.getString(IntentData.channelName)
?.replace("/c/", "")
?.replace("/user/", "")
}
channelId = args.channelId?.toID()
channelName = args.channelName?.replace("/c/", "")?.replace("/user/", "")
}
override fun onCreateView(

View File

@ -47,6 +47,7 @@ import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.hls.HlsMediaSource
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
import androidx.recyclerview.widget.LinearLayoutManager
import com.github.libretube.NavDirections
import com.github.libretube.R
import com.github.libretube.api.CronetHelper
import com.github.libretube.api.JsonHelper
@ -114,16 +115,16 @@ import com.github.libretube.util.TextUtils
import com.github.libretube.util.TextUtils.toTimeInSeconds
import com.github.libretube.util.YoutubeHlsPlaylistParser
import com.github.libretube.util.deArrow
import java.io.IOException
import java.util.*
import java.util.concurrent.Executors
import kotlin.math.abs
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.encodeToString
import retrofit2.HttpException
import java.io.IOException
import java.util.*
import java.util.concurrent.Executors
import kotlin.math.abs
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
class PlayerFragment : Fragment(), OnlinePlayerOptions {
@ -627,8 +628,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
if (!this::streams.isInitialized) return@setOnClickListener
val activity = view?.context as MainActivity
val bundle = bundleOf(IntentData.channelId to streams.uploaderUrl)
activity.navController.navigate(R.id.channelFragment, bundle)
activity.navController.navigate(NavDirections.openChannel(streams.uploaderUrl))
activity.binding.mainMotionLayout.transitionToEnd()
binding.playerMotionLayout.transitionToEnd()
}

View File

@ -13,9 +13,11 @@ import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.NavDirections
import com.github.libretube.R
import com.github.libretube.api.PlaylistsHelper
import com.github.libretube.api.RetrofitInstance
@ -29,7 +31,6 @@ import com.github.libretube.enums.PlaylistType
import com.github.libretube.extensions.TAG
import com.github.libretube.extensions.ceilHalf
import com.github.libretube.extensions.dpToPx
import com.github.libretube.extensions.serializable
import com.github.libretube.extensions.toID
import com.github.libretube.extensions.toastFromMainDispatcher
import com.github.libretube.helpers.ImageHelper
@ -51,9 +52,10 @@ import kotlinx.coroutines.withContext
class PlaylistFragment : DynamicLayoutManagerFragment() {
private var _binding: FragmentPlaylistBinding? = null
private val binding get() = _binding!!
private val args by navArgs<PlaylistFragmentArgs>()
// general playlist information
private var playlistId: String? = null
private lateinit var playlistId: String
private var playlistName: String? = null
private var playlistType = PlaylistType.PUBLIC
@ -75,10 +77,8 @@ class PlaylistFragment : DynamicLayoutManagerFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
playlistId = it.getString(IntentData.playlistId)!!.toID()
playlistType = it.serializable(IntentData.playlistType) ?: PlaylistType.PUBLIC
}
playlistId = args.playlistId
playlistType = args.playlistType
}
override fun onCreateView(
@ -100,7 +100,7 @@ class PlaylistFragment : DynamicLayoutManagerFragment() {
binding.playlistProgress.isVisible = true
isBookmarked = runBlocking(Dispatchers.IO) {
DatabaseHolder.Database.playlistBookmarkDao().includes(playlistId!!)
DatabaseHolder.Database.playlistBookmarkDao().includes(playlistId)
}
updateBookmarkRes()
@ -127,7 +127,7 @@ class PlaylistFragment : DynamicLayoutManagerFragment() {
lifecycleScope.launch {
val response = try {
withContext(Dispatchers.IO) {
PlaylistsHelper.getPlaylist(playlistId!!)
PlaylistsHelper.getPlaylist(playlistId)
}
} catch (e: Exception) {
Log.e(TAG(), e.toString())
@ -146,10 +146,8 @@ class PlaylistFragment : DynamicLayoutManagerFragment() {
binding.playlistInfo.text = getChannelAndVideoString(response, response.videos)
binding.playlistInfo.setOnClickListener {
(context as MainActivity).navController.navigate(
R.id.channelFragment,
bundleOf(IntentData.channelId to response.uploaderUrl?.toID())
)
(context as MainActivity).navController
.navigate(NavDirections.openChannel(response.uploaderUrl))
}
binding.playlistDescription.text = response.description?.parseAsHtml()
@ -162,7 +160,7 @@ class PlaylistFragment : DynamicLayoutManagerFragment() {
binding.optionsMenu.setOnClickListener {
val sheet = PlaylistOptionsBottomSheet()
sheet.arguments = bundleOf(
IntentData.playlistId to playlistId.orEmpty(),
IntentData.playlistId to playlistId,
IntentData.playlistName to playlistName.orEmpty(),
IntentData.playlistType to playlistType
)
@ -214,10 +212,10 @@ class PlaylistFragment : DynamicLayoutManagerFragment() {
lifecycleScope.launch(Dispatchers.IO) {
if (!isBookmarked) {
DatabaseHolder.Database.playlistBookmarkDao()
.deleteById(playlistId!!)
.deleteById(playlistId)
} else {
DatabaseHolder.Database.playlistBookmarkDao()
.insert(response.toPlaylistBookmark(playlistId!!))
.insert(response.toPlaylistBookmark(playlistId))
}
}
}
@ -295,7 +293,7 @@ class PlaylistFragment : DynamicLayoutManagerFragment() {
playlistAdapter = PlaylistAdapter(
playlistFeed,
videos.toMutableList(),
playlistId!!,
playlistId,
playlistType
)
binding.playlistRecView.adapter = playlistAdapter
@ -373,9 +371,9 @@ class PlaylistFragment : DynamicLayoutManagerFragment() {
withContext(Dispatchers.IO) {
// load locally stored playlists with the auth api
if (playlistType == PlaylistType.PRIVATE) {
RetrofitInstance.authApi.getPlaylistNextPage(playlistId!!, nextPage!!)
RetrofitInstance.authApi.getPlaylistNextPage(playlistId, nextPage!!)
} else {
RetrofitInstance.api.getPlaylistNextPage(playlistId!!, nextPage!!)
RetrofitInstance.api.getPlaylistNextPage(playlistId, nextPage!!)
}
}
} catch (e: Exception) {

View File

@ -8,10 +8,10 @@ import android.view.ViewGroup
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.GridLayoutManager
import com.github.libretube.R
import com.github.libretube.api.RetrofitInstance
import com.github.libretube.constants.IntentData
import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.databinding.FragmentSearchResultBinding
import com.github.libretube.db.DatabaseHelper
@ -36,18 +36,13 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
class SearchResultFragment : DynamicLayoutManagerFragment() {
private var _binding: FragmentSearchResultBinding? = null
private val binding get() = _binding!!
private val args by navArgs<SearchResultFragmentArgs>()
private var nextPage: String? = null
private var query = ""
private lateinit var searchAdapter: SearchAdapter
private var searchFilter = "all"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
query = arguments?.getString(IntentData.query).toString()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -66,10 +61,10 @@ class SearchResultFragment : DynamicLayoutManagerFragment() {
// fixes a bug that the search query will stay the old one when searching for multiple
// different queries in a row and navigating to the previous ones through back presses
(context as MainActivity).setQuerySilent(query)
(context as MainActivity).setQuerySilent(args.query)
// add the query to the history
addToHistory(query)
addToHistory(args.query)
// filter options
binding.filterChipGroup.setOnCheckedStateChangeListener { _, _ ->
@ -109,11 +104,11 @@ class SearchResultFragment : DynamicLayoutManagerFragment() {
var timeStamp: Long? = null
// parse search URLs from YouTube entered in the search bar
val searchQuery = query.toHttpUrlOrNull()?.let {
val videoId = TextUtils.getVideoIdFromUrl(it.toString()) ?: query
val searchQuery = args.query.toHttpUrlOrNull()?.let {
val videoId = TextUtils.getVideoIdFromUrl(it.toString()) ?: args.query
timeStamp = it.queryParameter("t")?.toTimeInSeconds()
"${ShareDialog.YOUTUBE_FRONTEND_URL}/watch?v=$videoId"
} ?: query
} ?: args.query
view?.let { context?.hideKeyboard(it) }
val response = try {
@ -146,7 +141,7 @@ class SearchResultFragment : DynamicLayoutManagerFragment() {
val response = try {
withContext(Dispatchers.IO) {
RetrofitInstance.api.getSearchResultsNextPage(
query,
args.query,
searchFilter,
nextPage!!
).apply {

View File

@ -33,17 +33,43 @@
android:id="@+id/searchResultFragment"
android:name="com.github.libretube.ui.fragments.SearchResultFragment"
android:label="fragment_search"
tools:layout="@layout/fragment_search_result" />
tools:layout="@layout/fragment_search_result">
<argument
android:name="query"
app:argType="string"
app:nullable="false" />
</fragment>
<fragment
android:id="@+id/channelFragment"
android:name="com.github.libretube.ui.fragments.ChannelFragment"
android:label="channel"
tools:layout="@layout/fragment_channel" />
tools:layout="@layout/fragment_channel" >
<argument
android:name="channelId"
app:argType="string"
app:nullable="true"
android:defaultValue="@null" />
<argument
android:name="channelName"
app:argType="string"
app:nullable="true"
android:defaultValue="@null" />
</fragment>
<fragment
android:id="@+id/playlistFragment"
android:name="com.github.libretube.ui.fragments.PlaylistFragment"
android:label="fragment_playlist"
tools:layout="@layout/fragment_playlist" />
tools:layout="@layout/fragment_playlist">
<argument
android:name="playlistId"
app:argType="string"
app:nullable="false" />
<argument
android:name="playlistType"
app:argType="com.github.libretube.enums.PlaylistType"
app:nullable="false"
android:defaultValue="PUBLIC" />
</fragment>
<fragment
android:id="@+id/watchHistoryFragment"
android:name="com.github.libretube.ui.fragments.WatchHistoryFragment"
@ -54,4 +80,11 @@
android:name="com.github.libretube.ui.fragments.DownloadsFragment"
android:label="@string/downloads"
tools:layout="@layout/fragment_downloads" />
<action android:id="@+id/openChannel"
app:destination="@id/channelFragment" />
<action android:id="@+id/openPlaylist"
app:destination="@id/playlistFragment" />
<action android:id="@+id/showSearchResults"
app:destination="@id/searchResultFragment" />
</navigation>

View File

@ -9,6 +9,7 @@ buildscript {
classpath(libs.gradle)
classpath(libs.kotlin.gradle.plugin)
classpath(libs.kotlin.serialization)
classpath(libs.androidx.navigation.safeargs)
// NOTE: Do not place your application dependencies here, they belong
// in the individual module build.gradle.kts files

View File

@ -38,6 +38,7 @@ kotlin-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", v
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
androidx-navigation-fragment = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigation" }
androidx-navigation-ui = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigation" }
androidx-navigation-safeargs = { group = "androidx.navigation", name = "navigation-safe-args-gradle-plugin", version.ref = "navigation" }
androidx-legacySupport = { group = "androidx.legacy", name = "legacy-support-v4", version.ref = "legacySupport" }
androidx-preference = { group = "androidx.preference", name = "preference-ktx", version.ref = "preference" }
androidx-test-junit = { group = "androidx.test.ext", name = "junit", version.ref = "extJunit" }