package com.github.libretube.preferences import android.Manifest import android.content.ContentResolver import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.os.Bundle import android.text.TextUtils import android.util.Log import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.preference.ListPreference import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import com.github.libretube.R import com.github.libretube.activities.SettingsActivity import com.github.libretube.activities.requireMainActivityRestart import com.github.libretube.dialogs.CustomInstanceDialog import com.github.libretube.dialogs.DeleteAccountDialog import com.github.libretube.dialogs.LoginDialog import com.github.libretube.dialogs.LogoutDialog 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() { val TAG = "InstanceSettings" override fun onCreate(savedInstanceState: Bundle?) { MainSettings.getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? -> if (uri != null) { 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() 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) } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.instance_settings, rootKey) val settingsActivity = activity as SettingsActivity settingsActivity.binding.topBarTextView.text = getString(R.string.instance) val instance = findPreference("selectInstance") // fetchInstance() initCustomInstances() instance?.setOnPreferenceChangeListener { _, newValue -> requireMainActivityRestart = true RetrofitInstance.url = newValue.toString() RetrofitInstance.authUrl = newValue.toString() RetrofitInstance.lazyMgr.reset() logout() true } val customInstance = findPreference("customInstance") customInstance?.setOnPreferenceClickListener { val newFragment = CustomInstanceDialog() newFragment.show(childFragmentManager, "CustomInstanceDialog") true } val clearCustomInstances = findPreference("clearCustomInstances") clearCustomInstances?.setOnPreferenceClickListener { PreferenceHelper.removePreference(requireContext(), "customInstances") val intent = Intent(context, SettingsActivity::class.java) startActivity(intent) true } val login = findPreference("login_register") login?.setOnPreferenceClickListener { val token = PreferenceHelper.getToken(requireContext()) if (token == "") { val newFragment = LoginDialog() newFragment.show(childFragmentManager, "Login") } else { val newFragment = LogoutDialog() newFragment.show(childFragmentManager, "Logout") } true } val deleteAccount = findPreference("delete_account") deleteAccount?.setOnPreferenceClickListener { val token = PreferenceHelper.getToken(requireContext()) if (token != "") { val newFragment = DeleteAccountDialog() newFragment.show(childFragmentManager, "DeleteAccountDialog") } else { Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT).show() } true } val importFromYt = findPreference("import_from_yt") importFromYt?.setOnPreferenceClickListener { val token = PreferenceHelper.getToken(requireContext()) // 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 ) != PackageManager.PERMISSION_GRANTED ) { ActivityCompat.requestPermissions( this.requireActivity(), arrayOf( Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.MANAGE_EXTERNAL_STORAGE ), 1 ) // permission request code is just an int } else if (token != "") { MainSettings.getContent.launch("*/*") } else { Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT).show() } } else { if (ActivityCompat.checkSelfPermission( requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE ) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission( requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE ) != PackageManager.PERMISSION_GRANTED ) { ActivityCompat.requestPermissions( this.requireActivity(), arrayOf( Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ), 1 ) } else if (token != "") { MainSettings.getContent.launch("*/*") } else { Toast.makeText(context, R.string.login_first, Toast.LENGTH_SHORT).show() } } true } } private fun initCustomInstances() { val customInstances = PreferenceHelper.getCustomInstances(requireContext()) var instanceNames = resources.getStringArray(R.array.instances) var instanceValues = resources.getStringArray(R.array.instancesValue) customInstances.forEach { instance -> instanceNames += instance.name instanceValues += instance.apiUrl } // add custom instances to the list preference val instance = findPreference("selectInstance") instance?.entries = instanceNames instance?.entryValues = instanceValues instance?.summaryProvider = Preference.SummaryProvider { preference -> val text = preference.entry if (TextUtils.isEmpty(text)) { "kavin.rocks (Official)" } else { text } } } private fun logout() { PreferenceHelper.setToken(requireContext(), "") } private fun fetchInstance() { lifecycleScope.launchWhenCreated { val response = try { RetrofitInstance.api.getInstances("https://instances.tokhmi.xyz/") } catch (e: IOException) { println(e) Log.e("settings", "IOException, you might not have internet connection") return@launchWhenCreated } catch (e: HttpException) { Log.e("settings", "HttpException, unexpected response $e") return@launchWhenCreated } catch (e: Exception) { Log.e("settings", e.toString()) return@launchWhenCreated } val listEntries: MutableList = ArrayList() val listEntryValues: MutableList = ArrayList() for (item in response) { listEntries.add(item.name!!) listEntryValues.add(item.api_url!!) } // add custom instances to the list val entries = listEntries.toTypedArray() val entryValues = listEntryValues.toTypedArray() runOnUiThread { val instance = findPreference("selectInstance") instance?.entries = entries instance?.entryValues = entryValues instance?.summaryProvider = Preference.SummaryProvider { preference -> val text = preference.entry if (TextUtils.isEmpty(text)) { "kavin.rocks (Official)" } else { text } } } } } private fun Fragment?.runOnUiThread(action: () -> Unit) { this ?: return if (!isAdded) return // Fragment not attached to an Activity activity?.runOnUiThread(action) } private fun subscribe(channels: List) { fun run() { lifecycleScope.launchWhenCreated { val response = try { val token = PreferenceHelper.getToken(requireContext()) 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() } }