feat(core): crash overlay

This commit is contained in:
rhunk 2024-03-30 01:37:22 +01:00
parent f57d880ec8
commit a373c8fceb
2 changed files with 78 additions and 9 deletions

View File

@ -23,11 +23,11 @@ import me.rhunk.snapenhance.core.bridge.loadFromBridge
import me.rhunk.snapenhance.core.data.SnapClassCache
import me.rhunk.snapenhance.core.event.events.impl.NativeUnaryCallEvent
import me.rhunk.snapenhance.core.event.events.impl.SnapWidgetBroadcastReceiveEvent
import me.rhunk.snapenhance.core.ui.InAppOverlay
import me.rhunk.snapenhance.core.util.LSPatchUpdater
import me.rhunk.snapenhance.core.util.hook.HookAdapter
import me.rhunk.snapenhance.core.util.hook.HookStage
import me.rhunk.snapenhance.core.util.hook.hook
import java.lang.reflect.Modifier
import kotlin.system.measureTimeMillis
@ -60,18 +60,23 @@ class SnapEnhance {
bridgeClient.apply {
connect(
onFailure = {
crash("Snapchat can't connect to the SnapEnhance app. Please download stable version from https://github.com/rhunk/SnapEnhance/releases", it)
InAppOverlay.showCrashOverlay(
"Snapchat can't connect to the SnapEnhance app. Make sure you have the latest version installed on your device. You can download the latest stable version on github.com/rhunk/SnapEnhance",
throwable = it
)
}
) { bridgeResult ->
if (!bridgeResult) {
InAppOverlay.showCrashOverlay(
"Snapchat timed out while trying to connect to the SnapEnhance app. Make sure you have disabled any battery optimizations for SnapEnhance."
)
logCritical("Cannot connect to the SnapEnhance app")
softRestartApp()
return@connect
}
runCatching {
LSPatchUpdater.onBridgeConnected(appContext, bridgeClient)
}.onFailure {
logCritical("Failed to init LSPatchUpdater", it)
log.error("Failed to init LSPatchUpdater", it)
}
runCatching {
measureTimeMillis {
@ -85,6 +90,7 @@ class SnapEnhance {
isBridgeInitialized = true
}.onFailure {
logCritical("Failed to initialize bridge", it)
InAppOverlay.showCrashOverlay("SnapEnhance failed to initialize. Please check logs for more details.")
}
}
}
@ -315,7 +321,7 @@ class SnapEnhance {
}
val stringResources = material3RString.fields.filter {
Modifier.isStatic(it.modifiers) && it.type == Int::class.javaPrimitiveType
java.lang.reflect.Modifier.isStatic(it.modifiers) && it.type == Int::class.javaPrimitiveType
}.associate { it.getInt(null) to it.name }
Resources::class.java.getMethod("getString", Int::class.javaPrimitiveType).hook(HookStage.BEFORE) { param ->

View File

@ -1,6 +1,7 @@
package me.rhunk.snapenhance.core.ui
import android.app.Activity
import android.view.View
import android.widget.FrameLayout
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.LinearEasing
@ -15,10 +16,7 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Warning
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -30,13 +28,78 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.delay
import me.rhunk.snapenhance.common.ui.AppMaterialTheme
import me.rhunk.snapenhance.common.ui.createComposeView
import me.rhunk.snapenhance.common.util.ktx.copyToClipboard
import me.rhunk.snapenhance.core.util.hook.HookStage
import me.rhunk.snapenhance.core.util.hook.Hooker
import me.rhunk.snapenhance.core.util.ktx.isDarkTheme
import kotlin.math.roundToInt
class InAppOverlay {
companion object {
fun showCrashOverlay(content: String, throwable: Throwable? = null) {
Hooker.ephemeralHook(Activity::class.java, "onPostCreate", HookStage.AFTER) { param ->
val contentView = param.thisObject<Activity>().findViewById<FrameLayout>(android.R.id.content)
contentView.children().forEach { it.visibility = View.GONE }
lateinit var screenView: View
screenView = createComposeView(param.thisObject()) {
AppMaterialTheme(isDarkTheme = true) {
Surface(
color = MaterialTheme.colorScheme.surface
) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "SnapEnhance",
fontSize = 28.sp
)
Spacer(modifier = Modifier.height(40.dp))
Text(
text = content,
fontSize = 16.sp
)
Spacer(modifier = Modifier.height(40.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
throwable?.let {
Button(onClick = {
contentView.context.copyToClipboard(it.stackTraceToString())
}) {
Text("Copy error to clipboard")
}
}
Button(onClick = {
contentView.children().forEach { it.visibility = View.VISIBLE }
contentView.removeView(screenView)
}) {
Text("Ignore")
}
}
}
}
}
}
}.apply {
layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)
}
contentView.addView(screenView)
}
}
}
inner class Toast(
val composable: @Composable Toast.() -> Unit,
val durationMs: Int