diff --git a/.github/config/configuration.json b/.github/config/configuration.json
new file mode 100644
index 0000000..be6002f
--- /dev/null
+++ b/.github/config/configuration.json
@@ -0,0 +1,54 @@
+{
+ "categories": [
+ {
+ "title": "## ๐ Features",
+ "labels": [
+ "feat"
+ ]
+ },
+ {
+ "title": "## ๐ Fixes",
+ "labels": [
+ "fix"
+ ]
+ },
+ {
+ "title": "๐งฐ Maintenance",
+ "labels": [
+ "chore"
+ ]
+ },
+ {
+ "title": "## ๐งช Tests",
+ "labels": [
+ "test"
+ ]
+ },
+ {
+ "title": "## ๐๏ธ Documentation",
+ "labels": [
+ "doc"
+ ]
+ },
+ {
+ "title": "## ๐ฆ Dependencies",
+ "labels": [
+ "dependencies"
+ ]
+ }
+ ],
+ "sort": "ASC",
+ "template": "${{CHANGELOG}}\n\n\nUncategorized
\n\n${{UNCATEGORIZED}}\n ",
+ "pr_template": "${{TITLE}}",
+ "empty_template": "- no changes",
+ "label_extractor": [
+ {
+ "pattern": "(.+): (.+)",
+ "target": "$1"
+ }
+ ],
+ "exclude_merge_branches": [
+ "merge pull request",
+ "Merge pull request"
+ ]
+}
\ No newline at end of file
diff --git a/.github/config/labels.yml b/.github/config/labels.yml
new file mode 100644
index 0000000..24a6530
--- /dev/null
+++ b/.github/config/labels.yml
@@ -0,0 +1,51 @@
+- name: bug
+ description: Something isn't working
+ color: d73a4a
+- name: doc
+ description: Improvements to documentation
+ color: d4c5f9
+- name: duplicate
+ description: This issue or pull request already exists
+ color: cfd3d7
+- name: feature
+ color: 1d76db
+ description: New features
+- name: enhancement
+ description: Enhancement of existing functionality
+ color: 84b6eb
+- name: deprecated
+ description: Deprecating API
+ color: f4c21d
+- name: removed
+ description: Removing API
+ color: e4b21d
+- name: tests
+ description: Enhancement of tests
+ color: 0e8a16
+- name: java
+ description: Java/JDK changes
+ color: 03d0d6
+- name: gradle
+ description: Gradle changes
+ color: d0d603
+- name: maven
+ description: Maven changes
+ color: d60366
+- name: compose
+ description: Jetbrains Compose issues
+ color: 3cdc84
+- name: help
+ description: Help Wanted
+ color: 0e8a16
+- name: question
+ description: Questions and discussions
+ color: cc317c
+- name: dependencies
+ description: Changes that affect dependencies
+ color: 5319e7
+- name: docker
+ description: Container changes
+ color: e99695
+- name: github-actions
+ description: Github action changes
+ color: ff7619
\ No newline at end of file
diff --git a/.github/config/release-drafter.yml b/.github/config/release-drafter.yml
new file mode 100644
index 0000000..3e13770
--- /dev/null
+++ b/.github/config/release-drafter.yml
@@ -0,0 +1,39 @@
+name-template: 'v$RESOLVED_VERSION ๐'
+tag-template: 'v$RESOLVED_VERSION'
+categories:
+ - title: '๐ Features'
+ labels:
+ - 'feat'
+ - 'feature'
+ - 'enhancement'
+ - title: '๐ Bug Fixes'
+ labels:
+ - 'fix'
+ - 'bugfix'
+ - 'bug'
+ - title: '๐งฐ Maintenance'
+ label: 'chore'
+ - title: "๐ Documentation"
+ labels:
+ - 'doc'
+ - 'documentation'
+ - title: "๐งช Tests"
+ labels:
+ - 'test'
+change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
+change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
+version-resolver:
+ major:
+ labels:
+ - 'major'
+ minor:
+ labels:
+ - 'minor'
+ patch:
+ labels:
+ - 'patch'
+ default: patch
+template: |
+ ## Changes
+
+ $CHANGES
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..9d5241e
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,139 @@
+name: Logvue Build
+
+env:
+ GITHUB_DEPLOY: 'false'
+
+on:
+
+ pull_request:
+ branches:
+ - main
+
+ workflow_dispatch:
+ repository_dispatch:
+ types: [ app-release ]
+
+defaults:
+ run:
+ shell: bash
+
+jobs:
+ build:
+ name: Build Package
+ timeout-minutes: 15
+ continue-on-error: false
+ # if: github.event_name == 'pull_request'
+
+ runs-on: ${{ matrix.os }}
+ environment: Production
+ env:
+ SENTRY_ENDPOINT: ${{ secrets.SENTRY_ENDPOINT }}
+ SENTRY_DEBUG: ${{ secrets.SENTRY_DEBUG }}
+ strategy:
+ fail-fast: true
+ matrix:
+ os: [ arm64, ubuntu-latest, macos-latest, windows-latest ]
+ jdk: [ 18 ]
+
+ steps:
+ - name: Check out the source code
+ uses: actions/checkout@v2
+
+ - name: Download ${{ matrix.os }} OpenJDK ${{ matrix.jdk }}
+ id: download-jdk
+ uses: sormuras/download-jdk@v1
+ with:
+ feature: ${{ matrix.jdk }}
+
+ - name: Set up OpenJDK ${{ matrix.jdk }}
+ id: setup-java
+ uses: actions/setup-java@v2
+ if: always() && steps.download-jdk.outcome == 'success'
+ with:
+ distribution: jdkfile
+ java-version: ${{ env.JDK_VERSION }}
+ jdkFile: ${{ env.JDK_FILE }}
+
+ - name: Cache Gradle dependencies
+ uses: actions/cache@v2
+ with:
+ path: |
+ ~/.gradle/caches
+ ~/.gradle/wrapper
+ key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
+ restore-keys: |
+ ${{ runner.os }}-gradle-
+
+ - name: Deploy to GitHub Packages (Linux)
+ id: gradle-deploy
+ if: env.GITHUB_DEPLOY == 'true' && runner.os == 'Linux'
+ run: |
+ ./gradlew deploy
+ env:
+ GITHUB_USER: ${{ github.repository_owner }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Gradle Build
+ id: gradle-build
+ run: ./gradlew packageUberJarForCurrentOS package -DSENTRY_ENDPOINT=env.SENTRY_ENDPOINT -DSENTRY_DEBUG=env.SENTRY_DEBUG
+
+ - name: Uploading ${{ matrix.os }} uber jar
+ if: steps.gradle-build.outcome == 'success'
+ uses: actions/upload-artifact@v2
+ with:
+ name: ${{ steps.gradle-build.outputs.uber_jar_name }}
+ path: |
+ ${{ steps.gradle-build.outputs.uber_jar_path }}
+ if-no-files-found: error
+
+ - name: Uploading ${{ matrix.os }} native package
+ if: steps.gradle-build.outcome == 'success'
+ uses: actions/upload-artifact@v2
+ with:
+ name: ${{ steps.gradle-build.outputs.app_pkg_name }}
+ path: |
+ ${{ steps.gradle-build.outputs.app_pkg_path }}
+ if-no-files-found: error
+
+
+ release:
+ name: Release new version.
+ needs: [ build ]
+ if: startsWith(github.ref, 'refs/tags/')
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Check out the source code
+ uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+
+ - uses: ffurrer2/extract-release-notes@v1.10.0
+ id: extract_release_notes
+ if: ${{ false }}
+
+ - name: Build Changelog
+ id: github_release
+ uses: mikepenz/release-changelog-builder-action@v2
+ with:
+ configuration: ".github/config/configuration.json"
+ commitMode: true
+ ignorePreReleases: ${{ !contains(github.ref, '-') }}
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Download all the build artifacts
+ uses: actions/download-artifact@v2
+ with:
+ path: release-artifacts
+
+ - name: Github Release
+ uses: softprops/action-gh-release@v1
+ with:
+ body: ${{ steps.github_release.outputs.changelog }}
+ prerelease: ${{ contains(github.event.inputs.version, '-rc') || contains(github.event.inputs.version, '-b') || contains(github.event.inputs.version, '-a') }}
+ files: |
+ ${{ github.workspace }}/release-artifacts/**
+ fail_on_unmatched_files: true
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.github/workflows/gradle-wrapper.yml b/.github/workflows/gradle-wrapper.yml
new file mode 100644
index 0000000..ed37f78
--- /dev/null
+++ b/.github/workflows/gradle-wrapper.yml
@@ -0,0 +1,10 @@
+name: "Validate Gradle Wrapper"
+on: [ push, pull_request ]
+
+jobs:
+ validation:
+ name: "Validation"
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: gradle/wrapper-validation-action@v1
\ No newline at end of file
diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml
new file mode 100644
index 0000000..607a46d
--- /dev/null
+++ b/.github/workflows/release-drafter.yml
@@ -0,0 +1,19 @@
+name: Release Drafter
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ types: [ opened, reopened, synchronize ]
+
+jobs:
+ update_release_draft:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Drafts next Release notes
+ uses: release-drafter/release-drafter@v5
+ with:
+ config-name: config/release-drafter.yml
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml
new file mode 100644
index 0000000..6533746
--- /dev/null
+++ b/.github/workflows/sync-labels.yml
@@ -0,0 +1,19 @@
+name: Sync labels
+
+on:
+ push:
+ branches:
+ - main
+ paths:
+ - .github/config/labels.yml
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - uses: micnncim/action-label-syncer@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ manifest: .github/config/labels.yml
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 5dd598b..da33323 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -5,10 +5,11 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.6.10"
id("org.jetbrains.compose") version "1.0.1"
+ id("com.github.gmazzo.buildconfig") version "3.0.3"
}
-group = "com.gi"
-version = "1.0"
+group = "com.voxfinite"
+version = "1.0.0"
repositories {
google()
@@ -28,14 +29,14 @@ dependencies {
// embedded database
implementation("org.mapdb:mapdb:3.0.8")
implementation("org.snakeyaml:snakeyaml-engine:2.3")
- // https://mvnrepository.com/artifact/io.netty/netty-resolver-dns-native-macos
- runtimeOnly("io.netty:netty-resolver-dns-native-macos:4.1.72.Final") // not sure if needed now
+// runtimeOnly("io.netty:netty-resolver-dns-native-macos:4.1.72.Final") // not sure if needed now
implementation("com.android.tools.ddms:ddmlib:30.2.0-alpha06")
implementation("com.google.code.gson:gson:2.8.9")
// https://mvnrepository.com/artifact/com.googlecode.cqengine/cqengine
implementation("com.googlecode.cqengine:cqengine:3.6.0")
implementation("org.jetbrains.kotlin:kotlin-reflect:1.6.10")
- implementation("com.halilibo.compose-richtext:richtext-ui-material:0.10.0")
+
+ implementation("io.sentry:sentry-log4j2:5.5.2")
}
tasks.test {
@@ -55,8 +56,57 @@ compose.desktop {
mainClass = "MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
- packageName = "logvue"
- packageVersion = "1.0.0"
+ packageName = project.name
+ packageVersion = "${project.version}"
+ description = "Local Analytics"
+ linux {
+ debMaintainer = "kapoor.aman22@gmail.com"
+ iconFile.set(project.file("logo_icon.png"))
+ }
+ macOS {
+ bundleID = "${project.group}.${project.name}"
+ iconFile.set(project.file("logo_icon.icns"))
+ }
+ windows {
+ upgradeUuid = "8AEBC8BF-9C94-4D02-ACA8-AF543E0CEB98"
+ iconFile.set(project.file("logo_icon.ico"))
+ }
}
}
}
+
+buildConfig {
+ className("AppBuildConfig")
+ useKotlinOutput { topLevelConstants = true }
+ buildConfigField("String", "APP_NAME", "\"${project.name}\"")
+ buildConfigField("String", "APP_VERSION", "\"${project.version}\"")
+}
+
+/**
+ * Sets the Github Action output as package name and path to use in other steps.
+ */
+gradle.buildFinished {
+ val pkgFormat =
+ compose.desktop.application.nativeDistributions.targetFormats.firstOrNull { it.isCompatibleWithCurrentOS }
+ val nativePkg = buildDir.resolve("compose/binaries").findPkg(pkgFormat?.fileExt)
+ val jarPkg = buildDir.resolve("compose/jars").findPkg(".jar")
+ nativePkg.ghActionOutput("app_pkg")
+ jarPkg.ghActionOutput("uber_jar")
+}
+
+fun File.findPkg(format: String?) = when (format != null) {
+ true -> walk().firstOrNull { it.isFile && it.name.endsWith(format, ignoreCase = true) }
+ else -> null
+}
+
+fun File?.ghActionOutput(prefix: String) = this?.let {
+ when (System.getenv("GITHUB_ACTIONS").toBoolean()) {
+ true -> println(
+ """
+ ::set-output name=${prefix}_name::${it.name}
+ ::set-output name=${prefix}_path::${it.absolutePath}
+ """.trimIndent()
+ )
+ else -> println("$prefix: $this")
+ }
+}
diff --git a/logo_icon.icns b/logo_icon.icns
new file mode 100644
index 0000000..db8a1d2
Binary files /dev/null and b/logo_icon.icns differ
diff --git a/logo_icon.ico b/logo_icon.ico
new file mode 100644
index 0000000..f5acaf6
Binary files /dev/null and b/logo_icon.ico differ
diff --git a/logo_icon.png b/logo_icon.png
new file mode 100644
index 0000000..eb4e226
Binary files /dev/null and b/logo_icon.png differ
diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt
index e2644c9..7030ee7 100644
--- a/src/main/kotlin/Main.kt
+++ b/src/main/kotlin/Main.kt
@@ -21,13 +21,13 @@ import storage.Db
import ui.AppTheme
import ui.CustomTheme
import ui.components.BodyPanel
-import ui.components.IntroDialog
import ui.components.SideNavigation
-import utils.APP_NAME
-import utils.AppLog
-import utils.Helpers
+import ui.components.dialogs.CrashDialog
+import ui.components.dialogs.IntroDialog
+import utils.*
import java.awt.Desktop
+
@Composable
@Preview
fun App() {
@@ -49,20 +49,32 @@ fun App() {
AdbHelper.init()
}
LaunchIntroIfNeeded()
+ LaunchCrashDialogIfNeeded()
}
}
@Composable
fun LaunchIntroIfNeeded() {
- var introLaunched by remember { mutableStateOf(Db.configs["isIntroLaunched"].toBoolean()) }
+ var introLaunched by remember { mutableStateOf(AppSettings.getFlag("isIntroLaunched")) }
if (!introLaunched) {
IntroDialog {
- Db.configs["isIntroLaunched"] = "true"
+ AppSettings.setFlag("isIntroLaunched", true)
introLaunched = true
}
}
}
+@Composable
+fun LaunchCrashDialogIfNeeded() {
+ if (!CustomExceptionHandler.isLastTimeCrashed()) return
+ var launched by remember { mutableStateOf(true) }
+ if (launched) {
+ CrashDialog {
+ launched = false
+ }
+ }
+}
+
@OptIn(ExperimentalComposeUiApi::class)
fun main() = application(false) {
fun onClose(source: String) {
@@ -78,11 +90,10 @@ fun main() = application(false) {
onClose("User Close")
exitApplication()
}
+ Thread.setDefaultUncaughtExceptionHandler(CustomExceptionHandler())
+ SentryHelper.init()
val windowState = rememberWindowState(WindowPlacement.Floating, size = DpSize(1440.dp, 1024.dp))
- Window(onCloseRequest = onCloseRequest, title = APP_NAME, state = windowState) {
-// window.exceptionHandler = WindowExceptionHandler {
-// println(it)
-// }
+ Window(onCloseRequest = onCloseRequest, title = CustomTheme.strings.appName, state = windowState) {
App()
}
}
diff --git a/src/main/kotlin/inputs/adb/CancelException.kt b/src/main/kotlin/inputs/adb/CancelException.kt
deleted file mode 100644
index 56db764..0000000
--- a/src/main/kotlin/inputs/adb/CancelException.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package inputs.adb
-
-class CancelException : Exception {
- constructor() : super("Logging is Cancelled")
- constructor(message: String) : super(message)
- constructor(message: String, cause: Throwable) : super(message, cause)
- constructor(cause: Throwable) : super("Logging is Cancelled", cause)
-}
diff --git a/src/main/kotlin/inputs/adb/ddmlib/AdbHelper.kt b/src/main/kotlin/inputs/adb/ddmlib/AdbHelper.kt
index c08f20a..5bd9416 100644
--- a/src/main/kotlin/inputs/adb/ddmlib/AdbHelper.kt
+++ b/src/main/kotlin/inputs/adb/ddmlib/AdbHelper.kt
@@ -17,6 +17,7 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import models.LogCatMessage2
import utils.Either
+import utils.reportException
import java.io.File
import java.util.*
import java.util.concurrent.TimeUnit
@@ -50,7 +51,7 @@ object AdbHelper {
try {
AndroidDebugBridge.terminate()
} catch (e: Exception) {
- // ignore
+ e.reportException()
}
}
@@ -81,7 +82,6 @@ object AdbHelper {
clientPid = client.clientData.pid
}
if (clientPid < 0) {
- println("Client is null")
send(Either.Left(LogErrorPackageIssue))
close()
awaitClose()
@@ -127,6 +127,7 @@ object AdbHelper {
val androidEnvHome: File? = try {
System.getenv("ANDROID_HOME") ?: System.getenv("ANDROID_SDK_ROOT")
} catch (e: SecurityException) {
+ e.reportException()
null
}?.let { File(it) }
diff --git a/src/main/kotlin/inputs/adb/ddmlib/Devices.kt b/src/main/kotlin/inputs/adb/ddmlib/Devices.kt
index 3793c29..bb11d3b 100644
--- a/src/main/kotlin/inputs/adb/ddmlib/Devices.kt
+++ b/src/main/kotlin/inputs/adb/ddmlib/Devices.kt
@@ -10,7 +10,7 @@ class Devices : AndroidDebugBridge.IDeviceChangeListener {
companion object {
private val _devicesFlow: MutableStateFlow> = MutableStateFlow(emptyList())
val devicesFlow: MutableStateFlow> = _devicesFlow
- private val devices: HashSet = hashSetOf()
+ private val connectedDevices: HashSet = hashSetOf()
private val _currentDeviceFlow: MutableStateFlow = MutableStateFlow(null)
val currentDeviceFlow = _currentDeviceFlow
@@ -24,23 +24,23 @@ class Devices : AndroidDebugBridge.IDeviceChangeListener {
override fun deviceConnected(device: IDevice) {
val details2 = DeviceDetails2(device)
- devices.add(details2)
+ connectedDevices.add(details2)
_devicesFlow.value = currentDevices()
}
override fun deviceDisconnected(device: IDevice) {
val details2 = DeviceDetails2(device)
- devices.remove(details2)
+ connectedDevices.remove(details2)
_devicesFlow.value = currentDevices()
}
override fun deviceChanged(device: IDevice, changeMask: Int) {
var details2 = DeviceDetails2(device)
- devices.remove(details2)
+ connectedDevices.remove(details2)
details2 = DeviceDetails2(device)
- devices.add(details2)
+ connectedDevices.add(details2)
_devicesFlow.value = currentDevices()
}
- private fun currentDevices() = devices.toList().sortedBy { it.sortKey() }
+ private fun currentDevices() = connectedDevices.toList().sortedBy { it.sortKey() }
}
diff --git a/src/main/kotlin/inputs/adb/ddmlib/LogCatRunner.kt b/src/main/kotlin/inputs/adb/ddmlib/LogCatRunner.kt
index 5ec8af6..a4370a9 100644
--- a/src/main/kotlin/inputs/adb/ddmlib/LogCatRunner.kt
+++ b/src/main/kotlin/inputs/adb/ddmlib/LogCatRunner.kt
@@ -4,9 +4,9 @@ import com.android.ddmlib.*
import com.android.ddmlib.logcat.LogCatMessageParser
import models.LogCatHeader2
import models.LogCatMessage2
+import utils.reportException
import java.io.IOException
import java.time.Instant
-import java.util.*
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import javax.annotation.concurrent.GuardedBy
@@ -61,9 +61,11 @@ class LogCatRunner(
} catch (e: TimeoutException) {
notifyListeners(arrayListOf(sConnectionTimeoutMsg))
} catch (ignored: AdbCommandRejectedException) {
+ ignored.reportException()
// will not be thrown as long as the shell supports logcat
} catch (ignored: ShellCommandUnresponsiveException) {
// this will not be thrown since the last argument is 0
+ ignored.reportException()
} catch (e: IOException) {
notifyListeners(arrayListOf(sConnectionErrorMsg))
}
diff --git a/src/main/kotlin/processor/MainProcessor.kt b/src/main/kotlin/processor/MainProcessor.kt
index dc23438..1eb39ed 100644
--- a/src/main/kotlin/processor/MainProcessor.kt
+++ b/src/main/kotlin/processor/MainProcessor.kt
@@ -4,6 +4,8 @@ import com.android.ddmlib.Log
import inputs.adb.AndroidLogStreamer
import inputs.adb.LogCatErrors
import inputs.adb.LogErrorNoSession
+import io.sentry.Sentry
+import io.sentry.SpanStatus
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
@@ -11,10 +13,10 @@ import kotlinx.coroutines.withContext
import models.LogItem
import models.SessionInfo
import storage.Db
-import utils.AppLog
import utils.Helpers
import utils.failureOrNull
import utils.getOrNull
+import utils.reportException
class MainProcessor {
@@ -125,11 +127,17 @@ class MainProcessor {
if (!isNewStream) {
indexedCollection.clear()
}
+ val sentryTransaction = Sentry.startTransaction("filterLogs", "filter", true)
+ sentryTransaction.setData("query", filterQuery ?: "")
try {
filterLogs(indexedCollection, list, parser, fQuery)
} catch (e: Exception) {
- e.printStackTrace()
+ e.reportException()
+ sentryTransaction.throwable = e
+ sentryTransaction.status = SpanStatus.INTERNAL_ERROR
listOf(LogItem.errorContent("Error in query\n${e.message}"))
+ } finally {
+ sentryTransaction.finish()
}
}
if (filterResult.isEmpty() && !isNewStream) {
@@ -141,10 +149,6 @@ class MainProcessor {
}
fun pause() {
- try {
- streamer.stop()
- } catch (e: Exception) {
- AppLog.d("unnecessary", "keeping exception for now in pause")
- }
+ streamer.stop()
}
}
diff --git a/src/main/kotlin/processor/QueryHelper.kt b/src/main/kotlin/processor/QueryHelper.kt
index 2ec2838..4d7628d 100644
--- a/src/main/kotlin/processor/QueryHelper.kt
+++ b/src/main/kotlin/processor/QueryHelper.kt
@@ -11,6 +11,7 @@ import com.googlecode.cqengine.index.radixreversed.ReversedRadixTreeIndex
import com.googlecode.cqengine.query.parser.sql.SQLParser
import models.LogItem
import utils.AppLog
+import utils.reportException
import kotlin.reflect.KProperty1
import kotlin.time.ExperimentalTime
import kotlin.time.measureTimedValue
@@ -48,7 +49,6 @@ fun filterLogs(
): List {
indexedCollection.addAll(list)
registerPropertiesInParser(list, parser, indexedCollection)
- println("Filtering logs")
val filterResult = measureTimedValue {
parser.retrieve(indexedCollection, filterQuery)
}
@@ -119,7 +119,7 @@ fun registerAndAddIndex(
addIndex(ReversedRadixTreeIndex.onAttribute(att))
parser.registerAttribute(att)
} catch (e: Exception) {
- // TODO: Make sure to log these exceptions somewhere so that we can analyse them
+ e.reportException()
addGenericAttribute(key, value, parser)
}
}
@@ -127,9 +127,10 @@ fun registerAndAddIndex(
try {
val att: ParameterizedAttribute> = ParameterizedAttribute(key, value.javaClass)
addIndex(HashIndex.onAttribute(att))
- addIndex(NavigableIndex.onAttribute(att))
+// addIndex(NavigableIndex.onAttribute(att)) // TODO: break it to specific types
parser.registerAttribute(att)
} catch (e: Exception) {
+ e.reportException()
addGenericAttribute(key, value, parser)
}
}
diff --git a/src/main/kotlin/storage/Db.kt b/src/main/kotlin/storage/Db.kt
index a9593ae..6d6858d 100644
--- a/src/main/kotlin/storage/Db.kt
+++ b/src/main/kotlin/storage/Db.kt
@@ -101,9 +101,6 @@ object Db {
newCatalog.remove(it)
}
db.nameCatalogSave(newCatalog)
- db.getAllNames().forEach {
- println(it)
- }
db.commit()
}
diff --git a/src/main/kotlin/storage/serializer/ObjectSerializer.kt b/src/main/kotlin/storage/serializer/ObjectSerializer.kt
index ee1bfb1..c95e9fc 100644
--- a/src/main/kotlin/storage/serializer/ObjectSerializer.kt
+++ b/src/main/kotlin/storage/serializer/ObjectSerializer.kt
@@ -6,7 +6,8 @@ import org.mapdb.DataOutput2
import org.mapdb.serializer.GroupSerializerObjectArray
import java.io.*
-class ObjectSerializer(val classLoader: ClassLoader = Thread.currentThread().contextClassLoader) : GroupSerializerObjectArray() {
+class ObjectSerializer(val classLoader: ClassLoader = Thread.currentThread().contextClassLoader) :
+ GroupSerializerObjectArray() {
override fun serialize(out: DataOutput2, value: T) {
val out2 = ObjectOutputStream(out as OutputStream)
diff --git a/src/main/kotlin/ui/components/BodyHeader.kt b/src/main/kotlin/ui/components/BodyHeader.kt
index 3bf3afb..6133299 100644
--- a/src/main/kotlin/ui/components/BodyHeader.kt
+++ b/src/main/kotlin/ui/components/BodyHeader.kt
@@ -17,6 +17,8 @@ import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import processor.QUERY_PREFIX
import ui.CustomTheme
+import ui.components.dialogs.FilterFaqDialog
+import ui.components.dialogs.SettingsDialog
@Composable
fun BodyHeader(
diff --git a/src/main/kotlin/ui/components/BodyPanel.kt b/src/main/kotlin/ui/components/BodyPanel.kt
index b3ebed3..0201691 100644
--- a/src/main/kotlin/ui/components/BodyPanel.kt
+++ b/src/main/kotlin/ui/components/BodyPanel.kt
@@ -20,7 +20,6 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
-import inputs.adb.CancelException
import inputs.adb.LogCatErrors
import inputs.adb.ddmlib.Devices
import inputs.adb.logcatErrorString
@@ -95,7 +94,7 @@ fun BodyPanel(
if (isOpen) {
val sessionInfo = processor.getSessionInfo(sessionId.orEmpty())
if (sessionInfo != null) {
- ExportDialog(sessionInfo, logItems) {
+ ui.components.dialogs.ExportDialog(sessionInfo, logItems) {
isOpen = false
}
}
@@ -298,9 +297,5 @@ private fun fetchOldData(
}
private fun pauseProcessor(processor: MainProcessor) {
- try {
- processor.pause()
- } catch (ex: CancelException) {
- println(ex.message)
- }
+ processor.pause()
}
diff --git a/src/main/kotlin/ui/components/NewSessionBox.kt b/src/main/kotlin/ui/components/NewSessionBox.kt
index d9150de..08a00ac 100644
--- a/src/main/kotlin/ui/components/NewSessionBox.kt
+++ b/src/main/kotlin/ui/components/NewSessionBox.kt
@@ -21,6 +21,7 @@ import inputs.adb.ddmlib.Devices
import models.DeviceDetails2
import models.SessionInfo
import ui.CustomTheme
+import ui.components.dialogs.StyledCustomVerticalDialog
@OptIn(ExperimentalMaterialApi::class, ExperimentalComposeUiApi::class)
@Composable
diff --git a/src/main/kotlin/ui/components/SideNavigation.kt b/src/main/kotlin/ui/components/SideNavigation.kt
index d74b2c8..f3a875a 100644
--- a/src/main/kotlin/ui/components/SideNavigation.kt
+++ b/src/main/kotlin/ui/components/SideNavigation.kt
@@ -1,6 +1,5 @@
package ui.components
-import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.Divider
@@ -8,15 +7,12 @@ import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import inputs.adb.ddmlib.Devices
import kotlinx.coroutines.launch
import processor.MainProcessor
import ui.CustomTheme
-import utils.APP_NAME
+import ui.components.common.AppLogo
@Composable
fun SideNavigation(
@@ -82,13 +78,3 @@ private fun SideNavHeader(header: String) {
style = CustomTheme.typography.headings.h3
)
}
-
-@Composable
-fun AppLogo(modifier: Modifier = Modifier) {
- Image(
- painterResource("icons/logo.svg"), APP_NAME,
- modifier,
- colorFilter = ColorFilter.tint(CustomTheme.colors.highContrast),
- contentScale = ContentScale.FillWidth
- )
-}
diff --git a/src/main/kotlin/ui/components/BasicComponents.kt b/src/main/kotlin/ui/components/common/BasicComponents.kt
similarity index 78%
rename from src/main/kotlin/ui/components/BasicComponents.kt
rename to src/main/kotlin/ui/components/common/BasicComponents.kt
index 5ad5006..354b56d 100644
--- a/src/main/kotlin/ui/components/BasicComponents.kt
+++ b/src/main/kotlin/ui/components/common/BasicComponents.kt
@@ -1,25 +1,38 @@
-package ui.components
+package ui.components.common
+import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.ClickableText
-import androidx.compose.material.Icon
-import androidx.compose.material.RadioButton
-import androidx.compose.material.Switch
-import androidx.compose.material.Text
+import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import models.MarkupText
+import models.SocialIcons
import ui.CustomTheme
+import ui.components.dialogs.openBrowser
import ui.views.DarkToggleButton
+@Composable
+fun AppLogo(modifier: Modifier = Modifier) {
+ Image(
+ painterResource("icons/logo.svg"), CustomTheme.strings.appName,
+ modifier,
+ colorFilter = ColorFilter.tint(CustomTheme.colors.highContrast),
+ contentScale = ContentScale.FillWidth
+ )
+}
+
@Composable
fun DarkModeSwitchItem(
isDarkMode: Boolean,
@@ -181,3 +194,32 @@ fun MultiLineRadioButton(
}
}
}
+
+@Composable
+fun WebLinkButton(
+ socialIcons: SocialIcons, text: String, modifier: Modifier = Modifier
+) {
+ val buttonColors = ButtonDefaults.textButtonColors(
+ contentColor = CustomTheme.colors.mediumContrast
+ )
+ TextButton({ openBrowser(socialIcons.url) }, modifier, colors = buttonColors) {
+ Icon(painterResource(socialIcons.icon), socialIcons.name)
+ Spacer(Modifier.width(4.dp))
+ Text(text, style = CustomTheme.typography.bodySmall)
+ }
+}
+
+@Composable
+fun WebLinkButtonFilled(
+ socialIcons: SocialIcons, text: String, modifier: Modifier = Modifier
+) {
+ val buttonColors = ButtonDefaults.buttonColors(
+ backgroundColor = CustomTheme.colors.mediumContrast
+ )
+ val elevation = ButtonDefaults.elevation(defaultElevation = 0.dp)
+ Button({ openBrowser(socialIcons.url) }, modifier, colors = buttonColors, elevation = elevation) {
+ Icon(painterResource(socialIcons.icon), socialIcons.name)
+ Spacer(Modifier.width(4.dp))
+ Text(text, style = CustomTheme.typography.bodySmall)
+ }
+}
diff --git a/src/main/kotlin/ui/components/dialogs/CrashDialog.kt b/src/main/kotlin/ui/components/dialogs/CrashDialog.kt
new file mode 100644
index 0000000..853cc20
--- /dev/null
+++ b/src/main/kotlin/ui/components/dialogs/CrashDialog.kt
@@ -0,0 +1,34 @@
+package ui.components.dialogs
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import models.SocialIcons
+import ui.CustomTheme
+import ui.components.common.WebLinkButtonFilled
+import utils.CustomExceptionHandler
+
+@Composable
+fun CrashDialog(onDismissRequest: () -> Unit) {
+ CustomExceptionHandler.setLastCrashConsumed()
+ SimpleVerticalDialog("Share crash", onDismissRequest) {
+ Image(
+ painterResource("icons/crash_illustration.xml"), "Crashed",
+ Modifier.fillMaxWidth(0.9f), contentScale = ContentScale.FillWidth
+ )
+ Spacer(Modifier.height(16.dp))
+ Text(CustomTheme.strings.appCrashText, textAlign = TextAlign.Center, style = CustomTheme.typography.body)
+ Spacer(Modifier.height(16.dp))
+ Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp)) {
+ WebLinkButtonFilled(SocialIcons.GithubIssues, "Github Issue", Modifier.weight(0.5f))
+ WebLinkButtonFilled(SocialIcons.Email, "Mail Us", Modifier.weight(0.5f))
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/ui/components/CustomDialog.kt b/src/main/kotlin/ui/components/dialogs/CustomDialog.kt
similarity index 98%
rename from src/main/kotlin/ui/components/CustomDialog.kt
rename to src/main/kotlin/ui/components/dialogs/CustomDialog.kt
index 335577a..f84a189 100644
--- a/src/main/kotlin/ui/components/CustomDialog.kt
+++ b/src/main/kotlin/ui/components/dialogs/CustomDialog.kt
@@ -1,4 +1,4 @@
-package ui.components
+package ui.components.dialogs
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@@ -91,7 +91,7 @@ fun CustomDialog(
Floats.constrainToRange(backgroundAlpha, 0.0f, 1.0f)
Floats.constrainToRange(dialogWidthRatio, 0.1f, 1.0f)
Floats.constrainToRange(dialogHeightRatio, 0.1f, 1.0f)
- with(UndecoratedWindowAlertDialogProvider) {
+ with(PopupAlertDialogProvider) {
AlertDialog(onDismissRequest) {
Dialog(
onCloseRequest = onDismissRequest,
diff --git a/src/main/kotlin/ui/components/ExportDialog.kt b/src/main/kotlin/ui/components/dialogs/ExportDialog.kt
similarity index 97%
rename from src/main/kotlin/ui/components/ExportDialog.kt
rename to src/main/kotlin/ui/components/dialogs/ExportDialog.kt
index 3ed60f7..2e167e0 100644
--- a/src/main/kotlin/ui/components/ExportDialog.kt
+++ b/src/main/kotlin/ui/components/dialogs/ExportDialog.kt
@@ -1,4 +1,4 @@
-package ui.components
+package ui.components.dialogs
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
@@ -15,6 +15,8 @@ import kotlinx.coroutines.launch
import models.*
import processor.Exporter
import storage.Db
+import ui.components.common.MultiLineRadioButton
+import ui.components.common.SwitchItem
import utils.Helpers
import java.nio.file.Path
import kotlin.io.path.absolutePathString
diff --git a/src/main/kotlin/ui/components/FaqDialog.kt b/src/main/kotlin/ui/components/dialogs/FaqDialog.kt
similarity index 97%
rename from src/main/kotlin/ui/components/FaqDialog.kt
rename to src/main/kotlin/ui/components/dialogs/FaqDialog.kt
index 10382dd..664ebf2 100644
--- a/src/main/kotlin/ui/components/FaqDialog.kt
+++ b/src/main/kotlin/ui/components/dialogs/FaqDialog.kt
@@ -1,4 +1,4 @@
-package ui.components
+package ui.components.dialogs
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
@@ -13,6 +13,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import models.Faq
import ui.CustomTheme
+import ui.components.common.SimpleListItem
@Composable
fun FaqDialog(
diff --git a/src/main/kotlin/ui/components/FileDialog.kt b/src/main/kotlin/ui/components/dialogs/FileDialog.kt
similarity index 97%
rename from src/main/kotlin/ui/components/FileDialog.kt
rename to src/main/kotlin/ui/components/dialogs/FileDialog.kt
index 7020ddd..f8f5227 100644
--- a/src/main/kotlin/ui/components/FileDialog.kt
+++ b/src/main/kotlin/ui/components/dialogs/FileDialog.kt
@@ -1,4 +1,4 @@
-package ui.components
+package ui.components.dialogs
import androidx.compose.runtime.Composable
import androidx.compose.ui.window.AwtWindow
diff --git a/src/main/kotlin/ui/components/IntroDialog.kt b/src/main/kotlin/ui/components/dialogs/IntroDialog.kt
similarity index 97%
rename from src/main/kotlin/ui/components/IntroDialog.kt
rename to src/main/kotlin/ui/components/dialogs/IntroDialog.kt
index bc9465a..05e0b51 100644
--- a/src/main/kotlin/ui/components/IntroDialog.kt
+++ b/src/main/kotlin/ui/components/dialogs/IntroDialog.kt
@@ -1,4 +1,4 @@
-package ui.components
+package ui.components.dialogs
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
@@ -13,6 +13,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import ui.CustomTheme
+import ui.components.common.AppLogo
@Composable
fun IntroDialog(onDismissRequest: () -> Unit) {
diff --git a/src/main/kotlin/ui/components/SettingsDialog.kt b/src/main/kotlin/ui/components/dialogs/SettingsDialog.kt
similarity index 89%
rename from src/main/kotlin/ui/components/SettingsDialog.kt
rename to src/main/kotlin/ui/components/dialogs/SettingsDialog.kt
index 3f58af5..ea6a8ce 100644
--- a/src/main/kotlin/ui/components/SettingsDialog.kt
+++ b/src/main/kotlin/ui/components/dialogs/SettingsDialog.kt
@@ -1,7 +1,9 @@
-package ui.components
+package ui.components.dialogs
import androidx.compose.foundation.layout.*
-import androidx.compose.material.*
+import androidx.compose.material.Divider
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -14,12 +16,13 @@ import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import models.SocialIcons
import ui.CustomTheme
+import ui.components.ItemHeader
+import ui.components.common.*
import utils.AppSettings
import utils.Helpers
@Composable
fun SettingsDialog(onDismissRequest: () -> Unit) {
-
SimpleVerticalDialog(header = "Settings", onDismissRequest = onDismissRequest) {
GeneralSettingBlock(Modifier.fillMaxWidth())
Spacer(Modifier.height(16.dp))
@@ -103,7 +106,6 @@ fun OtherSettingBlock(modifier: Modifier = Modifier) {
}
Row(Modifier.padding(start = 32.dp)) {
SocialIcons.DefaultIcons.forEach {
- println(it)
SocialIcon(it)
}
}
@@ -111,16 +113,6 @@ fun OtherSettingBlock(modifier: Modifier = Modifier) {
}
}
-@Composable
-private fun WebLinkButton(socialIcons: SocialIcons, text: String) {
- val buttonColors = ButtonDefaults.textButtonColors(contentColor = CustomTheme.colors.mediumContrast)
- TextButton({ openBrowser(socialIcons.url) }, colors = buttonColors) {
- Icon(painterResource(socialIcons.icon), socialIcons.name)
- Spacer(Modifier.width(4.dp))
- Text(text, style = CustomTheme.typography.bodySmall)
- }
-}
-
@Composable
private fun SocialIcon(icon: SocialIcons) {
IconButton({ openBrowser(icon.url) }) {
diff --git a/src/main/kotlin/utils/AppLog.kt b/src/main/kotlin/utils/AppLog.kt
index b074c05..3a400df 100644
--- a/src/main/kotlin/utils/AppLog.kt
+++ b/src/main/kotlin/utils/AppLog.kt
@@ -1,5 +1,6 @@
package utils
+import io.sentry.Sentry
import java.util.logging.Level
import java.util.logging.LogRecord
import java.util.logging.SimpleFormatter
@@ -17,3 +18,11 @@ object AppLog {
d("Debug", msg)
}
}
+
+fun Throwable?.reportException() {
+ if (this == null) {
+ Sentry.captureException(UnsupportedOperationException("Throwable should not be null"))
+ return
+ }
+ Sentry.captureException(this)
+}
diff --git a/src/main/kotlin/utils/AppResources.kt b/src/main/kotlin/utils/AppResources.kt
index 2e821cd..68439ca 100644
--- a/src/main/kotlin/utils/AppResources.kt
+++ b/src/main/kotlin/utils/AppResources.kt
@@ -1,15 +1,17 @@
package utils
-const val APP_NAME = "LogVue"
-
//TODO: Move all strings here to support languages in future
interface StringRes {
val appName: String
val filterFaqTitle: String
+ val appCrashText: String
}
class EnglishStringRes : StringRes {
- override val appName: String = APP_NAME
+ override val appName: String = "LogVue"
override val filterFaqTitle: String = "Filter FAQโs"
+ override val appCrashText: String = "Unfortunately the app was crashed last time. " +
+ "While we look into this, you can also report this issue to us on github or through " +
+ "mail describing the scenario that caused this crash."
}
diff --git a/src/main/kotlin/utils/CustomExceptionHandler.kt b/src/main/kotlin/utils/CustomExceptionHandler.kt
new file mode 100644
index 0000000..5762ff2
--- /dev/null
+++ b/src/main/kotlin/utils/CustomExceptionHandler.kt
@@ -0,0 +1,23 @@
+package utils
+
+class CustomExceptionHandler : Thread.UncaughtExceptionHandler {
+
+ override fun uncaughtException(t: Thread?, e: Throwable?) {
+ e?.printStackTrace()
+ setCrashed()
+ }
+
+ companion object {
+ private const val DB_KEY = "lastTimeException"
+
+ private fun setCrashed() {
+ AppSettings.setFlag(DB_KEY, true)
+ }
+
+ fun setLastCrashConsumed() {
+ AppSettings.setFlag(DB_KEY, false)
+ }
+
+ fun isLastTimeCrashed() = AppSettings.getFlag(DB_KEY)
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/utils/Helpers.kt b/src/main/kotlin/utils/Helpers.kt
index 22a7a01..0bae37f 100644
--- a/src/main/kotlin/utils/Helpers.kt
+++ b/src/main/kotlin/utils/Helpers.kt
@@ -191,7 +191,7 @@ object Helpers {
val dump = Dump(settings)
dump.dumpToString(properties)
} catch (e: Exception) {
- AppLog.d("YamlConverter", e.localizedMessage)
+ e.reportException()
null
}
}
@@ -222,7 +222,7 @@ object Helpers {
val dump = Dump(settings)
dump.dump(properties, YamlWriter(printWriter))
} catch (e: Exception) {
- AppLog.d("YamlConverter", e.localizedMessage)
+ e.reportException()
}
}
@@ -231,7 +231,7 @@ object Helpers {
val dump = Dump(settings)
dump.dump(properties, streamDataWriter)
} catch (e: Exception) {
- AppLog.d("YamlConverter", e.localizedMessage)
+ e.reportException()
}
}
@@ -292,7 +292,7 @@ object Helpers {
}
Runtime.getRuntime().exec(command)
} catch (e: Exception) {
- AppLog.d("failed to open file manager")
+ e.reportException()
}
}
diff --git a/src/main/kotlin/utils/ItemObjectMapper.kt b/src/main/kotlin/utils/ItemObjectMapper.kt
index 3516366..2c83af3 100644
--- a/src/main/kotlin/utils/ItemObjectMapper.kt
+++ b/src/main/kotlin/utils/ItemObjectMapper.kt
@@ -22,8 +22,7 @@ class ItemObjectMapper {
}
}
} catch (e: Exception) {
- LOGGER.error("Unexpected exception!", e)
- println("Unexpected Exception! (item=$item e = ${e.message}")
+ Exception("Item mapping failed for item=$item", e).reportException()
item.stringRepresentation
}
}
@@ -31,7 +30,6 @@ class ItemObjectMapper {
private fun parseObject(item: ObjectItem): HashMap {
val map = hashMapOf()
item.getAttributes().forEach { entry ->
-// println("parsing for field: $entry")
val stringRepresentation = entry.value.stringRepresentation ?: ""
val key = entry.key.removePrefix("{").removeSuffix("}")
diff --git a/src/main/kotlin/utils/SentryHelper.kt b/src/main/kotlin/utils/SentryHelper.kt
new file mode 100644
index 0000000..a3736bf
--- /dev/null
+++ b/src/main/kotlin/utils/SentryHelper.kt
@@ -0,0 +1,33 @@
+package utils
+
+import com.voxfinite.logvue.APP_VERSION
+import io.sentry.Breadcrumb
+import io.sentry.Sentry
+import io.sentry.SentryOptions
+
+object SentryHelper {
+
+ private const val SAMPLE_RATE = 0.3
+
+ fun init() {
+ Sentry.init { options: SentryOptions ->
+ options.dsn = System.getProperty("SENTRY_ENDPOINT").orEmpty()
+ // Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
+ // We recommend adjusting this value in production.
+ options.tracesSampleRate = SAMPLE_RATE
+ // When first trying Sentry it's good to see what the SDK is doing:
+ options.setDebug(System.getProperty("SENTRY_DEBUG").toBoolean())
+ }
+ Sentry.configureScope { scope ->
+ scope.setTag("os.name", System.getProperty("os.name"))
+ scope.setTag("os.arch", System.getProperty("os.arch"))
+ scope.setTag("os.version", System.getProperty("os.version"))
+ scope.setTag("build.version", APP_VERSION)
+ }
+ }
+
+ fun breadcrumb(breadcrumb: Breadcrumb) {
+ Sentry.addBreadcrumb(breadcrumb)
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/resources/icons/crash_illustration.xml b/src/main/resources/icons/crash_illustration.xml
new file mode 100644
index 0000000..034bea8
--- /dev/null
+++ b/src/main/resources/icons/crash_illustration.xml
@@ -0,0 +1,528 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
index f6fa73b..5db49b8 100644
--- a/src/main/resources/log4j2.xml
+++ b/src/main/resources/log4j2.xml
@@ -1,14 +1,14 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file