parent
584838b98d
commit
c4aef08a63
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue