mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-29 16:30:31 +05:30
Merge pull request #3527 from Isira-Seneviratne/repeatOnLifecycle
Switch to repeatOnLifecycle extension.
This commit is contained in:
commit
2ba3ebf358
@ -7,7 +7,9 @@ import android.widget.ArrayAdapter
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.PlaylistsHelper
|
import com.github.libretube.api.PlaylistsHelper
|
||||||
import com.github.libretube.api.RetrofitInstance
|
import com.github.libretube.api.RetrofitInstance
|
||||||
@ -45,33 +47,34 @@ class AddToPlaylistDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchPlaylists(binding: DialogAddToPlaylistBinding) {
|
private fun fetchPlaylists(binding: DialogAddToPlaylistBinding) {
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launch {
|
||||||
val response = try {
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
PlaylistsHelper.getPlaylists()
|
val response = try {
|
||||||
} catch (e: Exception) {
|
PlaylistsHelper.getPlaylists()
|
||||||
Log.e(TAG(), e.toString())
|
} catch (e: Exception) {
|
||||||
Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show()
|
Log.e(TAG(), e.toString())
|
||||||
return@launchWhenCreated
|
Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show()
|
||||||
}
|
return@repeatOnLifecycle
|
||||||
if (response.isEmpty()) return@launchWhenCreated
|
}
|
||||||
val names = response.mapNotNull { it.name }
|
if (response.isEmpty()) return@repeatOnLifecycle
|
||||||
val arrayAdapter =
|
val names = response.mapNotNull { it.name }
|
||||||
ArrayAdapter(requireContext(), R.layout.dropdown_item, names)
|
val arrayAdapter = ArrayAdapter(requireContext(), R.layout.dropdown_item, names)
|
||||||
binding.playlistsSpinner.adapter = arrayAdapter
|
binding.playlistsSpinner.adapter = arrayAdapter
|
||||||
|
|
||||||
// select the last used playlist
|
// select the last used playlist
|
||||||
viewModel.lastSelectedPlaylistId?.let { id ->
|
viewModel.lastSelectedPlaylistId?.let { id ->
|
||||||
binding.playlistsSpinner.setSelection(
|
binding.playlistsSpinner.setSelection(
|
||||||
response.indexOfFirst { it.id == id }.takeIf { it >= 0 } ?: 0
|
response.indexOfFirst { it.id == id }.takeIf { it >= 0 } ?: 0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
binding.addToPlaylist.setOnClickListener {
|
binding.addToPlaylist.setOnClickListener {
|
||||||
val index = binding.playlistsSpinner.selectedItemPosition
|
val index = binding.playlistsSpinner.selectedItemPosition
|
||||||
viewModel.lastSelectedPlaylistId = response[index].id!!
|
viewModel.lastSelectedPlaylistId = response[index].id!!
|
||||||
dialog?.hide()
|
dialog?.hide()
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
addToPlaylist(response[index].id!!)
|
addToPlaylist(response[index].id!!)
|
||||||
dialog?.dismiss()
|
dialog?.dismiss()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,9 @@ import android.os.Bundle
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.RetrofitInstance
|
import com.github.libretube.api.RetrofitInstance
|
||||||
import com.github.libretube.api.obj.DeleteUserRequest
|
import com.github.libretube.api.obj.DeleteUserRequest
|
||||||
@ -13,6 +15,9 @@ import com.github.libretube.databinding.DialogDeleteAccountBinding
|
|||||||
import com.github.libretube.extensions.TAG
|
import com.github.libretube.extensions.TAG
|
||||||
import com.github.libretube.helpers.PreferenceHelper
|
import com.github.libretube.helpers.PreferenceHelper
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class DeleteAccountDialog(
|
class DeleteAccountDialog(
|
||||||
private val onLogout: () -> Unit
|
private val onLogout: () -> Unit
|
||||||
@ -39,20 +44,24 @@ class DeleteAccountDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun deleteAccount(password: String) {
|
private fun deleteAccount(password: String) {
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launch {
|
||||||
val token = PreferenceHelper.getToken()
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
|
val token = PreferenceHelper.getToken()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
RetrofitInstance.authApi.deleteAccount(token, DeleteUserRequest(password))
|
withContext(Dispatchers.IO) {
|
||||||
} catch (e: Exception) {
|
RetrofitInstance.authApi.deleteAccount(token, DeleteUserRequest(password))
|
||||||
Log.e(TAG(), e.toString())
|
}
|
||||||
Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show()
|
} catch (e: Exception) {
|
||||||
return@launchWhenCreated
|
Log.e(TAG(), e.toString())
|
||||||
|
Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show()
|
||||||
|
return@repeatOnLifecycle
|
||||||
|
}
|
||||||
|
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
|
||||||
|
|
||||||
|
onLogout.invoke()
|
||||||
|
dialog?.dismiss()
|
||||||
}
|
}
|
||||||
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
|
|
||||||
|
|
||||||
onLogout.invoke()
|
|
||||||
dialog?.dismiss()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,9 @@ import android.view.View
|
|||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.RetrofitInstance
|
import com.github.libretube.api.RetrofitInstance
|
||||||
import com.github.libretube.api.obj.PipedStream
|
import com.github.libretube.api.obj.PipedStream
|
||||||
@ -21,6 +23,9 @@ import com.github.libretube.helpers.DownloadHelper
|
|||||||
import com.github.libretube.helpers.PreferenceHelper
|
import com.github.libretube.helpers.PreferenceHelper
|
||||||
import com.github.libretube.util.TextUtils
|
import com.github.libretube.util.TextUtils
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
|
|
||||||
@ -57,20 +62,24 @@ class DownloadDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchAvailableSources(binding: DialogDownloadBinding) {
|
private fun fetchAvailableSources(binding: DialogDownloadBinding) {
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launch {
|
||||||
val response = try {
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
RetrofitInstance.api.getStreams(videoId)
|
val response = try {
|
||||||
} catch (e: IOException) {
|
withContext(Dispatchers.IO) {
|
||||||
println(e)
|
RetrofitInstance.api.getStreams(videoId)
|
||||||
Log.e(TAG(), "IOException, you might not have internet connection")
|
}
|
||||||
Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show()
|
} catch (e: IOException) {
|
||||||
return@launchWhenCreated
|
println(e)
|
||||||
} catch (e: HttpException) {
|
Log.e(TAG(), "IOException, you might not have internet connection")
|
||||||
Log.e(TAG(), "HttpException, unexpected response")
|
Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show()
|
||||||
Toast.makeText(context, R.string.server_error, Toast.LENGTH_SHORT).show()
|
return@repeatOnLifecycle
|
||||||
return@launchWhenCreated
|
} catch (e: HttpException) {
|
||||||
|
Log.e(TAG(), "HttpException, unexpected response")
|
||||||
|
Toast.makeText(context, R.string.server_error, Toast.LENGTH_SHORT).show()
|
||||||
|
return@repeatOnLifecycle
|
||||||
|
}
|
||||||
|
initDownloadOptions(binding, response)
|
||||||
}
|
}
|
||||||
initDownloadOptions(binding, response)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,9 @@ import android.view.ViewGroup
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.RetrofitInstance
|
import com.github.libretube.api.RetrofitInstance
|
||||||
@ -105,93 +107,95 @@ class ChannelFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchChannel() {
|
private fun fetchChannel() {
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launch {
|
||||||
val response = try {
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
withContext(Dispatchers.IO) {
|
val response = try {
|
||||||
if (channelId != null) {
|
withContext(Dispatchers.IO) {
|
||||||
RetrofitInstance.api.getChannel(channelId!!)
|
if (channelId != null) {
|
||||||
} else {
|
RetrofitInstance.api.getChannel(channelId!!)
|
||||||
RetrofitInstance.api.getChannelByName(channelName!!)
|
} else {
|
||||||
|
RetrofitInstance.api.getChannelByName(channelName!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
binding.channelRefresh.isRefreshing = false
|
||||||
|
Log.e(TAG(), "IOException, you might not have internet connection")
|
||||||
|
return@repeatOnLifecycle
|
||||||
|
} catch (e: HttpException) {
|
||||||
|
binding.channelRefresh.isRefreshing = false
|
||||||
|
Log.e(TAG(), "HttpException, unexpected response")
|
||||||
|
return@repeatOnLifecycle
|
||||||
|
}
|
||||||
|
// needed if the channel gets loaded by the ID
|
||||||
|
channelId = response.id
|
||||||
|
channelName = response.name
|
||||||
|
val shareData = ShareData(currentChannel = response.name)
|
||||||
|
|
||||||
|
onScrollEnd = {
|
||||||
|
fetchChannelNextPage()
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch and update the subscription status
|
||||||
|
isSubscribed = SubscriptionHelper.isSubscribed(channelId!!)
|
||||||
|
if (isSubscribed == null) return@repeatOnLifecycle
|
||||||
|
|
||||||
|
binding.channelSubscribe.setupSubscriptionButton(
|
||||||
|
channelId,
|
||||||
|
channelName,
|
||||||
|
binding.notificationBell
|
||||||
|
)
|
||||||
|
|
||||||
|
binding.channelShare.setOnClickListener {
|
||||||
|
val shareDialog = ShareDialog(
|
||||||
|
response.id!!.toID(),
|
||||||
|
ShareObjectType.CHANNEL,
|
||||||
|
shareData
|
||||||
|
)
|
||||||
|
shareDialog.show(childFragmentManager, ShareDialog::class.java.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPage = response.nextpage
|
||||||
|
isLoading = false
|
||||||
|
binding.channelRefresh.isRefreshing = false
|
||||||
|
|
||||||
|
binding.channelScrollView.visibility = View.VISIBLE
|
||||||
|
binding.channelName.text = response.name
|
||||||
|
if (response.verified) {
|
||||||
|
binding.channelName.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
R.drawable.ic_verified,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
binding.channelSubs.text = resources.getString(
|
||||||
|
R.string.subscribers,
|
||||||
|
response.subscriberCount.formatShort()
|
||||||
|
)
|
||||||
|
if (response.description.isBlank()) {
|
||||||
|
binding.channelDescription.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
binding.channelDescription.text = response.description.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.channelDescription.setOnClickListener {
|
||||||
|
(it as TextView).apply {
|
||||||
|
it.maxLines = if (it.maxLines == Int.MAX_VALUE) 2 else Int.MAX_VALUE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: IOException) {
|
|
||||||
binding.channelRefresh.isRefreshing = false
|
|
||||||
Log.e(TAG(), "IOException, you might not have internet connection")
|
|
||||||
return@launchWhenCreated
|
|
||||||
} catch (e: HttpException) {
|
|
||||||
binding.channelRefresh.isRefreshing = false
|
|
||||||
Log.e(TAG(), "HttpException, unexpected response")
|
|
||||||
return@launchWhenCreated
|
|
||||||
}
|
|
||||||
// needed if the channel gets loaded by the ID
|
|
||||||
channelId = response.id
|
|
||||||
channelName = response.name
|
|
||||||
val shareData = ShareData(currentChannel = response.name)
|
|
||||||
|
|
||||||
onScrollEnd = {
|
ImageHelper.loadImage(response.bannerUrl, binding.channelBanner)
|
||||||
fetchChannelNextPage()
|
ImageHelper.loadImage(response.avatarUrl, binding.channelImage)
|
||||||
}
|
|
||||||
|
|
||||||
// fetch and update the subscription status
|
// recyclerview of the videos by the channel
|
||||||
isSubscribed = SubscriptionHelper.isSubscribed(channelId!!)
|
channelAdapter = VideosAdapter(
|
||||||
if (isSubscribed == null) return@launchWhenCreated
|
response.relatedStreams.toMutableList(),
|
||||||
|
forceMode = VideosAdapter.Companion.ForceMode.CHANNEL
|
||||||
binding.channelSubscribe.setupSubscriptionButton(
|
|
||||||
channelId,
|
|
||||||
channelName,
|
|
||||||
binding.notificationBell
|
|
||||||
)
|
|
||||||
|
|
||||||
binding.channelShare.setOnClickListener {
|
|
||||||
val shareDialog = ShareDialog(
|
|
||||||
response.id!!.toID(),
|
|
||||||
ShareObjectType.CHANNEL,
|
|
||||||
shareData
|
|
||||||
)
|
)
|
||||||
shareDialog.show(childFragmentManager, ShareDialog::class.java.name)
|
binding.channelRecView.adapter = channelAdapter
|
||||||
|
|
||||||
|
setupTabs(response.tabs)
|
||||||
}
|
}
|
||||||
|
|
||||||
nextPage = response.nextpage
|
|
||||||
isLoading = false
|
|
||||||
binding.channelRefresh.isRefreshing = false
|
|
||||||
|
|
||||||
binding.channelScrollView.visibility = View.VISIBLE
|
|
||||||
binding.channelName.text = response.name
|
|
||||||
if (response.verified) {
|
|
||||||
binding.channelName.setCompoundDrawablesWithIntrinsicBounds(
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
R.drawable.ic_verified,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
binding.channelSubs.text = resources.getString(
|
|
||||||
R.string.subscribers,
|
|
||||||
response.subscriberCount.formatShort()
|
|
||||||
)
|
|
||||||
if (response.description.isBlank()) {
|
|
||||||
binding.channelDescription.visibility = View.GONE
|
|
||||||
} else {
|
|
||||||
binding.channelDescription.text = response.description.trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.channelDescription.setOnClickListener {
|
|
||||||
(it as TextView).apply {
|
|
||||||
it.maxLines = if (it.maxLines == Int.MAX_VALUE) 2 else Int.MAX_VALUE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImageHelper.loadImage(response.bannerUrl, binding.channelBanner)
|
|
||||||
ImageHelper.loadImage(response.avatarUrl, binding.channelImage)
|
|
||||||
|
|
||||||
// recyclerview of the videos by the channel
|
|
||||||
channelAdapter = VideosAdapter(
|
|
||||||
response.relatedStreams.toMutableList(),
|
|
||||||
forceMode = VideosAdapter.Companion.ForceMode.CHANNEL
|
|
||||||
)
|
|
||||||
binding.channelRecView.adapter = channelAdapter
|
|
||||||
|
|
||||||
setupTabs(response.tabs)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,22 +257,24 @@ class ChannelFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchChannelNextPage() {
|
private fun fetchChannelNextPage() {
|
||||||
fun run() {
|
if (nextPage == null || isLoading) return
|
||||||
if (nextPage == null || isLoading) return
|
isLoading = true
|
||||||
isLoading = true
|
binding.channelRefresh.isRefreshing = true
|
||||||
binding.channelRefresh.isRefreshing = true
|
|
||||||
|
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
val response = try {
|
val response = try {
|
||||||
RetrofitInstance.api.getChannelNextPage(channelId!!, nextPage!!)
|
withContext(Dispatchers.IO) {
|
||||||
|
RetrofitInstance.api.getChannelNextPage(channelId!!, nextPage!!)
|
||||||
|
}
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
binding.channelRefresh.isRefreshing = false
|
binding.channelRefresh.isRefreshing = false
|
||||||
Log.e(TAG(), "IOException, you might not have internet connection")
|
Log.e(TAG(), "IOException, you might not have internet connection")
|
||||||
return@launchWhenCreated
|
return@repeatOnLifecycle
|
||||||
} catch (e: HttpException) {
|
} catch (e: HttpException) {
|
||||||
binding.channelRefresh.isRefreshing = false
|
binding.channelRefresh.isRefreshing = false
|
||||||
Log.e(TAG(), "HttpException, unexpected response," + e.response())
|
Log.e(TAG(), "HttpException, unexpected response," + e.response())
|
||||||
return@launchWhenCreated
|
return@repeatOnLifecycle
|
||||||
}
|
}
|
||||||
nextPage = response.nextpage
|
nextPage = response.nextpage
|
||||||
channelAdapter?.insertItems(response.relatedStreams)
|
channelAdapter?.insertItems(response.relatedStreams)
|
||||||
@ -276,7 +282,6 @@ class ChannelFragment : Fragment() {
|
|||||||
binding.channelRefresh.isRefreshing = false
|
binding.channelRefresh.isRefreshing = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
run()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchTabNextPage(
|
private fun fetchTabNextPage(
|
||||||
|
@ -6,7 +6,9 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
@ -25,6 +27,9 @@ import com.github.libretube.ui.adapters.PlaylistsAdapter
|
|||||||
import com.github.libretube.ui.adapters.VideosAdapter
|
import com.github.libretube.ui.adapters.VideosAdapter
|
||||||
import com.github.libretube.ui.models.SubscriptionsViewModel
|
import com.github.libretube.ui.models.SubscriptionsViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class HomeFragment : Fragment() {
|
class HomeFragment : Fragment() {
|
||||||
@ -75,13 +80,15 @@ class HomeFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchHomeFeed() {
|
private fun fetchHomeFeed() {
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launch {
|
||||||
loadTrending()
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
loadBookmarks()
|
awaitAll(
|
||||||
}
|
async { loadTrending() },
|
||||||
lifecycleScope.launchWhenCreated {
|
async { loadBookmarks() },
|
||||||
loadFeed()
|
async { loadFeed() },
|
||||||
loadPlaylists()
|
async { loadPlaylists() }
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,9 @@ import androidx.core.view.isVisible
|
|||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
@ -122,47 +124,49 @@ class LibraryFragment : Fragment() {
|
|||||||
|
|
||||||
private fun fetchPlaylists() {
|
private fun fetchPlaylists() {
|
||||||
binding.playlistRefresh.isRefreshing = true
|
binding.playlistRefresh.isRefreshing = true
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launch {
|
||||||
var playlists = try {
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
withContext(Dispatchers.IO) {
|
var playlists = try {
|
||||||
PlaylistsHelper.getPlaylists()
|
withContext(Dispatchers.IO) {
|
||||||
}
|
PlaylistsHelper.getPlaylists()
|
||||||
} catch (e: Exception) {
|
}
|
||||||
Log.e(TAG(), e.toString())
|
} catch (e: Exception) {
|
||||||
Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show()
|
Log.e(TAG(), e.toString())
|
||||||
return@launchWhenCreated
|
Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show()
|
||||||
} finally {
|
return@repeatOnLifecycle
|
||||||
binding.playlistRefresh.isRefreshing = false
|
} finally {
|
||||||
}
|
binding.playlistRefresh.isRefreshing = false
|
||||||
if (playlists.isNotEmpty()) {
|
}
|
||||||
playlists = when (
|
if (playlists.isNotEmpty()) {
|
||||||
PreferenceHelper.getString(PreferenceKeys.PLAYLISTS_ORDER, "recent")
|
playlists = when (
|
||||||
) {
|
PreferenceHelper.getString(PreferenceKeys.PLAYLISTS_ORDER, "recent")
|
||||||
"recent" -> playlists
|
) {
|
||||||
"recent_reversed" -> playlists.reversed()
|
"recent" -> playlists
|
||||||
"name" -> playlists.sortedBy { it.name?.lowercase() }
|
"recent_reversed" -> playlists.reversed()
|
||||||
"name_reversed" -> playlists.sortedBy { it.name?.lowercase() }.reversed()
|
"name" -> playlists.sortedBy { it.name?.lowercase() }
|
||||||
else -> playlists
|
"name_reversed" -> playlists.sortedBy { it.name?.lowercase() }.reversed()
|
||||||
}
|
else -> playlists
|
||||||
|
|
||||||
val playlistsAdapter = PlaylistsAdapter(
|
|
||||||
playlists.toMutableList(),
|
|
||||||
PlaylistsHelper.getPrivatePlaylistType()
|
|
||||||
)
|
|
||||||
|
|
||||||
// listen for playlists to become deleted
|
|
||||||
playlistsAdapter.registerAdapterDataObserver(object :
|
|
||||||
RecyclerView.AdapterDataObserver() {
|
|
||||||
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
|
|
||||||
binding.nothingHere.isVisible = playlistsAdapter.itemCount == 0
|
|
||||||
super.onItemRangeRemoved(positionStart, itemCount)
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
binding.nothingHere.visibility = View.GONE
|
val playlistsAdapter = PlaylistsAdapter(
|
||||||
binding.playlistRecView.adapter = playlistsAdapter
|
playlists.toMutableList(),
|
||||||
} else {
|
PlaylistsHelper.getPrivatePlaylistType()
|
||||||
binding.nothingHere.visibility = View.VISIBLE
|
)
|
||||||
|
|
||||||
|
// listen for playlists to become deleted
|
||||||
|
playlistsAdapter.registerAdapterDataObserver(object :
|
||||||
|
RecyclerView.AdapterDataObserver() {
|
||||||
|
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
|
||||||
|
binding.nothingHere.isVisible = playlistsAdapter.itemCount == 0
|
||||||
|
super.onItemRangeRemoved(positionStart, itemCount)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
binding.nothingHere.visibility = View.GONE
|
||||||
|
binding.playlistRecView.adapter = playlistsAdapter
|
||||||
|
} else {
|
||||||
|
binding.nothingHere.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,9 @@ import android.view.ViewGroup
|
|||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
@ -109,112 +111,109 @@ class PlaylistFragment : Fragment() {
|
|||||||
|
|
||||||
private fun fetchPlaylist() {
|
private fun fetchPlaylist() {
|
||||||
binding.playlistScrollview.visibility = View.GONE
|
binding.playlistScrollview.visibility = View.GONE
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launch {
|
||||||
val response = try {
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
withContext(Dispatchers.IO) {
|
val response = try {
|
||||||
PlaylistsHelper.getPlaylist(playlistId!!)
|
withContext(Dispatchers.IO) {
|
||||||
}
|
PlaylistsHelper.getPlaylist(playlistId!!)
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG(), e.toString())
|
|
||||||
return@launchWhenCreated
|
|
||||||
}
|
|
||||||
playlistFeed = response.relatedStreams.toMutableList()
|
|
||||||
binding.playlistScrollview.visibility = View.VISIBLE
|
|
||||||
nextPage = response.nextpage
|
|
||||||
playlistName = response.name
|
|
||||||
isLoading = false
|
|
||||||
ImageHelper.loadImage(response.thumbnailUrl, binding.thumbnail)
|
|
||||||
binding.playlistProgress.visibility = View.GONE
|
|
||||||
binding.playlistName.text = response.name
|
|
||||||
|
|
||||||
binding.playlistName.setOnClickListener {
|
|
||||||
binding.playlistName.maxLines =
|
|
||||||
if (binding.playlistName.maxLines == 2) Int.MAX_VALUE else 2
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.playlistInfo.text = getChannelAndVideoString(response, response.videos)
|
|
||||||
|
|
||||||
// show playlist options
|
|
||||||
binding.optionsMenu.setOnClickListener {
|
|
||||||
PlaylistOptionsBottomSheet(
|
|
||||||
playlistId = playlistId.orEmpty(),
|
|
||||||
playlistName = playlistName.orEmpty(),
|
|
||||||
playlistType = playlistType,
|
|
||||||
onDelete = {
|
|
||||||
findNavController().popBackStack()
|
|
||||||
},
|
|
||||||
onRename = {
|
|
||||||
binding.playlistName.text = it
|
|
||||||
playlistName = it
|
|
||||||
}
|
}
|
||||||
).show(
|
} catch (e: Exception) {
|
||||||
childFragmentManager,
|
Log.e(TAG(), e.toString())
|
||||||
PlaylistOptionsBottomSheet::class.java.name
|
return@repeatOnLifecycle
|
||||||
)
|
}
|
||||||
}
|
playlistFeed = response.relatedStreams.toMutableList()
|
||||||
|
binding.playlistScrollview.visibility = View.VISIBLE
|
||||||
|
nextPage = response.nextpage
|
||||||
|
playlistName = response.name
|
||||||
|
isLoading = false
|
||||||
|
ImageHelper.loadImage(response.thumbnailUrl, binding.thumbnail)
|
||||||
|
binding.playlistProgress.visibility = View.GONE
|
||||||
|
binding.playlistName.text = response.name
|
||||||
|
|
||||||
binding.playAll.setOnClickListener {
|
binding.playlistName.setOnClickListener {
|
||||||
if (playlistFeed.isEmpty()) return@setOnClickListener
|
binding.playlistName.maxLines =
|
||||||
NavigationHelper.navigateVideo(
|
if (binding.playlistName.maxLines == 2) Int.MAX_VALUE else 2
|
||||||
requireContext(),
|
}
|
||||||
response.relatedStreams.first().url?.toID(),
|
|
||||||
playlistId
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (playlistType == PlaylistType.PUBLIC) {
|
binding.playlistInfo.text = getChannelAndVideoString(response, response.videos)
|
||||||
binding.bookmark.setOnClickListener {
|
|
||||||
isBookmarked = !isBookmarked
|
// show playlist options
|
||||||
updateBookmarkRes()
|
binding.optionsMenu.setOnClickListener {
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
PlaylistOptionsBottomSheet(
|
||||||
if (!isBookmarked) {
|
playlistId = playlistId.orEmpty(),
|
||||||
DatabaseHolder.Database.playlistBookmarkDao()
|
playlistName = playlistName.orEmpty(),
|
||||||
.deleteById(playlistId!!)
|
playlistType = playlistType,
|
||||||
} else {
|
onDelete = {
|
||||||
DatabaseHolder.Database.playlistBookmarkDao()
|
findNavController().popBackStack()
|
||||||
.insert(response.toPlaylistBookmark(playlistId!!))
|
},
|
||||||
|
onRename = {
|
||||||
|
binding.playlistName.text = it
|
||||||
|
playlistName = it
|
||||||
}
|
}
|
||||||
}
|
).show(
|
||||||
}
|
childFragmentManager,
|
||||||
} else {
|
PlaylistOptionsBottomSheet::class.java.name
|
||||||
// private playlist, means shuffle is possible because all videos are received at once
|
|
||||||
binding.bookmark.setIconResource(R.drawable.ic_shuffle)
|
|
||||||
binding.bookmark.text = getString(R.string.shuffle)
|
|
||||||
binding.bookmark.setOnClickListener {
|
|
||||||
if (playlistFeed.isEmpty()) return@setOnClickListener
|
|
||||||
val queue = playlistFeed.shuffled()
|
|
||||||
PlayingQueue.resetToDefaults()
|
|
||||||
PlayingQueue.add(*queue.toTypedArray())
|
|
||||||
NavigationHelper.navigateVideo(
|
|
||||||
requireContext(),
|
|
||||||
queue.first().url?.toID(),
|
|
||||||
playlistId = playlistId,
|
|
||||||
keepQueue = true
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
playlistAdapter = PlaylistAdapter(
|
binding.playAll.setOnClickListener {
|
||||||
playlistFeed,
|
if (playlistFeed.isEmpty()) return@setOnClickListener
|
||||||
playlistId!!,
|
NavigationHelper.navigateVideo(
|
||||||
playlistType
|
requireContext(),
|
||||||
)
|
response.relatedStreams.first().url?.toID(),
|
||||||
|
playlistId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// listen for playlist items to become deleted
|
if (playlistType == PlaylistType.PUBLIC) {
|
||||||
playlistAdapter!!.registerAdapterDataObserver(object :
|
binding.bookmark.setOnClickListener {
|
||||||
RecyclerView.AdapterDataObserver() {
|
isBookmarked = !isBookmarked
|
||||||
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
|
updateBookmarkRes()
|
||||||
if (positionStart == 0) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
ImageHelper.loadImage(
|
if (!isBookmarked) {
|
||||||
playlistFeed.firstOrNull()?.thumbnail ?: "",
|
DatabaseHolder.Database.playlistBookmarkDao()
|
||||||
binding.thumbnail
|
.deleteById(playlistId!!)
|
||||||
|
} else {
|
||||||
|
DatabaseHolder.Database.playlistBookmarkDao()
|
||||||
|
.insert(response.toPlaylistBookmark(playlistId!!))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// private playlist, means shuffle is possible because all videos are received at once
|
||||||
|
binding.bookmark.setIconResource(R.drawable.ic_shuffle)
|
||||||
|
binding.bookmark.text = getString(R.string.shuffle)
|
||||||
|
binding.bookmark.setOnClickListener {
|
||||||
|
if (playlistFeed.isEmpty()) return@setOnClickListener
|
||||||
|
val queue = playlistFeed.shuffled()
|
||||||
|
PlayingQueue.resetToDefaults()
|
||||||
|
PlayingQueue.add(*queue.toTypedArray())
|
||||||
|
NavigationHelper.navigateVideo(
|
||||||
|
requireContext(),
|
||||||
|
queue.first().url?.toID(),
|
||||||
|
playlistId = playlistId,
|
||||||
|
keepQueue = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.playlistInfo.text =
|
|
||||||
getChannelAndVideoString(response, playlistFeed.size)
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
playlistAdapter = PlaylistAdapter(playlistFeed, playlistId!!, playlistType)
|
||||||
|
|
||||||
|
// listen for playlist items to become deleted
|
||||||
|
playlistAdapter!!.registerAdapterDataObserver(object :
|
||||||
|
RecyclerView.AdapterDataObserver() {
|
||||||
|
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
|
||||||
|
if (positionStart == 0) {
|
||||||
|
ImageHelper.loadImage(
|
||||||
|
playlistFeed.firstOrNull()?.thumbnail ?: "",
|
||||||
|
binding.thumbnail
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.playlistInfo.text =
|
||||||
|
getChannelAndVideoString(response, playlistFeed.size)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
binding.playlistRecView.adapter = playlistAdapter
|
binding.playlistRecView.adapter = playlistAdapter
|
||||||
binding.playlistScrollview.viewTreeObserver.addOnScrollChangedListener {
|
binding.playlistScrollview.viewTreeObserver.addOnScrollChangedListener {
|
||||||
@ -232,41 +231,42 @@ class PlaylistFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// listener for swiping to the left or right
|
// listener for swiping to the left or right
|
||||||
if (playlistType != PlaylistType.PUBLIC) {
|
if (playlistType != PlaylistType.PUBLIC) {
|
||||||
val itemTouchCallback = object : ItemTouchHelper.SimpleCallback(
|
val itemTouchCallback = object : ItemTouchHelper.SimpleCallback(
|
||||||
0,
|
0,
|
||||||
ItemTouchHelper.LEFT
|
ItemTouchHelper.LEFT
|
||||||
) {
|
) {
|
||||||
override fun onMove(
|
override fun onMove(
|
||||||
recyclerView: RecyclerView,
|
recyclerView: RecyclerView,
|
||||||
viewHolder: RecyclerView.ViewHolder,
|
viewHolder: RecyclerView.ViewHolder,
|
||||||
target: RecyclerView.ViewHolder
|
target: RecyclerView.ViewHolder
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return false
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSwiped(
|
||||||
|
viewHolder: RecyclerView.ViewHolder,
|
||||||
|
direction: Int
|
||||||
|
) {
|
||||||
|
val position = viewHolder.absoluteAdapterPosition
|
||||||
|
playlistAdapter!!.removeFromPlaylist(requireContext(), position)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSwiped(
|
val itemTouchHelper = ItemTouchHelper(itemTouchCallback)
|
||||||
viewHolder: RecyclerView.ViewHolder,
|
itemTouchHelper.attachToRecyclerView(binding.playlistRecView)
|
||||||
direction: Int
|
|
||||||
) {
|
|
||||||
val position = viewHolder.absoluteAdapterPosition
|
|
||||||
playlistAdapter!!.removeFromPlaylist(requireContext(), position)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val itemTouchHelper = ItemTouchHelper(itemTouchCallback)
|
withContext(Dispatchers.IO) {
|
||||||
itemTouchHelper.attachToRecyclerView(binding.playlistRecView)
|
// update the playlist thumbnail if bookmarked
|
||||||
}
|
val playlistBookmark = DatabaseHolder.Database.playlistBookmarkDao().getAll()
|
||||||
|
.firstOrNull { it.playlistId == playlistId }
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
playlistBookmark?.let {
|
||||||
// update the playlist thumbnail if bookmarked
|
if (it.thumbnailUrl != response.thumbnailUrl) {
|
||||||
val playlistBookmark = DatabaseHolder.Database.playlistBookmarkDao().getAll()
|
it.thumbnailUrl = response.thumbnailUrl
|
||||||
.firstOrNull { it.playlistId == playlistId }
|
DatabaseHolder.Database.playlistBookmarkDao().update(it)
|
||||||
playlistBookmark?.let {
|
}
|
||||||
if (it.thumbnailUrl != response.thumbnailUrl) {
|
|
||||||
it.thumbnailUrl = response.thumbnailUrl
|
|
||||||
DatabaseHolder.Database.playlistBookmarkDao().update(it)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -284,22 +284,26 @@ class PlaylistFragment : Fragment() {
|
|||||||
if (nextPage == null || isLoading) return
|
if (nextPage == null || isLoading) return
|
||||||
isLoading = true
|
isLoading = true
|
||||||
|
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launch {
|
||||||
val response = try {
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
// load locally stored playlists with the auth api
|
val response = try {
|
||||||
if (playlistType == PlaylistType.PRIVATE) {
|
withContext(Dispatchers.IO) {
|
||||||
RetrofitInstance.authApi.getPlaylistNextPage(playlistId!!, nextPage!!)
|
// load locally stored playlists with the auth api
|
||||||
} else {
|
if (playlistType == PlaylistType.PRIVATE) {
|
||||||
RetrofitInstance.api.getPlaylistNextPage(playlistId!!, nextPage!!)
|
RetrofitInstance.authApi.getPlaylistNextPage(playlistId!!, nextPage!!)
|
||||||
|
} else {
|
||||||
|
RetrofitInstance.api.getPlaylistNextPage(playlistId!!, nextPage!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG(), e.toString())
|
||||||
|
return@repeatOnLifecycle
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG(), e.toString())
|
|
||||||
return@launchWhenCreated
|
|
||||||
}
|
|
||||||
|
|
||||||
nextPage = response.nextpage
|
nextPage = response.nextpage
|
||||||
playlistAdapter?.updateItems(response.relatedStreams)
|
playlistAdapter?.updateItems(response.relatedStreams)
|
||||||
isLoading = false
|
isLoading = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,9 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.github.libretube.api.RetrofitInstance
|
import com.github.libretube.api.RetrofitInstance
|
||||||
import com.github.libretube.databinding.FragmentSearchBinding
|
import com.github.libretube.databinding.FragmentSearchBinding
|
||||||
@ -69,20 +71,24 @@ class SearchFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchSuggestions(query: String) {
|
private fun fetchSuggestions(query: String) {
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launch {
|
||||||
val response = try {
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
RetrofitInstance.api.getSuggestions(query)
|
val response = try {
|
||||||
} catch (e: Exception) {
|
withContext(Dispatchers.IO) {
|
||||||
Log.e(TAG(), e.toString())
|
RetrofitInstance.api.getSuggestions(query)
|
||||||
return@launchWhenCreated
|
}
|
||||||
}
|
} catch (e: Exception) {
|
||||||
// only load the suggestions if the input field didn't get cleared yet
|
Log.e(TAG(), e.toString())
|
||||||
val suggestionsAdapter = SearchSuggestionsAdapter(
|
return@repeatOnLifecycle
|
||||||
response.reversed(),
|
}
|
||||||
(activity as MainActivity).searchView
|
// only load the suggestions if the input field didn't get cleared yet
|
||||||
)
|
val suggestionsAdapter = SearchSuggestionsAdapter(
|
||||||
if (isAdded && !viewModel.searchQuery.value.isNullOrEmpty()) {
|
response.reversed(),
|
||||||
binding.suggestionsRecycler.adapter = suggestionsAdapter
|
(activity as MainActivity).searchView
|
||||||
|
)
|
||||||
|
if (isAdded && !viewModel.searchQuery.value.isNullOrEmpty()) {
|
||||||
|
binding.suggestionsRecycler.adapter = suggestionsAdapter
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,9 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.RetrofitInstance
|
import com.github.libretube.api.RetrofitInstance
|
||||||
@ -87,49 +89,53 @@ class SearchResultFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchSearch() {
|
private fun fetchSearch() {
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launch {
|
||||||
view?.let { context?.hideKeyboard(it) }
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
val response = try {
|
view?.let { context?.hideKeyboard(it) }
|
||||||
withContext(Dispatchers.IO) {
|
val response = try {
|
||||||
RetrofitInstance.api.getSearchResults(query, apiSearchFilter)
|
withContext(Dispatchers.IO) {
|
||||||
|
RetrofitInstance.api.getSearchResults(query, apiSearchFilter)
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
println(e)
|
||||||
|
Log.e(TAG(), "IOException, you might not have internet connection $e")
|
||||||
|
return@repeatOnLifecycle
|
||||||
|
} catch (e: HttpException) {
|
||||||
|
Log.e(TAG(), "HttpException, unexpected response")
|
||||||
|
return@repeatOnLifecycle
|
||||||
}
|
}
|
||||||
} catch (e: IOException) {
|
searchAdapter = SearchAdapter()
|
||||||
println(e)
|
binding.searchRecycler.adapter = searchAdapter
|
||||||
Log.e(TAG(), "IOException, you might not have internet connection $e")
|
searchAdapter.submitList(response.items)
|
||||||
return@launchWhenCreated
|
binding.noSearchResult.isVisible = response.items.isEmpty()
|
||||||
} catch (e: HttpException) {
|
nextPage = response.nextpage
|
||||||
Log.e(TAG(), "HttpException, unexpected response")
|
|
||||||
return@launchWhenCreated
|
|
||||||
}
|
}
|
||||||
searchAdapter = SearchAdapter()
|
|
||||||
binding.searchRecycler.adapter = searchAdapter
|
|
||||||
searchAdapter.submitList(response.items)
|
|
||||||
binding.noSearchResult.isVisible = response.items.isEmpty()
|
|
||||||
nextPage = response.nextpage
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchNextSearchItems() {
|
private fun fetchNextSearchItems() {
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launch {
|
||||||
val response = try {
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
withContext(Dispatchers.IO) {
|
val response = try {
|
||||||
RetrofitInstance.api.getSearchResultsNextPage(
|
withContext(Dispatchers.IO) {
|
||||||
query,
|
RetrofitInstance.api.getSearchResultsNextPage(
|
||||||
apiSearchFilter,
|
query,
|
||||||
nextPage!!
|
apiSearchFilter,
|
||||||
)
|
nextPage!!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
println(e)
|
||||||
|
Log.e(TAG(), "IOException, you might not have internet connection")
|
||||||
|
return@repeatOnLifecycle
|
||||||
|
} catch (e: HttpException) {
|
||||||
|
Log.e(TAG(), "HttpException, unexpected response," + e.response())
|
||||||
|
return@repeatOnLifecycle
|
||||||
|
}
|
||||||
|
nextPage = response.nextpage!!
|
||||||
|
if (response.items.isNotEmpty()) {
|
||||||
|
searchAdapter.submitList(searchAdapter.currentList + response.items)
|
||||||
}
|
}
|
||||||
} catch (e: IOException) {
|
|
||||||
println(e)
|
|
||||||
Log.e(TAG(), "IOException, you might not have internet connection")
|
|
||||||
return@launchWhenCreated
|
|
||||||
} catch (e: HttpException) {
|
|
||||||
Log.e(TAG(), "HttpException, unexpected response," + e.response())
|
|
||||||
return@launchWhenCreated
|
|
||||||
}
|
|
||||||
nextPage = response.nextpage!!
|
|
||||||
if (response.items.isNotEmpty()) {
|
|
||||||
searchAdapter.submitList(searchAdapter.currentList + response.items)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,9 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.api.RetrofitInstance
|
import com.github.libretube.api.RetrofitInstance
|
||||||
import com.github.libretube.databinding.FragmentTrendsBinding
|
import com.github.libretube.databinding.FragmentTrendsBinding
|
||||||
@ -19,6 +21,7 @@ import com.github.libretube.ui.adapters.VideosAdapter
|
|||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
|
|
||||||
@ -51,40 +54,42 @@ class TrendsFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchTrending() {
|
private fun fetchTrending() {
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launch {
|
||||||
val response = try {
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
withContext(Dispatchers.IO) {
|
val response = try {
|
||||||
val region = LocaleHelper.getTrendingRegion(requireContext())
|
withContext(Dispatchers.IO) {
|
||||||
RetrofitInstance.api.getTrending(region)
|
val region = LocaleHelper.getTrendingRegion(requireContext())
|
||||||
}
|
RetrofitInstance.api.getTrending(region)
|
||||||
} catch (e: IOException) {
|
|
||||||
println(e)
|
|
||||||
Log.e(TAG(), "IOException, you might not have internet connection")
|
|
||||||
Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show()
|
|
||||||
return@launchWhenCreated
|
|
||||||
} catch (e: HttpException) {
|
|
||||||
Log.e(TAG(), "HttpException, unexpected response")
|
|
||||||
Toast.makeText(context, R.string.server_error, Toast.LENGTH_SHORT).show()
|
|
||||||
return@launchWhenCreated
|
|
||||||
}
|
|
||||||
|
|
||||||
val binding = _binding ?: return@launchWhenCreated
|
|
||||||
binding.homeRefresh.isRefreshing = false
|
|
||||||
binding.progressBar.visibility = View.GONE
|
|
||||||
|
|
||||||
// show a [SnackBar] if there are no trending videos available
|
|
||||||
if (response.isEmpty()) {
|
|
||||||
Snackbar.make(binding.root, R.string.change_region, Snackbar.LENGTH_LONG)
|
|
||||||
.setAction(R.string.settings) {
|
|
||||||
val settingsIntent = Intent(context, SettingsActivity::class.java)
|
|
||||||
startActivity(settingsIntent)
|
|
||||||
}
|
}
|
||||||
.show()
|
} catch (e: IOException) {
|
||||||
return@launchWhenCreated
|
println(e)
|
||||||
}
|
Log.e(TAG(), "IOException, you might not have internet connection")
|
||||||
|
Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show()
|
||||||
|
return@repeatOnLifecycle
|
||||||
|
} catch (e: HttpException) {
|
||||||
|
Log.e(TAG(), "HttpException, unexpected response")
|
||||||
|
Toast.makeText(context, R.string.server_error, Toast.LENGTH_SHORT).show()
|
||||||
|
return@repeatOnLifecycle
|
||||||
|
}
|
||||||
|
|
||||||
binding.recview.adapter = VideosAdapter(response.toMutableList())
|
val binding = _binding ?: return@repeatOnLifecycle
|
||||||
binding.recview.layoutManager = VideosAdapter.getLayout(requireContext())
|
binding.homeRefresh.isRefreshing = false
|
||||||
|
binding.progressBar.visibility = View.GONE
|
||||||
|
|
||||||
|
// show a [SnackBar] if there are no trending videos available
|
||||||
|
if (response.isEmpty()) {
|
||||||
|
Snackbar.make(binding.root, R.string.change_region, Snackbar.LENGTH_LONG)
|
||||||
|
.setAction(R.string.settings) {
|
||||||
|
val settingsIntent = Intent(context, SettingsActivity::class.java)
|
||||||
|
startActivity(settingsIntent)
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
return@repeatOnLifecycle
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.recview.adapter = VideosAdapter(response.toMutableList())
|
||||||
|
binding.recview.layoutManager = VideosAdapter.getLayout(requireContext())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,9 @@ package com.github.libretube.ui.preferences
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
@ -123,45 +125,51 @@ class InstanceSettings : BasePreferenceFragment() {
|
|||||||
private fun initInstancesPref(instancePrefs: List<ListPreference>) {
|
private fun initInstancesPref(instancePrefs: List<ListPreference>) {
|
||||||
val appContext = requireContext().applicationContext
|
val appContext = requireContext().applicationContext
|
||||||
|
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launch {
|
||||||
val customInstances = withContext(Dispatchers.IO) {
|
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||||
Database.customInstanceDao().getAll()
|
val customInstances = withContext(Dispatchers.IO) {
|
||||||
}
|
Database.customInstanceDao().getAll()
|
||||||
|
}
|
||||||
|
|
||||||
for (instancePref in instancePrefs) {
|
for (instancePref in instancePrefs) {
|
||||||
instancePref.summaryProvider =
|
instancePref.summaryProvider =
|
||||||
Preference.SummaryProvider<ListPreference> { preference ->
|
Preference.SummaryProvider<ListPreference> { preference ->
|
||||||
preference.entry
|
preference.entry
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetch official public instances from kavin.rocks as well as tokhmi.xyz as fallback
|
// fetch official public instances from kavin.rocks as well as tokhmi.xyz as
|
||||||
val instances = withContext(Dispatchers.IO) {
|
// fallback
|
||||||
runCatching {
|
val instances = withContext(Dispatchers.IO) {
|
||||||
RetrofitInstance.externalApi.getInstances(PIPED_INSTANCES_URL).toMutableList()
|
runCatching {
|
||||||
}.getOrNull() ?: runCatching {
|
RetrofitInstance.externalApi.getInstances(PIPED_INSTANCES_URL)
|
||||||
RetrofitInstance.externalApi.getInstances(FALLBACK_INSTANCES_URL).toMutableList()
|
.toMutableList()
|
||||||
}.getOrNull() ?: run {
|
}.getOrNull() ?: runCatching {
|
||||||
appContext.toastFromMainDispatcher(R.string.failed_fetching_instances)
|
RetrofitInstance.externalApi.getInstances(FALLBACK_INSTANCES_URL)
|
||||||
val instanceNames = resources.getStringArray(R.array.instances)
|
.toMutableList()
|
||||||
resources.getStringArray(R.array.instancesValue).mapIndexed { index, instanceValue ->
|
}.getOrNull() ?: run {
|
||||||
Instances(instanceNames[index], instanceValue)
|
appContext.toastFromMainDispatcher(R.string.failed_fetching_instances)
|
||||||
|
val instanceNames = resources.getStringArray(R.array.instances)
|
||||||
|
resources.getStringArray(R.array.instancesValue)
|
||||||
|
.mapIndexed { index, instanceValue ->
|
||||||
|
Instances(instanceNames[index], instanceValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
.sortedBy { it.name }
|
||||||
.sortedBy { it.name }
|
.toMutableList()
|
||||||
.toMutableList()
|
|
||||||
|
|
||||||
instances.addAll(customInstances.map { Instances(it.name, it.apiUrl) })
|
instances.addAll(customInstances.map { Instances(it.name, it.apiUrl) })
|
||||||
|
|
||||||
for (instancePref in instancePrefs) {
|
for (instancePref in instancePrefs) {
|
||||||
// add custom instances to the list preference
|
// add custom instances to the list preference
|
||||||
instancePref.entries = instances.map { it.name }.toTypedArray()
|
instancePref.entries = instances.map { it.name }.toTypedArray()
|
||||||
instancePref.entryValues = instances.map { it.apiUrl }.toTypedArray()
|
instancePref.entryValues = instances.map { it.apiUrl }.toTypedArray()
|
||||||
instancePref.summaryProvider =
|
instancePref.summaryProvider =
|
||||||
Preference.SummaryProvider<ListPreference> { preference ->
|
Preference.SummaryProvider<ListPreference> { preference ->
|
||||||
preference.entry
|
preference.entry
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user