feat: import settings on login page
This commit is contained in:
parent
d80b250650
commit
7a67fbb980
6 changed files with 123 additions and 4 deletions
|
@ -1,14 +1,25 @@
|
||||||
package ani.dantotsu.home
|
package ani.dantotsu.home
|
||||||
|
|
||||||
|
import android.app.AlertDialog
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
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 androidx.fragment.app.Fragment
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
import ani.dantotsu.databinding.FragmentLoginBinding
|
import ani.dantotsu.databinding.FragmentLoginBinding
|
||||||
import ani.dantotsu.openLinkInBrowser
|
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() {
|
class LoginFragment : Fragment() {
|
||||||
|
|
||||||
|
@ -21,6 +32,9 @@ class LoginFragment : Fragment() {
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
_binding = FragmentLoginBinding.inflate(layoutInflater, container, false)
|
_binding = FragmentLoginBinding.inflate(layoutInflater, container, false)
|
||||||
|
val layoutParams = binding.importSettingsButton.layoutParams as ViewGroup.MarginLayoutParams
|
||||||
|
layoutParams.topMargin += statusBarHeight
|
||||||
|
binding.importSettingsButton.layoutParams = layoutParams
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,5 +43,89 @@ class LoginFragment : Fragment() {
|
||||||
binding.loginDiscord.setOnClickListener { openLinkInBrowser(getString(R.string.discord)) }
|
binding.loginDiscord.setOnClickListener { openLinkInBrowser(getString(R.string.discord)) }
|
||||||
binding.loginGithub.setOnClickListener { openLinkInBrowser(getString(R.string.github)) }
|
binding.loginGithub.setOnClickListener { openLinkInBrowser(getString(R.string.github)) }
|
||||||
binding.loginTelegram.setOnClickListener { openLinkInBrowser(getString(R.string.telegram)) }
|
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<TextInputEditText>(R.id.userAgentTextBox)?.hint = "Password"
|
||||||
|
val subtitleTextView = dialogView.findViewById<TextView>(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<TextInputEditText>(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)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -74,7 +74,6 @@ class SettingsActivity : AppCompatActivity(), SimpleDialog.OnDialogResultListene
|
||||||
lateinit var binding: ActivitySettingsBinding
|
lateinit var binding: ActivitySettingsBinding
|
||||||
private val extensionInstaller = Injekt.get<BasePreferences>().extensionInstaller()
|
private val extensionInstaller = Injekt.get<BasePreferences>().extensionInstaller()
|
||||||
private var cursedCounter = 0
|
private var cursedCounter = 0
|
||||||
private var tempPassword: CharArray? = null
|
|
||||||
|
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
@SuppressLint("SetTextI18n", "Recycle")
|
@SuppressLint("SetTextI18n", "Recycle")
|
||||||
|
|
|
@ -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")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun importAllPrefs(prefs: Map<String, *>, prefLocation: Location): Boolean {
|
fun importAllPrefs(prefs: Map<String, *>, prefLocation: Location): Boolean {
|
||||||
val pref = getPrefLocation(prefLocation)
|
val pref = getPrefLocation(prefLocation)
|
||||||
|
|
|
@ -64,14 +64,17 @@ class PreferencePackager {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if successful, false if error
|
||||||
|
*/
|
||||||
private fun unpackagePreferences(map: Map<String, Map<String, *>>): Boolean {
|
private fun unpackagePreferences(map: Map<String, Map<String, *>>): Boolean {
|
||||||
var failed = false
|
var success = true
|
||||||
map.forEach { (location, prefMap) ->
|
map.forEach { (location, prefMap) ->
|
||||||
val locationEnum = locationFromString(location)
|
val locationEnum = locationFromString(location)
|
||||||
if (!PrefManager.importAllPrefs(prefMap, locationEnum))
|
if (!PrefManager.importAllPrefs(prefMap, locationEnum))
|
||||||
failed = true
|
success = false
|
||||||
}
|
}
|
||||||
return failed
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun locationFromString(location: String): Location {
|
private fun locationFromString(location: String): Location {
|
||||||
|
|
|
@ -12,6 +12,17 @@
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:ignore="ContentDescription" />
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/importSettingsButton"
|
||||||
|
android:layout_width="111dp"
|
||||||
|
android:layout_height="64dp"
|
||||||
|
android:layout_gravity="top|end"
|
||||||
|
android:backgroundTint="?attr/colorPrimaryContainer"
|
||||||
|
android:fontFamily="@font/poppins_bold"
|
||||||
|
android:text="@string/import_settings"
|
||||||
|
android:textColor="?attr/colorOnPrimaryContainer"
|
||||||
|
app:cornerRadius="16dp" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|
|
@ -654,5 +654,6 @@
|
||||||
<string name="share_username_in_crash_reports">Share username in crash reports</string>
|
<string name="share_username_in_crash_reports">Share username in crash reports</string>
|
||||||
<string name="pinned_sources">Pinned Sources</string>
|
<string name="pinned_sources">Pinned Sources</string>
|
||||||
<string name="import_export_settings">Import/Export Settings</string>
|
<string name="import_export_settings">Import/Export Settings</string>
|
||||||
|
<string name="import_settings">Import Settings</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue