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 // Shimmer - loading animations
implementation("com.valentinilk.shimmer:compose-shimmer:1.3.1") 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 // Testing
androidTestImplementation("androidx.test:runner:$androidXTestVersion") androidTestImplementation("androidx.test:runner:$androidXTestVersion")
androidTestImplementation("androidx.test:rules:$androidXTestVersion") androidTestImplementation("androidx.test:rules:$androidXTestVersion")

View File

@ -5,6 +5,7 @@ import android.os.Looper
import android.util.Log import android.util.Log
import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateMapOf
import chat.revolt.BuildConfig import chat.revolt.BuildConfig
import chat.revolt.RevoltApplication
import chat.revolt.api.internals.Members import chat.revolt.api.internals.Members
import chat.revolt.api.realtime.DisconnectionState import chat.revolt.api.realtime.DisconnectionState
import chat.revolt.api.realtime.RealtimeSocket 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.api.unreads.Unreads
import chat.revolt.persistence.Database import chat.revolt.persistence.Database
import chat.revolt.persistence.SqlStorage 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.HttpClient
import io.ktor.client.engine.okhttp.OkHttp import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.plugins.DefaultRequest import io.ktor.client.plugins.DefaultRequest
@ -103,6 +107,20 @@ val RevoltHttp = HttpClient(OkHttp) {
install(Logging) { level = LogLevel.INFO } 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 { engine {
addInterceptor { chain -> addInterceptor { chain ->
val request = chain.request().newBuilder() val request = chain.request().newBuilder()
@ -114,6 +132,7 @@ val RevoltHttp = HttpClient(OkHttp) {
.build() .build()
chain.proceed(request) chain.proceed(request)
} }
addInterceptor(chuckerInterceptor)
} }
defaultRequest { defaultRequest {

View File

@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.widthIn
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -14,7 +15,11 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource 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 androidx.compose.ui.unit.dp
import chat.revolt.BuildConfig
import chat.revolt.R import chat.revolt.R
@Composable @Composable
@ -42,6 +47,18 @@ fun NotificationRationaleDialog(
Text( Text(
text = stringResource(id = R.string.spark_notifications_rationale_description) 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 = { dismissButton = {

View File

@ -2,9 +2,13 @@ package chat.revolt.screens.chat
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.os.Build
import android.util.Log
import android.view.accessibility.AccessibilityManager import android.view.accessibility.AccessibilityManager
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row 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.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.core.app.NotificationManagerCompat
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.navigation.NavController import androidx.navigation.NavController
import chat.revolt.BuildConfig
import chat.revolt.R import chat.revolt.R
import chat.revolt.api.RevoltAPI import chat.revolt.api.RevoltAPI
import chat.revolt.api.internals.DirectMessages import chat.revolt.api.internals.DirectMessages
import chat.revolt.api.realtime.DisconnectionState import chat.revolt.api.realtime.DisconnectionState
import chat.revolt.api.realtime.RealtimeSocket import chat.revolt.api.realtime.RealtimeSocket
import chat.revolt.api.routes.push.subscribePush
import chat.revolt.callbacks.Action import chat.revolt.callbacks.Action
import chat.revolt.callbacks.ActionChannel import chat.revolt.callbacks.ActionChannel
import chat.revolt.components.chat.DisconnectedNotice import chat.revolt.components.chat.DisconnectedNotice
import chat.revolt.components.screens.chat.drawer.ChannelSideDrawer import chat.revolt.components.screens.chat.drawer.ChannelSideDrawer
import chat.revolt.components.screens.voice.VoiceChannelOverlay import chat.revolt.components.screens.voice.VoiceChannelOverlay
import chat.revolt.dialogs.NotificationRationaleDialog
import chat.revolt.internals.Changelogs import chat.revolt.internals.Changelogs
import chat.revolt.internals.extensions.zero import chat.revolt.internals.extensions.zero
import chat.revolt.persistence.KVStorage 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.LottieCompositionSpec
import com.airbnb.lottie.compose.animateLottieCompositionAsState import com.airbnb.lottie.compose.animateLottieCompositionAsState
import com.airbnb.lottie.compose.rememberLottieComposition 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.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import io.sentry.Sentry
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@ -137,6 +148,7 @@ class ChatRouterViewModel @Inject constructor(
var latestChangelogRead by mutableStateOf(true) var latestChangelogRead by mutableStateOf(true)
var latestChangelog by mutableStateOf("") var latestChangelog by mutableStateOf("")
var latestChangelogBody by mutableStateOf("") var latestChangelogBody by mutableStateOf("")
var showNotificationRationale by mutableStateOf(false)
private val changelogs = Changelogs(context, kvStorage) private val changelogs = Changelogs(context, kvStorage)
@ -158,6 +170,13 @@ class ChatRouterViewModel @Inject constructor(
if (!latestChangelogRead) { if (!latestChangelogRead) {
changelogs.markAsSeen() 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 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) { fun navigateToServer(serverId: String) {
viewModelScope.launch { viewModelScope.launch {
val savedLastChannel = kvStorage.get("lastChannel/$serverId") 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( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController import androidx.navigation.NavController
import chat.revolt.BuildConfig
import chat.revolt.R import chat.revolt.R
import chat.revolt.api.REVOLT_MARKETING import chat.revolt.api.REVOLT_MARKETING
import chat.revolt.components.generic.AnyLink
import chat.revolt.components.generic.Weblink import chat.revolt.components.generic.Weblink
import com.chuckerteam.chucker.api.Chucker
@Composable @Composable
fun LoginGreetingScreen(navController: NavController) { fun LoginGreetingScreen(navController: NavController) {
@ -159,6 +162,16 @@ fun LoginGreetingScreen(navController: NavController) {
text = stringResource(R.string.community_guidelines), text = stringResource(R.string.community_guidelines),
url = "$REVOLT_MARKETING/aup" url = "$REVOLT_MARKETING/aup"
) )
if (BuildConfig.DEBUG) {
AnyLink(
text = "Debug: Chucker",
action = {
Chucker.getLaunchIntent(context).apply {
context.startActivity(this)
}
}
)
}
} }
} }
} }