diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
index be23f69..427ed23 100644
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -8,10 +8,10 @@ jobs:
steps:
- uses: actions/checkout@v1
- - name: set up JDK 1.8
+ - name: set up JDK 11
uses: actions/setup-java@v1
with:
- java-version: 1.8
+ java-version: 11
- name: Build with Gradle
run: ./gradlew build test
\ No newline at end of file
diff --git a/.github/workflows/style-check.yml b/.github/workflows/style-check.yml
index 9cd7ce0..37d611f 100644
--- a/.github/workflows/style-check.yml
+++ b/.github/workflows/style-check.yml
@@ -8,10 +8,10 @@ jobs:
steps:
- uses: actions/checkout@v1
- - name: set up JDK 1.8
+ - name: set up JDK 11
uses: actions/setup-java@v1
with:
- java-version: 1.8
+ java-version: 11
- name: Ktlint check
run: ./gradlew ktlintCheck
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 61a9130..fb7f4a8 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..875a112
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 7bb9736..be9f721 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,7 +1,29 @@
+
+
+
-
+
diff --git a/app/build.gradle b/app/build.gradle
index e469a44..297b44a 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -41,7 +41,11 @@ android {
}
buildFeatures {
- viewBinding true
+ compose true
+ }
+
+ composeOptions {
+ kotlinCompilerExtensionVersion "1.0.5"
}
}
@@ -53,9 +57,12 @@ dependencies {
implementation libs.kotlinStdLib
implementation libs.appCompat
implementation libs.coreKtx
- implementation libs.fragmentKtx
- implementation libs.constraintLayout
- implementation libs.materialComponents
+
+ implementation libs.composeActivity
+ implementation libs.composeMaterial
+ implementation libs.composeTooling
+ implementation libs.accompanistSysUi
+ implementation libs.accompanistInsets
testImplementation libs.junit
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6840f78..b7bfb9e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -5,11 +5,13 @@
-
+
diff --git a/app/src/main/java/com/haroldadmin/crashyapp/MainActivity.kt b/app/src/main/java/com/haroldadmin/crashyapp/MainActivity.kt
index 4c890cc..a4cfa9d 100644
--- a/app/src/main/java/com/haroldadmin/crashyapp/MainActivity.kt
+++ b/app/src/main/java/com/haroldadmin/crashyapp/MainActivity.kt
@@ -1,20 +1,19 @@
package com.haroldadmin.crashyapp
import android.os.Bundle
+import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
-import com.haroldadmin.crashyapp.databinding.ActivityMainBinding
+import com.haroldadmin.crashyapp.ui.pages.HomePage
+import com.haroldadmin.crashyapp.ui.theme.CrashyAppTheme
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- val binding = ActivityMainBinding.inflate(layoutInflater)
- setContentView(binding.root)
-
- binding.crashButton.setOnClickListener {
- throw BecauseICanException()
+ setContent {
+ CrashyAppTheme {
+ HomePage()
+ }
}
}
}
-
-private class BecauseICanException : Exception("This exception is thrown purely because it can be thrown")
diff --git a/app/src/main/java/com/haroldadmin/crashyapp/ui/pages/HomePage.kt b/app/src/main/java/com/haroldadmin/crashyapp/ui/pages/HomePage.kt
new file mode 100644
index 0000000..d245e77
--- /dev/null
+++ b/app/src/main/java/com/haroldadmin/crashyapp/ui/pages/HomePage.kt
@@ -0,0 +1,41 @@
+package com.haroldadmin.crashyapp.ui.pages
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.*
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun HomePage() {
+ Scaffold(
+ topBar = {
+ TopAppBar {
+ Text(text = "Crashy App", style = MaterialTheme.typography.h6)
+ }
+ }
+ ) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center,
+ modifier = Modifier
+ .padding(8.dp)
+ .fillMaxWidth()
+ .fillMaxHeight()
+ ) {
+ Text(
+ text = "Press the button below to see error screen from WhatTheStack!",
+ textAlign = TextAlign.Center
+ )
+ Spacer(modifier = Modifier.height(16.dp))
+ Button(onClick = { throw BecauseICanException() }) {
+ Text(text = "Crash!")
+ }
+ }
+ }
+}
+
+private class BecauseICanException :
+ Exception("This exception is thrown purely because it can be thrown")
diff --git a/app/src/main/java/com/haroldadmin/crashyapp/ui/theme/CrashyAppTheme.kt b/app/src/main/java/com/haroldadmin/crashyapp/ui/theme/CrashyAppTheme.kt
new file mode 100644
index 0000000..c14ff85
--- /dev/null
+++ b/app/src/main/java/com/haroldadmin/crashyapp/ui/theme/CrashyAppTheme.kt
@@ -0,0 +1,20 @@
+package com.haroldadmin.crashyapp.ui.theme
+
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.lightColors
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
+
+private val ColorPalette = lightColors(
+ primary = Color(0xffd32f2f),
+ primaryVariant = Color(0xff9a0007),
+ secondary = Color(0xff616161),
+ secondaryVariant = Color(0x33373737),
+)
+
+@Composable
+fun CrashyAppTheme(
+ content: @Composable () -> Unit
+) {
+ MaterialTheme(colors = ColorPalette, content = content)
+}
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
deleted file mode 100644
index 0131968..0000000
--- a/app/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
deleted file mode 100644
index f12742b..0000000
--- a/app/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
- #d32f2f
- #ff6659
- #9a0007
- #616161
- #8e8e8e
- #373737
- #ffffff
- #ffffff
-
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
deleted file mode 100644
index d1882f5..0000000
--- a/app/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
- Crashy App
-
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 05ba9fc..f3f0b8f 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -1,13 +1,4 @@
-
-
-
diff --git a/build.gradle b/build.gradle
index 613bb94..658afa7 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,9 +1,9 @@
buildscript {
ext.buildConfig = [
"applicationId": "com.haroldadmin.crashyapp",
- "compileSdk" : 29,
+ "compileSdk" : 31,
"minSdk" : 21,
- "targetSdk" : 29,
+ "targetSdk" : 31,
"versionCode" : 1,
"versionName" : "0.0.1"
]
@@ -33,7 +33,7 @@ allprojects {
subprojects {
apply plugin: "org.jlleitschuh.gradle.ktlint"
ktlint {
- version = "0.36.0"
+ version = "0.43.0"
ignoreFailures = false
disabledRules = ["no-wildcard-imports"]
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 2a3d007..35a7f95 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,6 +1,6 @@
[versions]
-kotlin = "1.5.20"
-agp = "4.2.0"
+kotlin = "1.5.31"
+agp = "7.0.3"
ktlint = "10.1.0"
appCompat = "1.3.0"
coreTest = "2.0.0"
@@ -17,9 +17,17 @@ espressoCore = "3.2.0"
mockk = "1.9.3"
robolectric = "4.3.1"
startup = "1.0.0"
+composeActivity = "1.3.1"
+compose = "1.0.5"
+accompanist = "0.21.3-beta"
[libraries]
+composeActivity = { module = "androidx.activity:activity-compose", version.ref = "composeActivity" }
+composeMaterial = { module = "androidx.compose.material:material", version.ref = "compose" }
+composeTooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" }
+accompanistSysUi = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanist" }
+accompanistInsets = { module = "com.google.accompanist:accompanist-insets", version.ref = "accompanist" }
agp = { module = "com.android.tools.build:gradle", version.ref = "agp" }
kotlinGradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
ktlintGradlePlugin = { module = "org.jlleitschuh.gradle:ktlint-gradle", version.ref = "ktlint" }
diff --git a/what-the-stack/build.gradle b/what-the-stack/build.gradle
index 564ebb8..a9122bc 100644
--- a/what-the-stack/build.gradle
+++ b/what-the-stack/build.gradle
@@ -36,7 +36,11 @@ android {
}
buildFeatures {
- viewBinding true
+ compose true
+ }
+
+ composeOptions {
+ kotlinCompilerExtensionVersion libs.versions.compose.get()
}
}
@@ -46,11 +50,13 @@ dependencies {
implementation libs.kotlinStdLib
implementation libs.appCompat
implementation libs.coreKtx
- implementation libs.fragmentKtx
- implementation libs.constraintLayout
- implementation libs.materialComponents
implementation libs.startup
- implementation libs.insetter
+
+ implementation libs.composeActivity
+ implementation libs.composeMaterial
+ implementation libs.composeTooling
+ implementation libs.accompanistSysUi
+ implementation libs.accompanistInsets
testImplementation libs.junit
testImplementation libs.mockk
diff --git a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ExceptionProcessor.kt b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ExceptionProcessor.kt
index d02d8bb..2d564a5 100644
--- a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ExceptionProcessor.kt
+++ b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ExceptionProcessor.kt
@@ -1,7 +1,7 @@
package com.haroldadmin.whatthestack
import android.os.Parcelable
-import kotlinx.android.parcel.Parcelize
+import kotlinx.parcelize.Parcelize
/**
* Represents the data of the exception to be displayed to the user.
diff --git a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackActivity.kt b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackActivity.kt
index 54c46c6..108afee 100644
--- a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackActivity.kt
+++ b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackActivity.kt
@@ -1,18 +1,14 @@
package com.haroldadmin.whatthestack
-import android.content.ClipData
-import android.content.ClipboardManager
-import android.content.Context
-import android.content.Intent
-import android.net.Uri
import android.os.Bundle
-import android.text.method.ScrollingMovementMethod
+import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
-import com.google.android.material.snackbar.Snackbar
-import com.haroldadmin.whatthestack.databinding.ActivityWhatTheStackBinding
-import dev.chrisbanes.insetter.Insetter
-import dev.chrisbanes.insetter.windowInsetTypesOf
+import com.google.accompanist.insets.ProvideWindowInsets
+import com.google.accompanist.systemuicontroller.rememberSystemUiController
+import com.haroldadmin.whatthestack.ui.pages.ExceptionPage
+import com.haroldadmin.whatthestack.ui.theme.SystemBarsColor
+import com.haroldadmin.whatthestack.ui.theme.WhatTheStackTheme
/**
* An Activity which displays various pieces of information regarding the exception which
@@ -20,90 +16,27 @@ import dev.chrisbanes.insetter.windowInsetTypesOf
*/
class WhatTheStackActivity : AppCompatActivity() {
- private lateinit var binding: ActivityWhatTheStackBinding
-
- private val clipboardManager: ClipboardManager by lazy {
- getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
- }
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- binding = ActivityWhatTheStackBinding.inflate(layoutInflater)
- setContentView(binding.root)
-
WindowCompat.setDecorFitsSystemWindows(window, false)
- Insetter.builder()
- .padding(windowInsetTypesOf(statusBars = true, navigationBars = true))
- .applyToView(binding.root)
-
- val type = intent.getStringExtra(KEY_EXCEPTION_TYPE)
- val cause = intent.getStringExtra(KEY_EXCEPTION_CAUSE)
- val message = intent.getStringExtra(KEY_EXCEPTION_MESSAGE)
- val stackTrace = intent.getStringExtra(KEY_EXCEPTION_STACKTRACE)
-
- binding.stacktrace.apply {
- text = stackTrace
- setHorizontallyScrolling(true)
- movementMethod = ScrollingMovementMethod()
- }
-
- binding.exceptionName.apply {
- text = getString(R.string.exception_name, type)
- }
-
- binding.exceptionCause.apply {
- text = getString(R.string.exception_cause, cause)
- }
-
- binding.exceptionMessage.apply {
- text = getString(R.string.exception_message, message)
- }
-
- binding.copyStacktrace.apply {
- setOnClickListener {
- val clipping = ClipData.newPlainText("stacktrace", stackTrace)
- clipboardManager.setPrimaryClip(clipping)
- snackbar { R.string.copied_message }
- }
- }
-
- binding.shareStacktrace.apply {
- setOnClickListener {
- val sendIntent: Intent = Intent().apply {
- this.action = Intent.ACTION_SEND
- this.putExtra(Intent.EXTRA_TEXT, stackTrace)
- this.type = "text/plain"
+ val type = intent.getStringExtra(KEY_EXCEPTION_TYPE) ?: ""
+ val message = intent.getStringExtra(KEY_EXCEPTION_MESSAGE) ?: ""
+ val stackTrace = intent.getStringExtra(KEY_EXCEPTION_STACKTRACE) ?: ""
+
+ setContent {
+ val sysUiController = rememberSystemUiController()
+ sysUiController.setSystemBarsColor(SystemBarsColor)
+
+ WhatTheStackTheme {
+ ProvideWindowInsets {
+ ExceptionPage(
+ type = type,
+ message = message,
+ stackTrace = stackTrace
+ )
}
-
- val shareIntent = Intent.createChooser(sendIntent, null)
- startActivity(shareIntent)
- }
- }
-
- binding.launchApplication.apply {
- setOnClickListener {
- context.packageManager.getLaunchIntentForPackage(applicationContext.packageName)
- ?.let {
- it.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
- startActivity(it)
- }
}
}
-
- binding.searchStackoverflow.apply {
- setOnClickListener {
- val searchQuery = "$cause: $message"
- val searchIntent: Intent = Intent().apply {
- this.action = Intent.ACTION_VIEW
- this.data = Uri.parse(generateStackoverflowSearchUrl(searchQuery))
- }
- startActivity(searchIntent)
- }
- }
- }
-
- private inline fun snackbar(messageProvider: () -> Int) {
- Snackbar.make(binding.nestedScrollRoot, messageProvider(), Snackbar.LENGTH_SHORT).show()
}
}
diff --git a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackConnection.kt b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackConnection.kt
index 16df4d5..3e1f45b 100644
--- a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackConnection.kt
+++ b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackConnection.kt
@@ -5,7 +5,7 @@ import android.content.ServiceConnection
import android.os.IBinder
internal class WhatTheStackConnection(
- private val onDisconnected: () -> Unit = { Unit },
+ private val onDisconnected: () -> Unit = { },
private val onConnected: (IBinder) -> Unit
) : ServiceConnection {
diff --git a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackExceptionHandler.kt b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackExceptionHandler.kt
index d581270..98de608 100644
--- a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackExceptionHandler.kt
+++ b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackExceptionHandler.kt
@@ -19,14 +19,16 @@ internal class WhatTheStackExceptionHandler(
val exceptionData = e.process()
- service.send(Message().apply {
- data = bundleOf(
- KEY_EXCEPTION_TYPE to exceptionData.type,
- KEY_EXCEPTION_CAUSE to exceptionData.cause,
- KEY_EXCEPTION_MESSAGE to exceptionData.message,
- KEY_EXCEPTION_STACKTRACE to exceptionData.stacktrace
- )
- })
+ service.send(
+ Message().apply {
+ data = bundleOf(
+ KEY_EXCEPTION_TYPE to exceptionData.type,
+ KEY_EXCEPTION_CAUSE to exceptionData.cause,
+ KEY_EXCEPTION_MESSAGE to exceptionData.message,
+ KEY_EXCEPTION_STACKTRACE to exceptionData.stacktrace
+ )
+ }
+ )
defaultHandler?.uncaughtException(t, e) ?: Process.killProcess(Process.myPid())
}
diff --git a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackService.kt b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackService.kt
index 19a8cd5..e93833b 100644
--- a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackService.kt
+++ b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/WhatTheStackService.kt
@@ -3,10 +3,7 @@ package com.haroldadmin.whatthestack
import android.app.Service
import android.content.Context
import android.content.Intent
-import android.os.Handler
-import android.os.IBinder
-import android.os.Message
-import android.os.Messenger
+import android.os.*
/**
* A Bound Service which runs in a separate process than the application.
@@ -28,7 +25,7 @@ class WhatTheStackService : Service() {
internal class WhatTheStackHandler(
private val applicationContext: Context
- ) : Handler() {
+ ) : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
val type = msg.data.getString(KEY_EXCEPTION_TYPE)
val cause = msg.data.getString(KEY_EXCEPTION_CAUSE)
diff --git a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/components/OutlinedIconButton.kt b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/components/OutlinedIconButton.kt
new file mode 100644
index 0000000..6d47bcc
--- /dev/null
+++ b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/components/OutlinedIconButton.kt
@@ -0,0 +1,38 @@
+package com.haroldadmin.whatthestack.ui.components
+
+import androidx.annotation.DrawableRes
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material.*
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.TextAlign
+
+@Composable
+internal fun OutlinedIconButton(
+ text: String,
+ @DrawableRes iconId: Int,
+ onClick: () -> Unit,
+ contentDescription: String,
+ modifier: Modifier = Modifier,
+) {
+ OutlinedButton(
+ onClick = onClick,
+ modifier = modifier.fillMaxWidth(),
+ colors = ButtonDefaults.outlinedButtonColors(
+ backgroundColor = MaterialTheme.colors.background,
+ contentColor = MaterialTheme.colors.onBackground,
+ disabledContentColor = MaterialTheme.colors.onBackground.copy(alpha = 0.5f)
+ ),
+ ) {
+ Icon(
+ painter = painterResource(id = iconId),
+ contentDescription = contentDescription
+ )
+ Text(
+ text = text,
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center,
+ )
+ }
+}
diff --git a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/components/OverlineLabel.kt b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/components/OverlineLabel.kt
new file mode 100644
index 0000000..3017f02
--- /dev/null
+++ b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/components/OverlineLabel.kt
@@ -0,0 +1,20 @@
+package com.haroldadmin.whatthestack.ui.components
+
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+
+/**
+ * A text label with the "overline" typography style
+ */
+@Composable
+internal fun OverlineLabel(label: String, modifier: Modifier = Modifier) {
+ Text(
+ text = label,
+ style = MaterialTheme.typography.overline,
+ color = if (isSystemInDarkTheme()) MaterialTheme.colors.primary else MaterialTheme.colors.primaryVariant,
+ modifier = modifier
+ )
+}
diff --git a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/pages/ExceptionPage.kt b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/pages/ExceptionPage.kt
new file mode 100644
index 0000000..da2efaa
--- /dev/null
+++ b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/pages/ExceptionPage.kt
@@ -0,0 +1,229 @@
+package com.haroldadmin.whatthestack.ui.pages
+
+import android.content.Intent
+import android.content.res.Configuration.UI_MODE_NIGHT_YES
+import android.net.Uri
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.text.selection.SelectionContainer
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalClipboardManager
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.google.accompanist.insets.navigationBarsHeight
+import com.google.accompanist.insets.statusBarsHeight
+import com.haroldadmin.whatthestack.R
+import com.haroldadmin.whatthestack.generateStackoverflowSearchUrl
+import com.haroldadmin.whatthestack.ui.components.OutlinedIconButton
+import com.haroldadmin.whatthestack.ui.components.OverlineLabel
+import com.haroldadmin.whatthestack.ui.preview.SampleData
+import com.haroldadmin.whatthestack.ui.theme.WhatTheStackTheme
+import kotlinx.coroutines.launch
+
+@Composable
+fun ExceptionPage(
+ type: String,
+ message: String,
+ stackTrace: String
+) {
+ val clipboard = LocalClipboardManager.current
+ val context = LocalContext.current
+ val scaffoldState = rememberScaffoldState()
+ val coroutineScope = rememberCoroutineScope()
+
+ val snackbarMessage = stringResource(id = R.string.copied_message)
+
+ Scaffold(scaffoldState = scaffoldState) {
+ Column(
+ modifier = Modifier
+ .padding(horizontal = 8.dp)
+ .verticalScroll(rememberScrollState())
+ ) {
+ Spacer(modifier = Modifier.statusBarsHeight(additional = 8.dp))
+ PageHeader()
+ ExceptionDetails(
+ type = type,
+ message = message,
+ modifier = Modifier.padding(vertical = 8.dp)
+ )
+ ExceptionOptions(
+ onCopy = {
+ coroutineScope.launch {
+ clipboard.setText(AnnotatedString(stackTrace))
+ scaffoldState.snackbarHostState.showSnackbar(snackbarMessage)
+ }
+ },
+ onShare = {
+ val sendIntent: Intent = Intent().apply {
+ this.action = Intent.ACTION_SEND
+ this.putExtra(Intent.EXTRA_TEXT, stackTrace)
+ this.type = "text/plain"
+ }
+
+ val shareIntent = Intent.createChooser(sendIntent, "Stacktrace")
+ context.startActivity(shareIntent)
+ },
+ onSearch = {
+ val searchQuery = "$type: $message"
+ val url = generateStackoverflowSearchUrl(searchQuery)
+ val searchIntent = Intent().apply {
+ action = Intent.ACTION_VIEW
+ data = Uri.parse(url)
+ }
+ context.startActivity(searchIntent)
+ },
+ onRestart = {
+ val applicationContext = context.applicationContext
+ val packageManager = applicationContext.packageManager
+ val packageName = applicationContext.packageName
+
+ val launchIntent = packageManager.getLaunchIntentForPackage(packageName)
+ if (launchIntent != null) {
+ launchIntent.flags =
+ Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+ context.startActivity(launchIntent)
+ }
+ }
+ )
+ Stacktrace(
+ stackTrace = stackTrace,
+ modifier = Modifier.padding(top = 8.dp)
+ )
+ Spacer(modifier = Modifier.navigationBarsHeight(additional = 8.dp))
+ }
+ }
+}
+
+@Composable
+fun PageHeader() {
+ Text(
+ stringResource(id = R.string.header_text),
+ style = MaterialTheme.typography.h4,
+ modifier = Modifier.padding(vertical = 4.dp),
+ color = MaterialTheme.colors.onBackground
+ )
+ Spacer(modifier = Modifier.height(4.dp))
+ Text(
+ text = stringResource(id = R.string.explanation_text),
+ color = MaterialTheme.colors.onBackground
+ )
+}
+
+@Composable
+fun ExceptionDetails(type: String, message: String, modifier: Modifier) {
+ Column(modifier = modifier) {
+ OverlineLabel(label = stringResource(id = R.string.exception_name))
+ Text(
+ text = type,
+ fontFamily = FontFamily.Monospace,
+ color = MaterialTheme.colors.onBackground
+ )
+ Spacer(modifier = Modifier.height(8.dp))
+ OverlineLabel(label = stringResource(id = R.string.exception_message))
+ Text(
+ text = message,
+ fontFamily = FontFamily.Monospace,
+ color = MaterialTheme.colors.onBackground
+ )
+ }
+}
+
+@Composable
+fun ExceptionOptions(
+ onCopy: () -> Unit,
+ onShare: () -> Unit,
+ onRestart: () -> Unit,
+ onSearch: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ Column(modifier = modifier) {
+ OutlinedIconButton(
+ text = stringResource(id = R.string.copy_stacktrace),
+ iconId = R.drawable.ic_outline_content_copy_24,
+ onClick = onCopy,
+ contentDescription = "Copy",
+ modifier = Modifier.padding(vertical = 4.dp),
+ )
+ OutlinedIconButton(
+ text = stringResource(id = R.string.share_stacktrace),
+ iconId = R.drawable.ic_outline_share_24,
+ onClick = onShare,
+ contentDescription = "Share",
+ modifier = Modifier.padding(vertical = 4.dp)
+ )
+ OutlinedIconButton(
+ text = stringResource(id = R.string.search_stackoverflow),
+ iconId = R.drawable.ic_round_search_24,
+ onClick = onSearch,
+ contentDescription = "Search Stackoverflow",
+ modifier = Modifier.padding(vertical = 4.dp)
+ )
+ OutlinedIconButton(
+ text = stringResource(id = R.string.restart_application),
+ iconId = R.drawable.ic_baseline_refresh_24,
+ onClick = onRestart,
+ contentDescription = "Restart"
+ )
+ }
+}
+
+@Composable
+fun Stacktrace(stackTrace: String, modifier: Modifier) {
+ Column(modifier) {
+ OverlineLabel(label = stringResource(id = R.string.stacktrace))
+ Surface(
+ color = Color.LightGray.copy(alpha = 0.5f),
+ modifier = Modifier.padding(top = 4.dp)
+ ) {
+ SelectionContainer {
+ Text(
+ text = stackTrace,
+ style = MaterialTheme.typography.body2.copy(fontSize = 12.sp),
+ fontFamily = FontFamily.Monospace,
+ color = MaterialTheme.colors.onBackground,
+ modifier = Modifier
+ .padding(4.dp)
+ .horizontalScroll(rememberScrollState())
+ )
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+fun ExceptionPagePreview() {
+ WhatTheStackTheme {
+ ExceptionPage(
+ type = SampleData.ExceptionType,
+ message = SampleData.ExceptionMessage,
+ stackTrace = SampleData.Stacktrace
+ )
+ }
+}
+
+@Preview(uiMode = UI_MODE_NIGHT_YES)
+@Composable
+fun ExceptionPagePreviewNightMode() {
+ WhatTheStackTheme {
+ ExceptionPage(
+ type = SampleData.ExceptionType,
+ message = SampleData.ExceptionMessage,
+ stackTrace = SampleData.Stacktrace
+ )
+ }
+}
diff --git a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/preview/SampleData.kt b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/preview/SampleData.kt
new file mode 100644
index 0000000..a16438f
--- /dev/null
+++ b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/preview/SampleData.kt
@@ -0,0 +1,32 @@
+package com.haroldadmin.whatthestack.ui.preview
+
+object SampleData {
+ const val ExceptionType = "Runtime Exception"
+
+ const val ExceptionMessage = "This exception was thrown purely because it can be thrown"
+
+ const val Stacktrace =
+ """java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
+ at com.android.internal.os.RuntimeInitMethodAndArgsCaller.run(RuntimeInit.java:558)
+ at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
+Caused by: java.lang.reflect.InvocationTargetException
+ at java.lang.reflect.Method.invoke(Native Method)
+ at com.android.internal.os.RuntimeInitMethodAndArgsCaller.run(RuntimeInit.java:548)
+ ... 1 more
+Caused by: com.haroldadmin.crashyapp.BecauseICanException: This exception is thrown purely because it can be thrown
+ at com.haroldadmin.crashyapp.MainActivity.onCreatelambda-0(MainActivity.kt:15)
+ at com.haroldadmin.crashyapp.MainActivity.r8lambdapFZVHP1EeT4E2LW7TLA5yGBRTTk(Unknown Source:0)
+ at com.haroldadmin.crashyapp.MainActivityxternalSyntheticLambda0.onClick(Unknown Source:0)
+ at android.view.View.performClick(View.java:7441)
+ at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1119)
+ at android.view.View.performClickInternal(View.java:7418)
+ at android.view.View.access$3700(View.java:835)
+ at android.view.ViewPerformClick.run(View.java:28676)
+ at android.os.Handler.handleCallback(Handler.java:938)
+ at android.os.Handler.dispatchMessage(Handler.java:99)
+ at android.os.Looper.loopOnce(Looper.java:201)
+ at android.os.Looper.loop(Looper.java:288)
+ at android.app.ActivityThread.main(ActivityThread.java:7839)
+ ... 3 more
+"""
+}
diff --git a/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/theme/WhatTheStackTheme.kt b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/theme/WhatTheStackTheme.kt
new file mode 100644
index 0000000..120e11f
--- /dev/null
+++ b/what-the-stack/src/main/java/com/haroldadmin/whatthestack/ui/theme/WhatTheStackTheme.kt
@@ -0,0 +1,38 @@
+package com.haroldadmin.whatthestack.ui.theme
+
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.darkColors
+import androidx.compose.material.lightColors
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
+
+private val DarkColorPalette = darkColors(
+ primary = Color(0xffd32f2f),
+ primaryVariant = Color(0xffff6659),
+ secondary = Color(0xff616161),
+ secondaryVariant = Color(0xff373737),
+)
+
+private val LightColorPalette = lightColors(
+ primary = Color(0xffd32f2f),
+ primaryVariant = Color(0xff9a0007),
+ secondary = Color(0xff616161),
+ secondaryVariant = Color(0x33373737),
+)
+
+internal val SystemBarsColor = Color(0x33373737)
+
+@Composable
+fun WhatTheStackTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ content: @Composable () -> Unit
+) {
+ val colors = if (darkTheme) {
+ DarkColorPalette
+ } else {
+ LightColorPalette
+ }
+
+ MaterialTheme(colors = colors, content = content)
+}
diff --git a/what-the-stack/src/main/res/drawable/ic_baseline_refresh_24.xml b/what-the-stack/src/main/res/drawable/ic_baseline_refresh_24.xml
index f2be45b..5ab492c 100644
--- a/what-the-stack/src/main/res/drawable/ic_baseline_refresh_24.xml
+++ b/what-the-stack/src/main/res/drawable/ic_baseline_refresh_24.xml
@@ -2,8 +2,7 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
- android:viewportHeight="24"
- android:tint="?attr/colorControlNormal">
+ android:viewportHeight="24">
diff --git a/what-the-stack/src/main/res/drawable/ic_outline_content_copy_24.xml b/what-the-stack/src/main/res/drawable/ic_outline_content_copy_24.xml
index 79c5a76..eb384e5 100644
--- a/what-the-stack/src/main/res/drawable/ic_outline_content_copy_24.xml
+++ b/what-the-stack/src/main/res/drawable/ic_outline_content_copy_24.xml
@@ -2,8 +2,7 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
- android:viewportHeight="24"
- android:tint="?attr/colorOnPrimary">
+ android:viewportHeight="24">
diff --git a/what-the-stack/src/main/res/drawable/ic_outline_share_24.xml b/what-the-stack/src/main/res/drawable/ic_outline_share_24.xml
index 3a6a059..394e3ef 100644
--- a/what-the-stack/src/main/res/drawable/ic_outline_share_24.xml
+++ b/what-the-stack/src/main/res/drawable/ic_outline_share_24.xml
@@ -2,8 +2,7 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
- android:viewportHeight="24"
- android:tint="?attr/colorControlNormal">
+ android:viewportHeight="24">
diff --git a/what-the-stack/src/main/res/drawable/ic_round_search_24.xml b/what-the-stack/src/main/res/drawable/ic_round_search_24.xml
index c1818d5..b86e6fc 100644
--- a/what-the-stack/src/main/res/drawable/ic_round_search_24.xml
+++ b/what-the-stack/src/main/res/drawable/ic_round_search_24.xml
@@ -2,8 +2,7 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
- android:viewportHeight="24"
- android:tint="?attr/colorControlNormal">
+ android:viewportHeight="24">
diff --git a/what-the-stack/src/main/res/layout/activity_what_the_stack.xml b/what-the-stack/src/main/res/layout/activity_what_the_stack.xml
deleted file mode 100644
index 7e85fa7..0000000
--- a/what-the-stack/src/main/res/layout/activity_what_the_stack.xml
+++ /dev/null
@@ -1,183 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/what-the-stack/src/main/res/values/colors.xml b/what-the-stack/src/main/res/values/colors.xml
deleted file mode 100644
index 9f135e1..0000000
--- a/what-the-stack/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
- #d32f2f
- #ff6659
- #9a0007
- #616161
- #8e8e8e
- #373737
- #33373737
- #ffffff
- #ffffff
-
\ No newline at end of file
diff --git a/what-the-stack/src/main/res/values/strings.xml b/what-the-stack/src/main/res/values/strings.xml
index 2fecf52..54394d8 100644
--- a/what-the-stack/src/main/res/values/strings.xml
+++ b/what-the-stack/src/main/res/values/strings.xml
@@ -26,14 +26,13 @@
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
- Exception: %1$s
- Cause: %1$s
+ Exception
Stacktrace
- Message: %1$s
- Copy
- Share
- Restart
+ Message
+ Copy Stacktrace
+ Share Stacktrace
+ Restart Application
Relaunch App
Stacktrace copied!
- Stackoverflow
+ Search Stackoverflow
\ No newline at end of file
diff --git a/what-the-stack/src/main/res/values/styles.xml b/what-the-stack/src/main/res/values/styles.xml
index 98b8f55..0870988 100644
--- a/what-the-stack/src/main/res/values/styles.xml
+++ b/what-the-stack/src/main/res/values/styles.xml
@@ -1,14 +1,4 @@
-
+
\ No newline at end of file
diff --git a/what-the-stack/src/test/java/com/haroldadmin/whatthestack/ExceptionProcessingTest.kt b/what-the-stack/src/test/java/com/haroldadmin/whatthestack/ExceptionProcessingTest.kt
index a9a116c..17364ce 100644
--- a/what-the-stack/src/test/java/com/haroldadmin/whatthestack/ExceptionProcessingTest.kt
+++ b/what-the-stack/src/test/java/com/haroldadmin/whatthestack/ExceptionProcessingTest.kt
@@ -1,7 +1,7 @@
package com.haroldadmin.whatthestack
-import java.io.PrintStream
import org.junit.Test
+import java.io.PrintStream
internal class ExceptionProcessingTest {