Merge pull request #958 from Bnyro/master

unauthenticated subscriptions
This commit is contained in:
Bnyro 2022-08-03 13:42:36 +02:00 committed by GitHub
commit 36f3cc295a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 211 additions and 319 deletions

View File

@ -13,18 +13,15 @@ import com.github.libretube.databinding.VideoRowBinding
import com.github.libretube.dialogs.PlaylistOptionsDialog
import com.github.libretube.dialogs.VideoOptionsDialog
import com.github.libretube.obj.SearchItem
import com.github.libretube.obj.Subscribe
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.ConnectionHelper
import com.github.libretube.util.NavigationHelper
import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.SubscriptionHelper
import com.github.libretube.util.formatShort
import com.github.libretube.util.setWatchProgressLength
import com.github.libretube.util.toID
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.IOException
class SearchAdapter(
private val searchItems: MutableList<SearchItem>,
@ -128,80 +125,41 @@ class SearchAdapter(
NavigationHelper.navigateChannel(root.context, item.url)
}
val channelId = item.url.toID()
val token = PreferenceHelper.getToken()
// only show subscribe button if logged in
if (token != "") isSubscribed(channelId, token, binding)
isSubscribed(channelId, binding)
}
}
private fun isSubscribed(channelId: String, token: String, binding: ChannelRowBinding) {
var isSubscribed = false
private fun isSubscribed(channelId: String, binding: ChannelRowBinding) {
// check whether the user subscribed to the channel
CoroutineScope(Dispatchers.Main).launch {
val response = try {
RetrofitInstance.authApi.isSubscribed(
channelId,
token
)
} catch (e: Exception) {
return@launch
}
var isSubscribed = SubscriptionHelper.isSubscribed(channelId)
// if subscribed change text to unsubscribe
if (response.subscribed == true) {
isSubscribed = true
if (isSubscribed == true) {
binding.searchSubButton.text = binding.root.context.getString(R.string.unsubscribe)
}
// make sub button visible and set the on click listeners to (un)subscribe
if (response.subscribed != null) {
binding.searchSubButton.visibility = View.VISIBLE
if (isSubscribed == null) return@launch
binding.searchSubButton.visibility = View.VISIBLE
binding.searchSubButton.setOnClickListener {
if (!isSubscribed) {
subscribe(token, channelId)
binding.searchSubButton.text =
binding.root.context.getString(R.string.unsubscribe)
isSubscribed = true
} else {
unsubscribe(token, channelId)
binding.searchSubButton.text =
binding.root.context.getString(R.string.subscribe)
isSubscribed = false
}
binding.searchSubButton.setOnClickListener {
if (isSubscribed == false) {
SubscriptionHelper.subscribe(channelId)
binding.searchSubButton.text =
binding.root.context.getString(R.string.unsubscribe)
isSubscribed = true
} else {
SubscriptionHelper.unsubscribe(channelId)
binding.searchSubButton.text =
binding.root.context.getString(R.string.subscribe)
isSubscribed = false
}
}
}
}
private fun subscribe(token: String, channelId: String) {
CoroutineScope(Dispatchers.IO).launch {
try {
RetrofitInstance.authApi.subscribe(
token,
Subscribe(channelId)
)
} catch (e: Exception) {
return@launch
}
}
}
private fun unsubscribe(token: String, channelId: String) {
CoroutineScope(Dispatchers.IO).launch {
try {
RetrofitInstance.authApi.unsubscribe(
token,
Subscribe(channelId)
)
} catch (e: IOException) {
return@launch
}
}
}
private fun bindPlaylist(item: SearchItem, binding: PlaylistSearchRowBinding) {
binding.apply {
ConnectionHelper.loadImage(item.thumbnail, searchThumbnail)

View File

@ -1,21 +1,15 @@
package com.github.libretube.adapters
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.R
import com.github.libretube.databinding.ChannelSubscriptionRowBinding
import com.github.libretube.obj.Subscribe
import com.github.libretube.obj.Subscription
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.ConnectionHelper
import com.github.libretube.util.NavigationHelper
import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.SubscriptionHelper
import com.github.libretube.util.toID
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class SubscriptionChannelAdapter(private val subscriptions: MutableList<Subscription>) :
RecyclerView.Adapter<SubscriptionChannelViewHolder>() {
@ -46,51 +40,17 @@ class SubscriptionChannelAdapter(private val subscriptions: MutableList<Subscrip
val channelId = subscription.url.toID()
if (subscribed) {
subscriptionSubscribe.text = root.context.getString(R.string.subscribe)
unsubscribe(channelId)
SubscriptionHelper.unsubscribe(channelId)
subscribed = false
} else {
subscriptionSubscribe.text =
root.context.getString(R.string.unsubscribe)
subscribe(channelId)
SubscriptionHelper.subscribe(channelId)
subscribed = true
}
}
}
}
private fun subscribe(channelId: String) {
fun run() {
CoroutineScope(Dispatchers.IO).launch {
try {
val token = PreferenceHelper.getToken()
RetrofitInstance.authApi.subscribe(
token,
Subscribe(channelId)
)
} catch (e: Exception) {
Log.e(TAG, e.toString())
}
}
}
run()
}
private fun unsubscribe(channelId: String) {
fun run() {
CoroutineScope(Dispatchers.IO).launch {
try {
val token = PreferenceHelper.getToken()
RetrofitInstance.authApi.unsubscribe(
token,
Subscribe(channelId)
)
} catch (e: Exception) {
Log.e(TAG, e.toString())
}
}
}
run()
}
}
class SubscriptionChannelViewHolder(val binding: ChannelSubscriptionRowBinding) :

View File

@ -5,17 +5,15 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.github.libretube.R
import com.github.libretube.adapters.ChannelAdapter
import com.github.libretube.databinding.FragmentChannelBinding
import com.github.libretube.obj.Subscribe
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.util.ConnectionHelper
import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.SubscriptionHelper
import com.github.libretube.util.formatShort
import com.github.libretube.util.toID
import retrofit2.HttpException
@ -31,7 +29,7 @@ class ChannelFragment : Fragment() {
var nextPage: String? = null
private var channelAdapter: ChannelAdapter? = null
private var isLoading = true
private var isSubscribed: Boolean = false
private var isSubscribed: Boolean? = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -61,13 +59,7 @@ class ChannelFragment : Fragment() {
val refreshChannel = {
binding.channelRefresh.isRefreshing = true
fetchChannel()
if (PreferenceHelper.getToken() != "") {
isSubscribed()
} else {
binding.channelSubscribe.setOnClickListener {
Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT).show()
}
}
isSubscribed()
}
refreshChannel()
binding.channelRefresh.setOnRefreshListener {
@ -90,76 +82,28 @@ class ChannelFragment : Fragment() {
}
private fun isSubscribed() {
fun run() {
lifecycleScope.launchWhenCreated {
val response = try {
val token = PreferenceHelper.getToken()
RetrofitInstance.authApi.isSubscribed(
channelId!!,
token
)
} catch (e: Exception) {
Log.e(TAG, e.toString())
return@launchWhenCreated
lifecycleScope.launchWhenCreated {
isSubscribed = SubscriptionHelper.isSubscribed(channelId!!)
if (isSubscribed == null) return@launchWhenCreated
runOnUiThread {
if (isSubscribed == true) {
binding.channelSubscribe.text = getString(R.string.unsubscribe)
}
runOnUiThread {
if (response.subscribed == true) {
binding.channelSubscribe.setOnClickListener {
binding.channelSubscribe.text = if (isSubscribed == true) {
SubscriptionHelper.unsubscribe(channelId!!)
isSubscribed = false
getString(R.string.subscribe)
} else {
SubscriptionHelper.subscribe(channelId!!)
isSubscribed = true
binding.channelSubscribe.text = getString(R.string.unsubscribe)
}
binding.channelSubscribe.setOnClickListener {
if (response.subscribed != null) {
binding.channelSubscribe.text = if (isSubscribed) {
unsubscribe()
getString(R.string.subscribe)
} else {
subscribe()
getString(R.string.unsubscribe)
}
}
getString(R.string.unsubscribe)
}
}
}
}
run()
}
private fun subscribe() {
fun run() {
lifecycleScope.launchWhenCreated {
try {
val token = PreferenceHelper.getToken()
RetrofitInstance.authApi.subscribe(
token,
Subscribe(channelId)
)
} catch (e: Exception) {
Log.e(TAG, e.toString())
}
isSubscribed = true
}
}
run()
}
private fun unsubscribe() {
fun run() {
lifecycleScope.launchWhenCreated {
try {
val token = PreferenceHelper.getToken()
RetrofitInstance.authApi.unsubscribe(
token,
Subscribe(channelId)
)
} catch (e: Exception) {
Log.e(TAG, e.toString())
}
isSubscribed = false
}
}
run()
}
private fun fetchChannel() {

View File

@ -55,7 +55,6 @@ import com.github.libretube.obj.Segment
import com.github.libretube.obj.Segments
import com.github.libretube.obj.StreamItem
import com.github.libretube.obj.Streams
import com.github.libretube.obj.Subscribe
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.preferences.PreferenceKeys
import com.github.libretube.services.BackgroundMode
@ -66,6 +65,7 @@ import com.github.libretube.util.DescriptionAdapter
import com.github.libretube.util.OnDoubleTapEventListener
import com.github.libretube.util.PlayerHelper
import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.SubscriptionHelper
import com.github.libretube.util.formatShort
import com.github.libretube.util.hideKeyboard
import com.github.libretube.util.toID
@ -117,7 +117,7 @@ class PlayerFragment : Fragment() {
private var videoId: String? = null
private var playlistId: String? = null
private var channelId: String? = null
private var isSubscribed: Boolean = false
private var isSubscribed: Boolean? = false
private var isLive = false
/**
@ -1564,38 +1564,22 @@ class PlayerFragment : Fragment() {
private fun isSubscribed() {
fun run() {
lifecycleScope.launchWhenCreated {
val response = try {
RetrofitInstance.authApi.isSubscribed(
channelId!!,
token
)
} 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")
return@launchWhenCreated
}
isSubscribed = SubscriptionHelper.isSubscribed(channelId!!)
if (isSubscribed == null) return@launchWhenCreated
runOnUiThread {
if (response.subscribed == true) {
isSubscribed = true
if (isSubscribed == true) {
binding.playerSubscribe.text = getString(R.string.unsubscribe)
}
if (response.subscribed != null) {
binding.playerSubscribe.setOnClickListener {
if (isSubscribed) {
unsubscribe(channelId!!)
binding.playerSubscribe.text = getString(R.string.subscribe)
} else {
subscribe(channelId!!)
binding.playerSubscribe.text = getString(R.string.unsubscribe)
}
binding.playerSubscribe.setOnClickListener {
if (isSubscribed == true) {
SubscriptionHelper.unsubscribe(channelId!!)
binding.playerSubscribe.text = getString(R.string.subscribe)
} else {
SubscriptionHelper.subscribe(channelId!!)
binding.playerSubscribe.text = getString(R.string.unsubscribe)
}
} else {
Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT)
.show()
}
}
}
@ -1603,50 +1587,6 @@ class PlayerFragment : Fragment() {
run()
}
private fun subscribe(channelId: String) {
fun run() {
lifecycleScope.launchWhenCreated {
try {
RetrofitInstance.authApi.subscribe(
token,
Subscribe(channelId)
)
} 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")
return@launchWhenCreated
}
isSubscribed = true
}
}
run()
}
private fun unsubscribe(channel_id: String) {
fun run() {
lifecycleScope.launchWhenCreated {
try {
RetrofitInstance.authApi.unsubscribe(
token,
Subscribe(channel_id)
)
} 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")
return@launchWhenCreated
}
isSubscribed = false
}
}
run()
}
private fun Fragment?.runOnUiThread(action: () -> Unit) {
this ?: return
if (!isAdded) return // Fragment not attached to an Activity

View File

@ -19,6 +19,7 @@ import com.github.libretube.obj.StreamItem
import com.github.libretube.preferences.PreferenceHelper
import com.github.libretube.preferences.PreferenceKeys
import com.github.libretube.util.RetrofitInstance
import com.github.libretube.util.SubscriptionHelper
import com.github.libretube.util.toID
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import retrofit2.HttpException
@ -53,63 +54,57 @@ class SubscriptionsFragment : Fragment() {
super.onViewCreated(view, savedInstanceState)
token = PreferenceHelper.getToken()
if (token != "") {
binding.loginOrRegister.visibility = View.GONE
binding.subRefresh.isEnabled = true
binding.subRefresh.isEnabled = true
binding.subProgress.visibility = View.VISIBLE
binding.subProgress.visibility = View.VISIBLE
val grid = PreferenceHelper.getString(
PreferenceKeys.GRID_COLUMNS,
resources.getInteger(R.integer.grid_items).toString()
)
binding.subFeed.layoutManager = GridLayoutManager(view.context, grid.toInt())
val grid = PreferenceHelper.getString(
PreferenceKeys.GRID_COLUMNS,
resources.getInteger(R.integer.grid_items).toString()
)
binding.subFeed.layoutManager = GridLayoutManager(view.context, grid.toInt())
fetchFeed()
binding.subRefresh.setOnRefreshListener {
fetchChannels()
fetchFeed()
binding.subRefresh.setOnRefreshListener {
fetchChannels()
fetchFeed()
}
binding.sortTV.setOnClickListener {
showSortDialog()
}
binding.toggleSubs.visibility = View.VISIBLE
var loadedSubbedChannels = false
binding.toggleSubs.setOnClickListener {
if (!binding.subChannelsContainer.isVisible) {
if (!loadedSubbedChannels) {
binding.subChannels.layoutManager = LinearLayoutManager(context)
fetchChannels()
loadedSubbedChannels = true
}
binding.subChannelsContainer.visibility = View.VISIBLE
binding.subFeedContainer.visibility = View.GONE
} else {
binding.subChannelsContainer.visibility = View.GONE
binding.subFeedContainer.visibility = View.VISIBLE
}
}
binding.scrollviewSub.viewTreeObserver
.addOnScrollChangedListener {
if (binding.scrollviewSub.getChildAt(0).bottom
== (binding.scrollviewSub.height + binding.scrollviewSub.scrollY)
) {
// scroll view is at bottom
if (isLoaded) {
binding.subRefresh.isRefreshing = true
subscriptionAdapter?.updateItems()
binding.subRefresh.isRefreshing = false
}
}
}
} else {
binding.subRefresh.isEnabled = false
binding.subFeedContainer.visibility = View.GONE
}
binding.sortTV.setOnClickListener {
showSortDialog()
}
binding.toggleSubs.visibility = View.VISIBLE
var loadedSubbedChannels = false
binding.toggleSubs.setOnClickListener {
if (!binding.subChannelsContainer.isVisible) {
if (!loadedSubbedChannels) {
binding.subChannels.layoutManager = LinearLayoutManager(context)
fetchChannels()
loadedSubbedChannels = true
}
binding.subChannelsContainer.visibility = View.VISIBLE
binding.subFeedContainer.visibility = View.GONE
} else {
binding.subChannelsContainer.visibility = View.GONE
binding.subFeedContainer.visibility = View.VISIBLE
}
}
binding.scrollviewSub.viewTreeObserver
.addOnScrollChangedListener {
if (binding.scrollviewSub.getChildAt(0).bottom
== (binding.scrollviewSub.height + binding.scrollviewSub.scrollY)
) {
// scroll view is at bottom
if (isLoaded) {
binding.subRefresh.isRefreshing = true
subscriptionAdapter?.updateItems()
binding.subRefresh.isRefreshing = false
}
}
}
}
private fun showSortDialog() {
@ -130,7 +125,10 @@ class SubscriptionsFragment : Fragment() {
fun run() {
lifecycleScope.launchWhenCreated {
feed = try {
RetrofitInstance.authApi.getFeed(token)
if (token != "") RetrofitInstance.authApi.getFeed(token)
else RetrofitInstance.authApi.getUnauthenticatedFeed(
SubscriptionHelper.getFormattedLocalSubscriptions()
)
} catch (e: IOException) {
Log.e(TAG, e.toString())
Log.e(TAG, "IOException, you might not have internet connection")
@ -148,15 +146,7 @@ class SubscriptionsFragment : Fragment() {
showFeed()
} else {
runOnUiThread {
with(binding.boogh) {
visibility = View.VISIBLE
setImageResource(R.drawable.ic_list)
}
with(binding.textLike) {
visibility = View.VISIBLE
text = getString(R.string.emptyList)
}
binding.loginOrRegister.visibility = View.VISIBLE
binding.emptyFeed.visibility = View.VISIBLE
}
}
binding.subProgress.visibility = View.GONE
@ -185,7 +175,10 @@ class SubscriptionsFragment : Fragment() {
fun run() {
lifecycleScope.launchWhenCreated {
val response = try {
RetrofitInstance.authApi.subscriptions(token)
if (token != "") RetrofitInstance.authApi.subscriptions(token)
else RetrofitInstance.authApi.unauthenticatedSubscriptions(
SubscriptionHelper.getFormattedLocalSubscriptions()
)
} catch (e: IOException) {
Log.e(TAG, e.toString())
Log.e(TAG, "IOException, you might not have internet connection")

View File

@ -235,6 +235,21 @@ object PreferenceHelper {
return getString(PreferenceKeys.ERROR_LOG, "")
}
fun getLocalSubscriptions(): List<String> {
val json = settings.getString(PreferenceKeys.LOCAL_SUBSCRIPTIONS, "")
return try {
val type = object : TypeReference<List<String>>() {}
mapper.readValue(json, type)
} catch (e: Exception) {
listOf()
}
}
fun setLocalSubscriptions(channels: List<String>) {
val json = mapper.writeValueAsString(channels)
editor.putString(PreferenceKeys.LOCAL_SUBSCRIPTIONS, json).commit()
}
private fun getDefaultSharedPreferences(context: Context): SharedPreferences {
return PreferenceManager.getDefaultSharedPreferences(context)
}

View File

@ -61,7 +61,6 @@ object PreferenceKeys {
/**
* Download
*/
const val DOWNLOAD_VIDEO_FORMAT = "video_format"
const val DOWNLOAD_LOCATION = "download_location"
const val DOWNLOAD_FOLDER = "download_folder"
@ -86,4 +85,9 @@ object PreferenceKeys {
* Error logs
*/
const val ERROR_LOG = "error_log"
/**
* Data
*/
const val LOCAL_SUBSCRIPTIONS = "local_subscriptions"
}

View File

@ -99,6 +99,9 @@ interface PipedApi {
@GET("feed")
suspend fun getFeed(@Query("authToken") token: String?): List<StreamItem>
@GET("feed/unauthenticated")
suspend fun getUnauthenticatedFeed(@Query("channels") channels: String): List<StreamItem>
@GET("subscribed")
suspend fun isSubscribed(
@Query("channelId") channelId: String,
@ -108,6 +111,9 @@ interface PipedApi {
@GET("subscriptions")
suspend fun subscriptions(@Header("Authorization") token: String): List<Subscription>
@GET("subscriptions/unauthenticated")
suspend fun unauthenticatedSubscriptions(@Query("channels") channels: String): List<Subscription>
@POST("subscribe")
suspend fun subscribe(
@Header("Authorization") token: String,

View File

@ -0,0 +1,71 @@
package com.github.libretube.util
import android.util.Log
import com.github.libretube.obj.Subscribe
import com.github.libretube.preferences.PreferenceHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
object SubscriptionHelper {
val TAG = "SubscriptionHelper"
fun subscribe(channelId: String) {
if (PreferenceHelper.getToken() != "") {
CoroutineScope(Dispatchers.IO).launch {
try {
RetrofitInstance.authApi.subscribe(
PreferenceHelper.getToken(),
Subscribe(channelId)
)
} catch (e: Exception) {
Log.e(TAG, e.toString())
}
}
} else {
val channels = PreferenceHelper.getLocalSubscriptions().toMutableList()
channels.add(channelId)
PreferenceHelper.setLocalSubscriptions(channels)
}
}
fun unsubscribe(channelId: String) {
if (PreferenceHelper.getToken() != "") {
CoroutineScope(Dispatchers.IO).launch {
try {
RetrofitInstance.authApi.unsubscribe(
PreferenceHelper.getToken(),
Subscribe(channelId)
)
} catch (e: Exception) {
Log.e(TAG, e.toString())
}
}
} else {
val channels = PreferenceHelper.getLocalSubscriptions().toMutableList()
channels.remove(channelId)
PreferenceHelper.setLocalSubscriptions(channels)
}
}
suspend fun isSubscribed(channelId: String): Boolean? {
if (PreferenceHelper.getToken() != "") {
val isSubscribed = try {
RetrofitInstance.authApi.isSubscribed(
channelId,
PreferenceHelper.getToken()
)
} catch (e: Exception) {
Log.e(TAG, e.toString())
return null
}
return isSubscribed.subscribed
} else {
return PreferenceHelper.getLocalSubscriptions().contains(channelId)
}
}
fun getFormattedLocalSubscriptions(): String {
return PreferenceHelper.getLocalSubscriptions().joinToString(",")
}
}

View File

@ -15,10 +15,11 @@
android:visibility="gone" />
<RelativeLayout
android:id="@+id/loginOrRegister"
android:id="@+id/emptyFeed"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true">
android:layout_centerInParent="true"
android:visibility="gone">
<ImageView
android:id="@+id/boogh"
@ -26,7 +27,7 @@
android:layout_height="100dp"
android:layout_centerInParent="true"
android:layout_marginBottom="16dp"
android:src="@drawable/ic_login" />
android:src="@drawable/ic_list" />
<TextView
android:id="@+id/textLike"
@ -36,7 +37,7 @@
android:layout_centerHorizontal="true"
android:layout_marginHorizontal="10dp"
android:gravity="center"
android:text="@string/please_login"
android:text="@string/emptyList"
android:textSize="20sp"
android:textStyle="bold" />
</RelativeLayout>