Merge branch 'libre-tube:master' into master

This commit is contained in:
Giles Munn 2022-11-05 18:45:45 +00:00 committed by GitHub
commit a2c1c9121a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 142 additions and 83 deletions

View File

@ -1,12 +1,16 @@
package com.github.libretube.api package com.github.libretube.api
import android.content.Context
import android.util.Log import android.util.Log
import com.github.libretube.R
import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.db.DatabaseHolder.Companion.Database import com.github.libretube.db.DatabaseHolder.Companion.Database
import com.github.libretube.db.obj.LocalSubscription import com.github.libretube.db.obj.LocalSubscription
import com.github.libretube.extensions.TAG import com.github.libretube.extensions.TAG
import com.github.libretube.extensions.awaitQuery import com.github.libretube.extensions.awaitQuery
import com.github.libretube.extensions.query import com.github.libretube.extensions.query
import com.github.libretube.util.PreferenceHelper import com.github.libretube.util.PreferenceHelper
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -55,6 +59,24 @@ object SubscriptionHelper {
} }
} }
fun handleUnsubscribe(context: Context, channelId: String, channelName: String?, onUnsubscribe: () -> Unit) {
if (!PreferenceHelper.getBoolean(PreferenceKeys.CONFIRM_UNSUBSCRIBE, false)) {
unsubscribe(channelId)
onUnsubscribe.invoke()
return
}
MaterialAlertDialogBuilder(context)
.setTitle(R.string.unsubscribe)
.setMessage(context.getString(R.string.confirm_unsubscribe, channelName))
.setPositiveButton(R.string.unsubscribe) { _, _ ->
unsubscribe(channelId)
onUnsubscribe.invoke()
}
.setNegativeButton(R.string.cancel, null)
.show()
}
suspend fun isSubscribed(channelId: String): Boolean? { suspend fun isSubscribed(channelId: String): Boolean? {
if (PreferenceHelper.getToken() != "") { if (PreferenceHelper.getToken() != "") {
val isSubscribed = try { val isSubscribed = try {
@ -99,7 +121,7 @@ object SubscriptionHelper {
} }
} }
fun getLocalSubscriptions(): List<LocalSubscription> { private fun getLocalSubscriptions(): List<LocalSubscription> {
return awaitQuery { return awaitQuery {
Database.localSubscriptionDao().getAll() Database.localSubscriptionDao().getAll()
} }
@ -107,6 +129,6 @@ object SubscriptionHelper {
fun getFormattedLocalSubscriptions(): String { fun getFormattedLocalSubscriptions(): String {
val localSubscriptions = getLocalSubscriptions() val localSubscriptions = getLocalSubscriptions()
return localSubscriptions.map { it.channelId }.joinToString(",") return localSubscriptions.joinToString(",") { it.channelId }
} }
} }

View File

@ -104,6 +104,7 @@ object PreferenceKeys {
const val CLEAR_WATCH_HISTORY = "clear_watch_history" const val CLEAR_WATCH_HISTORY = "clear_watch_history"
const val CLEAR_WATCH_POSITIONS = "clear_watch_positions" const val CLEAR_WATCH_POSITIONS = "clear_watch_positions"
const val SHARE_WITH_TIME_CODE = "share_with_time_code" const val SHARE_WITH_TIME_CODE = "share_with_time_code"
const val CONFIRM_UNSUBSCRIBE = "confirm_unsubscribing"
/** /**
* History * History

View File

@ -1,6 +1,7 @@
package com.github.libretube.ui.adapters package com.github.libretube.ui.adapters
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -128,13 +129,13 @@ class SearchAdapter(
root.setOnClickListener { root.setOnClickListener {
NavigationHelper.navigateChannel(root.context, item.url) NavigationHelper.navigateChannel(root.context, item.url)
} }
val channelId = item.url!!.toID()
isSubscribed(channelId, binding) isSubscribed(root.context, item, binding)
} }
} }
private fun isSubscribed(channelId: String, binding: ChannelRowBinding) { private fun isSubscribed(context: Context, streamItem: ContentItem, binding: ChannelRowBinding) {
val channelId = streamItem.url!!.toID()
// check whether the user subscribed to the channel // check whether the user subscribed to the channel
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
var isSubscribed = SubscriptionHelper.isSubscribed(channelId) var isSubscribed = SubscriptionHelper.isSubscribed(channelId)
@ -151,18 +152,17 @@ class SearchAdapter(
binding.searchSubButton.setOnClickListener { binding.searchSubButton.setOnClickListener {
if (isSubscribed == false) { if (isSubscribed == false) {
SubscriptionHelper.subscribe(channelId) SubscriptionHelper.subscribe(channelId)
binding.searchSubButton.text = binding.searchSubButton.text = binding.root.context.getString(R.string.unsubscribe)
binding.root.context.getString(R.string.unsubscribe)
isSubscribed = true isSubscribed = true
} else { } else {
SubscriptionHelper.unsubscribe(channelId) SubscriptionHelper.handleUnsubscribe(context, channelId, streamItem.uploaderName) {
binding.searchSubButton.text = binding.searchSubButton.text = binding.root.context.getString(R.string.subscribe)
binding.root.context.getString(R.string.subscribe)
isSubscribed = false isSubscribed = false
} }
} }
} }
} }
}
private fun bindPlaylist( private fun bindPlaylist(
item: ContentItem, item: ContentItem,

View File

@ -38,13 +38,13 @@ class SubscriptionChannelAdapter(private val subscriptions: MutableList<com.gith
subscriptionSubscribe.setOnClickListener { subscriptionSubscribe.setOnClickListener {
val channelId = subscription.url!!.toID() val channelId = subscription.url!!.toID()
if (subscribed) { if (subscribed) {
SubscriptionHelper.handleUnsubscribe(root.context, channelId, subscription.name ?: "") {
subscriptionSubscribe.text = root.context.getString(R.string.subscribe) subscriptionSubscribe.text = root.context.getString(R.string.subscribe)
SubscriptionHelper.unsubscribe(channelId)
subscribed = false subscribed = false
}
} else { } else {
subscriptionSubscribe.text =
root.context.getString(R.string.unsubscribe)
SubscriptionHelper.subscribe(channelId) SubscriptionHelper.subscribe(channelId)
subscriptionSubscribe.text = root.context.getString(R.string.unsubscribe)
subscribed = true subscribed = true
} }
} }

View File

@ -6,7 +6,6 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.core.view.children
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.github.libretube.R import com.github.libretube.R
@ -25,7 +24,6 @@ import com.github.libretube.ui.adapters.VideosAdapter
import com.github.libretube.ui.base.BaseFragment import com.github.libretube.ui.base.BaseFragment
import com.github.libretube.ui.dialogs.ShareDialog import com.github.libretube.ui.dialogs.ShareDialog
import com.github.libretube.util.ImageHelper import com.github.libretube.util.ImageHelper
import com.google.android.material.chip.Chip
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -74,7 +72,9 @@ class ChannelFragment : BaseFragment() {
binding.channelRefresh.isRefreshing = true binding.channelRefresh.isRefreshing = true
fetchChannel() fetchChannel()
} }
refreshChannel() refreshChannel()
binding.channelRefresh.setOnRefreshListener { binding.channelRefresh.setOnRefreshListener {
refreshChannel() refreshChannel()
} }
@ -112,6 +112,7 @@ class ChannelFragment : BaseFragment() {
} }
// needed if the channel gets loaded by the ID // needed if the channel gets loaded by the ID
channelId = response.id channelId = response.id
channelName = response.name
val shareData = ShareData(currentChannel = response.name) val shareData = ShareData(currentChannel = response.name)
onScrollEnd = { onScrollEnd = {
@ -128,14 +129,15 @@ class ChannelFragment : BaseFragment() {
} }
binding.channelSubscribe.setOnClickListener { binding.channelSubscribe.setOnClickListener {
binding.channelSubscribe.text = if (isSubscribed == true) { if (isSubscribed == true) {
SubscriptionHelper.unsubscribe(channelId!!) SubscriptionHelper.handleUnsubscribe(requireContext(), channelId!!, channelName) {
isSubscribed = false isSubscribed = false
getString(R.string.subscribe) binding.channelSubscribe.text = getString(R.string.subscribe)
}
} else { } else {
SubscriptionHelper.subscribe(channelId!!) SubscriptionHelper.subscribe(channelId!!)
isSubscribed = true isSubscribed = true
getString(R.string.unsubscribe) binding.channelSubscribe.text = getString(R.string.unsubscribe)
} }
} }
@ -192,40 +194,56 @@ class ChannelFragment : BaseFragment() {
binding.channelRecView.adapter = channelAdapter binding.channelRecView.adapter = channelAdapter
} }
binding.videos.setOnClickListener { setupTabs(response.tabs)
}
}
private fun setupTabs(tabs: List<ChannelTab>?) {
tabs?.firstOrNull { it.name == "Playlists" }?.let {
binding.playlists.visibility = View.VISIBLE
}
tabs?.firstOrNull { it.name == "Channels" }?.let {
binding.playlists.visibility = View.VISIBLE
}
tabs?.firstOrNull { it.name == "Livestreams" }?.let {
binding.playlists.visibility = View.VISIBLE
}
tabs?.firstOrNull { it.name == "Shorts" }?.let {
binding.playlists.visibility = View.VISIBLE
}
binding.tabChips.setOnCheckedStateChangeListener { _, _ ->
reactToTabChange(tabs)
}
}
private fun reactToTabChange(tabs: List<ChannelTab>?) {
when (binding.tabChips.checkedChipId) {
binding.videos.id -> {
binding.channelRecView.adapter = channelAdapter binding.channelRecView.adapter = channelAdapter
onScrollEnd = { onScrollEnd = {
fetchChannelNextPage() fetchChannelNextPage()
} }
binding.tabChips.children.forEach { child ->
if (child != it) (child as Chip).isChecked = false
} }
binding.channels.id -> {
tabs?.first { it.name == "Channels" }?.let { loadTab(it) }
} }
binding.playlists.id -> {
response.tabs?.firstOrNull { it.name == "Playlists" }?.let { tabs?.first { it.name == "Playlists" }?.let { loadTab(it) }
setupTab(binding.playlists, it)
} }
binding.livestreams.id -> {
response.tabs?.firstOrNull { it.name == "Channels" }?.let { tabs?.first { it.name == "Livestreams" }?.let { loadTab(it) }
setupTab(binding.channels, it)
} }
binding.shorts.id -> {
response.tabs?.firstOrNull { it.name == "Livestreams" }?.let { tabs?.first { it.name == "Shorts" }?.let { loadTab(it) }
setupTab(binding.livestreams, it)
}
response.tabs?.firstOrNull { it.name == "Shorts" }?.let {
setupTab(binding.shorts, it)
} }
} }
} }
private fun setupTab(chip: Chip, tab: ChannelTab) { private fun loadTab(tab: ChannelTab) {
chip.visibility = View.VISIBLE
chip.setOnClickListener {
binding.tabChips.children.forEach {
if (it != chip) (it as Chip).isChecked = false
}
scope.launch { scope.launch {
val response = try { val response = try {
RetrofitInstance.api.getChannelTab(tab.data!!) RetrofitInstance.api.getChannelTab(tab.data!!)
@ -252,7 +270,6 @@ class ChannelFragment : BaseFragment() {
} }
} }
} }
}
private fun fetchChannelNextPage() { private fun fetchChannelNextPage() {
fun run() { fun run() {

View File

@ -863,11 +863,10 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
} }
}) })
// check if livestream
if (response.duration > 0) {
// download clicked
binding.relPlayerDownload.setOnClickListener { binding.relPlayerDownload.setOnClickListener {
if (!DownloadService.IS_DOWNLOAD_RUNNING) { if (response.duration <= 0) {
Toast.makeText(context, R.string.cannotDownload, Toast.LENGTH_SHORT).show()
} else if (!DownloadService.IS_DOWNLOAD_RUNNING) {
val newFragment = DownloadDialog(videoId!!) val newFragment = DownloadDialog(videoId!!)
newFragment.show(childFragmentManager, DownloadDialog::class.java.name) newFragment.show(childFragmentManager, DownloadDialog::class.java.name)
} else { } else {
@ -875,9 +874,6 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
.show() .show()
} }
} }
} else {
Toast.makeText(context, R.string.cannotDownload, Toast.LENGTH_SHORT).show()
}
if (response.hls != null) { if (response.hls != null) {
binding.relPlayerOpen.setOnClickListener { binding.relPlayerOpen.setOnClickListener {
@ -1271,9 +1267,10 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
} }
binding.playerSubscribe.setOnClickListener { binding.playerSubscribe.setOnClickListener {
if (isSubscribed == true) { if (isSubscribed == true) {
SubscriptionHelper.unsubscribe(channelId) SubscriptionHelper.handleUnsubscribe(requireContext(), channelId, streams.uploader) {
binding.playerSubscribe.text = getString(R.string.subscribe) binding.playerSubscribe.text = getString(R.string.subscribe)
isSubscribed = false isSubscribed = false
}
} else { } else {
SubscriptionHelper.subscribe(channelId) SubscriptionHelper.subscribe(channelId)
binding.playerSubscribe.text = getString(R.string.unsubscribe) binding.playerSubscribe.text = getString(R.string.unsubscribe)

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z" />
</vector>

View File

@ -124,12 +124,15 @@
<com.google.android.material.chip.ChipGroup <com.google.android.material.chip.ChipGroup
android:id="@+id/tab_chips" android:id="@+id/tab_chips"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
app:checkedChip="@+id/videos"
app:selectionRequired="true"
app:singleLine="true"
app:singleSelection="true">
<com.google.android.material.chip.Chip <com.google.android.material.chip.Chip
android:id="@+id/videos" android:id="@id/videos"
style="@style/channelChip" style="@style/channelChip"
android:checked="true"
android:text="@string/videos" android:text="@string/videos"
android:visibility="visible" /> android:visibility="visible" />

View File

@ -356,6 +356,9 @@
<string name="alternative_videos_layout">Alternative videos layout</string> <string name="alternative_videos_layout">Alternative videos layout</string>
<string name="defaultIconLight">Default light</string> <string name="defaultIconLight">Default light</string>
<string name="playlistCloned">Playlist cloned</string> <string name="playlistCloned">Playlist cloned</string>
<string name="confirm_unsubscribe">Are you sure you want to unsubscribe %1$s?</string>
<string name="confirm_unsubscribing">Confirm unsubscribing</string>
<string name="confirm_unsubscribing_summary">Show a confirmation dialog before unsubscribing.</string>
<!-- Notification channel strings --> <!-- Notification channel strings -->
<string name="download_channel_name">Download Service</string> <string name="download_channel_name">Download Service</string>

View File

@ -186,7 +186,7 @@
<item name="animationMode">slide</item> <item name="animationMode">slide</item>
</style> </style>
<style name="channelChip" parent="@style/Widget.Material3.Chip.Filter.Elevated"> <style name="channelChip" parent="@style/Widget.Material3.Chip.Filter">
<item name="android:layout_width">wrap_content</item> <item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>

View File

@ -36,6 +36,12 @@
app:key="save_feed" app:key="save_feed"
app:title="@string/save_feed" /> app:title="@string/save_feed" />
<SwitchPreferenceCompat
android:icon="@drawable/ic_check"
android:summary="@string/confirm_unsubscribing_summary"
app:key="confirm_unsubscribing"
app:title="@string/confirm_unsubscribing" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory app:title="@string/backup_restore"> <PreferenceCategory app:title="@string/backup_restore">