diff --git a/app/build.gradle b/app/build.gradle index 9e344338d..c97e62b51 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -30,6 +30,7 @@ android { } } compileOptions { + coreLibraryDesugaringEnabled true sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } @@ -78,6 +79,8 @@ dependencies { implementation 'com.arthenica:ffmpeg-kit-min:4.5.1.LTS' + + coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.1.5" implementation 'com.google.android.exoplayer:extension-cronet:2.17.1' implementation 'org.chromium.net:cronet-embedded:98.4758.101' diff --git a/app/src/main/java/com/github/libretube/CreatePlaylistDialog.kt b/app/src/main/java/com/github/libretube/CreatePlaylistDialog.kt index d128dd79d..dc6ece7bf 100644 --- a/app/src/main/java/com/github/libretube/CreatePlaylistDialog.kt +++ b/app/src/main/java/com/github/libretube/CreatePlaylistDialog.kt @@ -12,7 +12,7 @@ import androidx.appcompat.app.AlertDialog import androidx.fragment.app.DialogFragment import com.github.libretube.obj.Login -class CreatePlaylistDialog: DialogFragment() { +class CreatePlaylistDialog : DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return activity?.let { val builder = AlertDialog.Builder(it) @@ -35,6 +35,7 @@ class CreatePlaylistDialog: DialogFragment() { } dialog?.dismiss() } + dialog?.dismiss() } builder.setView(view) builder.create() diff --git a/app/src/main/java/com/github/libretube/PipedApi.kt b/app/src/main/java/com/github/libretube/PipedApi.kt index 278c047c5..4c872211e 100644 --- a/app/src/main/java/com/github/libretube/PipedApi.kt +++ b/app/src/main/java/com/github/libretube/PipedApi.kt @@ -53,7 +53,7 @@ interface PipedApi { suspend fun unsubscribe(@Header("Authorization") token: String, @Body subscribe: Subscribe): Message @POST("import") - suspend fun importSubscriptions(@Query("override") override: String, @Header("Authorization") token: String, @Body channels: List): Message + suspend fun importSubscriptions(@Query("override") override: Boolean, @Header("Authorization") token: String, @Body channels: List): Message @GET("user/playlists") suspend fun playlists(@Header("Authorization") token: String): List diff --git a/app/src/main/java/com/github/libretube/Settings.kt b/app/src/main/java/com/github/libretube/Settings.kt index bf2681768..6fa7fb935 100644 --- a/app/src/main/java/com/github/libretube/Settings.kt +++ b/app/src/main/java/com/github/libretube/Settings.kt @@ -20,14 +20,15 @@ import androidx.lifecycle.lifecycleScope import androidx.preference.ListPreference import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat -import com.blankj.utilcode.util.UriUtils import retrofit2.HttpException import java.io.* -import java.util.zip.ZipFile +import java.util.zip.ZipEntry +import java.util.zip.ZipInputStream class Settings : PreferenceFragmentCompat() { val TAG = "Settings" + companion object { lateinit var getContent: ActivityResultLauncher } @@ -36,76 +37,52 @@ class Settings : PreferenceFragmentCompat() { getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? -> if (uri != null) { - try{ - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + try { + // Open a specific media item using ParcelFileDescriptor. + val resolver: ContentResolver = + requireActivity() + .contentResolver - // 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) - // "rw" for read-and-write; - // "rwt" for truncating or overwriting existing file contents. - val readOnlyMode = "r" - // uri - I have got from onActivityResult - //uri = data.getData(); - val parcelFile = resolver.openFileDescriptor(uri, readOnlyMode) - val fileReader = FileReader(parcelFile!!.fileDescriptor) - val reader = BufferedReader(fileReader) - var line: String? - var channels: MutableList = emptyList().toMutableList() - var subscribedCount = 0 - while (reader.readLine().also { line = it } != null) { - if (line!!.replace(" ","") != "" && subscribedCount >0) { - val channel = line!!.split(",")[0] - channels.add(channel) + var inputStream: InputStream? = resolver.openInputStream(uri) - Log.d(TAG, "subscribed: " + line + " total: " + subscribedCount) + if (type == "application/zip") { + val zis = ZipInputStream(inputStream) + var entry: ZipEntry? = zis.nextEntry + while (entry != null) { + if (entry.name.endsWith(".csv")) { + inputStream = zis + break } - subscribedCount++ + entry = zis.nextEntry } - subscribe(channels) - reader.close() - fileReader.close() - }else{ - Log.d(TAG,UriUtils.uri2File(uri).toString()) - val file = UriUtils.uri2File(uri) - var inputStream: InputStream? = null - if (file.extension == "zip") { - var zipfile = ZipFile(file) - - var zipentry = - zipfile.getEntry("Takeout/YouTube and YouTube Music/subscriptions/subscriptions.csv") - - inputStream = zipfile.getInputStream(zipentry) - }else if(file.extension == "csv"){ - inputStream = file.inputStream() - } - val baos = ByteArrayOutputStream() - - inputStream?.use { it.copyTo(baos) } - - var subscriptions = baos.toByteArray().decodeToString() - var channels: MutableList = emptyList().toMutableList() - var subscribedCount = 0 - for (text in subscriptions.lines().subList(1,subscriptions.lines().size)) { - if (text.replace(" ","") != "") { - val channel = text.split(",")[0] - channels.add(channel) - subscribedCount++ - Log.d(TAG, "subscribed: " + text + " total: " + subscribedCount) - } - } - subscribe(channels) } - }catch (e: Exception){ - Log.e(TAG,e.toString()) + val channels = ArrayList() + + 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() - } + } } @@ -141,11 +118,14 @@ class Settings : PreferenceFragmentCompat() { val importFromYt = findPreference("import_from_yt") importFromYt?.setOnPreferenceClickListener { val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE) - val token = sharedPref?.getString("token","")!! + val token = sharedPref?.getString("token", "")!! //check StorageAccess if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { Log.d("myz", "" + Build.VERSION.SDK_INT) - if (ContextCompat.checkSelfPermission(this.requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE) + if (ContextCompat.checkSelfPermission( + this.requireContext(), + Manifest.permission.READ_EXTERNAL_STORAGE + ) != PackageManager.PERMISSION_GRANTED ) { ActivityCompat.requestPermissions( @@ -154,9 +134,9 @@ class Settings : PreferenceFragmentCompat() { Manifest.permission.MANAGE_EXTERNAL_STORAGE ), 1 ) //permission request code is just an int - }else if (token != ""){ + } else if (token != "") { getContent.launch("*/*") - }else{ + } else { Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT).show() } } else { @@ -176,9 +156,9 @@ class Settings : PreferenceFragmentCompat() { ), 1 ) - }else if (token != ""){ + } else if (token != "") { getContent.launch("*/*") - }else{ + } else { Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT).show() } } @@ -249,7 +229,11 @@ class Settings : PreferenceFragmentCompat() { lifecycleScope.launchWhenCreated { val response = try { val sharedPref = context?.getSharedPreferences("token", Context.MODE_PRIVATE) - RetrofitInstance.api.importSubscriptions("false",sharedPref?.getString("token", "")!!,channels) + RetrofitInstance.api.importSubscriptions( + false, + sharedPref?.getString("token", "")!!, + channels + ) } catch (e: IOException) { Log.e(TAG, "IOException, you might not have internet connection") return@launchWhenCreated @@ -257,7 +241,7 @@ class Settings : PreferenceFragmentCompat() { Log.e(TAG, "HttpException, unexpected response$e") return@launchWhenCreated } - if(response.message == "ok"){ + if (response.message == "ok") { Toast.makeText( context, R.string.importsuccess, diff --git a/app/src/main/java/com/github/libretube/adapters/PlaylistsAdapter.kt b/app/src/main/java/com/github/libretube/adapters/PlaylistsAdapter.kt index 7feb76f41..db7a52776 100644 --- a/app/src/main/java/com/github/libretube/adapters/PlaylistsAdapter.kt +++ b/app/src/main/java/com/github/libretube/adapters/PlaylistsAdapter.kt @@ -19,8 +19,6 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.preference.PreferenceManager import androidx.recyclerview.widget.RecyclerView -import com.blankj.utilcode.util.StringUtils.getString -import com.blankj.utilcode.util.ThreadUtils.runOnUiThread import com.github.libretube.* import com.github.libretube.obj.PlaylistId import com.squareup.picasso.Picasso @@ -54,8 +52,8 @@ class PlaylistsAdapter(private val playlists: MutableList): RecyclerV holder.v.findViewById(R.id.playlist_title).text = playlist.name holder.v.findViewById(R.id.delete_playlist).setOnClickListener { val builder = AlertDialog.Builder(holder.v.context) - builder.setTitle(getString(R.string.deletePlaylist)) - builder.setMessage(getString(R.string.areYouSure)) + builder.setTitle(R.string.deletePlaylist) + builder.setMessage(R.string.areYouSure) builder.setPositiveButton(R.string.yes) { dialog, which -> val sharedPref = holder.v.context.getSharedPreferences("token", Context.MODE_PRIVATE) val token = sharedPref?.getString("token","")!! @@ -92,7 +90,8 @@ class PlaylistsAdapter(private val playlists: MutableList): RecyclerV if(response.message == "ok"){ Log.d(TAG,"deleted!") playlists.removeAt(position) - runOnUiThread{notifyDataSetChanged()} + // FIXME: This needs to run on UI thread? + notifyDataSetChanged() /*if(playlists.isEmpty()){ view.findViewById(R.id.boogh2).visibility=View.VISIBLE }*/