Skip to content

Commit 07bcfbe

Browse files
authored
Merge pull request #694 from PhilKes/feat/improve-crash-handling
Improve crash activity and handle long crash logs
2 parents cf0118a + 0cd78e8 commit 07bcfbe

11 files changed

Lines changed: 248 additions & 130 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% (307/307) |
23-
| 🇪🇸 Catalan | 21% (65/307) |
24-
| 🇨🇿 Czech | 98% (301/307) |
25-
| 🇩🇰 Danish | 22% (69/307) |
26-
| 🇩🇪 German | 99% (305/307) |
27-
| 🇬🇷 Greek | 23% (72/307) |
28-
| 🇪🇸 Spanish | 98% (301/307) |
29-
| 🇫🇷 French | 98% (301/307) |
30-
| 🇭🇺 Hungarian | 21% (65/307) |
31-
| 🇮🇩 Indonesian | 24% (75/307) |
32-
| 🇮🇹 Italian | 94% (291/307) |
33-
| 🇯🇵 Japanese | 23% (73/307) |
34-
| 🇲🇲 Burmese | 29% (90/307) |
35-
| 🇳🇴 Norwegian Bokmål | 34% (106/307) |
36-
| 🇳🇱 Dutch | 69% (212/307) |
37-
| 🇳🇴 Norwegian Nynorsk | 34% (106/307) |
38-
| 🇵🇱 Polish | 97% (300/307) |
39-
| 🇧🇷 Portuguese (Brazil) | 21% (66/307) |
40-
| 🇵🇹 Portuguese (Portugal) | 23% (71/307) |
41-
| 🇷🇴 Romanian | 98% (301/307) |
42-
| 🇷🇺 Russian | 97% (298/307) |
43-
| 🇸🇰 Slovak | 21% (65/307) |
44-
| 🇸🇮 Slovenian | 35% (109/307) |
45-
| 🇸🇪 Swedish | 20% (63/307) |
46-
| 🇵🇭 Tagalog | 21% (65/307) |
47-
| 🇹🇷 Turkish | 23% (73/307) |
48-
| 🇺🇦 Ukrainian | 21% (65/307) |
49-
| 🇻🇳 Vietnamese | 34% (107/307) |
50-
| 🇨🇳 Chinese (Simplified) | 99% (304/307) |
51-
| 🇹🇼 Chinese (Traditional) | 95% (294/307) |
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) |
5252
<!-- translations:end -->

app/src/main/java/com/philkes/notallyx/presentation/activity/main/fragment/settings/SettingsFragment.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.philkes.notallyx.presentation.activity.main.fragment.settings
33
import android.Manifest
44
import android.app.Application
55
import android.content.ActivityNotFoundException
6+
import android.content.ContextWrapper
67
import android.content.Intent
78
import android.content.Intent.ACTION_OPEN_DOCUMENT_TREE
89
import android.content.pm.PackageManager
@@ -59,6 +60,7 @@ import com.philkes.notallyx.utils.getLogFile
5960
import com.philkes.notallyx.utils.getUriForFile
6061
import com.philkes.notallyx.utils.reportBug
6162
import com.philkes.notallyx.utils.security.showBiometricOrPinPrompt
63+
import com.philkes.notallyx.utils.viewLogs
6264
import com.philkes.notallyx.utils.wrapWithChooser
6365
import java.util.Date
6466

@@ -805,6 +807,7 @@ class SettingsFragment : Fragment() {
805807
.show()
806808
}
807809
Donate.setOnClickListener { openLink("https://ko-fi.com/philkes") }
810+
ViewLogs.setOnClickListener { (requireContext() as ContextWrapper).viewLogs() }
808811

809812
try {
810813
val pInfo =

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

Lines changed: 79 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@ import androidx.annotation.ColorInt
2222
import androidx.annotation.RequiresApi
2323
import androidx.core.content.ContextCompat
2424
import androidx.core.content.FileProvider
25+
import androidx.core.net.toUri
2526
import androidx.documentfile.provider.DocumentFile
2627
import androidx.fragment.app.Fragment
2728
import androidx.lifecycle.LifecycleOwner
2829
import androidx.lifecycle.LiveData
2930
import androidx.lifecycle.MediatorLiveData
3031
import androidx.lifecycle.Observer
32+
import com.google.android.material.dialog.MaterialAlertDialogBuilder
3133
import com.philkes.notallyx.BuildConfig
3234
import com.philkes.notallyx.R
3335
import com.philkes.notallyx.data.model.BaseNote
@@ -36,8 +38,10 @@ import com.philkes.notallyx.data.model.toText
3638
import com.philkes.notallyx.presentation.activity.note.EditActivity.Companion.EXTRA_SELECTED_BASE_NOTE
3739
import com.philkes.notallyx.presentation.activity.note.EditListActivity
3840
import com.philkes.notallyx.presentation.activity.note.EditNoteActivity
41+
import com.philkes.notallyx.presentation.setCancelButton
3942
import com.philkes.notallyx.presentation.showToast
4043
import com.philkes.notallyx.presentation.view.misc.NotNullLiveData
44+
import com.philkes.notallyx.presentation.viewmodel.ExportMimeType
4145
import java.io.BufferedReader
4246
import java.io.File
4347
import java.io.InputStreamReader
@@ -148,7 +152,14 @@ fun ContextWrapper.log(
148152
) {
149153
val folder = getLogsDir()
150154
folder.mkdir()
151-
logToFile(tag, DocumentFile.fromFile(folder), APP_LOG_FILE_NAME, msg, throwable, stackTrace)
155+
logToFile(
156+
tag,
157+
DocumentFile.fromFile(folder),
158+
"$APP_LOG_FILE_NAME.txt",
159+
msg,
160+
throwable,
161+
stackTrace,
162+
)
152163
}
153164

154165
fun ContextWrapper.getLastExceptionLog(): String? {
@@ -159,9 +170,21 @@ fun ContextWrapper.getLastExceptionLog(): String? {
159170
return null
160171
}
161172

173+
fun ContextWrapper.viewLogs() {
174+
val logFile = getLogFile()
175+
if (logFile.exists()) {
176+
viewFile(getUriForFile(logFile), ExportMimeType.TXT.mimeType)
177+
} else {
178+
showToast(getString(R.string.not_exists, logFile.name))
179+
}
180+
}
181+
182+
fun ContextWrapper.getLogFileUri() =
183+
getLogFile().let { if (it.exists()) getUriForFile(it) else null }
184+
162185
private const val MAX_LOGS_FILE_SIZE_KB: Long = 2048
163186

164-
fun Context.logToFile(
187+
private fun Context.logToFile(
165188
tag: String,
166189
folder: DocumentFile,
167190
fileName: String,
@@ -233,12 +256,6 @@ fun Context.logToFile(
233256
} ?: Log.e(tag, "Error: log file could not be found or created")
234257
}
235258

236-
fun Fragment.reportBug(stackTrace: String?) {
237-
requireContext().catchNoBrowserInstalled {
238-
startActivity(requireContext().createReportBugIntent(stackTrace))
239-
}
240-
}
241-
242259
fun Fragment.getExtraBooleanFromBundleOrIntent(
243260
bundle: Bundle?,
244261
key: String,
@@ -250,8 +267,29 @@ fun Fragment.getExtraBooleanFromBundleOrIntent(
250267
)
251268
}
252269

270+
private fun Context.showConfirmCrashLogTooLong() {
271+
MaterialAlertDialogBuilder(this)
272+
.setMessage(
273+
getString(R.string.report_bug_stacktrace_too_long, getString(R.string.continue_))
274+
)
275+
.setPositiveButton(R.string.continue_) { _, _ -> reportBug("<PASTE CRASH LOGS HERE>") }
276+
.setCancelButton()
277+
.show()
278+
}
279+
253280
fun Context.reportBug(stackTrace: String?) {
254-
catchNoBrowserInstalled { startActivity(createReportBugIntent(stackTrace)) }
281+
catchNoBrowserInstalled {
282+
try {
283+
startActivity(createReportBugIntent(stackTrace))
284+
} catch (_: IllegalArgumentException) {
285+
copyToClipBoard(stackTrace!!)
286+
showConfirmCrashLogTooLong()
287+
}
288+
}
289+
}
290+
291+
fun Fragment.reportBug(stackTrace: String?) {
292+
requireContext().reportBug(stackTrace)
255293
}
256294

257295
fun Context.catchNoBrowserInstalled(callback: () -> Unit) {
@@ -270,20 +308,30 @@ fun Context.createReportBugIntent(
270308
fun String?.asQueryParam(paramName: String): String {
271309
return this?.let { "&$paramName=${URLEncoder.encode(this, "UTF-8")}" } ?: ""
272310
}
273-
return Intent(
274-
Intent.ACTION_VIEW,
275-
Uri.parse(
276-
"https://github.com/PhilKes/NotallyX/issues/new?labels=bug&projects=&template=bug_report.yml${
277-
title.asQueryParam("title")
278-
}&version=${BuildConfig.VERSION_NAME}&android-version=${Build.VERSION.SDK_INT}${
279-
stackTrace.asQueryParam("logs")
280-
}${
281-
body.asQueryParam("what-happened")
282-
}"
283-
.take(2000)
284-
),
285-
)
286-
.wrapWithChooser(this)
311+
312+
val url =
313+
"https://github.com/PhilKes/NotallyX/issues/new?labels=bug&projects=&template=bug_report.yml${
314+
title.asQueryParam("title")
315+
}&version=${BuildConfig.VERSION_NAME}&android-version=${Build.VERSION.SDK_INT}${
316+
stackTrace.asQueryParam("logs")
317+
}${
318+
body.asQueryParam("what-happened")
319+
}"
320+
if (url.length > 2000) {
321+
throw IllegalArgumentException("Given stacktrace is too long to build a valid URL!")
322+
}
323+
return Intent(Intent.ACTION_VIEW, url.toUri()).wrapWithChooser(this)
324+
}
325+
326+
fun Context.viewFile(uri: Uri, mimeType: String) {
327+
val intent =
328+
Intent(Intent.ACTION_VIEW)
329+
.apply {
330+
setDataAndType(uri, mimeType)
331+
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
332+
}
333+
.wrapWithChooser(this)
334+
startActivity(intent)
287335
}
288336

289337
fun ContextWrapper.shareNote(note: BaseNote) {
@@ -393,9 +441,14 @@ fun DocumentFolder.createFileSafe(
393441
fileName: String,
394442
fileExtension: String,
395443
): DocumentFile {
396-
return requireNotNull(
397-
createFile(mimeType, fileName + fileExtension) ?: createFile(mimeType, fileName)
398-
) {
444+
var createdFile = createFile(mimeType, fileName)
445+
if (createdFile == null) {
446+
createdFile = createFile(mimeType, fileName + fileExtension)
447+
} else if (createdFile.name?.endsWith(fileExtension) == false) {
448+
createdFile.delete()
449+
createdFile = createFile(mimeType, fileName + fileExtension)
450+
}
451+
return requireNotNull(createdFile) {
399452
"Could not create '$fileName$fileExtension' in Folder '$name' (uri: '$uri')"
400453
}
401454
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@ class ErrorActivity : AppCompatActivity() {
2626
val stackTrace = CustomActivityOnCrash.getStackTraceFromIntent(intent)
2727
stackTrace?.let {
2828
application.log(TAG, stackTrace = it)
29-
Exception.text = stackTrace.lines().firstOrNull() ?: ""
29+
ExceptionTitle.text = stackTrace.lines().firstOrNull()?.replaceFirst(":", ":\n")
30+
ExceptionDetails.text = stackTrace.lines().drop(1).joinToString("\n")
31+
CopyButton.setOnClickListener { copyToClipBoard(stackTrace) }
3032
}
3133
ReportButton.setOnClickListener { reportBug(stackTrace) }
34+
ViewLogsButton.setOnClickListener { viewLogs() }
3235
}
3336
}
3437

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,13 @@ fun Context.getBackupDir() = getEmptyFolder("backup")
148148

149149
fun Context.getExportedPath() = getEmptyFolder("exported")
150150

151-
fun ContextWrapper.getLogsDir() = File(filesDir, "logs").also { it.mkdir() }
151+
fun ContextWrapper.getLogsDir() =
152+
getExternalMediaDirectory("logs") ?: File(filesDir, "logs").also { it.mkdir() }
152153

153-
const val APP_LOG_FILE_NAME = "notallyx-logs.txt"
154+
const val APP_LOG_FILE_NAME = "notallyx-logs"
154155

155156
fun ContextWrapper.getLogFile(): File {
156-
return File(getLogsDir(), APP_LOG_FILE_NAME)
157+
return File(getLogsDir(), "$APP_LOG_FILE_NAME.txt")
157158
}
158159

159160
private fun ContextWrapper.getExternalMediaDirectory(name: String): File? {

0 commit comments

Comments
 (0)