Skip to content

Commit

Permalink
feat(app): add crash screen
Browse files Browse the repository at this point in the history
"inspired" by mihon
  • Loading branch information
abdallahmehiz committed Aug 2, 2024
1 parent 84e0025 commit b3b9017
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 2 deletions.
11 changes: 11 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import io.gitlab.arturbosch.detekt.Detekt
import java.time.LocalDateTime
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter

plugins {
alias(libs.plugins.ksp)
Expand All @@ -24,6 +27,13 @@ android {
vectorDrawables {
useSupportLibrary = true
}

val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")
buildConfigField(
"String",
"BUILD_TIME",
"\"${LocalDateTime.now(ZoneOffset.UTC).format(dateTimeFormatter)}\"",
)
}
splits {
abi {
Expand Down Expand Up @@ -63,6 +73,7 @@ android {
buildFeatures {
compose = true
viewBinding = true
buildConfig = true
}
composeCompiler {
includeSourceInformation = true
Expand Down
15 changes: 13 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@
<activity
android:name=".ui.player.PlayerActivity"
android:autoRemoveFromRecents="true"
android:configChanges="orientation|screenLayout|screenSize|smallestScreenSize|keyboardHidden|keyboard|uiMode"
android:exported="true"
android:launchMode="singleTask"
android:supportsPictureInPicture="true"
android:resizeableActivity="true"
android:configChanges="orientation|screenLayout|screenSize|smallestScreenSize|keyboardHidden|keyboard|uiMode"
android:supportsPictureInPicture="true"
android:theme="@style/Theme.MpvKt">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
Expand Down Expand Up @@ -70,5 +70,16 @@
<data android:scheme="udp" />
</intent-filter>
</activity>
<activity android:name=".presentation.crash.CrashActivity" />

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application>
</manifest>
3 changes: 3 additions & 0 deletions app/src/main/java/live/mehiz/mpvkt/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ package live.mehiz.mpvkt
import android.app.Application
import live.mehiz.mpvkt.di.DatabaseModule
import live.mehiz.mpvkt.di.PreferencesModule
import live.mehiz.mpvkt.presentation.crash.CrashActivity
import live.mehiz.mpvkt.presentation.crash.GlobalExceptionHandler
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin

class App : Application() {
override fun onCreate() {
super.onCreate()
Thread.setDefaultUncaughtExceptionHandler(GlobalExceptionHandler(applicationContext, CrashActivity::class.java))
startKoin {
androidContext(this@App)
modules(
Expand Down
206 changes: 206 additions & 0 deletions app/src/main/java/live/mehiz/mpvkt/presentation/crash/CrashActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package live.mehiz.mpvkt.presentation.crash

import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ContentCopy
import androidx.compose.material.icons.outlined.BugReport
import androidx.compose.material3.Button
import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBarDefaults
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.core.content.FileProvider
import `is`.xyz.mpv.Utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import live.mehiz.mpvkt.BuildConfig
import live.mehiz.mpvkt.MainActivity
import live.mehiz.mpvkt.R
import live.mehiz.mpvkt.ui.theme.MpvKtTheme
import java.io.File

class CrashActivity : ComponentActivity() {

private val clipboardManager by lazy { getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager }

override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
setContent {
MpvKtTheme {
CrashScreen(intent.getStringExtra("exception") ?: "")
}
}
}

private fun createLogs(): String {
return """
App version: ${BuildConfig.VERSION_NAME} (${BuildConfig.BUILD_TIME})
Android version: ${Build.VERSION.RELEASE} (${Build.VERSION.SDK_INT})
Device brand: ${Build.BRAND}
Device manufacturer: ${Build.MANUFACTURER}
Device model: ${Build.MODEL} (${Build.DEVICE})
MPV version: ${Utils.VERSIONS.mpv}
ffmpeg version: ${Utils.VERSIONS.ffmpeg}
libplacebo version: ${Utils.VERSIONS.libPlacebo}
""".trimIndent()
}

private suspend fun dumpLogs(
exceptionString: String
) {
withContext(NonCancellable) {
val file = File(applicationContext.cacheDir, "mptKt_logs.txt")
if (file.exists()) file.delete()
file.createNewFile()
file.appendText(createLogs())
file.appendText("\n\n")
file.appendText(exceptionString)
val uri = FileProvider.getUriForFile(applicationContext, BuildConfig.APPLICATION_ID + ".provider", file)
val intent = Intent(Intent.ACTION_SEND)
intent.putExtra(Intent.EXTRA_STREAM, uri)
intent.clipData = ClipData.newRawUri(null, uri)
intent.type = "text/plain"
intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
this@CrashActivity.startActivity(
Intent.createChooser(intent, applicationContext.getString(R.string.crash_screen_share))
)
}
}

@Composable
fun CrashScreen(
exceptionString: String,
modifier: Modifier = Modifier,
) {
val scope = rememberCoroutineScope()
Scaffold(
modifier = modifier.fillMaxSize(),
bottomBar = {
val borderColor = MaterialTheme.colorScheme.outline
Column(
Modifier
.windowInsetsPadding(NavigationBarDefaults.windowInsets)
.drawBehind {
drawLine(
borderColor,
Offset.Zero,
Offset(size.width, 0f),
strokeWidth = Dp.Hairline.value,
)
}
.padding(vertical = 8.dp, horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Button(
onClick = {
scope.launch(Dispatchers.IO) {
dumpLogs(exceptionString)
}
},
modifier = Modifier.weight(1f),
) { Text(stringResource(R.string.crash_screen_share)) }
FilledIconButton(
onClick = {
clipboardManager.setPrimaryClip(ClipData.newPlainText(null, createLogs()))
},
) {
Icon(Icons.Default.ContentCopy, null)
}
}
OutlinedButton(
onClick = {
startActivity(Intent(this@CrashActivity, MainActivity::class.java))
finishAndRemoveTask()
},
modifier = Modifier.fillMaxWidth(),
) {
Text(stringResource(R.string.crash_screen_restart))
}
}
},
) { paddingValues ->
Column(
modifier = Modifier
.padding(paddingValues)
.padding(horizontal = 16.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
Spacer(Modifier.height(paddingValues.calculateTopPadding()))
Icon(
Icons.Outlined.BugReport,
null,
modifier = Modifier.size(48.dp),
tint = MaterialTheme.colorScheme.primary,
)
Text(
stringResource(R.string.crash_screen_title),
style = MaterialTheme.typography.headlineLarge,
)
Text(
stringResource(R.string.crash_screen_subtitle, stringResource(R.string.app_name)),
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
Text(
stringResource(R.string.crash_screen_logs_title),
style = MaterialTheme.typography.headlineSmall,
)
Surface(
shape = RoundedCornerShape(16.dp),
color = MaterialTheme.colorScheme.surfaceVariant,
) {
SelectionContainer {
Text(
text = exceptionString,
fontFamily = FontFamily.Monospace,
style = MaterialTheme.typography.labelMedium,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp),
)
}
}
Spacer(Modifier.height(8.dp))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package live.mehiz.mpvkt.presentation.crash

import android.content.Context
import android.content.Intent
import kotlin.system.exitProcess

class GlobalExceptionHandler(
private val context: Context,
private val activity: Class<*>
) : Thread.UncaughtExceptionHandler {

override fun uncaughtException(t: Thread, e: Throwable) {
val intent = Intent(context, activity)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
intent.putExtra("exception", e.stackTraceToString())
context.startActivity(intent)
exitProcess(0)
}
}
7 changes: 7 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<string name="app_name">mpvKt</string>

<string name="generic_reset">Reset</string>
<string name="generic_share">Share</string>

<string name="pref_appearance_title">Appearance</string>
<string name="pref_appearance_category_theme">Theme</string>
Expand Down Expand Up @@ -89,4 +90,10 @@
<string name="player_aspect_fit">Fit Screen</string>
<string name="player_aspect_crop">Crop</string>
<string name="player_aspect_stretch">Stretch</string>

<string name="crash_screen_title">Oops</string>
<string name="crash_screen_subtitle">Looks like %s experienced an unexpected error. Consider sharing the crash logs in a GitHub issue.</string>
<string name="crash_screen_logs_title">Crash logs:</string>
<string name="crash_screen_share">Share crash logs</string>
<string name="crash_screen_restart">Restart the app</string>
</resources>
9 changes: 9 additions & 0 deletions app/src/main/res/xml/provider_paths.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path
name="cache_files"
path="." />
<files-path
name="files_path"
path="." />
</paths>

0 comments on commit b3b9017

Please sign in to comment.