diff --git a/app/src/main/java/ani/dantotsu/home/LoginFragment.kt b/app/src/main/java/ani/dantotsu/home/LoginFragment.kt index 58bb2fc9..9ca63137 100644 --- a/app/src/main/java/ani/dantotsu/home/LoginFragment.kt +++ b/app/src/main/java/ani/dantotsu/home/LoginFragment.kt @@ -1,14 +1,25 @@ package ani.dantotsu.home +import android.app.AlertDialog +import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.TextView +import androidx.activity.result.contract.ActivityResultContracts +import androidx.documentfile.provider.DocumentFile import androidx.fragment.app.Fragment import ani.dantotsu.R import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.databinding.FragmentLoginBinding import ani.dantotsu.openLinkInBrowser +import ani.dantotsu.settings.saving.internal.PreferenceKeystore +import ani.dantotsu.settings.saving.internal.PreferencePackager +import ani.dantotsu.statusBarHeight +import ani.dantotsu.toast +import com.google.android.material.textfield.TextInputEditText +import eltos.simpledialogfragment.SimpleDialog class LoginFragment : Fragment() { @@ -21,6 +32,9 @@ class LoginFragment : Fragment() { savedInstanceState: Bundle? ): View { _binding = FragmentLoginBinding.inflate(layoutInflater, container, false) + val layoutParams = binding.importSettingsButton.layoutParams as ViewGroup.MarginLayoutParams + layoutParams.topMargin += statusBarHeight + binding.importSettingsButton.layoutParams = layoutParams return binding.root } @@ -29,5 +43,89 @@ class LoginFragment : Fragment() { binding.loginDiscord.setOnClickListener { openLinkInBrowser(getString(R.string.discord)) } binding.loginGithub.setOnClickListener { openLinkInBrowser(getString(R.string.github)) } binding.loginTelegram.setOnClickListener { openLinkInBrowser(getString(R.string.telegram)) } + + val openDocumentLauncher = registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> + if (uri != null) { + try { + val jsonString = requireActivity().contentResolver.openInputStream(uri)?.readBytes() + ?: throw Exception("Error reading file") + val name = DocumentFile.fromSingleUri(requireActivity(), uri)?.name ?: "settings" + //.sani is encrypted, .ani is not + if (name.endsWith(".sani")) { + passwordAlertDialog() { password -> + if (password != null) { + val salt = jsonString.copyOfRange(0, 16) + val encrypted = jsonString.copyOfRange(16, jsonString.size) + val decryptedJson = PreferenceKeystore.decryptWithPassword( + password, + encrypted, + salt + ) + if(PreferencePackager.unpack(decryptedJson)) + println("Settings imported") + restartApp() + } else { + toast("Password cannot be empty") + } + } + } else { + val decryptedJson = jsonString.toString(Charsets.UTF_8) + if(PreferencePackager.unpack(decryptedJson)) + restartApp() + } + } catch (e: Exception) { + e.printStackTrace() + toast("Error importing settings") + } + } + } + + binding.importSettingsButton.setOnClickListener { + openDocumentLauncher.launch(arrayOf("*/*")) + } } + + private fun passwordAlertDialog(callback: (CharArray?) -> Unit) { + val password = CharArray(16).apply { fill('0') } + + // Inflate the dialog layout + val dialogView = LayoutInflater.from(requireActivity()).inflate(R.layout.dialog_user_agent, null) + dialogView.findViewById(R.id.userAgentTextBox)?.hint = "Password" + val subtitleTextView = dialogView.findViewById(R.id.subtitle) + subtitleTextView?.visibility = View.VISIBLE + subtitleTextView?.text = "Enter your password to decrypt the file" + + val dialog = AlertDialog.Builder(requireActivity(), R.style.MyPopup) + .setTitle("Enter Password") + .setView(dialogView) + .setPositiveButton("OK", null) + .setNegativeButton("Cancel") { dialog, _ -> + password.fill('0') + dialog.dismiss() + callback(null) + } + .create() + + dialog.window?.setDimAmount(0.8f) + dialog.show() + + // Override the positive button here + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { + val editText = dialog.findViewById(R.id.userAgentTextBox) + if (editText?.text?.isNotBlank() == true) { + editText.text?.toString()?.trim()?.toCharArray(password) + dialog.dismiss() + callback(password) + } else { + toast("Password cannot be empty") + } + } + } + + private fun restartApp() { + val intent = Intent(requireActivity(), requireActivity().javaClass) + requireActivity().finish() + startActivity(intent) + } + } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt index 20ed17ca..63b29345 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt @@ -74,7 +74,6 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene lateinit var binding: ActivitySettingsBinding private val extensionInstaller = Injekt.get().extensionInstaller() private var cursedCounter = 0 - private var tempPassword: CharArray? = null @OptIn(UnstableApi::class) @SuppressLint("SetTextI18n", "Recycle") diff --git a/app/src/main/java/ani/dantotsu/settings/saving/PrefManager.kt b/app/src/main/java/ani/dantotsu/settings/saving/PrefManager.kt index 4f2baf06..4bc23d10 100644 --- a/app/src/main/java/ani/dantotsu/settings/saving/PrefManager.kt +++ b/app/src/main/java/ani/dantotsu/settings/saving/PrefManager.kt @@ -242,6 +242,13 @@ object PrefManager { ) } + + /** + * @param prefs Map of preferences to import + * @param prefLocation Location to import to + * @return true if successful, false if error + */ + @Suppress("UNCHECKED_CAST") fun importAllPrefs(prefs: Map, prefLocation: Location): Boolean { val pref = getPrefLocation(prefLocation) diff --git a/app/src/main/java/ani/dantotsu/settings/saving/internal/PreferencePackager.kt b/app/src/main/java/ani/dantotsu/settings/saving/internal/PreferencePackager.kt index cad3a22e..c873bc55 100644 --- a/app/src/main/java/ani/dantotsu/settings/saving/internal/PreferencePackager.kt +++ b/app/src/main/java/ani/dantotsu/settings/saving/internal/PreferencePackager.kt @@ -64,14 +64,17 @@ class PreferencePackager { } + /** + * @return true if successful, false if error + */ private fun unpackagePreferences(map: Map>): Boolean { - var failed = false + var success = true map.forEach { (location, prefMap) -> val locationEnum = locationFromString(location) if (!PrefManager.importAllPrefs(prefMap, locationEnum)) - failed = true + success = false } - return failed + return success } private fun locationFromString(location: String): Location { diff --git a/app/src/main/res/layout/fragment_login.xml b/app/src/main/res/layout/fragment_login.xml index c93d5700..9e707eaa 100644 --- a/app/src/main/res/layout/fragment_login.xml +++ b/app/src/main/res/layout/fragment_login.xml @@ -12,6 +12,17 @@ android:visibility="gone" tools:ignore="ContentDescription" /> +