11package com.philkes.notallyx.utils
22
3+ import android.content.Intent
34import android.os.Bundle
5+ import androidx.activity.result.ActivityResultLauncher
6+ import androidx.activity.result.contract.ActivityResultContracts
47import androidx.appcompat.app.AppCompatActivity
8+ import androidx.lifecycle.MutableLiveData
9+ import androidx.lifecycle.lifecycleScope
510import cat.ereza.customactivityoncrash.CustomActivityOnCrash
11+ import com.google.android.material.dialog.MaterialAlertDialogBuilder
12+ import com.philkes.notallyx.R
613import com.philkes.notallyx.databinding.ActivityErrorBinding
14+ import com.philkes.notallyx.databinding.DialogErrorBinding
15+ import com.philkes.notallyx.presentation.getQuantityString
16+ import com.philkes.notallyx.presentation.setCancelButton
17+ import com.philkes.notallyx.presentation.setupProgressDialog
18+ import com.philkes.notallyx.presentation.showToast
19+ import com.philkes.notallyx.presentation.view.misc.Progress
20+ import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
21+ import com.philkes.notallyx.utils.backup.BACKUP_TIMESTAMP_FORMATTER
22+ import com.philkes.notallyx.utils.backup.exportAsZip
23+ import java.util.Date
24+ import kotlinx.coroutines.CoroutineExceptionHandler
25+ import kotlinx.coroutines.Dispatchers
26+ import kotlinx.coroutines.launch
27+ import kotlinx.coroutines.withContext
728
829/* *
930 * Activity used when the app is about to crash. Implicitly used by
1031 * `cat.ereza:customactivityoncrash`.
1132 */
1233class ErrorActivity : AppCompatActivity () {
1334
35+ private lateinit var exportBackupActivityResultLauncher: ActivityResultLauncher <Intent >
36+ private val exportBackupProgress = MutableLiveData <Progress >()
37+
1438 override fun onCreate (savedInstanceState : Bundle ? ) {
1539 super .onCreate(savedInstanceState)
1640 val binding = ActivityErrorBinding .inflate(layoutInflater)
@@ -22,17 +46,101 @@ class ErrorActivity : AppCompatActivity() {
2246 CustomActivityOnCrash .getConfigFromIntent(intent)!! ,
2347 )
2448 }
25-
26- val stackTrace = CustomActivityOnCrash .getStackTraceFromIntent(intent)
27- stackTrace?.let {
49+ val stacktrace = CustomActivityOnCrash .getStackTraceFromIntent(intent)
50+ stacktrace?.let {
2851 application.log(TAG , stackTrace = it)
29- ExceptionTitle .text = stackTrace .lines().firstOrNull()?.replaceFirst(" :" , " :\n " )
30- ExceptionDetails .text = stackTrace .lines().drop(1 ).joinToString(" \n " )
31- CopyButton .setOnClickListener { copyToClipBoard(stackTrace ) }
52+ ExceptionTitle .text = stacktrace .lines().firstOrNull()?.replaceFirst(" :" , " :\n " )
53+ ExceptionDetails .text = stacktrace .lines().drop(1 ).joinToString(" \n " )
54+ CopyButton .setOnClickListener { copyToClipBoard(stacktrace ) }
3255 }
33- ReportButton .setOnClickListener { reportBug(stackTrace ) }
56+ ReportButton .setOnClickListener { reportBug(stacktrace ) }
3457 ViewLogsButton .setOnClickListener { viewLogs() }
58+ setupExportBackup(binding, stacktrace)
59+ }
60+ }
61+
62+ private fun setupExportBackup (binding : ActivityErrorBinding , stacktrace : String? ) {
63+ binding.ExportBackupButton .setOnClickListener {
64+ MaterialAlertDialogBuilder (this )
65+ .setMessage(
66+ getString(
67+ R .string.crash_export_backup_message,
68+ getString(R .string.continue_),
69+ getString(R .string.report_bug),
70+ )
71+ )
72+ .setPositiveButton(R .string.continue_) { _, _ ->
73+ val intent =
74+ Intent (Intent .ACTION_CREATE_DOCUMENT )
75+ .apply {
76+ type = MIME_TYPE_ZIP
77+ addCategory(Intent .CATEGORY_OPENABLE )
78+ putExtra(
79+ Intent .EXTRA_TITLE ,
80+ " NotallyX_Crash_Backup-${BACKUP_TIMESTAMP_FORMATTER .format(Date ())} " ,
81+ )
82+ }
83+ .wrapWithChooser(this @ErrorActivity)
84+ exportBackupActivityResultLauncher.launch(intent)
85+ }
86+ .setCancelButton()
87+ .show()
3588 }
89+ exportBackupActivityResultLauncher =
90+ registerForActivityResult(ActivityResultContracts .StartActivityForResult ()) { result ->
91+ if (result.resultCode == RESULT_OK ) {
92+ result.data?.data?.let { uri ->
93+ val preferences = NotallyXPreferences .getInstance(this )
94+ val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
95+ // MaterialAlertDialogBuilder(this)
96+ // .setTitle(R.string.auto_backup_failed)
97+ //
98+ // .setMessage(throwable.stackTraceToString())
99+ // .setCancelButton()
100+ // .show()
101+ showErrorDialog(throwable, stacktrace)
102+ }
103+ lifecycleScope.launch(exceptionHandler) {
104+ val exportedNotes =
105+ withContext(Dispatchers .IO ) {
106+ throw IllegalArgumentException (" idiot" )
107+ return @withContext application.exportAsZip(
108+ uri,
109+ password = preferences.backupPassword.value,
110+ backupProgress = exportBackupProgress,
111+ )
112+ }
113+ val message =
114+ application.getQuantityString(
115+ R .plurals.exported_notes,
116+ exportedNotes,
117+ )
118+ application.showToast(message)
119+ }
120+ }
121+ }
122+ }
123+ exportBackupProgress.setupProgressDialog(this )
124+ }
125+
126+ private fun showErrorDialog (throwable : Throwable , originalStacktrace : String? ) {
127+ val stacktrace = throwable.stackTraceToString()
128+ val layout =
129+ DialogErrorBinding .inflate(layoutInflater, null , false ).apply {
130+ ExceptionTitle .text =
131+ getString(R .string.crash_export_backup_failed, getString(R .string.report_bug))
132+ ExceptionDetails .text = stacktrace
133+ CopyButton .setOnClickListener { copyToClipBoard(stacktrace) }
134+ }
135+ MaterialAlertDialogBuilder (this )
136+ .setTitle(R .string.auto_backup_failed)
137+ .setView(layout.root)
138+ .setPositiveButton(R .string.report_bug) { dialog, _ ->
139+ dialog.cancel()
140+ reportBug(originalStacktrace)
141+ }
142+ .setCancelButton()
143+ .show()
36144 }
37145
38146 companion object {
0 commit comments