feat(debug): chucker

Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
Infi 2024-11-08 16:43:33 +01:00
parent 584838b98d
commit c4aef08a63
5 changed files with 125 additions and 0 deletions

View File

@ -338,6 +338,10 @@ dependencies {
// Shimmer - loading animations
implementation("com.valentinilk.shimmer:compose-shimmer:1.3.1")
// Chucker - HTTP inspector
debugImplementation("com.github.chuckerteam.chucker:library:4.0.0")
releaseImplementation("com.github.chuckerteam.chucker:library-no-op:4.0.0")
// Testing
androidTestImplementation("androidx.test:runner:$androidXTestVersion")
androidTestImplementation("androidx.test:rules:$androidXTestVersion")

View File

@ -5,6 +5,7 @@ import android.os.Looper
import android.util.Log
import androidx.compose.runtime.mutableStateMapOf
import chat.revolt.BuildConfig
import chat.revolt.RevoltApplication
import chat.revolt.api.internals.Members
import chat.revolt.api.realtime.DisconnectionState
import chat.revolt.api.realtime.RealtimeSocket
@ -18,6 +19,9 @@ import chat.revolt.api.schemas.User
import chat.revolt.api.unreads.Unreads
import chat.revolt.persistence.Database
import chat.revolt.persistence.SqlStorage
import com.chuckerteam.chucker.api.ChuckerCollector
import com.chuckerteam.chucker.api.ChuckerInterceptor
import com.chuckerteam.chucker.api.RetentionManager
import io.ktor.client.HttpClient
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.plugins.DefaultRequest
@ -103,6 +107,20 @@ val RevoltHttp = HttpClient(OkHttp) {
install(Logging) { level = LogLevel.INFO }
val chuckerCollector = ChuckerCollector(
context = RevoltApplication.instance,
showNotification = true,
retentionPeriod = RetentionManager.Period.ONE_DAY
)
val chuckerInterceptor = ChuckerInterceptor.Builder(RevoltApplication.instance)
.collector(chuckerCollector)
.maxContentLength(250_000L)
.redactHeaders(RevoltAPI.TOKEN_HEADER_NAME)
.alwaysReadResponseBody(true)
.createShortcut(false)
.build()
engine {
addInterceptor { chain ->
val request = chain.request().newBuilder()
@ -114,6 +132,7 @@ val RevoltHttp = HttpClient(OkHttp) {
.build()
chain.proceed(request)
}
addInterceptor(chuckerInterceptor)
}
defaultRequest {

View File

@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.widthIn
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
@ -14,7 +15,11 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import chat.revolt.BuildConfig
import chat.revolt.R
@Composable
@ -42,6 +47,18 @@ fun NotificationRationaleDialog(
Text(
text = stringResource(id = R.string.spark_notifications_rationale_description)
)
if (BuildConfig.DEBUG) {
Text(
text = buildAnnotatedString {
pushStyle(SpanStyle(fontWeight = FontWeight.Bold))
append("Debug build:")
pop()
append(" Required to open Chucker for network debugging. ")
append("You can show this dialogue again from Settings -> Debug.")
},
color = MaterialTheme.colorScheme.error
)
}
}
},
dismissButton = {

View File

@ -2,9 +2,13 @@ package chat.revolt.screens.chat
import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import android.util.Log
import android.view.accessibility.AccessibilityManager
import android.view.inputmethod.InputMethodManager
import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@ -46,20 +50,24 @@ import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.core.app.NotificationManagerCompat
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavController
import chat.revolt.BuildConfig
import chat.revolt.R
import chat.revolt.api.RevoltAPI
import chat.revolt.api.internals.DirectMessages
import chat.revolt.api.realtime.DisconnectionState
import chat.revolt.api.realtime.RealtimeSocket
import chat.revolt.api.routes.push.subscribePush
import chat.revolt.callbacks.Action
import chat.revolt.callbacks.ActionChannel
import chat.revolt.components.chat.DisconnectedNotice
import chat.revolt.components.screens.chat.drawer.ChannelSideDrawer
import chat.revolt.components.screens.voice.VoiceChannelOverlay
import chat.revolt.dialogs.NotificationRationaleDialog
import chat.revolt.internals.Changelogs
import chat.revolt.internals.extensions.zero
import chat.revolt.persistence.KVStorage
@ -84,8 +92,11 @@ import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.animateLottieCompositionAsState
import com.airbnb.lottie.compose.rememberLottieComposition
import com.google.android.gms.tasks.OnCompleteListener
import com.google.firebase.messaging.FirebaseMessaging
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import io.sentry.Sentry
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -137,6 +148,7 @@ class ChatRouterViewModel @Inject constructor(
var latestChangelogRead by mutableStateOf(true)
var latestChangelog by mutableStateOf("")
var latestChangelogBody by mutableStateOf("")
var showNotificationRationale by mutableStateOf(false)
private val changelogs = Changelogs(context, kvStorage)
@ -158,6 +170,13 @@ class ChatRouterViewModel @Inject constructor(
if (!latestChangelogRead) {
changelogs.markAsSeen()
}
val hasNotificationPermission =
NotificationManagerCompat.from(context).areNotificationsEnabled()
// right now we only show this in debug builds so Chucker can show its notification
if (!hasNotificationPermission && BuildConfig.DEBUG) {
showNotificationRationale = true
}
}
}
@ -181,6 +200,32 @@ class ChatRouterViewModel @Inject constructor(
sidebarSparkDisplayed = true
}
fun setRegisterForNotifications() {
showNotificationRationale = false
FirebaseMessaging.getInstance().token.addOnCompleteListener(
OnCompleteListener { task ->
if (!task.isSuccessful) {
Log.w("FCM", "Fetching FCM registration token failed", task.exception)
task.exception?.let { Sentry.captureException(it) }
return@OnCompleteListener
}
val token = task.result
viewModelScope.launch {
kvStorage.set("fcmToken", token)
subscribePush(auth = token)
}
}
)
}
fun markNotificationsRejected() {
showNotificationRationale = false
viewModelScope.launch {
kvStorage.set("pushNotificationsRejected", true)
}
}
fun navigateToServer(serverId: String) {
viewModelScope.launch {
val savedLastChannel = kvStorage.get("lastChannel/$serverId")
@ -692,6 +737,33 @@ fun ChatRouterScreen(
}
}
val askNotificationsPermission =
rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
viewModel.setRegisterForNotifications()
} else {
viewModel.showNotificationRationale = false
}
}
if (viewModel.showNotificationRationale) {
NotificationRationaleDialog(
onDismiss = {
viewModel.showNotificationRationale = false
},
onSelected = { accepted ->
if (accepted) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
askNotificationsPermission.launch(android.Manifest.permission.POST_NOTIFICATIONS)
} else {
viewModel.setRegisterForNotifications()
}
} else {
viewModel.markNotificationsRejected()
}
}
)
}
Column(
modifier = Modifier
.fillMaxWidth()

View File

@ -38,9 +38,12 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import chat.revolt.BuildConfig
import chat.revolt.R
import chat.revolt.api.REVOLT_MARKETING
import chat.revolt.components.generic.AnyLink
import chat.revolt.components.generic.Weblink
import com.chuckerteam.chucker.api.Chucker
@Composable
fun LoginGreetingScreen(navController: NavController) {
@ -159,6 +162,16 @@ fun LoginGreetingScreen(navController: NavController) {
text = stringResource(R.string.community_guidelines),
url = "$REVOLT_MARKETING/aup"
)
if (BuildConfig.DEBUG) {
AnyLink(
text = "Debug: Chucker",
action = {
Chucker.getLaunchIntent(context).apply {
context.startActivity(this)
}
}
)
}
}
}
}