mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-01-06 01:20:29 +05:30
Merge pull request #982 from Bnyro/master
subscription import refactor and crashes fixed
This commit is contained in:
commit
870118aff8
@ -15,8 +15,6 @@ class SubscriptionChannelAdapter(private val subscriptions: MutableList<Subscrip
|
|||||||
RecyclerView.Adapter<SubscriptionChannelViewHolder>() {
|
RecyclerView.Adapter<SubscriptionChannelViewHolder>() {
|
||||||
val TAG = "SubChannelAdapter"
|
val TAG = "SubChannelAdapter"
|
||||||
|
|
||||||
private var subscribed = true
|
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
return subscriptions.size
|
return subscriptions.size
|
||||||
}
|
}
|
||||||
@ -30,6 +28,8 @@ class SubscriptionChannelAdapter(private val subscriptions: MutableList<Subscrip
|
|||||||
|
|
||||||
override fun onBindViewHolder(holder: SubscriptionChannelViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: SubscriptionChannelViewHolder, position: Int) {
|
||||||
val subscription = subscriptions[position]
|
val subscription = subscriptions[position]
|
||||||
|
var subscribed = true
|
||||||
|
|
||||||
holder.binding.apply {
|
holder.binding.apply {
|
||||||
subscriptionChannelName.text = subscription.name
|
subscriptionChannelName.text = subscription.name
|
||||||
ConnectionHelper.loadImage(subscription.avatar, subscriptionChannelImage)
|
ConnectionHelper.loadImage(subscription.avatar, subscriptionChannelImage)
|
||||||
|
@ -26,14 +26,12 @@ class TrendingAdapter(
|
|||||||
|
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
return if (showAllAtOne) streamItems.size
|
return if (showAllAtOne) streamItems.size
|
||||||
|
else if (index >= streamItems.size) streamItems.size - 1
|
||||||
else index
|
else index
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateItems() {
|
fun updateItems() {
|
||||||
index += 10
|
index += 10
|
||||||
if (index > streamItems.size) {
|
|
||||||
index = streamItems.size
|
|
||||||
}
|
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.github.libretube.obj
|
||||||
|
|
||||||
|
data class NewPipeSubscription(
|
||||||
|
val name: String? = null,
|
||||||
|
val service_id: Int? = null,
|
||||||
|
val url: String? = null
|
||||||
|
)
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.github.libretube.obj
|
||||||
|
|
||||||
|
data class NewPipeSubscriptions(
|
||||||
|
val app_version: String = "",
|
||||||
|
val app_version_int: Int = 0,
|
||||||
|
val subscriptions: List<NewPipeSubscription>? = null
|
||||||
|
)
|
@ -1,10 +1,8 @@
|
|||||||
package com.github.libretube.preferences
|
package com.github.libretube.preferences
|
||||||
|
|
||||||
import android.content.ContentResolver
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
@ -21,91 +19,20 @@ import com.github.libretube.dialogs.CustomInstanceDialog
|
|||||||
import com.github.libretube.dialogs.DeleteAccountDialog
|
import com.github.libretube.dialogs.DeleteAccountDialog
|
||||||
import com.github.libretube.dialogs.LoginDialog
|
import com.github.libretube.dialogs.LoginDialog
|
||||||
import com.github.libretube.dialogs.LogoutDialog
|
import com.github.libretube.dialogs.LogoutDialog
|
||||||
|
import com.github.libretube.util.ImportHelper
|
||||||
import com.github.libretube.util.PermissionHelper
|
import com.github.libretube.util.PermissionHelper
|
||||||
import com.github.libretube.util.RetrofitInstance
|
import com.github.libretube.util.RetrofitInstance
|
||||||
import org.json.JSONObject
|
|
||||||
import org.json.JSONTokener
|
|
||||||
import retrofit2.HttpException
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.util.zip.ZipEntry
|
|
||||||
import java.util.zip.ZipInputStream
|
|
||||||
|
|
||||||
class InstanceSettings : PreferenceFragmentCompat() {
|
class InstanceSettings : PreferenceFragmentCompat() {
|
||||||
val TAG = "InstanceSettings"
|
val TAG = "InstanceSettings"
|
||||||
|
private lateinit var getContent: ActivityResultLauncher<String>
|
||||||
companion object {
|
|
||||||
lateinit var getContent: ActivityResultLauncher<String>
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
getContent =
|
getContent =
|
||||||
registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
|
registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
|
||||||
if (uri != null) {
|
ImportHelper(requireActivity() as AppCompatActivity).importSubscriptions(uri)
|
||||||
try {
|
|
||||||
// Open a specific media item using ParcelFileDescriptor.
|
|
||||||
val resolver: ContentResolver =
|
|
||||||
requireActivity()
|
|
||||||
.contentResolver
|
|
||||||
|
|
||||||
// "rw" for read-and-write;
|
|
||||||
// "rwt" for truncating or overwriting existing file contents.
|
|
||||||
// val readOnlyMode = "r"
|
|
||||||
// uri - I have got from onActivityResult
|
|
||||||
val type = resolver.getType(uri)
|
|
||||||
|
|
||||||
var inputStream: InputStream? = resolver.openInputStream(uri)
|
|
||||||
val channels = ArrayList<String>()
|
|
||||||
if (type == "application/json") {
|
|
||||||
val json = inputStream?.bufferedReader()?.readLines()?.get(0)
|
|
||||||
val jsonObject = JSONTokener(json).nextValue() as JSONObject
|
|
||||||
Log.e(TAG, jsonObject.getJSONArray("subscriptions").toString())
|
|
||||||
for (
|
|
||||||
i in 0 until jsonObject.getJSONArray("subscriptions")
|
|
||||||
.length()
|
|
||||||
) {
|
|
||||||
var url =
|
|
||||||
jsonObject.getJSONArray("subscriptions").getJSONObject(i)
|
|
||||||
.getString("url")
|
|
||||||
url = url.replace("https://www.youtube.com/channel/", "")
|
|
||||||
Log.e(TAG, url)
|
|
||||||
channels.add(url)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (type == "application/zip") {
|
|
||||||
val zis = ZipInputStream(inputStream)
|
|
||||||
var entry: ZipEntry? = zis.nextEntry
|
|
||||||
while (entry != null) {
|
|
||||||
if (entry.name.endsWith(".csv")) {
|
|
||||||
inputStream = zis
|
|
||||||
break
|
|
||||||
}
|
|
||||||
entry = zis.nextEntry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inputStream?.bufferedReader()?.readLines()?.forEach {
|
|
||||||
if (it.isNotBlank()) {
|
|
||||||
val channelId = it.substringBefore(",")
|
|
||||||
if (channelId.length == 24) {
|
|
||||||
channels.add(channelId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
inputStream?.close()
|
|
||||||
|
|
||||||
subscribe(channels)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, e.toString())
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
R.string.error,
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,7 +128,13 @@ class InstanceSettings : PreferenceFragmentCompat() {
|
|||||||
|
|
||||||
val importFromYt = findPreference<Preference>(PreferenceKeys.IMPORT_SUBS)
|
val importFromYt = findPreference<Preference>(PreferenceKeys.IMPORT_SUBS)
|
||||||
importFromYt?.setOnPreferenceClickListener {
|
importFromYt?.setOnPreferenceClickListener {
|
||||||
importSubscriptions()
|
// check StorageAccess
|
||||||
|
val accessGranted =
|
||||||
|
PermissionHelper.isStoragePermissionGranted(activity as AppCompatActivity)
|
||||||
|
// import subscriptions
|
||||||
|
if (accessGranted) getContent.launch("*/*")
|
||||||
|
// request permissions if not granted
|
||||||
|
else PermissionHelper.requestReadWrite(activity as AppCompatActivity)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -210,8 +143,8 @@ class InstanceSettings : PreferenceFragmentCompat() {
|
|||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launchWhenCreated {
|
||||||
val customInstances = PreferenceHelper.getCustomInstances()
|
val customInstances = PreferenceHelper.getCustomInstances()
|
||||||
|
|
||||||
var instanceNames = arrayListOf<String>()
|
val instanceNames = arrayListOf<String>()
|
||||||
var instanceValues = arrayListOf<String>()
|
val instanceValues = arrayListOf<String>()
|
||||||
|
|
||||||
// fetch official public instances
|
// fetch official public instances
|
||||||
|
|
||||||
@ -256,46 +189,4 @@ class InstanceSettings : PreferenceFragmentCompat() {
|
|||||||
if (!isAdded) return // Fragment not attached to an Activity
|
if (!isAdded) return // Fragment not attached to an Activity
|
||||||
activity?.runOnUiThread(action)
|
activity?.runOnUiThread(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun importSubscriptions() {
|
|
||||||
val token = PreferenceHelper.getToken()
|
|
||||||
if (token != "") {
|
|
||||||
// check StorageAccess
|
|
||||||
val accessGranted =
|
|
||||||
PermissionHelper.isStoragePermissionGranted(activity as AppCompatActivity)
|
|
||||||
if (accessGranted) getContent.launch("*/*")
|
|
||||||
else PermissionHelper.requestReadWrite(activity as AppCompatActivity)
|
|
||||||
} else {
|
|
||||||
Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun subscribe(channels: List<String>) {
|
|
||||||
fun run() {
|
|
||||||
lifecycleScope.launchWhenCreated {
|
|
||||||
val response = try {
|
|
||||||
val token = PreferenceHelper.getToken()
|
|
||||||
RetrofitInstance.authApi.importSubscriptions(
|
|
||||||
false,
|
|
||||||
token,
|
|
||||||
channels
|
|
||||||
)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
Log.e(TAG, "IOException, you might not have internet connection")
|
|
||||||
return@launchWhenCreated
|
|
||||||
} catch (e: HttpException) {
|
|
||||||
Log.e(TAG, "HttpException, unexpected response$e")
|
|
||||||
return@launchWhenCreated
|
|
||||||
}
|
|
||||||
if (response.message == "ok") {
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
R.string.importsuccess,
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
run()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
90
app/src/main/java/com/github/libretube/util/ImportHelper.kt
Normal file
90
app/src/main/java/com/github/libretube/util/ImportHelper.kt
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package com.github.libretube.util
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.github.libretube.R
|
||||||
|
import com.github.libretube.obj.NewPipeSubscriptions
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.BufferedReader
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.InputStreamReader
|
||||||
|
import java.util.zip.ZipEntry
|
||||||
|
import java.util.zip.ZipInputStream
|
||||||
|
|
||||||
|
class ImportHelper(
|
||||||
|
private val activity: AppCompatActivity
|
||||||
|
) {
|
||||||
|
private val TAG = "ImportHelper"
|
||||||
|
|
||||||
|
fun importSubscriptions(uri: Uri?) {
|
||||||
|
if (uri == null) return
|
||||||
|
try {
|
||||||
|
val type = activity.contentResolver.getType(uri)
|
||||||
|
|
||||||
|
var inputStream: InputStream? = activity.contentResolver.openInputStream(uri)
|
||||||
|
var channels = ArrayList<String>()
|
||||||
|
if (type == "application/json") {
|
||||||
|
val mapper = ObjectMapper()
|
||||||
|
val json = readTextFromUri(uri)
|
||||||
|
val subscriptions = mapper.readValue(json, NewPipeSubscriptions::class.java)
|
||||||
|
channels = subscriptions.subscriptions?.map {
|
||||||
|
it.url?.replace("https://www.youtube.com/channel/", "")!!
|
||||||
|
} as ArrayList<String>
|
||||||
|
} else if (type == "application/zip") {
|
||||||
|
val zis = ZipInputStream(inputStream)
|
||||||
|
var entry: ZipEntry? = zis.nextEntry
|
||||||
|
|
||||||
|
while (entry != null) {
|
||||||
|
if (entry.name.endsWith(".csv")) {
|
||||||
|
inputStream = zis
|
||||||
|
break
|
||||||
|
}
|
||||||
|
entry = zis.nextEntry
|
||||||
|
inputStream?.bufferedReader()?.readLines()?.forEach {
|
||||||
|
if (it.isNotBlank()) {
|
||||||
|
val channelId = it.substringBefore(",")
|
||||||
|
if (channelId.length == 24) {
|
||||||
|
channels.add(channelId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inputStream?.close()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw IllegalArgumentException("unsupported type")
|
||||||
|
}
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
SubscriptionHelper.importSubscriptions(channels)
|
||||||
|
}
|
||||||
|
|
||||||
|
Toast.makeText(activity, R.string.importsuccess, Toast.LENGTH_SHORT).show()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, e.toString())
|
||||||
|
Toast.makeText(
|
||||||
|
activity,
|
||||||
|
R.string.error,
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readTextFromUri(uri: Uri): String {
|
||||||
|
val stringBuilder = StringBuilder()
|
||||||
|
activity.contentResolver.openInputStream(uri)?.use { inputStream ->
|
||||||
|
BufferedReader(InputStreamReader(inputStream)).use { reader ->
|
||||||
|
var line: String? = reader.readLine()
|
||||||
|
while (line != null) {
|
||||||
|
stringBuilder.append(line)
|
||||||
|
line = reader.readLine()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stringBuilder.toString()
|
||||||
|
}
|
||||||
|
}
|
@ -65,6 +65,27 @@ object SubscriptionHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun importSubscriptions(newChannels: List<String>) {
|
||||||
|
if (PreferenceHelper.getToken() != "") {
|
||||||
|
try {
|
||||||
|
val token = PreferenceHelper.getToken()
|
||||||
|
RetrofitInstance.authApi.importSubscriptions(
|
||||||
|
false,
|
||||||
|
token,
|
||||||
|
newChannels
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val channels = PreferenceHelper.getLocalSubscriptions().toMutableList()
|
||||||
|
newChannels.forEach {
|
||||||
|
if (!channels.contains(it)) channels += it
|
||||||
|
}
|
||||||
|
PreferenceHelper.setLocalSubscriptions(channels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getFormattedLocalSubscriptions(): String {
|
fun getFormattedLocalSubscriptions(): String {
|
||||||
return PreferenceHelper.getLocalSubscriptions().joinToString(",")
|
return PreferenceHelper.getLocalSubscriptions().joinToString(",")
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user