summaryrefslogtreecommitdiff
path: root/wallet/src/main/java/net/taler/wallet/settings
diff options
context:
space:
mode:
Diffstat (limited to 'wallet/src/main/java/net/taler/wallet/settings')
-rw-r--r--wallet/src/main/java/net/taler/wallet/settings/BackupSettingsFragment.kt43
-rw-r--r--wallet/src/main/java/net/taler/wallet/settings/SettingsFragment.kt125
-rw-r--r--wallet/src/main/java/net/taler/wallet/settings/SettingsManager.kt171
3 files changed, 239 insertions, 100 deletions
diff --git a/wallet/src/main/java/net/taler/wallet/settings/BackupSettingsFragment.kt b/wallet/src/main/java/net/taler/wallet/settings/BackupSettingsFragment.kt
deleted file mode 100644
index 0dc7124..0000000
--- a/wallet/src/main/java/net/taler/wallet/settings/BackupSettingsFragment.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.wallet.settings
-
-import android.content.Intent
-import android.os.Bundle
-import android.view.View
-import androidx.preference.Preference
-import androidx.preference.PreferenceFragmentCompat
-import net.taler.wallet.R
-import org.gnu.anastasis.ui.MainActivity
-
-class BackupSettingsFragment : PreferenceFragmentCompat() {
-
- override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
- setPreferencesFromResource(R.xml.settings_backup, rootKey)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- val recoveryPref: Preference = findPreference("pref_backup_recovery")!!
- recoveryPref.setOnPreferenceClickListener {
- val intent = Intent(requireContext(), MainActivity::class.java)
- startActivity(intent)
- true
- }
- }
-
-}
diff --git a/wallet/src/main/java/net/taler/wallet/settings/SettingsFragment.kt b/wallet/src/main/java/net/taler/wallet/settings/SettingsFragment.kt
index 1a17d78..38eeb9b 100644
--- a/wallet/src/main/java/net/taler/wallet/settings/SettingsFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/settings/SettingsFragment.kt
@@ -18,91 +18,100 @@ package net.taler.wallet.settings
import android.os.Bundle
import android.view.View
-import android.widget.Toast
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.appcompat.app.AlertDialog
+import androidx.activity.result.contract.ActivityResultContracts.OpenDocument
+import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
import androidx.fragment.app.activityViewModels
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_SHORT
import com.google.android.material.snackbar.Snackbar
import net.taler.common.showError
-import net.taler.common.toRelativeTime
import net.taler.wallet.BuildConfig.FLAVOR
import net.taler.wallet.BuildConfig.VERSION_CODE
import net.taler.wallet.BuildConfig.VERSION_NAME
-import net.taler.wallet.BuildConfig.WALLET_CORE_VERSION
import net.taler.wallet.MainViewModel
import net.taler.wallet.R
+import net.taler.wallet.showError
import net.taler.wallet.withdraw.WithdrawTestStatus
+import java.lang.System.currentTimeMillis
class SettingsFragment : PreferenceFragmentCompat() {
private val model: MainViewModel by activityViewModels()
+ private val settingsManager get() = model.settingsManager
private val withdrawManager by lazy { model.withdrawManager }
- private lateinit var prefBackup: Preference
private lateinit var prefDevMode: SwitchPreferenceCompat
private lateinit var prefWithdrawTest: Preference
private lateinit var prefLogcat: Preference
+ private lateinit var prefExportDb: Preference
+ private lateinit var prefImportDb: Preference
private lateinit var prefVersionApp: Preference
private lateinit var prefVersionCore: Preference
private lateinit var prefVersionExchange: Preference
private lateinit var prefVersionMerchant: Preference
+ private lateinit var prefTest: Preference
private lateinit var prefReset: Preference
private val devPrefs by lazy {
listOf(
- prefBackup,
+ prefVersionCore,
prefWithdrawTest,
prefLogcat,
- prefVersionApp,
- prefVersionCore,
+ prefExportDb,
+ prefImportDb,
prefVersionExchange,
prefVersionMerchant,
- prefReset
+ prefTest,
+ prefReset,
)
}
- val createDocumentActivity =
- registerForActivityResult(ActivityResultContracts.CreateDocument()) { uri ->
-
+ private val logLauncher = registerForActivityResult(CreateDocument("text/plain")) { uri ->
+ settingsManager.exportLogcat(uri)
+ }
+ private val dbExportLauncher =
+ registerForActivityResult(CreateDocument("application/json")) { uri ->
+ settingsManager.exportDb(uri)
+ }
+ private val dbImportLauncher =
+ registerForActivityResult(OpenDocument()) { uri ->
+ settingsManager.importDb(uri)
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.settings_main, rootKey)
- prefBackup = findPreference("pref_backup")!!
prefDevMode = findPreference("pref_dev_mode")!!
prefWithdrawTest = findPreference("pref_testkudos")!!
prefLogcat = findPreference("pref_logcat")!!
+ prefExportDb = findPreference("pref_export_db")!!
+ prefImportDb = findPreference("pref_import_db")!!
prefVersionApp = findPreference("pref_version_app")!!
prefVersionCore = findPreference("pref_version_core")!!
prefVersionExchange = findPreference("pref_version_protocol_exchange")!!
prefVersionMerchant = findPreference("pref_version_protocol_merchant")!!
+ prefTest = findPreference("pref_test")!!
prefReset = findPreference("pref_reset")!!
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- model.lastBackup.observe(viewLifecycleOwner) {
- val time = it.toRelativeTime(requireContext())
- prefBackup.summary = getString(R.string.backup_last, time)
- }
+ prefVersionApp.summary = "$VERSION_NAME ($FLAVOR $VERSION_CODE)"
+ prefVersionCore.summary = "${model.walletVersion} (${model.walletVersionHash?.take(7)})"
+ model.exchangeVersion?.let { prefVersionExchange.summary = it }
+ model.merchantVersion?.let { prefVersionMerchant.summary = it }
model.devMode.observe(viewLifecycleOwner) { enabled ->
prefDevMode.isChecked = enabled
- if (enabled) {
- prefVersionApp.summary = "$VERSION_NAME ($FLAVOR $VERSION_CODE)"
- prefVersionCore.summary = WALLET_CORE_VERSION
- model.exchangeVersion?.let { prefVersionExchange.summary = it }
- model.merchantVersion?.let { prefVersionMerchant.summary = it }
- }
devPrefs.forEach { it.isVisible = enabled }
}
prefDevMode.setOnPreferenceChangeListener { _, newValue ->
- model.devMode.value = newValue as Boolean
+ model.setDevMode(newValue as Boolean) { error ->
+ showError(error)
+ }
true
}
@@ -122,48 +131,50 @@ class SettingsFragment : PreferenceFragmentCompat() {
}
prefLogcat.setOnPreferenceClickListener {
- val toast =
- Toast.makeText(requireActivity(), "Log export currently unavailable", Toast.LENGTH_LONG)
- toast.show()
-
-// val myPid = android.os.Process.myPid()
-// val proc = Runtime.getRuntime()
-// .exec(arrayOf("logcat", "-d", "--pid=$myPid", "*:V"))
-// val bytes = proc.inputStream.readBytes()
-// val f = File(requireActivity().getExternalFilesDir(null),
-// "taler-wallet-log-${System.currentTimeMillis()}.txt")
-// f.writeBytes(bytes)
-// val toast = Toast.makeText(requireActivity(), "Saved to ${f.absolutePath}", Toast.LENGTH_LONG)
-// toast.show()
-// val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
-// addCategory(Intent.CATEGORY_OPENABLE)
-// type = "application/pdf"
-// putExtra(Intent.EXTRA_TITLE, "invoice.pdf")
-//
-// // Optionally, specify a URI for the directory that should be opened in
-// // the system file picker before your app creates the document.
-// putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
-// }
-// startActivityForResult(intent, CREATE_FILE)
-// ActivityResultContracts.CreateDocument
+ logLauncher.launch("taler-wallet-log-${currentTimeMillis()}.txt")
+ true
+ }
+ prefExportDb.setOnPreferenceClickListener {
+ dbExportLauncher.launch("taler-wallet-db-${currentTimeMillis()}.json")
+ true
+ }
+ prefImportDb.setOnPreferenceClickListener {
+ showImportDialog()
+ true
+ }
+ prefTest.setOnPreferenceClickListener {
+ model.runIntegrationTest()
true
}
-
prefReset.setOnPreferenceClickListener {
showResetDialog()
true
}
}
+ private fun showImportDialog() {
+ MaterialAlertDialogBuilder(requireContext(), R.style.MaterialAlertDialog_Material3)
+ .setMessage(R.string.settings_dialog_import_message)
+ .setNegativeButton(R.string.import_db) { _, _ ->
+ dbImportLauncher.launch(arrayOf("application/json"))
+ }
+ .setPositiveButton(R.string.cancel) { _, _ ->
+ Snackbar.make(requireView(), getString(R.string.settings_alert_import_canceled), LENGTH_SHORT).show()
+ }
+ .show()
+ }
+
private fun showResetDialog() {
- AlertDialog.Builder(requireContext(), R.style.DialogTheme)
- .setMessage("Do you really want to reset the wallet and lose all coins and purchases?")
- .setPositiveButton("Reset") { _, _ ->
- model.dangerouslyReset()
- Snackbar.make(requireView(), "Wallet has been reset", LENGTH_SHORT).show()
+ MaterialAlertDialogBuilder(requireContext(), R.style.MaterialAlertDialog_Material3)
+ .setMessage(R.string.settings_dialog_reset_message)
+ .setNegativeButton(R.string.reset) { _, _ ->
+ settingsManager.clearDb {
+ model.dangerouslyReset()
+ }
+ Snackbar.make(requireView(), getString(R.string.settings_alert_reset_done), LENGTH_SHORT).show()
}
- .setNegativeButton("Cancel") { _, _ ->
- Snackbar.make(requireView(), "Reset cancelled", LENGTH_SHORT).show()
+ .setPositiveButton(R.string.cancel) { _, _ ->
+ Snackbar.make(requireView(), getString(R.string.settings_alert_reset_canceled), LENGTH_SHORT).show()
}
.show()
}
diff --git a/wallet/src/main/java/net/taler/wallet/settings/SettingsManager.kt b/wallet/src/main/java/net/taler/wallet/settings/SettingsManager.kt
new file mode 100644
index 0000000..8331d59
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/settings/SettingsManager.kt
@@ -0,0 +1,171 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2022 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.settings
+
+import android.content.Context
+import android.net.Uri
+import android.util.Log
+import android.widget.Toast
+import android.widget.Toast.LENGTH_LONG
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+import net.taler.wallet.R
+import net.taler.wallet.backend.WalletBackendApi
+import net.taler.wallet.backend.WalletResponse.Error
+import net.taler.wallet.backend.WalletResponse.Success
+import org.json.JSONObject
+
+class SettingsManager(
+ private val context: Context,
+ private val api: WalletBackendApi,
+ private val scope: CoroutineScope,
+) {
+ fun exportLogcat(uri: Uri?) {
+ if (uri == null) {
+ onLogExportError()
+ return
+ }
+ scope.launch(Dispatchers.IO) {
+ try {
+ context.contentResolver.openOutputStream(uri, "wt")?.use { outputStream ->
+ val command = arrayOf("logcat", "-d", "*:V")
+ val proc = Runtime.getRuntime().exec(command)
+ proc.inputStream.copyTo(outputStream)
+ } ?: onLogExportError()
+ } catch (e: Exception) {
+ Log.e(SettingsManager::class.simpleName, "Error exporting log: ", e)
+ onLogExportError()
+ return@launch
+ }
+ withContext(Dispatchers.Main) {
+ Toast.makeText(context, R.string.settings_logcat_success, LENGTH_LONG).show()
+ }
+ }
+ }
+
+ private fun onLogExportError() {
+ Toast.makeText(context, R.string.settings_logcat_error, LENGTH_LONG).show()
+ }
+
+ fun exportDb(uri: Uri?) {
+ if (uri == null) {
+ onDbExportError()
+ return
+ }
+
+ scope.launch(Dispatchers.IO) {
+ when (val response = api.rawRequest("exportDb")) {
+ is Success -> {
+ try {
+ context.contentResolver.openOutputStream(uri, "wt")?.use { outputStream ->
+ val data = Json.encodeToString(response.result)
+ val writer = outputStream.bufferedWriter()
+ writer.write(data)
+ writer.close()
+ }
+ } catch(e: Exception) {
+ Log.e(SettingsManager::class.simpleName, "Error exporting db: ", e)
+ withContext(Dispatchers.Main) {
+ onDbExportError()
+ }
+ return@launch
+ }
+
+ withContext(Dispatchers.Main) {
+ Toast.makeText(context, R.string.settings_db_export_success, LENGTH_LONG).show()
+ }
+ }
+ is Error -> {
+ Log.e(SettingsManager::class.simpleName, "Error exporting db: ${response.error}")
+ withContext(Dispatchers.Main) {
+ onDbExportError()
+ }
+ return@launch
+ }
+ }
+ }
+ }
+
+ fun importDb(uri: Uri?) {
+ if (uri == null) {
+ onDbImportError()
+ return
+ }
+
+ scope.launch(Dispatchers.IO) {
+ context.contentResolver.openInputStream(uri)?.use { inputStream ->
+ try {
+ val reader = inputStream.bufferedReader()
+ val strData = reader.readText()
+ reader.close()
+ val jsonData = JSONObject(strData)
+ when (val response = api.rawRequest("importDb") {
+ put("dump", jsonData)
+ }) {
+ is Success -> {
+ withContext(Dispatchers.Main) {
+ Toast.makeText(context, R.string.settings_db_import_success, LENGTH_LONG).show()
+ }
+ }
+ is Error -> {
+ Log.e(SettingsManager::class.simpleName, "Error importing db: ${response.error}")
+ withContext(Dispatchers.Main) {
+ onDbImportError()
+ }
+ return@launch
+ }
+ }
+ } catch (e: Exception) {
+ Log.e(SettingsManager::class.simpleName, "Error importing db: ", e)
+ withContext(Dispatchers.Main) {
+ onDbImportError()
+ }
+ return@launch
+ }
+ }
+ }
+ }
+
+ fun clearDb(onSuccess: () -> Unit) {
+ scope.launch {
+ when (val response = api.rawRequest("clearDb")) {
+ is Success -> onSuccess()
+ is Error -> {
+ Log.e(SettingsManager::class.simpleName, "Error cleaning db: ${response.error}")
+ onDbClearError()
+ }
+ }
+ }
+ }
+
+ private fun onDbExportError() {
+ Toast.makeText(context, R.string.settings_db_export_error, LENGTH_LONG).show()
+ }
+
+ private fun onDbImportError() {
+ Toast.makeText(context, R.string.settings_db_import_error, LENGTH_LONG).show()
+ }
+
+ private fun onDbClearError() {
+ Toast.makeText(context, R.string.settings_db_clear_error, LENGTH_LONG).show()
+ }
+
+}