Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add local crash tracking and viewing #945

Merged
merged 5 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ dependencies {
// gif support
implementation("io.coil-kt:coil-gif:2.4.0")

// crash handling
implementation("com.github.FunkyMuse:Crashy:1.2.0")

// To use Kotlin annotation processing tool
ksp("androidx.room:room-compiler:2.5.2")

Expand Down
9 changes: 9 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,15 @@
<!--#INSTANCE_LIST_END#-->
</intent-filter>
</activity>
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="com.crazylegend.crashyreporter.initializer.CrashyInitializer"
android:value="androidx.startup" />
</provider>
</application>

</manifest>
7 changes: 7 additions & 0 deletions app/src/main/java/com/jerboa/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ import com.jerboa.ui.components.report.post.CreatePostReportActivity
import com.jerboa.ui.components.settings.SettingsActivity
import com.jerboa.ui.components.settings.about.AboutActivity
import com.jerboa.ui.components.settings.account.AccountSettingsActivity
import com.jerboa.ui.components.settings.crashlogs.CrashLogsActivity
import com.jerboa.ui.components.settings.lookandfeel.LookAndFeelActivity
import com.jerboa.ui.theme.JerboaTheme
import com.jerboa.util.BackConfirmation.addConfirmationDialog
Expand Down Expand Up @@ -658,6 +659,12 @@ class MainActivity : AppCompatActivity() {
)
}

composable(route = Route.CRASH_LOGS) {
CrashLogsActivity(
navController = navController,
)
}

composable(
route = Route.VIEW,
arguments = listOf(
Expand Down
20 changes: 20 additions & 0 deletions app/src/main/java/com/jerboa/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.pager.PagerState
import androidx.compose.material3.DrawerState
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.TabPosition
import androidx.compose.runtime.Stable
import androidx.compose.runtime.mutableStateListOf
Expand Down Expand Up @@ -782,6 +784,24 @@ fun scrollToTop(
}
}

fun showSnackbar(
scope: CoroutineScope,
snackbarHostState: SnackbarHostState,
message: String,
actionLabel: String?,
withDismissAction: Boolean = false,
snackbarDuration: SnackbarDuration,
) {
scope.launch {
snackbarHostState.showSnackbar(
message,
actionLabel,
withDismissAction,
snackbarDuration,
)
}
}

// https://stackoverflow.com/questions/69234880/how-to-get-intent-data-in-a-composable
fun Context.findActivity(): Activity? = when (this) {
is Activity -> this
Expand Down
15 changes: 14 additions & 1 deletion app/src/main/java/com/jerboa/ui/components/common/AppBars.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import com.jerboa.R
import com.jerboa.datatypes.samplePerson
import com.jerboa.datatypes.samplePost
Expand All @@ -59,6 +60,7 @@ fun SimpleTopAppBar(
text: String,
navController: NavController,
scrollBehavior: TopAppBarScrollBehavior? = null,
actions: @Composable RowScope.() -> Unit = {},
) {
TopAppBar(
scrollBehavior = scrollBehavior,
Expand All @@ -75,9 +77,18 @@ fun SimpleTopAppBar(
)
}
},
actions = actions,
)
}

@Preview
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SimpleTopAppBarPreview() {
SimpleTopAppBar(text = "Preview", navController = rememberNavController()) {
}
}

@Composable
fun BottomAppBarAll(
selectedTab: NavTab,
Expand Down Expand Up @@ -552,5 +563,7 @@ fun Modifier.simpleVerticalScrollbar(
fun LoadingBar(
padding: PaddingValues = PaddingValues(0.dp),
) {
LinearProgressIndicator(modifier = Modifier.fillMaxWidth().padding(padding).testTag("jerboa:loading"))
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth().padding(padding).testTag("jerboa:loading"),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ fun NavController.toLookAndFeel() = navigate(Route.LOOK_AND_FEEL)

fun NavController.toAbout() = navigate(Route.ABOUT)

fun NavController.toCrashLogs() = navigate(Route.CRASH_LOGS)

fun NavController.toView(url: String) {
val encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8.name())
navigate(Route.ViewArgs.makeRoute(encodedUrl))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ object Route {
const val LOOK_AND_FEEL = "lookAndFeel"
const val ACCOUNT_SETTINGS = "accountSettings"
const val ABOUT = "about"
const val CRASH_LOGS = "crashLogs"

val VIEW = ViewArgs.route

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import com.alorma.compose.settings.ui.SettingsMenuLink
import com.jerboa.R
import com.jerboa.openLink
import com.jerboa.ui.components.common.SimpleTopAppBar
import com.jerboa.ui.components.common.toCrashLogs

const val githubUrl = "https://github.com/dessalines/jerboa"
const val jerboaMatrixChat = "https://matrix.to/#/#jerboa-dev:matrix.org"
Expand Down Expand Up @@ -97,6 +98,16 @@ fun AboutActivity(
openLink("$githubUrl/issues")
},
)
SettingsMenuLink(
title = { Text(stringResource(R.string.crash_logs)) },
icon = {
Icon(
imageVector = Icons.Outlined.Build,
contentDescription = null,
)
},
onClick = { navController.toCrashLogs() },
)
SettingsMenuLink(
title = { Text(stringResource(R.string.settings_about_developer_matrix_chatroom)) },
icon = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package com.jerboa.ui.components.settings.crashlogs

import android.content.Context
import android.util.Log
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ContentCopy
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import com.crazylegend.crashyreporter.CrashyReporter
import com.jerboa.R
import com.jerboa.copyToClipboard
import com.jerboa.showSnackbar
import com.jerboa.ui.components.common.SimpleTopAppBar
import com.jerboa.ui.theme.MEDIUM_PADDING
import com.jerboa.ui.theme.SMALL_PADDING

@Composable
fun CrashLogsActivity(
navController: NavController,
) {
Log.d("jerboa", "Got to Crash log activity")

val ctx = LocalContext.current

val crashes = CrashyReporter.getLogsAsStrings()?.toMutableStateList() ?: run { mutableStateListOf() }

CrashLogs(ctx = ctx, navController = navController, crashes = crashes)
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CrashLogs(ctx: Context, navController: NavController, crashes: MutableList<String>) {
val scope = rememberCoroutineScope()

val snackbarHostState = remember { SnackbarHostState() }
val deleteMessage = stringResource(R.string.crash_logs_all_deleted)

Scaffold(
snackbarHost = { SnackbarHost(snackbarHostState) },
topBar = {
SimpleTopAppBar(
text = stringResource(R.string.crash_logs),
navController = navController,
actions = {
IconButton(
onClick = {
CrashyReporter.purgeLogs()
crashes.clear()
showSnackbar(
scope,
snackbarHostState,
deleteMessage,
null,
true,
SnackbarDuration.Short,
)
},
) {
Icon(
Icons.Outlined.Delete,
contentDescription = stringResource(R.string.crash_logs_delete),
)
}
},
)
},
content = { padding ->
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(padding),
) {
crashes.forEachIndexed { _, crash ->
CrashLog(ctx = ctx, crash = crash)
}
}
},
)
}

@Composable
fun CrashLog(ctx: Context, crash: String) {
var expanded by remember { mutableStateOf(false) }
val textModifier = Modifier.clickable(onClick = { expanded = !expanded })

Row(
modifier = Modifier
.fillMaxWidth()
.padding(MEDIUM_PADDING),
) {
Column(
modifier = Modifier.padding(MEDIUM_PADDING),
) {
IconButton(onClick = {
copyToClipboard(ctx, crash, "crash")
}) {
Icon(
Icons.Outlined.ContentCopy,
contentDescription = stringResource(R.string.crash_logs_copy),
)
}
}
if (expanded) {
Text(
text = crash,
modifier = textModifier,
)
} else {
Text(
text = crash,
maxLines = 4,
overflow = TextOverflow.Ellipsis,
modifier = textModifier,
)
}
}
Divider(modifier = Modifier.padding(bottom = SMALL_PADDING))
}

@Preview
@Composable
fun CrashLogsPreview() {
CrashLogs(
ctx = LocalContext.current,
navController = rememberNavController(),
crashes = mutableListOf(
"A really bad one\nlots\nof\ntrace\nlines\nhere",
"NullPointerException!",
),
)
}
4 changes: 4 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@
<string name="community_sortBy">Sort by</string>
<string name="community_subscribe">Subscribe</string>
<string name="community_users_month">%1$s users / month</string>
<string name="crash_logs">Crash Logs</string>
<string name="crash_logs_all_deleted">All crash logs deleted</string>
<string name="crash_logs_copy">Copy crash log</string>
<string name="crash_logs_delete">Delete all crash logs</string>
<string name="createPost_selectCommunity">Select community from list</string>
<string name="create_post_a_title_here">a title here....</string>
<string name="create_post_community">Community</string>
Expand Down