diff --git a/app/src/main/java/ani/dantotsu/download/DownloadsManager.kt b/app/src/main/java/ani/dantotsu/download/DownloadsManager.kt index 6512faf8..fa01cc79 100644 --- a/app/src/main/java/ani/dantotsu/download/DownloadsManager.kt +++ b/app/src/main/java/ani/dantotsu/download/DownloadsManager.kt @@ -8,8 +8,12 @@ import ani.dantotsu.media.MediaType import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString +import ani.dantotsu.util.Logger +import com.anggrayudi.storage.callback.FolderCallback import com.anggrayudi.storage.file.deleteRecursively import com.anggrayudi.storage.file.findFolder +import com.anggrayudi.storage.file.moveFileTo +import com.anggrayudi.storage.file.moveFolderTo import com.google.gson.Gson import com.google.gson.reflect.TypeToken import kotlinx.coroutines.CoroutineScope @@ -123,6 +127,52 @@ class DownloadsManager(private val context: Context) { } } + fun moveDownloadsDir(context: Context, oldUri: Uri, newUri: Uri, finished: (Boolean, String) -> Unit) { + try { + if (oldUri == newUri) { + finished(false, "Source and destination are the same") + return + } + CoroutineScope(Dispatchers.IO).launch { + + val oldBase = + DocumentFile.fromTreeUri(context, oldUri) ?: throw Exception("Old base is null") + val newBase = + DocumentFile.fromTreeUri(context, newUri) ?: throw Exception("New base is null") + val folder = + oldBase.findFolder(BASE_LOCATION) ?: throw Exception("Base folder not found") + folder.moveFolderTo(context, newBase, false, BASE_LOCATION, object: + FolderCallback() { + override fun onFailed(errorCode: ErrorCode) { + when (errorCode) { + ErrorCode.CANCELED -> finished(false, "Move canceled") + ErrorCode.CANNOT_CREATE_FILE_IN_TARGET -> finished(false, "Cannot create file in target") + ErrorCode.INVALID_TARGET_FOLDER -> finished(true, "Invalid target folder") // seems to still work + ErrorCode.NO_SPACE_LEFT_ON_TARGET_PATH -> finished(false, "No space left on target path") + ErrorCode.UNKNOWN_IO_ERROR -> finished(false, "Unknown IO error") + ErrorCode.SOURCE_FOLDER_NOT_FOUND -> finished(false, "Source folder not found") + ErrorCode.STORAGE_PERMISSION_DENIED -> finished(false, "Storage permission denied") + ErrorCode.TARGET_FOLDER_CANNOT_HAVE_SAME_PATH_WITH_SOURCE_FOLDER -> finished(false, "Target folder cannot have same path with source folder") + else -> finished(false, "Failed to move downloads: $errorCode") + } + Logger.log("Failed to move downloads: $errorCode") + super.onFailed(errorCode) + } + + override fun onCompleted(result: Result) { + finished(true, "Successfully moved downloads") + super.onCompleted(result) + } + }) + } + + } catch (e: Exception) { + snackString("Error: ${e.message}") + finished(false, "Failed to move downloads: ${e.message}") + return + } + } + fun queryDownload(downloadedType: DownloadedType): Boolean { return downloadsList.contains(downloadedType) } diff --git a/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt b/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt index 022496af..4f93cdde 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaDetailsActivity.kt @@ -4,7 +4,6 @@ import android.animation.ObjectAnimator import android.annotation.SuppressLint import android.content.Intent import android.content.res.Configuration -import android.net.Uri import android.os.Bundle import android.text.SpannableStringBuilder import android.util.TypedValue @@ -14,7 +13,6 @@ import android.view.View import android.view.ViewGroup import android.view.animation.AccelerateDecelerateInterpolator import android.widget.ImageView -import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity @@ -56,9 +54,7 @@ import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import ani.dantotsu.statusBarHeight import ani.dantotsu.themes.ThemeManager -import ani.dantotsu.toast import ani.dantotsu.util.LauncherWrapper -import ani.dantotsu.util.StoragePermissions import com.flaviofaria.kenburnsview.RandomTransitionGenerator import com.google.android.material.appbar.AppBarLayout import kotlinx.coroutines.CoroutineScope diff --git a/app/src/main/java/ani/dantotsu/others/Download.kt b/app/src/main/java/ani/dantotsu/others/Download.kt index 5a28dd46..b80c941d 100644 --- a/app/src/main/java/ani/dantotsu/others/Download.kt +++ b/app/src/main/java/ani/dantotsu/others/Download.kt @@ -36,16 +36,8 @@ object Download { } private fun getDownloadDir(context: Context): File { - val direct: File - if (PrefManager.getVal(PrefName.SdDl)) { - val arrayOfFiles = ContextCompat.getExternalFilesDirs(context, null) - val parentDirectory = arrayOfFiles[1].toString() - direct = File(parentDirectory) - if (!direct.exists()) direct.mkdirs() - } else { - direct = File("storage/emulated/0/${Environment.DIRECTORY_DOWNLOADS}/Dantotsu/") - if (!direct.exists()) direct.mkdirs() - } + val direct = File("storage/emulated/0/${Environment.DIRECTORY_DOWNLOADS}/Dantotsu/") + if (!direct.exists()) direct.mkdirs() return direct } diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt index 3eb848f7..ca0dae83 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt @@ -5,6 +5,7 @@ import android.app.AlertDialog import android.content.Context import android.content.Intent import android.graphics.drawable.Animatable +import android.net.Uri import android.os.Build import android.os.Build.BRAND import android.os.Build.DEVICE @@ -79,7 +80,9 @@ import ani.dantotsu.startMainActivity import ani.dantotsu.statusBarHeight import ani.dantotsu.themes.ThemeManager import ani.dantotsu.toast +import ani.dantotsu.util.LauncherWrapper import ani.dantotsu.util.Logger +import ani.dantotsu.util.StoragePermissions.Companion.accessAlertDialog import ani.dantotsu.util.StoragePermissions.Companion.downloadsPermission import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.textfield.TextInputEditText @@ -89,7 +92,9 @@ import eltos.simpledialogfragment.color.SimpleColorDialog import eu.kanade.domain.base.BasePreferences import io.noties.markwon.Markwon import io.noties.markwon.SoftBreakAddsNewLinePlugin +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import uy.kohesive.injekt.Injekt @@ -102,6 +107,7 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene override fun handleOnBackPressed() = startMainActivity(this@SettingsActivity) } lateinit var binding: ActivitySettingsBinding + lateinit var launcher: LauncherWrapper private lateinit var bindingAccounts: ActivitySettingsAccountsBinding private lateinit var bindingTheme: ActivitySettingsThemeBinding private lateinit var bindingExtensions: ActivitySettingsExtensionsBinding @@ -113,6 +119,7 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene private val extensionInstaller = Injekt.get().extensionInstaller() private var cursedCounter = 0 + @kotlin.OptIn(DelicateCoroutinesApi::class) @OptIn(UnstableApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -164,6 +171,8 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene } } } + val contract = ActivityResultContracts.OpenDocumentTree() + launcher = LauncherWrapper(this, contract) binding.settingsVersion.text = getString(R.string.version_current, BuildConfig.VERSION_NAME) binding.settingsVersion.setOnLongClickListener { @@ -699,20 +708,6 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene restartApp(binding.root) } - settingsDownloadInSd.isChecked = PrefManager.getVal(PrefName.SdDl) - settingsDownloadInSd.setOnCheckedChangeListener { _, isChecked -> - if (isChecked) { - val arrayOfFiles = ContextCompat.getExternalFilesDirs(this@SettingsActivity, null) - if (arrayOfFiles.size > 1 && arrayOfFiles[1] != null) { - PrefManager.setVal(PrefName.SdDl, true) - } else { - settingsDownloadInSd.isChecked = false - PrefManager.setVal(PrefName.SdDl, true) - snackString(getString(R.string.noSdFound)) - } - } else PrefManager.setVal(PrefName.SdDl, true) - } - settingsContinueMedia.isChecked = PrefManager.getVal(PrefName.ContinueMedia) settingsContinueMedia.setOnCheckedChangeListener { _, isChecked -> PrefManager.setVal(PrefName.ContinueMedia, isChecked) @@ -732,6 +727,44 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene PrefManager.setVal(PrefName.AdultOnly, isChecked) restartApp(binding.root) } + + settingsDownloadLocation.setOnClickListener { + val dialog = AlertDialog.Builder(this@SettingsActivity, R.style.MyPopup) + .setTitle(R.string.change_download_location) + .setMessage(R.string.download_location_msg) + .setPositiveButton(R.string.ok) { dialog, _ -> + val oldUri = PrefManager.getVal(PrefName.DownloadsDir) + launcher.registerForCallback { success -> + if (success) { + toast(getString(R.string.please_wait)) + val newUri = PrefManager.getVal(PrefName.DownloadsDir) + GlobalScope.launch(Dispatchers.IO) { + Injekt.get().moveDownloadsDir( + this@SettingsActivity, + Uri.parse(oldUri), Uri.parse(newUri) + ) { finished, message -> + if (finished) { + toast(getString(R.string.success)) + } else { + toast(message) + } + } + } + } else { + toast(getString(R.string.error)) + } + } + launcher.launch() + dialog.dismiss() + } + .setNeutralButton(R.string.cancel) { dialog, _ -> + dialog.dismiss() + } + .create() + dialog.window?.setDimAmount(0.8f) + dialog.show() + } + var previousStart: View = when (PrefManager.getVal(PrefName.DefaultStartUpTab)) { 0 -> uiSettingsAnime 1 -> uiSettingsHome diff --git a/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt index 239ab7ce..414a45cb 100644 --- a/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt +++ b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt @@ -13,7 +13,6 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files OfflineView(Pref(Location.General, Int::class, 0)), DownloadManager(Pref(Location.General, Int::class, 0)), NSFWExtension(Pref(Location.General, Boolean::class, true)), - SdDl(Pref(Location.General, Boolean::class, false)), ContinueMedia(Pref(Location.General, Boolean::class, true)), SearchSources(Pref(Location.General, Boolean::class, true)), RecentlyListOnly(Pref(Location.General, Boolean::class, false)), diff --git a/app/src/main/java/ani/dantotsu/util/StoragePermissions.kt b/app/src/main/java/ani/dantotsu/util/StoragePermissions.kt index 7d6ee9af..485b0dd9 100644 --- a/app/src/main/java/ani/dantotsu/util/StoragePermissions.kt +++ b/app/src/main/java/ani/dantotsu/util/StoragePermissions.kt @@ -62,8 +62,11 @@ class StoragePermissions { } fun AppCompatActivity.accessAlertDialog(launcher: LauncherWrapper, - complete: (Boolean) -> Unit) { - if (PrefManager.getVal(PrefName.DownloadsDir).isNotEmpty()) { + force: Boolean = false, + complete: (Boolean) -> Unit + ) { + if ((PrefManager.getVal(PrefName.DownloadsDir).isNotEmpty() || hasDirAccess(this)) && !force) { + complete(true) return } val builder = AlertDialog.Builder(this, R.style.MyPopup) diff --git a/app/src/main/res/layout/activity_settings_common.xml b/app/src/main/res/layout/activity_settings_common.xml index 3eab0449..f0778d90 100644 --- a/app/src/main/res/layout/activity_settings_common.xml +++ b/app/src/main/res/layout/activity_settings_common.xml @@ -191,27 +191,6 @@ android:layout_marginBottom="16dp" android:background="?android:attr/listDivider" /> - - - + +