Skip to content

Commit da71229

Browse files
authored
Merge pull request #696 from PhilKes/feat/crash-export-backup
Add export backup button to error activity
2 parents 07bcfbe + 05bcec1 commit da71229

8 files changed

Lines changed: 234 additions & 42 deletions

File tree

TRANSLATIONS.md

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,34 +19,34 @@ See [Android Translations Converter](https://github.com/PhilKes/android-translat
1919
<!-- translations:start -->
2020
| Language | Coverage |
2121
|----------|----------|
22-
| 🇺🇸 English | 100% (310/310) |
23-
| 🇪🇸 Catalan | 20% (65/310) |
24-
| 🇨🇿 Czech | 97% (301/310) |
25-
| 🇩🇰 Danish | 22% (69/310) |
26-
| 🇩🇪 German | 98% (305/310) |
27-
| 🇬🇷 Greek | 23% (72/310) |
28-
| 🇪🇸 Spanish | 97% (301/310) |
29-
| 🇫🇷 French | 97% (301/310) |
30-
| 🇭🇺 Hungarian | 20% (65/310) |
31-
| 🇮🇩 Indonesian | 24% (75/310) |
32-
| 🇮🇹 Italian | 93% (291/310) |
33-
| 🇯🇵 Japanese | 23% (73/310) |
34-
| 🇲🇲 Burmese | 29% (90/310) |
35-
| 🇳🇴 Norwegian Bokmål | 34% (106/310) |
36-
| 🇳🇱 Dutch | 68% (212/310) |
37-
| 🇳🇴 Norwegian Nynorsk | 34% (106/310) |
38-
| 🇵🇱 Polish | 96% (300/310) |
39-
| 🇧🇷 Portuguese (Brazil) | 21% (66/310) |
40-
| 🇵🇹 Portuguese (Portugal) | 22% (71/310) |
41-
| 🇷🇴 Romanian | 97% (301/310) |
42-
| 🇷🇺 Russian | 96% (298/310) |
43-
| 🇸🇰 Slovak | 20% (65/310) |
44-
| 🇸🇮 Slovenian | 35% (109/310) |
45-
| 🇸🇪 Swedish | 20% (63/310) |
46-
| 🇵🇭 Tagalog | 20% (65/310) |
47-
| 🇹🇷 Turkish | 23% (73/310) |
48-
| 🇺🇦 Ukrainian | 20% (65/310) |
49-
| 🇻🇳 Vietnamese | 34% (107/310) |
50-
| 🇨🇳 Chinese (Simplified) | 98% (304/310) |
51-
| 🇹🇼 Chinese (Traditional) | 94% (294/310) |
22+
| 🇺🇸 English | 100% (312/312) |
23+
| 🇪🇸 Catalan | 20% (65/312) |
24+
| 🇨🇿 Czech | 96% (301/312) |
25+
| 🇩🇰 Danish | 22% (69/312) |
26+
| 🇩🇪 German | 97% (305/312) |
27+
| 🇬🇷 Greek | 23% (72/312) |
28+
| 🇪🇸 Spanish | 96% (301/312) |
29+
| 🇫🇷 French | 96% (301/312) |
30+
| 🇭🇺 Hungarian | 20% (65/312) |
31+
| 🇮🇩 Indonesian | 24% (75/312) |
32+
| 🇮🇹 Italian | 93% (291/312) |
33+
| 🇯🇵 Japanese | 23% (73/312) |
34+
| 🇲🇲 Burmese | 28% (90/312) |
35+
| 🇳🇴 Norwegian Bokmål | 33% (106/312) |
36+
| 🇳🇱 Dutch | 67% (212/312) |
37+
| 🇳🇴 Norwegian Nynorsk | 33% (106/312) |
38+
| 🇵🇱 Polish | 96% (300/312) |
39+
| 🇧🇷 Portuguese (Brazil) | 21% (66/312) |
40+
| 🇵🇹 Portuguese (Portugal) | 22% (71/312) |
41+
| 🇷🇴 Romanian | 96% (301/312) |
42+
| 🇷🇺 Russian | 95% (298/312) |
43+
| 🇸🇰 Slovak | 20% (65/312) |
44+
| 🇸🇮 Slovenian | 34% (109/312) |
45+
| 🇸🇪 Swedish | 20% (63/312) |
46+
| 🇵🇭 Tagalog | 20% (65/312) |
47+
| 🇹🇷 Turkish | 23% (73/312) |
48+
| 🇺🇦 Ukrainian | 20% (65/312) |
49+
| 🇻🇳 Vietnamese | 34% (107/312) |
50+
| 🇨🇳 Chinese (Simplified) | 97% (304/312) |
51+
| 🇹🇼 Chinese (Traditional) | 94% (294/312) |
5252
<!-- translations:end -->

app/src/main/java/com/philkes/notallyx/NotallyXApplication.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,12 +170,16 @@ class NotallyXApplication : Application(), Application.ActivityLifecycleCallback
170170

171171
private fun checkUpdateAutoBackupOnSave(backupFolderBefore: String?, backupFolder: String) {
172172
if (preferences.backupOnSave.value) {
173-
if (backupFolderBefore == null && !autoBackupOnSaveFileExists(backupFolder)) {
173+
if (
174+
backupFolderBefore == null &&
175+
backupFolder != EMPTY_PATH &&
176+
!autoBackupOnSaveFileExists(backupFolder)
177+
) {
174178
runOnIODispatcher {
175179
autoBackupOnSave(backupFolder, preferences.backupPassword.value, null)
176180
}
177181
}
178-
} else if (backupFolderBefore != backupFolder) {
182+
} else if (backupFolderBefore != backupFolder && backupFolder != EMPTY_PATH) {
179183
runOnIODispatcher {
180184
autoBackupOnSave(backupFolder, preferences.backupPassword.value, null)
181185
}

app/src/main/java/com/philkes/notallyx/utils/ErrorActivity.kt

Lines changed: 115 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,40 @@
11
package com.philkes.notallyx.utils
22

3+
import android.content.Intent
34
import android.os.Bundle
5+
import androidx.activity.result.ActivityResultLauncher
6+
import androidx.activity.result.contract.ActivityResultContracts
47
import androidx.appcompat.app.AppCompatActivity
8+
import androidx.lifecycle.MutableLiveData
9+
import androidx.lifecycle.lifecycleScope
510
import cat.ereza.customactivityoncrash.CustomActivityOnCrash
11+
import com.google.android.material.dialog.MaterialAlertDialogBuilder
12+
import com.philkes.notallyx.R
613
import 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
*/
1233
class 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 {

app/src/main/java/com/philkes/notallyx/utils/backup/ExportExtensions.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ private const val OUTPUT_DATA_BACKUP_URI = "backupUri"
9797
const val AUTO_BACKUP_WORK_NAME = "com.philkes.notallyx.AutoBackupWork"
9898
const val OUTPUT_DATA_EXCEPTION = "exception"
9999

100+
val BACKUP_TIMESTAMP_FORMATTER = SimpleDateFormat("yyyyMMdd-HHmmssSSS", Locale.ENGLISH)
100101
private const val ON_SAVE_BACKUP_FILE = "NotallyX_AutoBackup"
101102
private const val PERIODIC_BACKUP_FILE_PREFIX = "NotallyX_Backup_"
102103

@@ -114,9 +115,9 @@ fun ContextWrapper.createBackup(): Result {
114115
"Periodic Backup failed, because auto-backup path '$path' is invalid",
115116
) ?: return Result.success()
116117
try {
117-
val formatter = SimpleDateFormat("yyyyMMdd-HHmmssSSS", Locale.ENGLISH)
118118
val backupFilePrefix = PERIODIC_BACKUP_FILE_PREFIX
119-
val name = "$backupFilePrefix${formatter.format(System.currentTimeMillis())}"
119+
val name =
120+
"$backupFilePrefix${BACKUP_TIMESTAMP_FORMATTER.format(System.currentTimeMillis())}"
120121
log(TAG, msg = "Creating '$uri/$name.zip'...")
121122
val zipUri = folder.createFileSafe(MIME_TYPE_ZIP, name, ".zip").uri
122123
val exportedNotes = app.exportAsZip(zipUri, password = preferences.backupPassword.value)

app/src/main/res/layout/activity_error.xml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,17 @@
102102
android:layout_width="wrap_content"
103103
android:layout_height="wrap_content"
104104
android:layout_marginTop="@dimen/customactivityoncrash_activity_vertical_margin"
105-
android:text="@string/report_crash"
105+
android:text="@string/report_bug"
106+
android:textColor="?colorPrimary" />
107+
108+
<com.google.android.material.button.MaterialButton
109+
android:id="@+id/ExportBackupButton"
110+
style="@style/Widget.Material3.Button.OutlinedButton"
111+
app:strokeColor="?attr/colorPrimary"
112+
android:layout_width="wrap_content"
113+
android:layout_height="wrap_content"
114+
android:layout_marginTop="@dimen/customactivityoncrash_activity_vertical_margin"
115+
android:text="@string/export_backup"
106116
android:textColor="?colorPrimary" />
107117

108118
</LinearLayout>
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto"
4+
android:layout_width="match_parent"
5+
android:layout_height="match_parent"
6+
android:gravity="center"
7+
android:orientation="vertical">
8+
9+
<TextView
10+
android:id="@+id/ExceptionTitle"
11+
android:layout_width="match_parent"
12+
android:layout_height="wrap_content"
13+
android:scrollHorizontally="false"
14+
android:singleLine="false"
15+
android:gravity="center"
16+
android:padding="4dp"
17+
android:textColor="@android:color/black"
18+
android:textIsSelectable="true"
19+
android:textSize="14sp"
20+
android:textStyle="bold"
21+
android:layout_marginTop="@dimen/customactivityoncrash_activity_vertical_margin"
22+
/>
23+
24+
<LinearLayout
25+
android:layout_width="match_parent"
26+
android:layout_height="200dp"
27+
android:gravity="center"
28+
android:orientation="horizontal"
29+
android:weightSum="10">
30+
31+
<HorizontalScrollView
32+
android:layout_width="0dp"
33+
android:layout_weight="9"
34+
android:layout_height="match_parent"
35+
android:scrollbars="horizontal">
36+
<TextView
37+
android:id="@+id/ExceptionDetails"
38+
android:layout_width="0dp"
39+
android:layout_height="match_parent"
40+
android:ellipsize="none"
41+
android:padding="8dp"
42+
android:scrollbars="vertical|horizontal"
43+
android:scrollHorizontally="false"
44+
android:singleLine="false"
45+
android:textColor="@android:color/black"
46+
android:textIsSelectable="true"
47+
android:textSize="14sp" />
48+
</HorizontalScrollView>
49+
50+
<ImageButton
51+
android:id="@+id/CopyButton"
52+
android:layout_width="40dp"
53+
android:layout_height="40dp"
54+
android:background="?attr/selectableItemBackgroundBorderless"
55+
android:contentDescription="@string/copy"
56+
android:src="@drawable/content_copy" />
57+
</LinearLayout>
58+
59+
<TextView
60+
android:id="@+id/Message"
61+
android:layout_width="match_parent"
62+
android:layout_height="wrap_content"
63+
android:visibility="gone"
64+
android:padding="8dp"
65+
android:textColor="@android:color/black"
66+
android:textSize="14sp" />
67+
</LinearLayout>

app/src/main/res/values/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@
8484
<string name="copied_link">Copied Link to Clipboard</string>
8585
<string name="copy">Copy</string>
8686
<string name="count" translatable="false">%1$d / %2$d</string>
87+
<string name="crash_export_backup_failed">Unfortunately, exporting the backup failed.\nPlease use the \'%1$s\' button to report the crash</string>
88+
<string name="crash_export_backup_message">You can try to create a backup of your notes by clicking \'%1$s\'.\nThis might fail if the crash lead to broken/corrupted data. In that case, please use the \'%2$s\' button to report the crash</string>
8789
<string name="crash_message">An unexpected error occurred.\nSorry for the inconvenience.</string>
8890
<string name="create_github_issue">Create Github Issue</string>
8991
<string name="creation_date">Created</string>

app/translations.xlsx

342 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)